@pmelab/gtd 8.3.1 → 8.5.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.
Files changed (5) hide show
  1. package/README.md +320 -118
  2. package/bin/gtd +80 -29
  3. package/dist/gtd.bundle.mjs +4684 -3247
  4. package/package.json +11 -9
  5. package/schema.json +147 -265
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "8.3.1",
3
+ "version": "8.5.0",
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": {
@@ -30,18 +30,20 @@
30
30
  "prepare": "husky",
31
31
  "dev": "node dev/run.mjs",
32
32
  "build": "tsdown",
33
- "postbuild": "jiti scripts/generate-schema.ts",
33
+ "postbuild": "jiti scripts/generate-schema.ts && node scripts/assert-no-test-doubles.mjs",
34
34
  "watch": "tsdown --watch",
35
35
  "test": "npm run format:check && npm run typecheck && npm run lint && npm run test:unit && npm run test:e2e && fallow --summary --quiet",
36
36
  "test:unit": "vitest run --project unit",
37
37
  "test:watch": "vitest",
38
- "pretest:e2e": "tsdown --logLevel silent",
39
- "test:e2e": "vitest run --project e2e",
38
+ "test:e2e": "npm run test:e2e:inmem && npm run test:e2e:live",
40
39
  "typecheck": "tsc --noEmit",
41
40
  "lint": "oxlint .",
42
41
  "format": "oxfmt --write .",
43
42
  "format:check": "oxfmt --check .",
44
- "test:mutation": "stryker run"
43
+ "test:mutation": "stryker run",
44
+ "test:e2e:inmem": "vitest run --project e2e-inmem",
45
+ "pretest:e2e:live": "tsdown --logLevel silent",
46
+ "test:e2e:live": "vitest run --project e2e-live"
45
47
  },
46
48
  "lint-staged": {
47
49
  "*": "oxfmt --no-error-on-unmatched-pattern --write"
@@ -56,7 +58,7 @@
56
58
  "@stryker-mutator/typescript-checker": "^9.6.1",
57
59
  "@stryker-mutator/vitest-runner": "^9.6.1",
58
60
  "@types/node": "^25.9.3",
59
- "fallow": "^2.104.0",
61
+ "fallow": "^3.14.0",
60
62
  "fast-check": "^4.8.0",
61
63
  "husky": "^9.1.7",
62
64
  "jiti": "^2.7.0",
@@ -67,8 +69,7 @@
67
69
  "quickpickle": "1.11.2",
68
70
  "semantic-release": "^25.0.5",
69
71
  "tsdown": "^0.22.8",
70
- "vitest": "^3.2.0",
71
- "yaml": "^2.8.2"
72
+ "vitest": "^3.2.0"
72
73
  },
73
74
  "peerDependencies": {
74
75
  "typescript": "^5"
@@ -80,6 +81,7 @@
80
81
  "effect": "^3.19.15",
81
82
  "eta": "^4.6.0",
82
83
  "vscode-languageserver": "^10.1.0",
83
- "vscode-languageserver-textdocument": "^1.0.13"
84
+ "vscode-languageserver-textdocument": "^1.0.13",
85
+ "yaml": "^2.8.2"
84
86
  }
85
87
  }
package/schema.json CHANGED
@@ -5,10 +5,11 @@
5
5
  "properties": {
6
6
  "workflow": {
7
7
  "type": "object",
8
- "description": "The whole machine definition: named states (plus the workflow's own vars: defaults and modes: steering-file modes). Compiled and validated by gtd at load time; content strings starting with ./ or ../ are file references inlined from the config file's directory (a modes: command never is — it is a shell command).",
8
+ "description": "The whole machine definition: a tree of named machines rooted at entry.default (plus the workflow's own vars: defaults and modes: steering-file modes). Compiled and validated by gtd at load time; content strings starting with ./ or ../ are file references inlined from the config file's directory (a modes: command never is — it is a shell command).",
9
9
  "additionalProperties": false,
10
10
  "required": [
11
- "states"
11
+ "entry",
12
+ "machines"
12
13
  ],
13
14
  "properties": {
14
15
  "vars": {
@@ -42,14 +43,30 @@
42
43
  }
43
44
  }
44
45
  },
45
- "submachines": {
46
+ "entry": {
46
47
  "type": "object",
47
- "description": "Optional reusable sub-machines, invoked by `use:` and expanded into concrete states at load time (see src/Submachines.ts).",
48
+ "description": "The workflow's root machine, resolved through the same resolver an `on`/`retry.otherwise` target uses (see src/Machines.ts) — accepts either a bare state path or an instance/reference-key path. Extra manual entry points are declared per-state instead, via a state's own `entry: true` (see `stateJsonSchema`'s `entry` property) — not here.",
49
+ "additionalProperties": false,
50
+ "required": [
51
+ "default"
52
+ ],
53
+ "properties": {
54
+ "default": {
55
+ "type": "string",
56
+ "description": "Which declared `machines:` entry is the ROOT instance."
57
+ }
58
+ }
59
+ },
60
+ "machines": {
61
+ "type": "object",
62
+ "description": "Named, reusable machines — at least the one entry.default names.",
63
+ "minProperties": 1,
48
64
  "additionalProperties": {
49
65
  "type": "object",
50
- "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.",
66
+ "description": "A named, reusable machine: an entry local plus a set of states, each either an ordinary state or a reference instantiating another machine as a child (see src/Machines.ts).",
51
67
  "additionalProperties": false,
52
68
  "required": [
69
+ "entry",
53
70
  "states"
54
71
  ],
55
72
  "properties": {
@@ -58,296 +75,161 @@
58
75
  "items": {
59
76
  "type": "string"
60
77
  },
61
- "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."
78
+ "description": "Advisory only — documents which $params a caller may bind via a reference's `with:`. A `$name` token used as a whole field value or `on`/`retry.otherwise` target is resolved against the binding."
79
+ },
80
+ "entry": {
81
+ "type": "string",
82
+ "description": "This machine's own default local (a local state name, or a reference key), resolved recursively."
83
+ },
84
+ "model": {
85
+ "type": "string",
86
+ "description": "Opaque harness hint stamped onto every one of this machine's own `prompt` states (e.g. \"smart\"), passed through `gtd next --json`/`gtd status --json`. The ONLY place a model may be declared — a state carrying its own `model:` is a config error. Never interpreted by gtd. A machine declaring this with no `prompt` state is a config error."
62
87
  },
63
88
  "states": {
64
89
  "type": "object",
65
- "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:`.",
90
+ "description": "This machine's local states, each either an ordinary state or a `{ machine, with }` reference.",
66
91
  "minProperties": 1,
67
92
  "additionalProperties": {
68
- "type": "object",
69
- "description": "One workflow state. Declare exactly one content kind (script/prompt/message/commit). A commit state is final: no actor, no on.",
70
- "additionalProperties": false,
71
- "properties": {
72
- "actor": {
73
- "type": "string",
74
- "description": "Who acts at this state. Required on every non-commit state; forbidden on a commit state."
75
- },
76
- "script": {
77
- "type": "string",
78
- "description": "Content kind: a shell script (Eta template). The loop driver executes it verbatim via bash, then steps this state's actor."
79
- },
80
- "prompt": {
81
- "type": "string",
82
- "description": "Content kind: an agent prompt (Eta template), emitted by `gtd next`."
83
- },
84
- "message": {
85
- "type": "string",
86
- "description": "Content kind: a human-facing message (Eta template), emitted by `gtd next`."
87
- },
88
- "commit": {
89
- "type": "string",
90
- "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."
91
- },
92
- "on": {
93
+ "oneOf": [
94
+ {
93
95
  "type": "object",
94
- "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).",
95
- "additionalProperties": {
96
- "oneOf": [
97
- {
98
- "type": "string",
99
- "description": "The target state name."
100
- },
101
- {
102
- "type": "object",
103
- "additionalProperties": false,
104
- "required": [
105
- "to"
106
- ],
107
- "properties": {
108
- "to": {
96
+ "description": "One workflow state. Declare exactly one content kind (script/prompt/message/commit). A commit state is final: no actor, no on.",
97
+ "additionalProperties": false,
98
+ "properties": {
99
+ "actor": {
100
+ "type": "string",
101
+ "description": "Who acts at this state. Required on every non-commit state; forbidden on a commit state."
102
+ },
103
+ "script": {
104
+ "type": "string",
105
+ "description": "Content kind: a shell script (Eta template). The loop driver executes it verbatim via bash, then steps this state's actor."
106
+ },
107
+ "prompt": {
108
+ "type": "string",
109
+ "description": "Content kind: an agent prompt (Eta template), emitted by `gtd next`."
110
+ },
111
+ "message": {
112
+ "type": "string",
113
+ "description": "Content kind: a human-facing message (Eta template), emitted by `gtd next`."
114
+ },
115
+ "commit": {
116
+ "type": "string",
117
+ "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."
118
+ },
119
+ "on": {
120
+ "type": "object",
121
+ "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, action } object whose describe/action are human-readable strings templates can surface as it.edges (e.g. in a human gate's message).",
122
+ "additionalProperties": {
123
+ "oneOf": [
124
+ {
109
125
  "type": "string",
110
126
  "description": "The target state name."
111
127
  },
112
- "describe": {
113
- "type": "string",
114
- "description": "Human-readable sentence describing where this change routes; surfaced verbatim (never Eta-rendered) to templates as it.edges[].describe."
128
+ {
129
+ "type": "object",
130
+ "additionalProperties": false,
131
+ "required": [
132
+ "to"
133
+ ],
134
+ "properties": {
135
+ "to": {
136
+ "type": "string",
137
+ "description": "The target state name."
138
+ },
139
+ "describe": {
140
+ "type": "string",
141
+ "description": "Human-readable sentence describing where this change routes; surfaced verbatim (never Eta-rendered) to templates as it.edges[].describe."
142
+ },
143
+ "action": {
144
+ "type": "string",
145
+ "description": "Imperative label for this edge (e.g. \"Accept plan\"); surfaced verbatim (never Eta-rendered) to templates/tooling as it.edges[].action."
146
+ }
147
+ }
115
148
  }
149
+ ]
150
+ }
151
+ },
152
+ "retry": {
153
+ "type": "object",
154
+ "description": "Redirect transitions INTO this state once it has been entered `max` times in the current process.",
155
+ "additionalProperties": false,
156
+ "required": [
157
+ "max",
158
+ "otherwise"
159
+ ],
160
+ "properties": {
161
+ "max": {
162
+ "type": "integer",
163
+ "minimum": 0,
164
+ "description": "Entries allowed this process before redirecting."
165
+ },
166
+ "otherwise": {
167
+ "type": "string",
168
+ "description": "Defined state to redirect to once over the cap."
116
169
  }
117
170
  }
118
- ]
119
- }
120
- },
121
- "initial": {
122
- "type": "boolean",
123
- "description": "Exactly one state in the workflow must declare initial: true (and it must not be a commit state)."
124
- },
125
- "retry": {
126
- "type": "object",
127
- "description": "Redirect transitions INTO this state once it has been entered `max` times in the current process.",
128
- "additionalProperties": false,
129
- "required": [
130
- "max",
131
- "otherwise"
132
- ],
133
- "properties": {
134
- "max": {
135
- "type": "integer",
136
- "minimum": 0,
137
- "description": "Entries allowed this process before redirecting."
138
171
  },
139
- "otherwise": {
172
+ "label": {
173
+ "type": "string",
174
+ "description": "Opaque display name passed through `gtd next --json`/`gtd status --json` so a driver/viewer can show something nicer than the raw state name (e.g. \"Running checks\"). Never interpreted by gtd. Forbidden on a commit state."
175
+ },
176
+ "file": {
177
+ "type": "string",
178
+ "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."
179
+ },
180
+ "mode": {
140
181
  "type": "string",
141
- "description": "Defined state to redirect to once over the cap."
182
+ "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."
183
+ },
184
+ "reviewWindow": {
185
+ "type": "boolean",
186
+ "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."
187
+ },
188
+ "reviewBase": {
189
+ "oneOf": [
190
+ {
191
+ "const": true
192
+ },
193
+ {
194
+ "type": "string"
195
+ }
196
+ ],
197
+ "description": "true marks the state whose most-recent in-process commit anchors the review window's diff base; absent any, the base is the process start. A string is a different shape: an Eta template rendering a commitish that becomes the WHOLE PROCESS's fixed diff base when this state is entered manually via `gtd --entry <state> --base <commitish>` (see the `entry` property below). Forbidden on a commit state."
198
+ },
199
+ "requireProgress": {
200
+ "type": "boolean",
201
+ "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."
202
+ },
203
+ "answerGate": {
204
+ "type": "boolean",
205
+ "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."
206
+ },
207
+ "entry": {
208
+ "const": true,
209
+ "description": "Marks this state as an extra manual entry point (WorkflowEntries.manual), enterable via `gtd --entry <state>`. Not to be confused with the top-level `entry:` key naming the root machine (entry.default) — same name, different level, by design."
142
210
  }
143
211
  }
144
212
  },
145
- "model": {
146
- "type": "string",
147
- "description": "Opaque harness hint passed through `gtd next --json`/`gtd status --json` (e.g. \"smart\"). Never interpreted by gtd. Forbidden on a commit state."
148
- },
149
- "memory": {
150
- "type": "string",
151
- "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."
152
- },
153
- "file": {
154
- "type": "string",
155
- "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."
156
- },
157
- "mode": {
158
- "type": "string",
159
- "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."
160
- },
161
- "reviewWindow": {
162
- "type": "boolean",
163
- "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."
164
- },
165
- "reviewBase": {
166
- "type": "boolean",
167
- "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."
168
- },
169
- "reviewEntry": {
170
- "type": "boolean",
171
- "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."
172
- },
173
- "fixEntry": {
174
- "type": "boolean",
175
- "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."
176
- },
177
- "requireProgress": {
178
- "type": "boolean",
179
- "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."
180
- },
181
- "answerGate": {
182
- "type": "boolean",
183
- "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."
184
- }
185
- }
186
- }
187
- }
188
- }
189
- }
190
- },
191
- "use": {
192
- "type": "array",
193
- "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.",
194
- "items": {
195
- "type": "object",
196
- "additionalProperties": false,
197
- "required": [
198
- "submachine"
199
- ],
200
- "properties": {
201
- "submachine": {
202
- "type": "string",
203
- "description": "Name of a declared sub-machine."
204
- },
205
- "name": {
206
- "type": "string",
207
- "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."
208
- },
209
- "as": {
210
- "type": "object",
211
- "description": "Rename map: local state name -> concrete state name (identity when omitted).",
212
- "additionalProperties": {
213
- "type": "string"
214
- }
215
- },
216
- "with": {
217
- "type": "object",
218
- "description": "Bindings for the sub-machine's `$param`s."
219
- },
220
- "set": {
221
- "type": "object",
222
- "description": "Extra fields merged onto a renamed local state, keyed by local name.",
223
- "additionalProperties": {
224
- "type": "object"
225
- }
226
- }
227
- }
228
- }
229
- },
230
- "states": {
231
- "type": "object",
232
- "description": "The workflow's named states. At least one; exactly one with initial: true.",
233
- "minProperties": 1,
234
- "additionalProperties": {
235
- "type": "object",
236
- "description": "One workflow state. Declare exactly one content kind (script/prompt/message/commit). A commit state is final: no actor, no on.",
237
- "additionalProperties": false,
238
- "properties": {
239
- "actor": {
240
- "type": "string",
241
- "description": "Who acts at this state. Required on every non-commit state; forbidden on a commit state."
242
- },
243
- "script": {
244
- "type": "string",
245
- "description": "Content kind: a shell script (Eta template). The loop driver executes it verbatim via bash, then steps this state's actor."
246
- },
247
- "prompt": {
248
- "type": "string",
249
- "description": "Content kind: an agent prompt (Eta template), emitted by `gtd next`."
250
- },
251
- "message": {
252
- "type": "string",
253
- "description": "Content kind: a human-facing message (Eta template), emitted by `gtd next`."
254
- },
255
- "commit": {
256
- "type": "string",
257
- "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."
258
- },
259
- "on": {
260
- "type": "object",
261
- "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).",
262
- "additionalProperties": {
263
- "oneOf": [
264
- {
265
- "type": "string",
266
- "description": "The target state name."
267
- },
268
213
  {
269
214
  "type": "object",
215
+ "description": "A reference: instantiates the named machine as a child, at this local's path (see src/Machines.ts). Expanded at load time into concrete, qualified states — the engine only ever sees the flattened result.",
270
216
  "additionalProperties": false,
271
217
  "required": [
272
- "to"
218
+ "machine"
273
219
  ],
274
220
  "properties": {
275
- "to": {
221
+ "machine": {
276
222
  "type": "string",
277
- "description": "The target state name."
223
+ "description": "Name of a declared entry in `machines:`."
278
224
  },
279
- "describe": {
280
- "type": "string",
281
- "description": "Human-readable sentence describing where this change routes; surfaced verbatim (never Eta-rendered) to templates as it.edges[].describe."
225
+ "with": {
226
+ "type": "object",
227
+ "description": "Bindings for the referenced machine's `params:`. A bound value naming another of the CALLER's own bindings (a whole-value `$name`) passes it down verbatim, scope intact."
282
228
  }
283
229
  }
284
230
  }
285
231
  ]
286
232
  }
287
- },
288
- "initial": {
289
- "type": "boolean",
290
- "description": "Exactly one state in the workflow must declare initial: true (and it must not be a commit state)."
291
- },
292
- "retry": {
293
- "type": "object",
294
- "description": "Redirect transitions INTO this state once it has been entered `max` times in the current process.",
295
- "additionalProperties": false,
296
- "required": [
297
- "max",
298
- "otherwise"
299
- ],
300
- "properties": {
301
- "max": {
302
- "type": "integer",
303
- "minimum": 0,
304
- "description": "Entries allowed this process before redirecting."
305
- },
306
- "otherwise": {
307
- "type": "string",
308
- "description": "Defined state to redirect to once over the cap."
309
- }
310
- }
311
- },
312
- "model": {
313
- "type": "string",
314
- "description": "Opaque harness hint passed through `gtd next --json`/`gtd status --json` (e.g. \"smart\"). Never interpreted by gtd. Forbidden on a commit state."
315
- },
316
- "memory": {
317
- "type": "string",
318
- "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."
319
- },
320
- "file": {
321
- "type": "string",
322
- "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."
323
- },
324
- "mode": {
325
- "type": "string",
326
- "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."
327
- },
328
- "reviewWindow": {
329
- "type": "boolean",
330
- "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."
331
- },
332
- "reviewBase": {
333
- "type": "boolean",
334
- "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."
335
- },
336
- "reviewEntry": {
337
- "type": "boolean",
338
- "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."
339
- },
340
- "fixEntry": {
341
- "type": "boolean",
342
- "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."
343
- },
344
- "requireProgress": {
345
- "type": "boolean",
346
- "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."
347
- },
348
- "answerGate": {
349
- "type": "boolean",
350
- "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."
351
233
  }
352
234
  }
353
235
  }