@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.
@@ -1,289 +0,0 @@
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.
@@ -1,120 +0,0 @@
1
- # Design: state ↔ steering-file association (`file:` + `mode:`)
2
-
3
- > Status: LANDED (2026-07-22). SUPERSEDED IN ONE RESPECT: `mode:` is no longer a
4
- > closed `qa`/`review` vocabulary — those two are now gtd's BUILT-IN modes and a
5
- > workflow may declare its own (a `format:`/`validate:` shell-command pair) in a
6
- > `modes:` map, see [pluggable-steering-modes.md](pluggable-steering-modes.md).
7
- > Everything below about the state↔file association and the LSP's config-driven
8
- > dispatch still holds (the LSP understands the two built-ins only). Goal:
9
- > retain the v2 relationship between states and steering files — but as pure
10
- > workflow CONFIGURATION that works for any state machine. Each state may
11
- > declare one associated `file:` (an Eta template, so filenames live in `vars:`
12
- > and are never repeated) and a `mode:` (`qa` | `review`) naming the file's
13
- > FORMAT. The LSP stops hardcoding basenames: it reads the gtd config from the
14
- > workspace (cwd/parent lookup, same cosmiconfig search the CLI uses), maps
15
- > rendered file paths to modes, and regains the jump-to-relevant- file command —
16
- > full v2 functionality, zero hardcoded workflow knowledge.
17
-
18
- ## 1. The two new state properties
19
-
20
- | Property | Meaning |
21
- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
22
- | `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. |
23
- | `mode:` | Optional, requires `file:`. The file's FORMAT, from a closed, documented vocabulary: `qa` (the open-questions format — `## Open Questions` / `## Answered Questions` sections, `###` question with a free-form body, status positional) 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. |
24
-
25
- Multiple states may (and in the default, do) share one file+mode — the mode
26
- describes the file, the association is per state.
27
-
28
- **Emission:** `gtd next --json` and `gtd status --json` gain optional `file`
29
- (rendered) and `mode` fields, omitted when unset (exactly like `model`); plain
30
- `gtd status` prints `File:`/`Mode:` lines when set. This gives drivers and
31
- editors the jump target without speaking LSP, and gives e2e an assertion
32
- surface.
33
-
34
- **Engine/compiler touch points:** `StateDef` gains `file?`/`mode?`;
35
- `validateDefinition` gains the rules above (non-empty strings, mode ∈ {qa,
36
- review}, mode requires file, both forbidden on commit states, errors
37
- aggregated); `PatternConfig` compiles the two keys (KNOWN_STATE_KEYS, string
38
- checks); `Edge`/`program` render `file:` at rest exactly where `model` is
39
- rendered (a render failure errors like a model render failure).
40
-
41
- ## 2. The bundled default gets the association
42
-
43
- New `vars:` entries (single source for the filenames):
44
-
45
- ```yaml
46
- vars:
47
- testCommand: npm test
48
- todoFile: .gtd/TODO.md
49
- reviewFile: .gtd/REVIEW.md
50
- feedbackFile: .gtd/FEEDBACK.md
51
- ```
52
-
53
- Per-state mapping (10 states):
54
-
55
- | States | `file:` | `mode:` |
56
- | ---------------------------------------------- | ----------------------------- | ------------------------- |
57
- | `grilling`, `grilling-answer`, `building` | `<%= it.vars.todoFile %>` | `qa` |
58
- | `reviewing`, `await-review`, `review-deciding` | `<%= it.vars.reviewFile %>` | `review` |
59
- | `fixing`, `escalate` | `<%= it.vars.feedbackFile %>` | — (plain text, no format) |
60
- | `idle`, `checking` | — | — |
61
-
62
- Prompts and scripts that already name these files switch to the vars
63
- (`<%~ it.vars.todoFile %>` etc.) so each filename has ONE source of truth in
64
- templates. **Known limitation, documented:** `on` pattern keys are NOT Eta
65
- templates — the default's patterns keep literal `.gtd/…` paths, so repointing a
66
- filename var (`.gtdrc`/`GTD_`) without also overriding the workflow's patterns
67
- desyncs the machine. The vars are a DRY mechanism inside templates and the
68
- state↔file association, not a rename switch. (Making pattern keys var-aware at
69
- compile time is noted as possible future work.)
70
-
71
- The advanced flow of the bundled unified template (see STATES.md §10) gains the
72
- same `file:`/`mode:` annotations (it is the LSP-heavy flow), including
73
- `architectureFile` for its architecting phases (also `qa`-format).
74
-
75
- ## 3. The LSP becomes config-driven
76
-
77
- - **Config discovery:** the server locates the gtd config by the SAME
78
- cosmiconfig search the CLI uses, from the workspace root (initialize
79
- `workspaceFolders`/`rootUri`), falling back to the open document's directory
80
- walking upward. Reuse `ConfigService`'s loading/compiling — no second config
81
- code path. Config is (re)loaded lazily per request (it is small); no watcher
82
- needed for v1.
83
- - **Path→mode dispatch:** compile the definition, render every state's `file:`
84
- (vars-layer context; the LSP process's env supplies `GTD_` overrides like any
85
- invocation), and build a map of absolute rendered paths → mode. Document
86
- features dispatch on that map: `qa` files get question symbols + parser-error
87
- diagnostics; `review` files get one headline symbol per chunk that still has
88
- an unchecked hunk (an outline of the packages left to review — no hunk
89
- children) + check/uncheck code actions + diagnostics. Conflicting modes for
90
- one path: first declaring state wins, log a warning. **Fallback:** when NO
91
- config resolves (or a `.gtd` file isn't mapped), keep today's basename
92
- dispatch (`TODO.md` → qa, `REVIEW.md` → review) so the server still works
93
- standalone.
94
- - **The jump command returns:** `gtd.openSteeringFile` — resolve the CURRENT
95
- state exactly like the CLI (config + git HEAD via the existing Edge helpers;
96
- this re-adds the git/config wiring the v2 server had), render its `file:`, and
97
- `ShowDocumentRequest` it; a state with no `file:` yields an informational
98
- message naming the state (v2 behavior). Registered as an executeCommand;
99
- documented for editor keybinding.
100
-
101
- ## 4. Change chart
102
-
103
- | Area | Change |
104
- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
105
- | `src/PatternMachine.ts` | `StateDef.file?/mode?` + validation rules (closed mode set). |
106
- | `src/PatternConfig.ts` | Compile `file`/`mode` keys. |
107
- | `src/Edge.ts` / `src/program.ts` | Render `file:` at rest; `file`/`mode` in both `--json` payloads + plain `status` lines. |
108
- | `src/Lsp.ts` | Config discovery, path→mode dispatch (with basename fallback), `gtd.openSteeringFile`. |
109
- | `src/workflows/default.yaml` | §2's vars + per-state `file:`/`mode:`; templates switch to filename vars. |
110
- | Docs | STATES.md property table + §10; configuration.md (`file:`/`mode:` section + mode vocabulary + the pattern-literal limitation); cli.md JSON contracts; the bundled unified template's advanced-flow annotations (STATES.md §10); upgrading note (jump command is back, config-driven). |
111
- | 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. |
112
- | 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`). |
113
-
114
- ## 5. Out of scope
115
-
116
- - Eta-rendering of `on` pattern keys (the rename-switch story) — future work,
117
- noted in §2.
118
- - New LSP modes beyond `qa`/`review` — the vocabulary can grow with new formats;
119
- adding one means: parser (spec), a `gtd validate` dispatch arm for the mode,
120
- LSP dispatch arm, docs.