@pmelab/gtd 7.0.0 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/cli.md CHANGED
@@ -25,6 +25,11 @@ Commands:
25
25
  state (fixEntry: true) that goes straight into repairing the
26
26
  current failing tests. Requires a clean tree resting at the
27
27
  workflow's initial state
28
+ abandon End the process currently underway without completing it:
29
+ close any open review checkout window, then rewind HEAD to
30
+ the commit the process started from, keeping everything it
31
+ produced as uncommitted changes. A no-op when no process is
32
+ underway
28
33
  next Print the resolved rest's rendered script/prompt/message
29
34
  (no mutation)
30
35
  status Print the resolved rest's state/actor and which declared
@@ -32,14 +37,15 @@ Commands:
32
37
  validate Format and validate the steering file the resolved rest
33
38
  declares, with its mode's commands (its file:/mode:);
34
39
  exits non-zero with the findings when it is invalid
35
- mermaid Print the active workflow's shape as Mermaid
36
- stateDiagram-v2 source (no mutation)
37
40
  lsp Start the LSP server for .gtd/ steering files (stdio)
41
+ visualize Serve an interactive diagram of the active workflow locally
38
42
  version Print version and exit
39
43
  help Print this help and exit
40
44
 
41
45
  Options:
42
46
  --json Output structured JSON instead of plain text
47
+ --port=<n> (gtd visualize only) port to serve on (default: a free port)
48
+ --no-open (gtd visualize only) do not open the browser
43
49
  --cost=<n> (gtd step only) record the invocation's token cost
44
50
  --model=<name> (gtd step only, with --cost) tag that cost's model
45
51
  --version, -v Print version and exit
@@ -166,7 +172,8 @@ written):
166
172
 
167
173
  - the machine currently resting at the workflow's **initial state** (a plain
168
174
  non-gtd branch resolves there via the inert-subject rule — the normal case; a
169
- process already underway refuses);
175
+ process already underway refuses — finish it, or
176
+ [`gtd abandon`](#gtd-abandon---json) it);
170
177
  - a **clean working tree**;
171
178
  - the active workflow declaring a **`reviewEntry: true`** state (otherwise:
172
179
  `gtd review: the active workflow declares no review entry state`);
@@ -212,7 +219,8 @@ Requires, in order (any failure is a plain refusal — exit non-zero, nothing
212
219
  written):
213
220
 
214
221
  - the machine currently resting at the workflow's **initial state** (a process
215
- already underway refuses);
222
+ already underway refuses — finish it, or [`gtd abandon`](#gtd-abandon---json)
223
+ it);
216
224
  - a **clean working tree**;
217
225
  - the active workflow declaring a **`fixEntry: true`** state (otherwise:
218
226
  `gtd fix: the active workflow declares no fix entry state`).
@@ -231,6 +239,62 @@ unified template the fix-entry state is `fix-check`: a red suite drops into the
231
239
  shared `fixing` loop and out through the review + squash tail; a green suite is
232
240
  a no-op back to `idle`.
233
241
 
242
+ ## `gtd abandon [--json]`
243
+
244
+ Ends the process currently underway **without completing it** — the way out of a
245
+ process nobody is going to finish, and the command `gtd review`/`gtd fix` name
246
+ when they refuse ("finish it, or run `gtd abandon`, before starting a review").
247
+ A workflow's own exit is its squash finale
248
+ ([STATES.md §8](../STATES.md#8-the-squash-lifecycle)); this is the human's, and
249
+ it targets the same boundary:
250
+
251
+ 1. any open **review checkout window** is closed first (the bracket every state
252
+ subcommand runs — see
253
+ [STATES.md §11](../STATES.md#11-the-review-checkout-window)), so the rewind
254
+ starts from the real head, not the rewound base;
255
+ 2. HEAD is `git reset --mixed`ed to the commit the process started from (its
256
+ start parent — the same target a squash resets to).
257
+
258
+ **Nothing is discarded.** Every turn commit the process wrote is dropped, and
259
+ everything they carried — the code, the `.gtd/` steering files — stays in the
260
+ working tree as uncommitted changes for you to keep, re-commit, or throw away
261
+ with ordinary git:
262
+
263
+ ```
264
+ abandoned the process resting at "await-review" — HEAD is back at 1a2b3c4
265
+ ("feat: add calculator"), resting at "idle".
266
+ Everything the process produced is kept as uncommitted changes (`git status`);
267
+ discard them with `git checkout -- . && git clean -fd .gtd` for a clean tree.
268
+ ```
269
+
270
+ Takes no positional argument (extra arguments are a usage error). Resting at the
271
+ workflow's initial state is a **no-op success**, not a refusal — a recovery
272
+ command that fails when there is nothing to recover is a worse tool:
273
+
274
+ ```
275
+ no gtd process is underway (resting at "idle") — nothing to abandon
276
+ ```
277
+
278
+ The one refusal is a process whose first commit is the repository's own root
279
+ commit: there is no earlier commit to rewind to, so its commits have to be
280
+ removed by hand.
281
+
282
+ `--json` emits `{state, abandoned, from, head}` — `state` is the initial state
283
+ the machine returns to, `abandoned` whether anything was rewound, and (only when
284
+ it was) `from` the state the abandoned process rested at plus `head` the full
285
+ hash HEAD now points at:
286
+
287
+ ```json
288
+ {
289
+ "state": "idle",
290
+ "abandoned": true,
291
+ "from": "await-review",
292
+ "head": "1a2b3c4…"
293
+ }
294
+ ```
295
+
296
+ The no-op reports `{"state": "idle", "abandoned": false}` and still exits 0.
297
+
234
298
  ## `gtd next [--json]`
235
299
 
236
300
  Pure emitter of the resolved rest's rendered content — it **never mutates** the
@@ -394,46 +458,6 @@ agent or human:
394
458
 
395
459
  `gtd validate` takes no arguments.
396
460
 
397
- ## `gtd mermaid`
398
-
399
- Pure emitter of the active workflow's **shape** — not the resolved rest — as
400
- Mermaid [`stateDiagram-v2`](https://mermaid.js.org/syntax/stateDiagram.html)
401
- source (see `src/Mermaid.ts`): one node per declared state, the `[*] -->`
402
- initial-state marker, one edge per declared `on` row labeled with its raw
403
- pattern string (same declaration order the engine itself evaluates), a `--> [*]`
404
- edge for every commit state (final, no outgoing edges — see
405
- [STATES.md §8](../STATES.md#8-the-squash-lifecycle)), and one `note right of`
406
- per rest naming its actor, content kind, and retry cap (e.g.
407
- `agent · prompt · retry 3→escalate`). No git, no HEAD resolution, no template
408
- rendering — purely a function of the compiled `WorkflowDefinition`, so its
409
- output is identical regardless of the current process/branch state.
410
-
411
- ```
412
- $ gtd mermaid
413
- stateDiagram-v2
414
- state "idle" as idle
415
- state "planning" as planning
416
- ...
417
- [*] --> idle
418
- idle --> planning : * **
419
- ...
420
- note right of idle : human · message
421
- ...
422
- ```
423
-
424
- Pipe it straight into a `.md`/`.mmd` file, a GitHub issue/PR description, or any
425
- Mermaid-aware renderer (GitHub, GitLab, VS Code, Obsidian, the
426
- [Mermaid Live Editor](https://mermaid.live)) to get a diagram of a custom
427
- `.gtdrc` `workflow:` with no hand-maintained docs required.
428
-
429
- State names are aliased to Mermaid-safe identifiers (non-word characters fold to
430
- `_`; a digit-led name gets an `s_` prefix) via a `state "<name>" as <alias>`
431
- declaration up front, so a hyphenated name like `plan-review` still displays
432
- with its exact declared spelling. Rejects `--json` (exit 1,
433
- `gtd mermaid does not accept --json`) — there is no structured shape to emit
434
- beyond the Mermaid source itself — and takes no arguments (extra positional args
435
- are rejected).
436
-
437
461
  ## `gtd lsp`
438
462
 
439
463
  Starts an LSP server over stdio for `.gtd/` steering files — document symbols
@@ -469,6 +493,39 @@ extra positional arguments — it's a long-running server, not a state command.
469
493
  Runs until the client disconnects (the LSP `exit` notification), then exits
470
494
  cleanly.
471
495
 
496
+ ## `gtd visualize [--port=<n>] [--no-open] [--json]`
497
+
498
+ Serve an interactive diagram of the ACTIVE workflow on a local web server: the
499
+ main flow as a graph (one box per sub-machine), and a click-through inspector
500
+ with each state's actor, content kind, model/memory, steering file+mode, retry,
501
+ flags, and outgoing/incoming edges. This is the replacement for the removed
502
+ `gtd mermaid` — a live viewer instead of a static diagram dump.
503
+
504
+ ```
505
+ $ gtd visualize
506
+ gtd visualize running at http://127.0.0.1:53017 — Ctrl-C to stop
507
+ ```
508
+
509
+ The server serves two routes: `/` (the self-contained HTML page) and
510
+ `/workflow.json` (the model the page renders). It runs until interrupted
511
+ (Ctrl-C), then closes cleanly. Options (orthogonal, `gtd visualize` only):
512
+
513
+ - `--port=<n>` (or `--port <n>`) — serve on a specific port (0–65535); the
514
+ default is a free ephemeral port, printed on start.
515
+ - `--no-open` — do not open the default browser (the URL is always printed).
516
+ - `--json` — print the workflow model to stdout and exit WITHOUT starting a
517
+ server. The model is `{ states, initial, groups, vars }`; each state carries
518
+ its `actor`/`kind`/`model`/`memory`/`file`/`mode`/`retry`/`flags`, its `on`
519
+ edges, its computed `incoming` edges, and its sub-machine `group`. `groups`
520
+ lists each sub-machine invocation and the concrete states it produced.
521
+
522
+ Dispatched before the repository-root guard and the config-reading path's review
523
+ window — it reads the active workflow (the built-in default when none is
524
+ configured) but touches no git/HEAD/review-window state. The diagram is rendered
525
+ with Mermaid loaded from a CDN, so the graph needs network access the first time
526
+ a browser loads the page; the inspector works offline regardless. Rejects
527
+ unknown options and unexpected positional arguments.
528
+
472
529
  ## Error envelope
473
530
 
474
531
  Every command, in `--json` mode, reports a failure as a machine-readable
@@ -496,8 +553,16 @@ the plain-text one.
496
553
  green run, and the check's `"C"` pattern never fires. Gitignore every path
497
554
  your scripts write before wiring gtd into a repo.
498
555
  - **Repository root invocation.** Every state subcommand (`step`/`review`/
499
- `next`/`status`/`mermaid`) must run from the git repository root — the
500
- workflow, pending changes, and process history are resolved against the
501
- process cwd. `--help`/`--version` (and the `help`/`version` subcommands),
502
- `format`, and `lsp` skip this guard entirely (and any git/`.gtdrc` dependency
503
- along with it).
556
+ `next`/`status`) must run from the git repository root — the workflow, pending
557
+ changes, and process history are resolved against the process cwd.
558
+ `--help`/`--version` (and the `help`/`version` subcommands), `lsp`, and
559
+ `visualize` skip this guard entirely (`visualize` still reads the `.gtdrc`
560
+ workflow, but needs no git state).
561
+ - **Linked worktrees are independent.** N `git worktree` worktrees of one
562
+ repository (sharing a single `.git`) each run their own gtd process: state is
563
+ derived from that worktree's own HEAD, and the review checkout window's refs
564
+ live in git's per-worktree `refs/worktree/gtd/*` namespace, so a review
565
+ resting in one worktree neither blocks nor rewrites any other
566
+ ([STATES.md §11](../STATES.md#11-the-review-checkout-window)). This holds from
567
+ 7.2 on; gtd ≤ 7.1 kept the window under the shared `refs/gtd/*` refs, where
568
+ the first worktree to open one clobbered every sibling's branch.
@@ -449,9 +449,10 @@ The unified template enables `reviewWindow: true` on `await-review` and marks
449
449
  `review-deciding` `reviewBase: true`, so the first review covers the whole cycle
450
450
  but each subsequent feedback round's window narrows to `last-review..HEAD`. The
451
451
  pure engine never observes an open window — it is opened/closed entirely at the
452
- edge; the real head is preserved under `refs/gtd/review-head` (the base under
453
- `refs/gtd/review-base`) for the window's lifetime. See
454
- [STATES.md §11](../STATES.md) for the full lifecycle.
452
+ edge; the real head is preserved under `refs/worktree/gtd/review-head` (the base
453
+ under `refs/worktree/gtd/review-base`) for the window's lifetime — git's
454
+ per-worktree ref namespace, so linked worktrees of one repository each get their
455
+ own window. See [STATES.md §11](../STATES.md) for the full lifecycle.
455
456
 
456
457
  ### `reviewEntry:` — the review entry point (`gtd review <commitish>`)
457
458
 
@@ -588,6 +589,71 @@ done:
588
589
  command), and gtd never interprets the number's unit (tokens, cents, whatever
589
590
  the driver records) or the model name — it only records, sums, and groups them.
590
591
 
592
+ ## Sub-machines (`submachines:` / `use:`)
593
+
594
+ Two OPTIONAL top-level keys let you factor repeated or complex clusters of
595
+ states into reusable **sub-machines** that are expanded into ordinary concrete
596
+ states at load time (see [STATES.md §13](../STATES.md) and
597
+ `src/Submachines.ts`). They add NO runtime behavior — the engine only ever sees
598
+ the expanded states — so a workflow authored with them is exactly a workflow
599
+ written flat.
600
+
601
+ ```yaml
602
+ workflow:
603
+ submachines:
604
+ gate: # authored once
605
+ params: [onGreen] # names referenced as `$onGreen` below
606
+ states:
607
+ check:
608
+ actor: check
609
+ script: <%~ it.vars.testCommand %>
610
+ on:
611
+ "A .gtd/FEEDBACK.md": blocked # a local state -> renamed by `as:`
612
+ "C": "$onGreen" # a param -> bound by `with:`
613
+ blocked:
614
+ actor: human
615
+ file: <%= it.vars.feedbackFile %>
616
+ message: "fix the baseline"
617
+ on: { "* **": check }
618
+ use:
619
+ - submachine: gate
620
+ as: { check: start-check, blocked: start-blocked } # local -> concrete (omit for identity)
621
+ with: { onGreen: planning } # bind each `$param`
622
+ set: { check: { reviewEntry: true } } # optional extra fields on a renamed state
623
+ states:
624
+ idle:
625
+ { actor: human, initial: true, message: "…", on: { "* **": start-check } }
626
+ planning: { actor: agent, prompt: "…", on: { "* **": start-check } }
627
+ ```
628
+
629
+ Rules:
630
+
631
+ - **`submachines: { <name>: { params, states } }`** — `states` are ordinary
632
+ state definitions, except an `on`/`retry.otherwise` target or any whole field
633
+ value may be a `$param` placeholder.
634
+ - **`use:`** is an ordered list. Each entry names a `submachine:` and may set:
635
+ - **`as:`** — a `local -> concreteName` rename map. Omit it (or a given local)
636
+ for identity. Alias to the names a flat workflow would use to keep history
637
+ and `git log` unchanged.
638
+ - **`with:`** — bindings for the sub-machine's `$param`s.
639
+ - **`set:`** — extra fields merged onto a renamed state (e.g. `reviewEntry`).
640
+ - **Substitution is WHOLE-VALUE only.** `$name` resolves only when it is the
641
+ entire value of a field or target — never a substring — so bash `$x`/`${x}`
642
+ and Eta `<%= it.vars.x %>` inside content are untouched. Pass per-instance
643
+ text (a whole message/prompt/`describe`) as a whole `$name` binding.
644
+ - A target naming one of the sub-machine's own locals is rewritten via `as:`;
645
+ any other target passes through verbatim (a concrete top-level state). `on`
646
+ PATTERN keys are never substituted.
647
+ - Expansion strips `submachines:`/`use:`; the merged `states` are then compiled
648
+ and `validateDefinition`'d normally. Unknown sub-machine, unbound `$param`,
649
+ and generated-name collisions are load-time errors, aggregated with the rest.
650
+
651
+ `gtd init` scaffolds the EXPANDED flat config, so a fresh project never sees
652
+ `submachines:`/`use:` unless it adds them itself. The bundled template is
653
+ authored with them (dedup: `assertGreen`/`makeGreen`/`qaLoop`; comprehension:
654
+ `planLoop`/`humanReview`/`specGate`/`packageLoop`) and its golden test pins that
655
+ expansion is byte-identical to the flat form.
656
+
591
657
  ## Variables
592
658
 
593
659
  Every template — `script`/`prompt`/`message`/`commit`, and `model`/`memory`/
package/docs/loop.md CHANGED
@@ -34,6 +34,19 @@ capture for the human as its opening move (see below), so re-launching the loop
34
34
  is the only thing the human ever does — at a gate they edit and re-run, and the
35
35
  loop picks their change up.
36
36
 
37
+ **A `"message"` rest is a real handoff, not a slow beat.** No driver signs off
38
+ for the human: in the bundled workflow the loop runs the whole cycle unattended
39
+ up to `await-review` and then STOPS there with the review checkout window open
40
+ (the reviewable diff surfaced as uncommitted changes — see
41
+ [STATES.md §11](../STATES.md#11-the-review-checkout-window)). Advancing takes a
42
+ human edit to `.gtd/REVIEW.md`: ticking every box with **no** comment signs off
43
+ into the squash finale, while any comment (a note, or a code edit of your own)
44
+ is feedback that routes into another build + re-review round. Re-launching the
45
+ loop after that edit picks the decision up. Until then the loop halts on every
46
+ run — that is the contract, not a stall. A review nobody is going to finish is
47
+ ended with [`gtd abandon`](cli.md#gtd-abandon---json), which drops the process's
48
+ commits and keeps their content as uncommitted changes.
49
+
37
50
  ```bash
38
51
  gtd next --json # ask who's up and what they should do
39
52
  ```
package/docs/upgrading.md CHANGED
@@ -80,8 +80,14 @@ upgrading.
80
80
  existing review/feedback machinery unmodified — see
81
81
  [STATES.md §11](../STATES.md#11-the-review-checkout-window) and
82
82
  [CLI reference](cli.md#gtd-review-commitish---json). The current command
83
- surface is `init` / `step` / `next` / `status` / `validate` / `mermaid` /
84
- `review` / `lsp`.
83
+ surface is `init` / `step` / `next` / `status` / `validate` / `review` /
84
+ `lsp`.
85
+
86
+ - **`gtd mermaid` → `gtd visualize`.** The static Mermaid emitter has been
87
+ replaced by `gtd visualize`, which serves an interactive diagram of the active
88
+ workflow (main flow, sub-machines, per-state details) on a local web server —
89
+ see [CLI reference](cli.md#gtd-visualize---portn---no-open---json). Both are
90
+ read-only views of the active workflow; nothing in the machine changes.
85
91
 
86
92
  - **`gtd format <file>`, and the bundled prettier with it.** gtd no longer
87
93
  formats anything on its own: a steering-file mode declares its own `format:`
@@ -153,6 +159,42 @@ loudly at load time rather than silently misinterpreting.
153
159
  or agent harness, upgrading the `gtd` binary also means re-copying that skill
154
160
  from this release.
155
161
 
162
+ ## 7.2: the review checkout window is now per worktree
163
+
164
+ The review checkout window's two refs moved from the SHARED `refs/gtd/*`
165
+ namespace to git's PER-WORKTREE `refs/worktree/*` one:
166
+
167
+ | before (≤ 7.1) | from 7.2 |
168
+ | ---------------------- | ------------------------------- |
169
+ | `refs/gtd/review-head` | `refs/worktree/gtd/review-head` |
170
+ | `refs/gtd/review-base` | `refs/worktree/gtd/review-base` |
171
+
172
+ Nothing in a `.gtdrc` or a workflow definition references those names, so **no
173
+ config change is needed**. What changes is behaviour in a repository checked out
174
+ as several linked worktrees (`git worktree add`, one `.git` shared between
175
+ them): each worktree now has its own window, so a review resting in one no
176
+ longer "closes" from another — which, on the shared refs, mixed-reset that other
177
+ worktree's branch onto the reviewing worktree's saved head and left every
178
+ sibling refusing with "a process is already underway".
179
+
180
+ A window an OLDER gtd left open across the upgrade still closes from the legacy
181
+ refs, so an in-flight review in a single-worktree repo needs no attention. In a
182
+ multi-worktree repo gtd cannot tell which worktree a shared ref belongs to, so
183
+ it closes only when HEAD is contained in the saved head and otherwise refuses
184
+ with the exact recovery commands — run them in the worktree that owns the
185
+ window:
186
+
187
+ ```bash
188
+ git reset --mixed refs/gtd/review-head
189
+ git update-ref -d refs/gtd/review-head
190
+ git update-ref -d refs/gtd/review-base
191
+ ```
192
+
193
+ 7.2 also adds [`gtd abandon`](cli.md#gtd-abandon---json), the supported way out
194
+ of a process that will never be finished (it closes the window, then rewinds
195
+ HEAD to the process's start parent, keeping everything the process produced as
196
+ uncommitted changes) — no hand-editing of refs required.
197
+
156
198
  ## Prior breaking change: the v1 → v2 label grammar (historical)
157
199
 
158
200
  v2 replaced v1's undifferentiated turn labels with six labels carrying the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "7.0.0",
3
+ "version": "7.1.1",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {
package/schema.json CHANGED
@@ -36,6 +36,182 @@
36
36
  }
37
37
  }
38
38
  },
39
+ "submachines": {
40
+ "type": "object",
41
+ "description": "Optional reusable sub-machines, invoked by `use:` and expanded into concrete states at load time (see src/Submachines.ts).",
42
+ "additionalProperties": {
43
+ "type": "object",
44
+ "description": "A reusable, parameterized cluster of states, expanded at load time by each `use:` invocation into concrete states (see src/Submachines.ts). Purely a source-authoring convenience — the engine only ever sees the expanded states.",
45
+ "additionalProperties": false,
46
+ "required": ["states"],
47
+ "properties": {
48
+ "params": {
49
+ "type": "array",
50
+ "items": {
51
+ "type": "string"
52
+ },
53
+ "description": "Parameter names. A `$name` token used as a whole field value or `on`/`retry.otherwise` target is replaced by the invocation's `with:` binding."
54
+ },
55
+ "states": {
56
+ "type": "object",
57
+ "description": "The sub-machine's local states. `on`/`retry.otherwise` targets naming another local are rewritten via `as:`; a `$name` value is bound from `with:`.",
58
+ "minProperties": 1,
59
+ "additionalProperties": {
60
+ "type": "object",
61
+ "description": "One workflow state. Declare exactly one content kind (script/prompt/message/commit). A commit state is final: no actor, no on.",
62
+ "additionalProperties": false,
63
+ "properties": {
64
+ "actor": {
65
+ "type": "string",
66
+ "description": "Who acts at this state. Required on every non-commit state; forbidden on a commit state."
67
+ },
68
+ "script": {
69
+ "type": "string",
70
+ "description": "Content kind: a shell script (Eta template). The loop driver executes it verbatim via bash, then steps this state's actor."
71
+ },
72
+ "prompt": {
73
+ "type": "string",
74
+ "description": "Content kind: an agent prompt (Eta template), emitted by `gtd next`."
75
+ },
76
+ "message": {
77
+ "type": "string",
78
+ "description": "Content kind: a human-facing message (Eta template), emitted by `gtd next`."
79
+ },
80
+ "commit": {
81
+ "type": "string",
82
+ "description": "Content kind: entering this state ends the process by squashing it into one commit with this message (Eta template). Final — no actor, no on."
83
+ },
84
+ "on": {
85
+ "type": "object",
86
+ "description": "Ordered map of change pattern -> target state. Patterns: \"C\" (clean tree) or \"<A|M|D|*> <glob>\" over the pending diff; first declared match wins. Every target must name a defined state, and every non-initial state must be reachable through these edges (or a retry.otherwise). A value is either the target state name (string) or a { to, describe } object whose describe is a human-readable sentence templates can surface as it.edges (e.g. in a human gate's message).",
87
+ "additionalProperties": {
88
+ "oneOf": [
89
+ {
90
+ "type": "string",
91
+ "description": "The target state name."
92
+ },
93
+ {
94
+ "type": "object",
95
+ "additionalProperties": false,
96
+ "required": ["to"],
97
+ "properties": {
98
+ "to": {
99
+ "type": "string",
100
+ "description": "The target state name."
101
+ },
102
+ "describe": {
103
+ "type": "string",
104
+ "description": "Human-readable sentence describing where this change routes; surfaced verbatim (never Eta-rendered) to templates as it.edges[].describe."
105
+ }
106
+ }
107
+ }
108
+ ]
109
+ }
110
+ },
111
+ "initial": {
112
+ "type": "boolean",
113
+ "description": "Exactly one state in the workflow must declare initial: true (and it must not be a commit state)."
114
+ },
115
+ "retry": {
116
+ "type": "object",
117
+ "description": "Redirect transitions INTO this state once it has been entered `max` times in the current process.",
118
+ "additionalProperties": false,
119
+ "required": ["max", "otherwise"],
120
+ "properties": {
121
+ "max": {
122
+ "type": "integer",
123
+ "minimum": 0,
124
+ "description": "Entries allowed this process before redirecting."
125
+ },
126
+ "otherwise": {
127
+ "type": "string",
128
+ "description": "Defined state to redirect to once over the cap."
129
+ }
130
+ }
131
+ },
132
+ "model": {
133
+ "type": "string",
134
+ "description": "Opaque harness hint passed through `gtd next --json`/`gtd status --json` (e.g. \"smart\"). Never interpreted by gtd. Forbidden on a commit state."
135
+ },
136
+ "memory": {
137
+ "type": "string",
138
+ "description": "Opaque memory-scope label passed through `gtd next --json`/`gtd status --json` (e.g. \"plan\"). A memory-aware driver retains an agent's memory across consecutive agent turns sharing this label and starts fresh when it changes. Never interpreted by gtd. Forbidden on a commit state."
139
+ },
140
+ "file": {
141
+ "type": "string",
142
+ "description": "The state's steering file: an Eta template naming the file a human/editor should look at while the machine rests here. Forbidden on a commit state."
143
+ },
144
+ "mode": {
145
+ "type": "string",
146
+ "description": "The steering file's format: the name of a built-in mode (qa/review, validated in-process by gtd) or of a `modes:` entry. gtd formats and validates the file with that mode before capturing a turn out of this state, and the LSP dispatches live diagnostics on the built-in names. Requires a sibling `file:`. Forbidden on a commit state."
147
+ },
148
+ "reviewWindow": {
149
+ "type": "boolean",
150
+ "description": "When true, gtd opens a review checkout window while the machine rests here — HEAD/index are rewound to the review base so the whole base..HEAD diff surfaces as uncommitted changes in the editor. Forbidden on a commit state."
151
+ },
152
+ "reviewBase": {
153
+ "type": "boolean",
154
+ "description": "Marks the state whose most-recent in-process commit anchors the review window's diff base; absent any, the base is the process start. Forbidden on a commit state."
155
+ },
156
+ "reviewEntry": {
157
+ "type": "boolean",
158
+ "description": "Marks the (at most one) state `gtd review <commitish>` enters to start a brand new process reviewing <commitish>..HEAD. Forbidden on a commit state and on the initial state."
159
+ },
160
+ "fixEntry": {
161
+ "type": "boolean",
162
+ "description": "Marks the (at most one) state `gtd fix` enters to start a brand new process that goes straight into repairing the current failing tests. Forbidden on a commit state and on the initial state."
163
+ },
164
+ "requireProgress": {
165
+ "type": "boolean",
166
+ "description": "When true, a step at this state is refused if its only pending change is deleting the state's own `file:` — a work-free turn that discards its input without addressing it. A `NOTHING ACTIONABLE` sentinel file is exempt (a legitimately non-actionable round makes no code change). Requires a `file:`. Forbidden on a commit state."
167
+ },
168
+ "answerGate": {
169
+ "type": "boolean",
170
+ "description": "When true, a step at this state is refused unless every open question in its qa-mode `file:` is answered — exactly one checkbox ticked per question. Requires a `file:` and `mode: qa`. Forbidden on a commit state."
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ },
178
+ "use": {
179
+ "type": "array",
180
+ "description": "Sub-machine invocations. Each clones a `submachines:` entry into concrete states: `as:` renames its locals (identity when omitted), `with:` binds its `$param`s, `set:` adds extra per-instance state fields.",
181
+ "items": {
182
+ "type": "object",
183
+ "additionalProperties": false,
184
+ "required": ["submachine"],
185
+ "properties": {
186
+ "submachine": {
187
+ "type": "string",
188
+ "description": "Name of a declared sub-machine."
189
+ },
190
+ "name": {
191
+ "type": "string",
192
+ "description": "Optional label for this instance (inert to expansion; used by tooling like `gtd visualize` to name the group). Defaults to the sub-machine name."
193
+ },
194
+ "as": {
195
+ "type": "object",
196
+ "description": "Rename map: local state name -> concrete state name (identity when omitted).",
197
+ "additionalProperties": {
198
+ "type": "string"
199
+ }
200
+ },
201
+ "with": {
202
+ "type": "object",
203
+ "description": "Bindings for the sub-machine's `$param`s."
204
+ },
205
+ "set": {
206
+ "type": "object",
207
+ "description": "Extra fields merged onto a renamed local state, keyed by local name.",
208
+ "additionalProperties": {
209
+ "type": "object"
210
+ }
211
+ }
212
+ }
213
+ }
214
+ },
39
215
  "states": {
40
216
  "type": "object",
41
217
  "description": "The workflow's named states. At least one; exactly one with initial: true.",
@@ -148,6 +324,10 @@
148
324
  "requireProgress": {
149
325
  "type": "boolean",
150
326
  "description": "When true, a step at this state is refused if its only pending change is deleting the state's own `file:` — a work-free turn that discards its input without addressing it. A `NOTHING ACTIONABLE` sentinel file is exempt (a legitimately non-actionable round makes no code change). Requires a `file:`. Forbidden on a commit state."
327
+ },
328
+ "answerGate": {
329
+ "type": "boolean",
330
+ "description": "When true, a step at this state is refused unless every open question in its qa-mode `file:` is answered — exactly one checkbox ticked per question. Requires a `file:` and `mode: qa`. Forbidden on a commit state."
151
331
  }
152
332
  }
153
333
  }