@pmelab/gtd 7.1.0 → 7.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.
- package/README.md +18 -2
- package/bin/gtd-loop +33 -0
- package/dist/gtd.bundle.mjs +326 -56
- package/docs/cli.md +120 -2
- package/docs/configuration.md +36 -3
- package/docs/loop.md +53 -3
- package/docs/upgrading.md +36 -0
- package/package.json +1 -1
package/docs/cli.md
CHANGED
|
@@ -25,6 +25,17 @@ 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
|
|
33
|
+
restore Hard-reset HEAD back to the pre-squash tip retained by the
|
|
34
|
+
last squash/abandon (refs/worktree/gtd/history), undoing a
|
|
35
|
+
squash or bringing back an abandoned process's turns.
|
|
36
|
+
Refuses on a dirty working tree, when there is no retained
|
|
37
|
+
history, or when HEAD has advanced past the squash with
|
|
38
|
+
commits that would be lost
|
|
28
39
|
next Print the resolved rest's rendered script/prompt/message
|
|
29
40
|
(no mutation)
|
|
30
41
|
status Print the resolved rest's state/actor and which declared
|
|
@@ -167,7 +178,8 @@ written):
|
|
|
167
178
|
|
|
168
179
|
- the machine currently resting at the workflow's **initial state** (a plain
|
|
169
180
|
non-gtd branch resolves there via the inert-subject rule — the normal case; a
|
|
170
|
-
process already underway refuses
|
|
181
|
+
process already underway refuses — finish it, or
|
|
182
|
+
[`gtd abandon`](#gtd-abandon---json) it);
|
|
171
183
|
- a **clean working tree**;
|
|
172
184
|
- the active workflow declaring a **`reviewEntry: true`** state (otherwise:
|
|
173
185
|
`gtd review: the active workflow declares no review entry state`);
|
|
@@ -213,7 +225,8 @@ Requires, in order (any failure is a plain refusal — exit non-zero, nothing
|
|
|
213
225
|
written):
|
|
214
226
|
|
|
215
227
|
- the machine currently resting at the workflow's **initial state** (a process
|
|
216
|
-
already underway refuses)
|
|
228
|
+
already underway refuses — finish it, or [`gtd abandon`](#gtd-abandon---json)
|
|
229
|
+
it);
|
|
217
230
|
- a **clean working tree**;
|
|
218
231
|
- the active workflow declaring a **`fixEntry: true`** state (otherwise:
|
|
219
232
|
`gtd fix: the active workflow declares no fix entry state`).
|
|
@@ -232,6 +245,103 @@ unified template the fix-entry state is `fix-check`: a red suite drops into the
|
|
|
232
245
|
shared `fixing` loop and out through the review + squash tail; a green suite is
|
|
233
246
|
a no-op back to `idle`.
|
|
234
247
|
|
|
248
|
+
## `gtd abandon [--json]`
|
|
249
|
+
|
|
250
|
+
Ends the process currently underway **without completing it** — the way out of a
|
|
251
|
+
process nobody is going to finish, and the command `gtd review`/`gtd fix` name
|
|
252
|
+
when they refuse ("finish it, or run `gtd abandon`, before starting a review").
|
|
253
|
+
A workflow's own exit is its squash finale
|
|
254
|
+
([STATES.md §8](../STATES.md#8-the-squash-lifecycle)); this is the human's, and
|
|
255
|
+
it targets the same boundary:
|
|
256
|
+
|
|
257
|
+
1. any open **review checkout window** is closed first (the bracket every state
|
|
258
|
+
subcommand runs — see
|
|
259
|
+
[STATES.md §11](../STATES.md#11-the-review-checkout-window)), so the rewind
|
|
260
|
+
starts from the real head, not the rewound base;
|
|
261
|
+
2. HEAD is `git reset --mixed`ed to the commit the process started from (its
|
|
262
|
+
start parent — the same target a squash resets to).
|
|
263
|
+
|
|
264
|
+
**Nothing is discarded.** Every turn commit the process wrote is dropped, and
|
|
265
|
+
everything they carried — the code, the `.gtd/` steering files — stays in the
|
|
266
|
+
working tree as uncommitted changes for you to keep, re-commit, or throw away
|
|
267
|
+
with ordinary git:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
abandoned the process resting at "await-review" — HEAD is back at 1a2b3c4
|
|
271
|
+
("feat: add calculator"), resting at "idle".
|
|
272
|
+
Everything the process produced is kept as uncommitted changes (`git status`);
|
|
273
|
+
discard them with `git checkout -- . && git clean -fd .gtd` for a clean tree.
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Takes no positional argument (extra arguments are a usage error). Resting at the
|
|
277
|
+
workflow's initial state is a **no-op success**, not a refusal — a recovery
|
|
278
|
+
command that fails when there is nothing to recover is a worse tool:
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
no gtd process is underway (resting at "idle") — nothing to abandon
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The one refusal is a process whose first commit is the repository's own root
|
|
285
|
+
commit: there is no earlier commit to rewind to, so its commits have to be
|
|
286
|
+
removed by hand.
|
|
287
|
+
|
|
288
|
+
`--json` emits `{state, abandoned, from, head}` — `state` is the initial state
|
|
289
|
+
the machine returns to, `abandoned` whether anything was rewound, and (only when
|
|
290
|
+
it was) `from` the state the abandoned process rested at plus `head` the full
|
|
291
|
+
hash HEAD now points at:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"state": "idle",
|
|
296
|
+
"abandoned": true,
|
|
297
|
+
"from": "await-review",
|
|
298
|
+
"head": "1a2b3c4…"
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The no-op reports `{"state": "idle", "abandoned": false}` and still exits 0.
|
|
303
|
+
|
|
304
|
+
## `gtd restore [--json]`
|
|
305
|
+
|
|
306
|
+
Undoes the last squash or `gtd abandon` by hard-resetting HEAD back to the
|
|
307
|
+
pre-squash tip that either one retains, before acting, on the per-worktree
|
|
308
|
+
`refs/worktree/gtd/history` ref
|
|
309
|
+
([STATES.md §8](../STATES.md#8-the-squash-lifecycle)):
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
restored the retained history — HEAD is back at 1a2b3c4 ("gtd(agent): drafting
|
|
313
|
+
→ working"), resting at "await-review". Resume with the loop, or `git reset`
|
|
314
|
+
to any earlier turn to restart from there.
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Refuses on a dirty working tree — commit, stash, or discard your changes first.
|
|
318
|
+
Refuses when there is no retained history to restore (nothing has been squashed
|
|
319
|
+
or abandoned yet, or a previous `restore` already consumed it). Refuses when
|
|
320
|
+
HEAD has advanced past the retained tip with commits that would be discarded by
|
|
321
|
+
resetting:
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
gtd restore: HEAD has advanced past the squash — restoring would discard
|
|
325
|
+
commits built on top of it — HEAD 4d5e6f7 is ahead of the retained tip
|
|
326
|
+
1a2b3c4.
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Takes no positional argument (extra arguments are a usage error).
|
|
330
|
+
|
|
331
|
+
`--json` emits `{state, restored, to, from}` — `state` is the resolved rest
|
|
332
|
+
after the reset, `restored` always `true` (a refusal exits non-zero instead),
|
|
333
|
+
`to` the full hash HEAD was reset to, and `from` the state the machine rested at
|
|
334
|
+
before the reset:
|
|
335
|
+
|
|
336
|
+
```json
|
|
337
|
+
{
|
|
338
|
+
"state": "await-review",
|
|
339
|
+
"restored": true,
|
|
340
|
+
"to": "1a2b3c4…",
|
|
341
|
+
"from": "idle"
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
235
345
|
## `gtd next [--json]`
|
|
236
346
|
|
|
237
347
|
Pure emitter of the resolved rest's rendered content — it **never mutates** the
|
|
@@ -495,3 +605,11 @@ the plain-text one.
|
|
|
495
605
|
`--help`/`--version` (and the `help`/`version` subcommands), `lsp`, and
|
|
496
606
|
`visualize` skip this guard entirely (`visualize` still reads the `.gtdrc`
|
|
497
607
|
workflow, but needs no git state).
|
|
608
|
+
- **Linked worktrees are independent.** N `git worktree` worktrees of one
|
|
609
|
+
repository (sharing a single `.git`) each run their own gtd process: state is
|
|
610
|
+
derived from that worktree's own HEAD, and the review checkout window's refs
|
|
611
|
+
live in git's per-worktree `refs/worktree/gtd/*` namespace, so a review
|
|
612
|
+
resting in one worktree neither blocks nor rewrites any other
|
|
613
|
+
([STATES.md §11](../STATES.md#11-the-review-checkout-window)). This holds from
|
|
614
|
+
7.2 on; gtd ≤ 7.1 kept the window under the shared `refs/gtd/*` refs, where
|
|
615
|
+
the first worktree to open one clobbered every sibling's branch.
|
package/docs/configuration.md
CHANGED
|
@@ -261,6 +261,38 @@ value) only when the resolved state declares one — **omitted entirely**, never
|
|
|
261
261
|
`null`, when unset. Plain `gtd status` prints a `Memory:` line (right after
|
|
262
262
|
`Model:`, when present).
|
|
263
263
|
|
|
264
|
+
### `label:` — the opaque display name, template-rendered
|
|
265
|
+
|
|
266
|
+
A state may declare `label: <string>` — an OPAQUE display name gtd never
|
|
267
|
+
interprets; it is only passed through so a driver/viewer can show something
|
|
268
|
+
friendlier than the raw state name. Unset means the state has no display name.
|
|
269
|
+
Forbidden on a commit state (never at rest, emits nothing):
|
|
270
|
+
|
|
271
|
+
```yaml
|
|
272
|
+
workflow:
|
|
273
|
+
states:
|
|
274
|
+
working:
|
|
275
|
+
actor: agent
|
|
276
|
+
label: Implement the feature
|
|
277
|
+
prompt: do the thing
|
|
278
|
+
on:
|
|
279
|
+
"* **": done
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Like `model`/`memory`, `label:` is rendered as an Eta template through the exact
|
|
283
|
+
same context as the state's content — a plain string with no Eta tags
|
|
284
|
+
(`Implement the feature` above) passes through unchanged, but
|
|
285
|
+
`label: "<%= it.vars.stateLabel %>"` resolves against the merged `it.vars` (see
|
|
286
|
+
["Variables"](#variables)). A render failure behaves exactly like a content
|
|
287
|
+
render failure at the same call site: `gtd next`/`gtd status` error out, nothing
|
|
288
|
+
committed.
|
|
289
|
+
|
|
290
|
+
`gtd next --json` and `gtd status --json` include a `"label"` key (the RENDERED
|
|
291
|
+
value) only when the resolved state declares one — it is **omitted entirely**,
|
|
292
|
+
never emitted as `null`, when unset. A driver/viewer choosing to fall back to
|
|
293
|
+
the raw state name when `label` is absent is that consumer's own fallback, not
|
|
294
|
+
something gtd itself does.
|
|
295
|
+
|
|
264
296
|
### `file:`/`mode:` — the steering-file association
|
|
265
297
|
|
|
266
298
|
A state may additionally declare `file:` — an Eta template naming THE steering
|
|
@@ -449,9 +481,10 @@ The unified template enables `reviewWindow: true` on `await-review` and marks
|
|
|
449
481
|
`review-deciding` `reviewBase: true`, so the first review covers the whole cycle
|
|
450
482
|
but each subsequent feedback round's window narrows to `last-review..HEAD`. The
|
|
451
483
|
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
|
|
453
|
-
`refs/gtd/review-base`) for the window's lifetime
|
|
454
|
-
|
|
484
|
+
edge; the real head is preserved under `refs/worktree/gtd/review-head` (the base
|
|
485
|
+
under `refs/worktree/gtd/review-base`) for the window's lifetime — git's
|
|
486
|
+
per-worktree ref namespace, so linked worktrees of one repository each get their
|
|
487
|
+
own window. See [STATES.md §11](../STATES.md) for the full lifecycle.
|
|
455
488
|
|
|
456
489
|
### `reviewEntry:` — the review entry point (`gtd review <commitish>`)
|
|
457
490
|
|
package/docs/loop.md
CHANGED
|
@@ -18,13 +18,18 @@ DRIVER runs the emitted `content` and then steps that state's own actor.
|
|
|
18
18
|
A loop is a simple cycle:
|
|
19
19
|
|
|
20
20
|
1. Run `gtd next --json` — see [`cli.md`](cli.md#gtd-next---json) for the exact
|
|
21
|
-
`{state, actor, kind, content, model?, memory?}` field shape. `kind`
|
|
22
|
-
dispatch key: `"message"` → halt (a human rest); `"script"` → execute
|
|
21
|
+
`{state, actor, kind, content, model?, memory?, label?}` field shape. `kind`
|
|
22
|
+
is the dispatch key: `"message"` → halt (a human rest); `"script"` → execute
|
|
23
23
|
`content` verbatim (ignoring its exit code), then `gtd step <actor>` to
|
|
24
24
|
capture the outcome; `"prompt"` → feed `content` to the agent, then
|
|
25
25
|
`gtd step <actor>` yourself once it's done acting. The optional `model` is an
|
|
26
26
|
opaque string the workflow author chose, which you map onto your own
|
|
27
|
-
harness's model selection if you use one.
|
|
27
|
+
harness's model selection if you use one. The optional `label` is a
|
|
28
|
+
human-readable display name for the resolved state, present only when that
|
|
29
|
+
state declares `label:` in the workflow; when it's absent, a driver that
|
|
30
|
+
wants to show something falls back to `state` itself (that fallback lives in
|
|
31
|
+
the driver, not gtd core — see `bin/gtd-loop`'s
|
|
32
|
+
`label="$(jq -r '.label // .state' <<<"$next_json")"`).
|
|
28
33
|
2. Repeat until a `"message"` rest halts the loop, or a zero-commit script step
|
|
29
34
|
at idle settles it (the green terminal signal).
|
|
30
35
|
|
|
@@ -34,6 +39,19 @@ capture for the human as its opening move (see below), so re-launching the loop
|
|
|
34
39
|
is the only thing the human ever does — at a gate they edit and re-run, and the
|
|
35
40
|
loop picks their change up.
|
|
36
41
|
|
|
42
|
+
**A `"message"` rest is a real handoff, not a slow beat.** No driver signs off
|
|
43
|
+
for the human: in the bundled workflow the loop runs the whole cycle unattended
|
|
44
|
+
up to `await-review` and then STOPS there with the review checkout window open
|
|
45
|
+
(the reviewable diff surfaced as uncommitted changes — see
|
|
46
|
+
[STATES.md §11](../STATES.md#11-the-review-checkout-window)). Advancing takes a
|
|
47
|
+
human edit to `.gtd/REVIEW.md`: ticking every box with **no** comment signs off
|
|
48
|
+
into the squash finale, while any comment (a note, or a code edit of your own)
|
|
49
|
+
is feedback that routes into another build + re-review round. Re-launching the
|
|
50
|
+
loop after that edit picks the decision up. Until then the loop halts on every
|
|
51
|
+
run — that is the contract, not a stall. A review nobody is going to finish is
|
|
52
|
+
ended with [`gtd abandon`](cli.md#gtd-abandon---json), which drops the process's
|
|
53
|
+
commits and keeps their content as uncommitted changes.
|
|
54
|
+
|
|
37
55
|
```bash
|
|
38
56
|
gtd next --json # ask who's up and what they should do
|
|
39
57
|
```
|
|
@@ -149,3 +167,35 @@ example, to drive a different agent CLI:
|
|
|
149
167
|
```bash
|
|
150
168
|
GTD_LOOP_AGENT_CMD='my-agent-cli --prompt "$GTD_LOOP_PROMPT"' gtd-loop
|
|
151
169
|
```
|
|
170
|
+
|
|
171
|
+
## Herdr integration (optional)
|
|
172
|
+
|
|
173
|
+
`bin/gtd-loop` optionally reports its lifecycle to [Herdr](https://herdr.dev) (a
|
|
174
|
+
terminal multiplexer for coding agents) via a `herdr` CLI binary, entirely at
|
|
175
|
+
the driver's edge — gtd core never talks to Herdr. Every call is best-effort
|
|
176
|
+
(guarded, output discarded, `|| true`), so a missing/failing `herdr` binary
|
|
177
|
+
never blocks, slows, or fails the loop.
|
|
178
|
+
|
|
179
|
+
The reporting is a no-op unless all three guard conditions hold: `HERDR_ENV=1`,
|
|
180
|
+
a non-empty `$HERDR_PANE_ID`, and `herdr` on `$PATH`. When they do, `gtd-loop`
|
|
181
|
+
makes three kinds of calls:
|
|
182
|
+
|
|
183
|
+
- `herdr pane report-agent --source herdr:gtd --agent gtd --state <state> --message <label> "$HERDR_PANE_ID"`
|
|
184
|
+
- `herdr pane release-agent --source herdr:gtd --agent gtd "$HERDR_PANE_ID"`
|
|
185
|
+
- `herdr notification show <title> --body <label> --sound request`
|
|
186
|
+
|
|
187
|
+
mapped onto the loop's states like this:
|
|
188
|
+
|
|
189
|
+
| Loop moment | Call(s) |
|
|
190
|
+
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
191
|
+
| Top of every non-`"message"` iteration | `report-agent --state working` |
|
|
192
|
+
| Right before halting at a `"message"` (human) gate | `report-agent --state blocked` + `notification show` |
|
|
193
|
+
| Right before a clean settle (a script rest, zero new commits) | `report-agent --state idle` + `release-agent` |
|
|
194
|
+
| Any non-zero exit (stall guard, validate-cap, `gtd next` failure, unhandled failure) | `report-agent --state blocked` + `notification show`, via an `EXIT` trap |
|
|
195
|
+
|
|
196
|
+
`working`/`blocked` reports use the resolved rest's `label` (falling back to
|
|
197
|
+
`state`, per the field above); the trap falls back to `$GTD_LAST_LABEL`, the
|
|
198
|
+
label tracked from the last completed iteration, when it fires outside the
|
|
199
|
+
normal per-iteration flow. `report-agent` claims display authority for the
|
|
200
|
+
`herdr:gtd` source, so `working` is re-reported every lap rather than once, to
|
|
201
|
+
stay fresh against Herdr's own heuristic detection of the inner agent process.
|
package/docs/upgrading.md
CHANGED
|
@@ -159,6 +159,42 @@ loudly at load time rather than silently misinterpreting.
|
|
|
159
159
|
or agent harness, upgrading the `gtd` binary also means re-copying that skill
|
|
160
160
|
from this release.
|
|
161
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
|
+
|
|
162
198
|
## Prior breaking change: the v1 → v2 label grammar (historical)
|
|
163
199
|
|
|
164
200
|
v2 replaced v1's undifferentiated turn labels with six labels carrying the
|