@pmelab/gtd 7.11.2 → 8.1.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 +409 -130
- package/bin/gtd +59 -287
- package/dist/gtd.bundle.mjs +187 -98
- package/package.json +1 -2
- package/schema.json +33 -9
- package/docs/cli.md +0 -747
- package/docs/configuration.md +0 -994
- package/docs/design/configurable-state-machine.md +0 -1439
- package/docs/design/delta-purity-plan.md +0 -283
- package/docs/design/pattern-machine-plan.md +0 -164
- package/docs/design/pluggable-steering-modes.md +0 -154
- package/docs/design/scripted-actor.md +0 -289
- package/docs/design/state-file-association.md +0 -120
- package/docs/design/state-machine-validation.md +0 -437
- package/docs/design/steering-file-loops.md +0 -182
- package/docs/design/steering-file-validation-command.md +0 -385
- package/docs/design/work-packages.md +0 -193
- package/docs/development.md +0 -109
- package/docs/loop.md +0 -289
- package/docs/upgrading.md +0 -293
package/docs/development.md
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# Development
|
|
2
|
-
|
|
3
|
-
```bash
|
|
4
|
-
npm install
|
|
5
|
-
npm run dev # run from source, no build (node dev/run.mjs)
|
|
6
|
-
npm run build # tsdown → dist/gtd.bundle.mjs
|
|
7
|
-
npm test # format:check, typecheck, lint, unit + e2e tests, fallow
|
|
8
|
-
npm run test:unit # vitest unit tests (the pure resolver) — --project unit
|
|
9
|
-
npm run test:e2e # gherkin e2e via vitest + quickpickle — --project e2e
|
|
10
|
-
npm run test:mutation # StrykerJS mutation testing (manual only, ~2 min)
|
|
11
|
-
npm run typecheck
|
|
12
|
-
npm run lint
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Pre-commit hook
|
|
16
|
-
|
|
17
|
-
A pre-commit hook is installed automatically via the `prepare` script when you
|
|
18
|
-
run `npm install` on a fresh clone — no manual setup needed. The hook runs
|
|
19
|
-
[lint-staged](https://github.com/lint-staged/lint-staged) with
|
|
20
|
-
[oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), formatting every staged
|
|
21
|
-
file before each commit (`oxfmt --no-error-on-unmatched-pattern --write`),
|
|
22
|
-
mirroring the `format:check` step enforced in CI (`oxfmt --check .`).
|
|
23
|
-
|
|
24
|
-
## Templates
|
|
25
|
-
|
|
26
|
-
Every state's content (`script`/`prompt`/`message`/`commit`) is an Eta template
|
|
27
|
-
string, rendered by `src/PatternTemplates.ts`'s `renderStateTemplate` against
|
|
28
|
-
the variable set documented in
|
|
29
|
-
[Configuration](configuration.md#template-variables). There is no template-file
|
|
30
|
-
registry to keep in sync: a workflow's content strings are either inline in its
|
|
31
|
-
YAML/`.gtdrc` or a `./`-relative file reference auto-inlined at config load
|
|
32
|
-
(`src/PatternConfig.ts`) — see
|
|
33
|
-
[Configuration](configuration.md#content-values-inline-or-a-file-reference). The
|
|
34
|
-
bundled workflow template (`src/workflows/unified.yaml`) gtd runs as its
|
|
35
|
-
built-in default (and materializes into config form via `renderInitConfig`) is
|
|
36
|
-
imported as raw text via tsdown's `.yaml`-as-text loader (`tsdown.config.ts`)
|
|
37
|
-
and compiled through the same path a `.gtdrc` `workflow:` key goes through
|
|
38
|
-
(`src/workflows/templates.ts`) — no privileged code path.
|
|
39
|
-
|
|
40
|
-
`npm run dev` runs `src/main.ts` directly via Node's native TypeScript
|
|
41
|
-
type-stripping (requires Node 22.6+). It registers `dev/hooks.mjs`, which
|
|
42
|
-
resolves `./Foo.js` specifiers to the on-disk `./Foo.ts` and imports `*.yaml`
|
|
43
|
-
files as raw text, mirroring tsdown's `.yaml`-as-text loader — this is what lets
|
|
44
|
-
`src/workflows/unified.yaml` resolve when running from source.
|
|
45
|
-
|
|
46
|
-
The decision core is pure and IO-free: the pattern machine's shape (states,
|
|
47
|
-
patterns, retry) is a plain-data `WorkflowDefinition` (`src/PatternMachine.ts`),
|
|
48
|
-
and the same module's `resolveState`/`step` are the pure resolver/interpreter
|
|
49
|
-
over it — so the whole engine is trivially unit-testable in isolation; all
|
|
50
|
-
git/filesystem/template IO is confined to the edge (`src/Edge.ts`).
|
|
51
|
-
|
|
52
|
-
`npm run build` produces `dist/gtd.bundle.mjs`, which npm exposes as the `gtd`
|
|
53
|
-
binary via the `bin` field in `package.json`.
|
|
54
|
-
|
|
55
|
-
## The LSP server
|
|
56
|
-
|
|
57
|
-
`gtd lsp` (`src/Lsp.ts`) serves editor tooling over `.gtd/`'s two steering-file
|
|
58
|
-
formats — document symbols and diagnostics for `.gtd/TODO.md`'s open questions
|
|
59
|
-
and `.gtd/REVIEW.md`'s review chunks/hunks, plus check/uncheck code actions for
|
|
60
|
-
the latter. It's keyed on file NAME, not workflow state: a v3 workflow declares
|
|
61
|
-
no state→file mapping (a state names no file), so the server needs no
|
|
62
|
-
git/`.gtdrc` dependency at all and serves whatever content the editor hands it
|
|
63
|
-
over the LSP protocol, exactly like any other document.
|
|
64
|
-
|
|
65
|
-
`src/OpenQuestions.ts` and `src/ReviewDoc.ts` are the pure parsers behind both
|
|
66
|
-
the LSP and the `gtd validate` command, which resolves the current state, reads
|
|
67
|
-
its `file:`, and evaluates it per its `mode:` — ONE source of truth per format,
|
|
68
|
-
no bash port and no dual-implementation contract to keep in sync. Mode dispatch
|
|
69
|
-
itself lives in `src/SteeringMode.ts`, half by half: the two BUILT-IN mode names
|
|
70
|
-
validate with these parsers (`qa` → `parseOpenQuestions`, `review` →
|
|
71
|
-
`parseReviewDoc`), while a `modes:` entry's `format:`/`validate:` shell commands
|
|
72
|
-
take precedence per half — gtd itself ships no formatter. See
|
|
73
|
-
[docs/design/steering-file-validation-command.md](design/steering-file-validation-command.md)
|
|
74
|
-
and
|
|
75
|
-
[docs/design/pluggable-steering-modes.md](design/pluggable-steering-modes.md)
|
|
76
|
-
and [STATES.md §12](../STATES.md) for the command, and each module's own doc
|
|
77
|
-
comment for the format it defines. Their unit tests are that format's spec
|
|
78
|
-
tests.
|
|
79
|
-
|
|
80
|
-
## Mutation testing
|
|
81
|
-
|
|
82
|
-
Run mutation testing on-demand with `npm run test:mutation` (StrykerJS) — never
|
|
83
|
-
run it as part of routine development; it is a deliberate, manually-triggered
|
|
84
|
-
check. `stryker.config.json`'s `mutate` list names the v3 pattern-machine module
|
|
85
|
-
set (`src/PatternMachine.ts`, `src/PatternConfig.ts`, `src/PatternTemplates.ts`,
|
|
86
|
-
`src/Edge.ts`, `src/Config.ts`, `src/Format.ts` — see
|
|
87
|
-
[Architecture](../AGENTS.md#the-pattern-machine-module-map) for the v3 module
|
|
88
|
-
map), plus the steering-file format parsers and the LSP built on them
|
|
89
|
-
(`src/OpenQuestions.ts`, `src/ReviewDoc.ts`, `src/Lsp.ts` — see
|
|
90
|
-
["The LSP server"](#the-lsp-server) below). `src/Lsp.ts`'s protocol-adapter tail
|
|
91
|
-
(`startLspServer`) is exercised only by the `@live` e2e (excluded from the
|
|
92
|
-
mutation run's Cucumber harness), so mutants confined to that tail may survive
|
|
93
|
-
without it being a coverage gap in the ordinary test suite — the pure
|
|
94
|
-
symbol/edit/diagnostic builders above it are unit-tested and mutation-checked
|
|
95
|
-
normally.
|
|
96
|
-
|
|
97
|
-
`src/Git.ts` is excluded: the Cucumber harness stubs git at the Effect boundary,
|
|
98
|
-
so `Git.ts` mutants have zero in-memory coverage.
|
|
99
|
-
|
|
100
|
-
The HTML report lands in `reports/mutation/mutation.html` (git-ignored).
|
|
101
|
-
|
|
102
|
-
## Releasing
|
|
103
|
-
|
|
104
|
-
Releases are automatic. Push releasable Conventional Commits (`fix:`, `feat:`,
|
|
105
|
-
or breaking changes) to `main` and the Release workflow runs the tests, then
|
|
106
|
-
`npx semantic-release`. Semantic-release computes the next version, writes it
|
|
107
|
-
into `package.json`, builds the bundle, commits the bump back as
|
|
108
|
-
`chore(release): X.Y.Z [skip ci]`, tags `vX.Y.Z`, and creates the GitHub release
|
|
109
|
-
with `gtd.bundle.mjs` attached.
|
package/docs/loop.md
DELETED
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
# Driving the loop
|
|
2
|
-
|
|
3
|
-
gtd (v3, the pattern machine) exposes three commands a loop driver combines:
|
|
4
|
-
|
|
5
|
-
- **`gtd step <actor>`** — authenticate as `<actor>`, match the resolved rest's
|
|
6
|
-
declared patterns against the pending changes, and commit (or squash) the one
|
|
7
|
-
resulting transition. Performs AT MOST one transition — there is no fixpoint
|
|
8
|
-
chain to drive. Pass `--cost=<n>` (optionally `--model=<name>`) to record the
|
|
9
|
-
just-finished invocation's token cost and model on the turn commit; gtd sums
|
|
10
|
-
them across the process into `it.processCost`/`it.processCostByModel` (see
|
|
11
|
-
[Configuration: Token cost](configuration.md#token-cost)).
|
|
12
|
-
- **`gtd next`** — print the resolved rest's rendered script/prompt/message,
|
|
13
|
-
without mutating anything.
|
|
14
|
-
|
|
15
|
-
gtd never executes a workflow script itself: at a `script`-content rest the
|
|
16
|
-
DRIVER runs the emitted `content` and then steps that state's own actor.
|
|
17
|
-
|
|
18
|
-
A loop is a simple cycle:
|
|
19
|
-
|
|
20
|
-
1. Run `gtd next --json` — see [`cli.md`](cli.md#gtd-next---json) for the exact
|
|
21
|
-
`{state, actor, kind, content, model?, memory?, label?}` field shape. `kind`
|
|
22
|
-
is the dispatch key: `"message"` → halt (a human rest); `"script"` → execute
|
|
23
|
-
`content` verbatim (ignoring its exit code), then `gtd step <actor>` to
|
|
24
|
-
capture the outcome; `"prompt"` → feed `content` to the agent, then
|
|
25
|
-
`gtd step <actor>` yourself once it's done acting. The optional `model` is an
|
|
26
|
-
opaque string the workflow author chose, which you map onto your own
|
|
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")"`).
|
|
33
|
-
2. Repeat until a `"message"` rest halts the loop, or a zero-commit script step
|
|
34
|
-
at idle settles it (the green terminal signal).
|
|
35
|
-
|
|
36
|
-
A human acts by editing files (e.g. writing/editing `.gtd/TODO.md`, fixing
|
|
37
|
-
code); `gtd step human` captures the edit as their turn. A driver can run that
|
|
38
|
-
capture for the human as its opening move (see below), so re-launching the loop
|
|
39
|
-
is the only thing the human ever does — at a gate they edit and re-run, and the
|
|
40
|
-
loop picks their change up.
|
|
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 rests 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. That gate is the
|
|
50
|
-
contract, not a stall — nothing advances until the edit is captured, whether the
|
|
51
|
-
capture happens within the same run or a later one. A review nobody is going to
|
|
52
|
-
finish is ended with [`gtd abandon`](cli.md#gtd-abandon---json), which drops the
|
|
53
|
-
process's commits and keeps their content as uncommitted changes.
|
|
54
|
-
|
|
55
|
-
**Editing is on by default.** Reaching a `"message"` rest, `bin/gtd` opens
|
|
56
|
-
`${VISUAL:-$EDITOR}` on the rest's declared `.file` (or the repo root `.` when
|
|
57
|
-
the state declares none), blocking until the editor exits, then runs
|
|
58
|
-
`gtd step human` to capture whatever changed:
|
|
59
|
-
|
|
60
|
-
- a new commit was captured → the loop keeps driving on its own; re-launching is
|
|
61
|
-
never necessary;
|
|
62
|
-
- zero commits were captured (a clean tree once the editor closed) → the loop
|
|
63
|
-
halts, exit 0 — "done for now";
|
|
64
|
-
- the step refuses (a malformed steering file, or an edit matching no declared
|
|
65
|
-
pattern) → the refusal is surfaced verbatim and the loop halts non-zero,
|
|
66
|
-
exactly as a manual `gtd step human` refusal would.
|
|
67
|
-
|
|
68
|
-
Set `GTD_NO_EDIT` (any non-empty value), or pass `--no-edit` — see
|
|
69
|
-
[`cli.md`](cli.md#--no-edit---edit---once) for its exact syntax and positioning
|
|
70
|
-
— to disable this and restore the original halt-and-print behavior: the loop
|
|
71
|
-
prints the gate via `gtd next` and exits 0 without touching an editor, leaving
|
|
72
|
-
the human to edit and re-launch the loop themselves.
|
|
73
|
-
|
|
74
|
-
Pass `--edit` (`-e`) to force the editor open at the current human gate right
|
|
75
|
-
now, overriding an ambient `GTD_NO_EDIT`/`--no-edit` for this one run — useful
|
|
76
|
-
when a shell profile sets `GTD_NO_EDIT` by default but you want to edit
|
|
77
|
-
interactively just this once. It only means something when the machine is
|
|
78
|
-
actually resting at a human gate: at any other rest, `bin/gtd` prints a note
|
|
79
|
-
that it isn't at one yet and simply keeps driving, rather than pretending to
|
|
80
|
-
force something undefined.
|
|
81
|
-
|
|
82
|
-
Pass `--once` to restrict the loop to exactly one beat — one human gate, one
|
|
83
|
-
script check+step, or one agent prompt+step — then exit, instead of driving all
|
|
84
|
-
the way to idle/settled. It combines freely with `--edit`/`--no-edit` (e.g.
|
|
85
|
-
`gtd --edit --once` forces the editor at the current gate, captures the edit,
|
|
86
|
-
and stops right there rather than continuing to drive).
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
gtd next --json # ask who's up and what they should do
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
See [`skills/loop/SKILL.md`](../skills/loop/SKILL.md) for the agent-facing
|
|
93
|
-
instructions that follow the same pinned contract. `gtd` itself (bare, or with
|
|
94
|
-
`loop` as its first argument — see below) is the packaged, ready-to-run
|
|
95
|
-
implementation of that same script for anyone who doesn't want to drive the loop
|
|
96
|
-
by hand.
|
|
97
|
-
|
|
98
|
-
## The reference loop driver
|
|
99
|
-
|
|
100
|
-
A minimal bash implementation of the pinned protocol, driving an agent CLI (e.g.
|
|
101
|
-
`claude -p`) against `gtd next --json` output. This is the authoritative
|
|
102
|
-
reference for what a loop driver must do; keep any other implementation
|
|
103
|
-
(including `skills/loop/SKILL.md`) consistent with it rather than editing both
|
|
104
|
-
independently. Both open with the same move — capture the human's pending edit
|
|
105
|
-
when the machine rests at a `"message"` gate — so `gtd` is the only command a
|
|
106
|
-
human runs. `bin/gtd` is the packaged entry point: invoked bare, or with `loop`
|
|
107
|
-
as its first argument, it runs this exact script; any other first argument (e.g.
|
|
108
|
-
`next`, `step`, `status`) hands off to `node dist/gtd.bundle.mjs` instead. The
|
|
109
|
-
loop body it runs adds six things on top of the reference script above: it opens
|
|
110
|
-
an editor at every `"message"` gate instead of just printing it (see "Editing is
|
|
111
|
-
on by default" above; `--no-edit`/`GTD_NO_EDIT` fall back to the reference
|
|
112
|
-
script's plain print-and-exit, while `--edit`/`-e` forces it on for this run
|
|
113
|
-
even over an ambient `GTD_NO_EDIT`); it can restrict itself to exactly one beat
|
|
114
|
-
via `--once` instead of driving to idle/settled; it stops with a diagnostic if
|
|
115
|
-
the same `"prompt"` state/content repeat with no progress (see
|
|
116
|
-
`skills/loop/SKILL.md`'s "Stall detection"); it lets `GTD_LOOP_AGENT_CMD` swap
|
|
117
|
-
in any coding agent CLI in place of the default `claude -p`, receiving the
|
|
118
|
-
prompt via `$GTD_LOOP_PROMPT`; it exports the resolved state's optional `model`
|
|
119
|
-
hint as `$GTD_LOOP_MODEL`, appending `--model "$GTD_LOOP_MODEL"` to the default
|
|
120
|
-
`claude -p` invocation whenever it's non-empty; and it acts on the optional
|
|
121
|
-
`memory` scope hint (see "Agent memory scope" below) — continuing one agent
|
|
122
|
-
session across consecutive same-scope turns and starting fresh when the scope
|
|
123
|
-
changes.
|
|
124
|
-
|
|
125
|
-
`bin/gtd` resolves both its bundle hand-off and the loop's own internal `gtd`
|
|
126
|
-
calls against `dist/gtd.bundle.mjs` next to its own location. Set `GTD_BIN` — a
|
|
127
|
-
full command, not just a path — to point both at source instead, e.g. for
|
|
128
|
-
development or testing:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
GTD_BIN="node $PWD/dev/run.mjs" bin/gtd
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
#!/usr/bin/env bash
|
|
136
|
-
set -euo pipefail
|
|
137
|
-
|
|
138
|
-
# Opening move: if the machine rests at a human gate, capture the human's
|
|
139
|
-
# pending edit first, so re-launching the loop is the ONLY thing a human does
|
|
140
|
-
# (they never run `gtd step human` by hand). Peek with the pure `gtd next
|
|
141
|
-
# --json` and step only at a `"message"` rest — at an agent/check rest
|
|
142
|
-
# `gtd step human` would refuse out-of-turn, and a mid-cycle restart must just
|
|
143
|
-
# resume driving. A clean gate is a no-op; a genuine refusal (malformed steering
|
|
144
|
-
# file, unrecognized edit) stops the loop instead of being driven past.
|
|
145
|
-
if [[ "$(gtd next --json | jq -r .kind)" == "message" ]]; then
|
|
146
|
-
gtd step human >/dev/null
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
while true; do
|
|
150
|
-
next_json="$(gtd next --json)"
|
|
151
|
-
state="$(jq -r .state <<<"$next_json")"
|
|
152
|
-
actor="$(jq -r .actor <<<"$next_json")"
|
|
153
|
-
kind="$(jq -r .kind <<<"$next_json")"
|
|
154
|
-
content="$(jq -r .content <<<"$next_json")"
|
|
155
|
-
|
|
156
|
-
if [[ "$kind" == "message" ]]; then
|
|
157
|
-
echo "your turn ($state)" # bin/gtd renders this via emit_gate instead
|
|
158
|
-
gtd next
|
|
159
|
-
exit 0
|
|
160
|
-
fi
|
|
161
|
-
|
|
162
|
-
if [[ "$kind" == "script" ]]; then
|
|
163
|
-
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
164
|
-
bash -c "$content" || true # run the emitted script; exit code ignored
|
|
165
|
-
gtd step "$actor" >/dev/null # capture whatever it left in the tree
|
|
166
|
-
head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
167
|
-
if [[ "$head_before" == "$head_after" ]]; then
|
|
168
|
-
echo "settled ($state: check passed, nothing to do)" # bin/gtd renders this via emit_settled instead
|
|
169
|
-
exit 0
|
|
170
|
-
fi
|
|
171
|
-
continue
|
|
172
|
-
fi
|
|
173
|
-
|
|
174
|
-
# kind == "prompt": feed the prompt to the agent, then close out its turn.
|
|
175
|
-
claude -p "$content" --dangerously-skip-permissions
|
|
176
|
-
gtd step "$actor" >/dev/null
|
|
177
|
-
done
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
The driver — not the prompt text — owns ending the agent's turn
|
|
181
|
-
(`gtd step "$actor"` right after the agent acts): every default-workflow agent
|
|
182
|
-
prompt says explicitly not to run `gtd step agent` itself.
|
|
183
|
-
|
|
184
|
-
`bin/gtd log` opens the current repo/worktree's loop logfile —
|
|
185
|
-
`"$(worktree_git_dir)/gtd-loop.log"` by default, or `$GTD_LOOP_LOG` verbatim
|
|
186
|
-
when set — in `${VISUAL:-$EDITOR}`, the same editor resolution `bin/gtd edit`
|
|
187
|
-
uses. It takes no arguments and errors out (rather than opening an empty buffer)
|
|
188
|
-
if no loop has produced a log yet. `worktree_git_dir` derives the git dir from
|
|
189
|
-
the cwd with `GIT_DIR`/`GIT_WORK_TREE` scrubbed, so an inherited `GIT_DIR`
|
|
190
|
-
(leaked from a parent git process, a hook, or another worktree's shell) can
|
|
191
|
-
never key the log — or the loop's memory marker — to a different worktree; two
|
|
192
|
-
worktrees looping at once each keep their own log.
|
|
193
|
-
|
|
194
|
-
## Rendered output and logging
|
|
195
|
-
|
|
196
|
-
`bin/gtd` detects its output capability once at startup (`FANCY`, true when
|
|
197
|
-
stdout is a real terminal AND `NO_COLOR` is unset): on a real terminal it prints
|
|
198
|
-
one colored/emoji line per event, and under `NO_COLOR` or a piped/redirected
|
|
199
|
-
stdout it prints the same events as plain ASCII with no escape codes, per the
|
|
200
|
-
[NO_COLOR convention](https://no-color.org). Normal events (a state transition,
|
|
201
|
-
a captured commit, an agent/check turn starting, settling at idle) render to
|
|
202
|
-
stdout; refusals, stalls, and validate-cap halts render to stderr, each followed
|
|
203
|
-
by a `📄 see <log>` pointer (plain: `see <log>`).
|
|
204
|
-
|
|
205
|
-
The three previously terminal-visible subprocess streams — the agent turn's own
|
|
206
|
-
output, the check script's own output, and `gtd step`'s own output — are no
|
|
207
|
-
longer printed directly: they're appended to the per-repo/per-worktree log file
|
|
208
|
-
(truncated once at the start of the run) instead, and surfaced with
|
|
209
|
-
`bin/gtd log` above.
|
|
210
|
-
|
|
211
|
-
## Agent memory scope
|
|
212
|
-
|
|
213
|
-
A `"prompt"` beat may carry an optional `memory` key — an opaque scope label the
|
|
214
|
-
workflow author chose (e.g. `plan`, `build`, `fix`). gtd never interprets it; it
|
|
215
|
-
signals to the driver whether this agent turn should continue from the previous
|
|
216
|
-
turn's memory or start fresh. The rule is a comparison, not a command: track the
|
|
217
|
-
`memory` of the last `"prompt"` beat (script/message beats have none and never
|
|
218
|
-
change it), and when a new `"prompt"` beat's `memory` **equals** the tracked
|
|
219
|
-
value, continue the same agent session; when it **differs** — or it is the first
|
|
220
|
-
turn, or either side has no `memory` — start fresh.
|
|
221
|
-
|
|
222
|
-
This is what makes a loop retain memory while a phase boundary clears it: a loop
|
|
223
|
-
that keeps re-entering one state emits the same label every lap, so the agent
|
|
224
|
-
accumulates context across the loop; the move to the next phase's
|
|
225
|
-
differently-labelled state resets it. `bin/gtd` implements this by mapping the
|
|
226
|
-
signal onto claude's session flags — `--session-id` to pin a fresh session when
|
|
227
|
-
the scope changes, `--resume` to continue it while the scope holds — and
|
|
228
|
-
persists the current scope + session id in the git dir (never the working tree,
|
|
229
|
-
so `gtd status` and the pending diff never see them) so retention survives even
|
|
230
|
-
across the restarts a human gate forces mid-loop. See `skills/loop/SKILL.md`'s
|
|
231
|
-
"Agent memory scope" for the same contract stated for a hand-driven loop.
|
|
232
|
-
|
|
233
|
-
## Using a different agent
|
|
234
|
-
|
|
235
|
-
`gtd` (bare, or `gtd loop`) defaults to
|
|
236
|
-
`claude -p "$GTD_LOOP_PROMPT" --dangerously-skip-permissions`, but the agent
|
|
237
|
-
invocation is swappable: set `GTD_LOOP_AGENT_CMD` to any shell command, and it
|
|
238
|
-
runs with the prompt available as `$GTD_LOOP_PROMPT` in its environment, along
|
|
239
|
-
with the resolved state's opaque `model` hint (if any) as `$GTD_LOOP_MODEL` and
|
|
240
|
-
its `memory` scope as `$GTD_LOOP_MEMORY` / `$GTD_LOOP_SESSION_ID` /
|
|
241
|
-
`$GTD_LOOP_MEMORY_RESUME` (`1` when this turn should continue the prior
|
|
242
|
-
session). An adapter that ignores any of these keeps working unchanged. For
|
|
243
|
-
example, to drive a different agent CLI:
|
|
244
|
-
|
|
245
|
-
```bash
|
|
246
|
-
GTD_LOOP_AGENT_CMD='my-agent-cli --prompt "$GTD_LOOP_PROMPT"' gtd
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
## Herdr integration (optional)
|
|
250
|
-
|
|
251
|
-
`bin/gtd` optionally reports its lifecycle to [Herdr](https://herdr.dev) (a
|
|
252
|
-
terminal multiplexer for coding agents) via a `herdr` CLI binary, entirely at
|
|
253
|
-
the driver's edge — gtd core never talks to Herdr. Every call is best-effort
|
|
254
|
-
(guarded, output discarded, `|| true`), so a missing/failing `herdr` binary
|
|
255
|
-
never blocks, slows, or fails the loop. Because that guard also hides a herdr
|
|
256
|
-
CLI-signature mismatch, set `GTD_HERDR_DEBUG=1` to surface every `herdr` call
|
|
257
|
-
and its exit code on stderr instead.
|
|
258
|
-
|
|
259
|
-
The reporting is a no-op unless all three guard conditions hold: `HERDR_ENV=1`,
|
|
260
|
-
a non-empty `$HERDR_PANE_ID`, and `herdr` on `$PATH`. When they do, `gtd` makes
|
|
261
|
-
three kinds of calls (note the positional `<PANE_ID>` comes BEFORE the options —
|
|
262
|
-
herdr's `pane` subcommands reject a trailing pane id):
|
|
263
|
-
|
|
264
|
-
- `herdr pane report-agent "$HERDR_PANE_ID" --source herdr:gtd --agent gtd --state <state> --message <label>`
|
|
265
|
-
- `herdr pane release-agent "$HERDR_PANE_ID" --source herdr:gtd --agent gtd`
|
|
266
|
-
- `herdr notification show <title> --body <label> --sound request`
|
|
267
|
-
|
|
268
|
-
mapped onto the loop's states like this:
|
|
269
|
-
|
|
270
|
-
| Loop moment | Call(s) |
|
|
271
|
-
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
272
|
-
| Top of every non-`"message"` iteration | `report-agent --state working` |
|
|
273
|
-
| Right before halting at a `"message"` (human) gate (editing off) | `report-agent --state blocked` + `notification show` |
|
|
274
|
-
| While the editor is open at a human gate (editing on) | `report-agent --state blocked`, flipped back to `working` once it closes |
|
|
275
|
-
| Right before a clean settle (a script rest, zero new commits) | `report-agent --state idle` + `release-agent` |
|
|
276
|
-
| Any non-zero exit (stall guard, validate-cap, `gtd next` failure, unhandled failure) | `report-agent --state blocked` + `notification show`, via an `EXIT` trap |
|
|
277
|
-
|
|
278
|
-
`working`/`blocked` reports use the resolved rest's `label` (falling back to
|
|
279
|
-
`state`, per the field above); the trap falls back to `$GTD_LAST_LABEL`, the
|
|
280
|
-
label tracked from the last completed iteration, when it fires outside the
|
|
281
|
-
normal per-iteration flow. `report-agent` claims display authority for the
|
|
282
|
-
`herdr:gtd` source, so `working` is re-reported every lap rather than once, to
|
|
283
|
-
stay fresh against Herdr's own heuristic detection of the inner agent process.
|
|
284
|
-
|
|
285
|
-
Set `GTD_NO_NOTIFY` (any non-empty value) or pass `--no-notify` to suppress the
|
|
286
|
-
two `notification show` calls above (the human-gate halt and the exit trap) for
|
|
287
|
-
this run — the OS popup + sound only. `report-agent`/`release-agent` (the silent
|
|
288
|
-
Herdr sidebar status) are untouched either way, mirroring
|
|
289
|
-
`--no-edit`/`GTD_NO_EDIT`'s flag/env shape.
|