@pmelab/gtd 3.4.0 → 3.6.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/bin/gtd-loop +58 -25
- package/dist/gtd.bundle.mjs +314 -63
- package/docs/cli.md +89 -17
- package/docs/configuration.md +60 -11
- package/docs/design/state-file-association.md +9 -8
- package/docs/design/state-machine-validation.md +2 -2
- package/docs/design/steering-file-loops.md +11 -0
- package/docs/design/steering-file-validation-command.md +386 -0
- package/docs/development.md +8 -6
- package/docs/examples/advanced-workflow.md +17 -1
- package/docs/loop.md +4 -1
- package/docs/upgrading.md +12 -9
- package/package.json +1 -1
package/docs/cli.md
CHANGED
|
@@ -6,13 +6,20 @@ Usage: gtd [command] [options]
|
|
|
6
6
|
Commands:
|
|
7
7
|
step <actor> Authenticate as <actor>, match the resolved rest's
|
|
8
8
|
declared patterns against the pending changes, and commit
|
|
9
|
-
(or squash) the one resulting transition
|
|
9
|
+
(or squash) the one resulting transition. Pass
|
|
10
|
+
--cost=<n> (optionally --model=<name>) to record the
|
|
11
|
+
just-finished invocation's token cost and model on the
|
|
12
|
+
turn commit (summed into it.processCost/processCostByModel)
|
|
10
13
|
next Print the resolved rest's rendered script/prompt/message
|
|
11
14
|
(no mutation)
|
|
12
15
|
run Execute the resolved rest's emitted script, then step its
|
|
13
16
|
actor (the built-in script driver)
|
|
14
17
|
status Print the resolved rest's state/actor and which declared
|
|
15
18
|
pattern (if any) each pending change matches (no mutation)
|
|
19
|
+
validate Validate the steering file the resolved rest declares
|
|
20
|
+
(its file:/mode:) against that mode's format; exits
|
|
21
|
+
non-zero with findings when the file violates it (no
|
|
22
|
+
mutation)
|
|
16
23
|
mermaid Print the active workflow's shape as Mermaid
|
|
17
24
|
stateDiagram-v2 source (no mutation)
|
|
18
25
|
format <file> Format a markdown file in place
|
|
@@ -20,6 +27,8 @@ Commands:
|
|
|
20
27
|
|
|
21
28
|
Options:
|
|
22
29
|
--json Output structured JSON instead of plain text
|
|
30
|
+
--cost=<n> (gtd step only) record the invocation's token cost
|
|
31
|
+
--model=<name> (gtd step only, with --cost) tag that cost's model
|
|
23
32
|
--version, -v Print version and exit
|
|
24
33
|
--help, -h Print this help and exit
|
|
25
34
|
```
|
|
@@ -32,11 +41,15 @@ without touching the repository. Every other command must be run from the
|
|
|
32
41
|
history relative to cwd, so it refuses with a clear error if invoked from a
|
|
33
42
|
subdirectory.
|
|
34
43
|
|
|
35
|
-
`--json
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
`--json`, `--cost=<n>`, and `--model=<name>` (the latter two only for
|
|
45
|
+
`gtd step`) are the only long options. Any other `--` option (including a typo
|
|
46
|
+
like `--jsn`) is rejected with a usage error rather than silently ignored, so a
|
|
47
|
+
mistyped flag can never degrade a JSON caller to plain-text mode. A bare
|
|
48
|
+
`--cost`/`--model` with no value, a non-numeric or negative `--cost`, an empty
|
|
49
|
+
`--model`, `--model` without `--cost`, or either flag on any command other than
|
|
50
|
+
`gtd step` are all usage errors.
|
|
38
51
|
|
|
39
|
-
## `gtd step <actor
|
|
52
|
+
## `gtd step <actor> [--cost=<n>] [--model=<name>]`
|
|
40
53
|
|
|
41
54
|
Authenticates `<actor>` against the resolved rest and performs the ONE resulting
|
|
42
55
|
transition. Unlike v2, there is no fixpoint chain to drive: the pattern
|
|
@@ -45,6 +58,16 @@ authors at most one commit (a normal turn) or performs one squash
|
|
|
45
58
|
(§[STATES.md](../STATES.md#8-the-squash-lifecycle)) — never both, never a chain
|
|
46
59
|
of several. A caller that wants several transitions issues several invocations.
|
|
47
60
|
|
|
61
|
+
`--cost=<n>` records the token cost of the invocation that produced the pending
|
|
62
|
+
changes (a non-negative number), and the optional `--model=<name>` tags it with
|
|
63
|
+
the model that ran, as a `Gtd-Cost: <n> <model>` trailer on the turn commit —
|
|
64
|
+
persisted in the git log, summed across the process into `it.processCost` and
|
|
65
|
+
grouped by model into `it.processCostByModel` (see
|
|
66
|
+
[Configuration: Token cost](configuration.md#token-cost)). `--model` requires
|
|
67
|
+
`--cost`. On a squash they are folded into the process total/breakdown the
|
|
68
|
+
`commit:` template renders rather than written as a trailer. Both are orthogonal
|
|
69
|
+
to `--json`.
|
|
70
|
+
|
|
48
71
|
- **Out-of-turn refusal** — `<actor>` isn't the resolved state's declared actor:
|
|
49
72
|
exit non-zero, zero commits.
|
|
50
73
|
`gtd step <actor>: out of turn — "<state>" awaits <awaited-actor>`
|
|
@@ -69,10 +92,17 @@ or, at a no-op:
|
|
|
69
92
|
nothing to do at "idle"
|
|
70
93
|
```
|
|
71
94
|
|
|
72
|
-
`--json` emits `{state, subject}` — `subject` is `null` at a no-op
|
|
95
|
+
`--json` emits `{state, subject}` — `subject` is `null` at a no-op — plus
|
|
96
|
+
`cost`/`model` keys echoing what `--cost`/`--model` recorded (each omitted when
|
|
97
|
+
not passed):
|
|
73
98
|
|
|
74
99
|
```json
|
|
75
|
-
{
|
|
100
|
+
{
|
|
101
|
+
"state": "grilling",
|
|
102
|
+
"subject": "gtd(human): grilling",
|
|
103
|
+
"cost": 1450,
|
|
104
|
+
"model": "claude-opus-4-8"
|
|
105
|
+
}
|
|
76
106
|
```
|
|
77
107
|
|
|
78
108
|
## `gtd next [--json]`
|
|
@@ -171,15 +201,21 @@ Pending:
|
|
|
171
201
|
or, on a clean tree: `Pending: (clean)`. A `Model: <value>` line appears right
|
|
172
202
|
after `Awaits:` when the resolved state declares a `model:` hint, and
|
|
173
203
|
`Memory: <value>`/`File: <value>`/`Mode: <value>` lines appear after that (in
|
|
174
|
-
that order) when declared — each independently, only when set.
|
|
204
|
+
that order) when declared — each independently, only when set. A `Cost: <n>`
|
|
205
|
+
line appears after those when the current process has accumulated any
|
|
206
|
+
`Gtd-Cost:` (see [`gtd step --cost`](#gtd-step-actor---costn-model-name)),
|
|
207
|
+
omitted when the running total is `0`; when the breakdown adds information (more
|
|
208
|
+
than one model, or a single tagged model), indented ` <model>: <cost>` lines
|
|
209
|
+
follow it.
|
|
175
210
|
|
|
176
211
|
`--json` emits
|
|
177
|
-
`{state, actor, changes: [{status, path, pattern}], model?, memory?, file?, mode?, edges?}`
|
|
212
|
+
`{state, actor, changes: [{status, path, pattern}], model?, memory?, file?, mode?, cost?, costByModel?, edges?}`
|
|
178
213
|
— `pattern` is `null` when no declared row matches that change;
|
|
179
214
|
`model`/`memory`/`file`/`mode` are present only when the resolved state declares
|
|
180
|
-
them (
|
|
181
|
-
|
|
182
|
-
|
|
215
|
+
them; `cost`/`costByModel` (the `[{model, cost}]` breakdown) only when the
|
|
216
|
+
running process total is above `0`; and `edges` is the resting state's `on`
|
|
217
|
+
edges as `[{ pattern, target, describe? }]` (same as `gtd next --json`), omitted
|
|
218
|
+
when the state has no `on` (all omitted entirely, never `null`, otherwise):
|
|
183
219
|
|
|
184
220
|
```json
|
|
185
221
|
{
|
|
@@ -194,6 +230,43 @@ omitted when the state has no `on`:
|
|
|
194
230
|
|
|
195
231
|
`gtd status` takes no arguments — extra positional args are rejected.
|
|
196
232
|
|
|
233
|
+
## `gtd validate [--json]`
|
|
234
|
+
|
|
235
|
+
Format **and** validate the steering file the resolved rest declares. It
|
|
236
|
+
resolves the current state exactly like `gtd status`, renders that state's
|
|
237
|
+
`file:`, **formats that file in place** (the same markdown formatter as
|
|
238
|
+
`gtd format`), then runs the parser its `mode:` selects over the formatted
|
|
239
|
+
contents — `qa` → `src/OpenQuestions.ts`, `review` → `src/ReviewDoc.ts` (the
|
|
240
|
+
same pure parsers the LSP publishes as diagnostics, so there is one source of
|
|
241
|
+
truth per format).
|
|
242
|
+
|
|
243
|
+
- A well-formed file exits `0` (`<file>: valid`, or `{"valid":true, ...}` with
|
|
244
|
+
`--json`) — and is left formatted.
|
|
245
|
+
- Violations exit **non-zero** with the findings, one per line — the signal a
|
|
246
|
+
producing agent, or the driving loop, loops on until the file is well-formed.
|
|
247
|
+
- A state that declares no `file:`/`mode:`, or whose file is **absent** (e.g.
|
|
248
|
+
`building` deleted `.gtd/TODO.md`, or a human deleted `.gtd/REVIEW.md` to
|
|
249
|
+
approve), has nothing to validate and exits `0`
|
|
250
|
+
(`nothing to validate at "<state>"`). Nothing is formatted in that case.
|
|
251
|
+
|
|
252
|
+
This is how the bundled default keeps its steering files tidy and well-formed
|
|
253
|
+
without an in-machine validation state — and the check runs whoever last touched
|
|
254
|
+
the file, agent or human:
|
|
255
|
+
|
|
256
|
+
- **The producing agent self-validates.** Plain `gtd next` appends a "run
|
|
257
|
+
`gtd validate` and fix all violations" instruction to a `prompt` rest that
|
|
258
|
+
declares `file:`+`mode:`; `gtd next --json` withholds it and the driving loop
|
|
259
|
+
runs `gtd validate` after the turn instead (see `bin/gtd-loop` /
|
|
260
|
+
[STATES.md §12](../STATES.md#12-steering-file-validation-gtd-validate)).
|
|
261
|
+
- **`gtd step` enforces the same gate.** Capturing a turn out of a state that
|
|
262
|
+
declares `file:`+`mode:` formats that file in place and validates it first,
|
|
263
|
+
and **refuses the step** (committing nothing) when it is invalid — so a
|
|
264
|
+
human's edit at a gate (answering at `grilling-answer`, reviewing at
|
|
265
|
+
`await-review`) is formatted and checked exactly like an agent's draft, and a
|
|
266
|
+
malformed steering file is never committed.
|
|
267
|
+
|
|
268
|
+
`gtd validate` takes no arguments.
|
|
269
|
+
|
|
197
270
|
## `gtd mermaid`
|
|
198
271
|
|
|
199
272
|
Pure emitter of the active workflow's **shape** — not the resolved rest — as
|
|
@@ -228,7 +301,7 @@ Mermaid-aware renderer (GitHub, GitLab, VS Code, Obsidian, the
|
|
|
228
301
|
|
|
229
302
|
State names are aliased to Mermaid-safe identifiers (non-word characters fold to
|
|
230
303
|
`_`; a digit-led name gets an `s_` prefix) via a `state "<name>" as <alias>`
|
|
231
|
-
declaration up front, so a hyphenated name like `
|
|
304
|
+
declaration up front, so a hyphenated name like `grilling-answer` still displays
|
|
232
305
|
with its exact declared spelling. Rejects `--json` (exit 1,
|
|
233
306
|
`gtd mermaid does not accept --json`) — there is no structured shape to emit
|
|
234
307
|
beyond the Mermaid source itself — and takes no arguments (extra positional args
|
|
@@ -256,10 +329,9 @@ Errors (all exit 1, message on stderr):
|
|
|
256
329
|
Starts an LSP server over stdio for `.gtd/` steering files — document symbols
|
|
257
330
|
for a `qa`-mode file's open questions and a `review`-mode file's review
|
|
258
331
|
chunks/hunks, code actions to check/uncheck a hunk or a whole chunk, and
|
|
259
|
-
diagnostics publishing the same parser findings
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
[STATES.md §10](../STATES.md#10-the-bundled-default-workflow)).
|
|
332
|
+
diagnostics publishing the same parser findings `gtd validate` reports (see
|
|
333
|
+
`src/OpenQuestions.ts` / `src/ReviewDoc.ts` and
|
|
334
|
+
[STATES.md §12](../STATES.md#12-steering-file-validation-gtd-validate)).
|
|
263
335
|
|
|
264
336
|
**Config-driven** (see
|
|
265
337
|
[docs/design/state-file-association.md](design/state-file-association.md)): the
|
package/docs/configuration.md
CHANGED
|
@@ -322,17 +322,66 @@ head is preserved under `refs/gtd/review-head` (the base under
|
|
|
322
322
|
Every `script`/`prompt`/`message`/`commit`/`model`/`memory`/`file` template is
|
|
323
323
|
rendered as an Eta template (`it.<name>`) with:
|
|
324
324
|
|
|
325
|
-
| Variable
|
|
326
|
-
|
|
|
327
|
-
| `startCommit`
|
|
328
|
-
| `currentCommit`
|
|
329
|
-
| `previousCommit`
|
|
330
|
-
| `state`
|
|
331
|
-
| `actor`
|
|
332
|
-
| `processDiff`
|
|
333
|
-
| `lastDiff`
|
|
334
|
-
| `
|
|
335
|
-
| `
|
|
325
|
+
| Variable | Meaning |
|
|
326
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
327
|
+
| `startCommit` | The hash the current process started from (before its first turn). |
|
|
328
|
+
| `currentCommit` | HEAD's hash at render time. |
|
|
329
|
+
| `previousCommit` | The hash before the last transition (HEAD's parent, in-process). |
|
|
330
|
+
| `state` | The state whose content is being rendered. |
|
|
331
|
+
| `actor` | The actor this render is for. |
|
|
332
|
+
| `processDiff` | `startCommit..HEAD` plus the pending working-tree diff. |
|
|
333
|
+
| `lastDiff` | The diff of the last transition alone. |
|
|
334
|
+
| `processCost` | The accumulated token cost of the current process — the sum of every `Gtd-Cost:` trailer recorded by `gtd step <actor> --cost=<n>` across its turn commits, plus the in-flight step's own `--cost` (so a `commit:` squash template sees the whole-process total). A number, `0` when none recorded. See ["Token cost"](#token-cost). |
|
|
335
|
+
| `processCostByModel` | The same accumulated cost broken down per model: an array of `{ model, cost }`, highest-cost first, one entry per distinct `--model` tag (costs recorded with no `--model` group under `"unspecified"`). A squash template iterates it to itemize the feature's cost per model. Empty when none recorded. See ["Token cost"](#token-cost). |
|
|
336
|
+
| `read(path)` | Reads a working-tree file (pending contents, not HEAD's) by repo-relative path. Throws for a missing/unreadable path — for a `commit:` template, that throw refuses the step (see [STATES.md §8](../STATES.md#8-the-squash-lifecycle)). |
|
|
337
|
+
| `vars` | The merged three-layer variable map, always a flat `Record<string, string>` — see ["Variables"](#variables) below. |
|
|
338
|
+
|
|
339
|
+
### Token cost
|
|
340
|
+
|
|
341
|
+
A loop driver that knows how many tokens the invocation it just drove cost — and
|
|
342
|
+
on which model — can record both with
|
|
343
|
+
`gtd step <actor> --cost=<n> [--model=<name>]` (`<n>` a non-negative number).
|
|
344
|
+
gtd appends them to that turn's commit as a `Gtd-Cost: <n> <model>` trailer — a
|
|
345
|
+
blank line then the trailer, below the untouched `gtd(<actor>): <state>` subject
|
|
346
|
+
— so the per-turn cost and model are persisted in the git log:
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
gtd(agent): reviewing
|
|
350
|
+
|
|
351
|
+
Gtd-Cost: 1450 claude-opus-4-8
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`--model` is optional and requires `--cost` (a model tag with no token count
|
|
355
|
+
records nothing to sum); a cost recorded without a model is grouped under
|
|
356
|
+
`"unspecified"`. `gtd status` shows the process's running total and, when the
|
|
357
|
+
breakdown adds information, a per-model split (a `Cost:` line with indented
|
|
358
|
+
`<model>: <cost>` lines / a `cost` number and a `costByModel` array, omitted
|
|
359
|
+
when nothing has been recorded). `gtd step --json` echoes what it recorded
|
|
360
|
+
(`{ "state": …, "subject": …, "cost": 1450, "model": "claude-opus-4-8" }`).
|
|
361
|
+
|
|
362
|
+
Every template sees the running total as `it.processCost` and the per-model
|
|
363
|
+
breakdown as `it.processCostByModel` (the table above): the current process's
|
|
364
|
+
turn-commit entries, plus the in-flight step's own `--cost`/`--model`. Their
|
|
365
|
+
intended use is a `commit:` squash template, which renders against the pending
|
|
366
|
+
tree as the process collapses — so the whole feature's total AND its per-model
|
|
367
|
+
itemization (including the squashing step itself) land in the squash message
|
|
368
|
+
even though the intermediate per-turn trailers are discarded with the turns they
|
|
369
|
+
rode on:
|
|
370
|
+
|
|
371
|
+
```yaml
|
|
372
|
+
done:
|
|
373
|
+
commit: |
|
|
374
|
+
feat: ship it
|
|
375
|
+
|
|
376
|
+
Total token cost: <%= it.processCost %>
|
|
377
|
+
<% it.processCostByModel.forEach(function(m){ %>
|
|
378
|
+
- <%= m.model %>: <%= m.cost %>
|
|
379
|
+
<% }) %>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`--cost`/`--model` are accepted only by `gtd step` (a usage error on any other
|
|
383
|
+
command), and gtd never interprets the number's unit (tokens, cents, whatever
|
|
384
|
+
the driver records) or the model name — it only records, sums, and groups them.
|
|
336
385
|
|
|
337
386
|
## Variables
|
|
338
387
|
|
|
@@ -45,14 +45,14 @@ vars:
|
|
|
45
45
|
feedbackFile: .gtd/FEEDBACK.md
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
Per-state mapping (
|
|
48
|
+
Per-state mapping (10 states):
|
|
49
49
|
|
|
50
|
-
| States
|
|
51
|
-
|
|
|
52
|
-
| `grilling`, `
|
|
53
|
-
| `reviewing`, `
|
|
54
|
-
| `fixing`, `escalate`
|
|
55
|
-
| `idle`, `checking`
|
|
50
|
+
| States | `file:` | `mode:` |
|
|
51
|
+
| ---------------------------------------------- | ----------------------------- | ------------------------- |
|
|
52
|
+
| `grilling`, `grilling-answer`, `building` | `<%= it.vars.todoFile %>` | `qa` |
|
|
53
|
+
| `reviewing`, `await-review`, `review-deciding` | `<%= it.vars.reviewFile %>` | `review` |
|
|
54
|
+
| `fixing`, `escalate` | `<%= it.vars.feedbackFile %>` | — (plain text, no format) |
|
|
55
|
+
| `idle`, `checking` | — | — |
|
|
56
56
|
|
|
57
57
|
Prompts and scripts that already name these files switch to the vars
|
|
58
58
|
(`<%~ it.vars.todoFile %>` etc.) so each filename has ONE source of truth in
|
|
@@ -109,4 +109,5 @@ The advanced example (`docs/examples/advanced-workflow.md`) gains the same
|
|
|
109
109
|
- Eta-rendering of `on` pattern keys (the rename-switch story) — future work,
|
|
110
110
|
noted in §2.
|
|
111
111
|
- New LSP modes beyond `qa`/`review` — the vocabulary can grow with new formats;
|
|
112
|
-
adding one means: parser (spec),
|
|
112
|
+
adding one means: parser (spec), a `gtd validate` dispatch arm for the mode,
|
|
113
|
+
LSP dispatch arm, docs.
|
|
@@ -51,7 +51,7 @@ Each of these passes today's validation:
|
|
|
51
51
|
`retry: {max: 3, otherwise: escalate}`; delete that key and the machine
|
|
52
52
|
validates clean but can ping-pong forever with no human in the loop.
|
|
53
53
|
5. **Shadowed `on` row** — `on` is first-match-wins, so
|
|
54
|
-
`{"* **": a, "A .gtd/
|
|
54
|
+
`{"* **": a, "A .gtd/FEEDBACK.md": b}` never routes to `b`: the catch-all
|
|
55
55
|
subsumes the specific row. Dead rows are silent today.
|
|
56
56
|
6. **`retry.otherwise` cycle / self-reference** — `applyRetry` terminates at
|
|
57
57
|
runtime via its `visited` guard, but the guard's "accept the repeated target
|
|
@@ -400,7 +400,7 @@ the definition) — they fit `PatternMachine.step`'s purity discipline.
|
|
|
400
400
|
- **Self-explaining refusals.** A no-match refusal already lists the state's
|
|
401
401
|
declared patterns; for a user debugging their own machine, also list the
|
|
402
402
|
pending changes that failed to match (status + path, first N). "Your tree has
|
|
403
|
-
`M src/x.ts` but this state only matches `A .gtd/
|
|
403
|
+
`M src/x.ts` but this state only matches `A .gtd/FEEDBACK.md`" turns the most
|
|
404
404
|
common custom-workflow dead end from a mystery into a diff-vs-pattern
|
|
405
405
|
diagnosis the user can act on immediately.
|
|
406
406
|
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Plan: steering-file validation loops in the default workflow
|
|
2
2
|
|
|
3
|
+
> SUPERSEDED (§2–§4): the in-machine validation-loop states this plan added —
|
|
4
|
+
> `todo-validating` and `review-validating` — and their `.gtd/FORMAT.md`
|
|
5
|
+
> steering file have all been DELETED. Deterministic format-checking now lives
|
|
6
|
+
> in the `gtd validate` command (which runs the same pure `parseOpenQuestions` /
|
|
7
|
+
> `parseReviewDoc` parsers the state's `mode:` selects) plus producing-agent
|
|
8
|
+
> self-validation, rather than in bash-port `check`/`script` states. The default
|
|
9
|
+
> workflow is now 10 states, not 12. See
|
|
10
|
+
> [docs/design/steering-file-validation-command.md](steering-file-validation-command.md)
|
|
11
|
+
> and [STATES.md §12](../STATES.md). §1 (the file formats) and §5 (the LSP
|
|
12
|
+
> resurrection) below remain valid and current.
|
|
13
|
+
|
|
3
14
|
> Status: §2–§4 (the two validation loops in the default workflow) AND §5 (the
|
|
4
15
|
> LSP resurrection) both LANDED 2026-07-22. Goal: the bundled default workflow
|
|
5
16
|
> completely maps the functionality the deleted v2 LSP server (`src/Lsp.ts`,
|