@pmelab/gtd 7.11.1 → 8.0.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 +22 -35
- package/bin/gtd +82 -277
- package/dist/gtd.bundle.mjs +3 -6
- package/docs/cli.md +24 -94
- package/docs/loop.md +26 -96
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,16 +107,10 @@ handful of commands drive it:
|
|
|
107
107
|
and a "Current state" panel showing where the active process rests, its
|
|
108
108
|
pending changes, and which action leads where. The panel and diagram highlight
|
|
109
109
|
refresh live (~every 3s) while the page is open, so advancing the process
|
|
110
|
-
elsewhere shows up without a manual refresh.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
current human gate, then keep driving", pass `--edit` to the loop instead (see
|
|
115
|
-
below).
|
|
116
|
-
|
|
117
|
-
`gtd version` (or `gtd --version`/`-v`) prints the installed version and exits;
|
|
118
|
-
`gtd help` (or `gtd --help`/`-h`) prints the command list. Both short-circuit
|
|
119
|
-
before any repo work, so they run anywhere.
|
|
110
|
+
elsewhere shows up without a manual refresh. `gtd version` (or
|
|
111
|
+
`gtd --version`/`-v`) prints the installed version and exits; `gtd help` (or
|
|
112
|
+
`gtd --help`/`-h`) prints the command list. Both short-circuit before any repo
|
|
113
|
+
work, so they run anywhere.
|
|
120
114
|
|
|
121
115
|
The loop is one beat, repeated: run `gtd next --json` and dispatch on `kind` —
|
|
122
116
|
`"message"` means it's a human's move (stop and hand off); `"script"` means the
|
|
@@ -183,23 +177,20 @@ keep their own scratch/bookkeeping, independent of the per-file vars — relocat
|
|
|
183
177
|
one steering file without touching it.
|
|
184
178
|
|
|
185
179
|
Bare `gtd` (or `gtd loop`) is a ready-to-run driver for the whole protocol —
|
|
186
|
-
point it at a repo and it runs the loop until it's your turn. It
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
`gtd step human` by hand.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
when piped — and redirects the noisier agent/check/step subprocess output to a
|
|
201
|
-
per-repo/per-worktree log file. `gtd log` opens that logfile in your editor. See
|
|
202
|
-
[Driving the loop](docs/loop.md).
|
|
180
|
+
point it at a repo and it runs the loop until it's your turn. It drives the
|
|
181
|
+
autonomous states (agent turns, check runs) and stops at the first
|
|
182
|
+
non-autonomous one: reaching a human gate it prints the gate and exits. You act
|
|
183
|
+
by editing files (answer a plan question, tick a review box, fix code) and
|
|
184
|
+
re-launching it — its opening move captures whatever you left, so you never run
|
|
185
|
+
`gtd step human` by hand. Anything richer at that boundary — opening your
|
|
186
|
+
editor, desktop notifications, terminal-multiplexer status — is the job of an
|
|
187
|
+
outer wrapper around `gtd`, not of the loop itself. Pass `--once` to restrict a
|
|
188
|
+
run to exactly one beat — one human-gate capture, one check, or one agent turn —
|
|
189
|
+
instead of driving all the way to idle. Bare `gtd` prints one line per event —
|
|
190
|
+
colored and emoji on a real terminal, plain ASCII under `NO_COLOR` or when piped
|
|
191
|
+
— and redirects the noisier agent/check/step subprocess output to a
|
|
192
|
+
per-repo/per-worktree log file (its path is the run's first output line, ready
|
|
193
|
+
to `tail -f`). See [Driving the loop](docs/loop.md).
|
|
203
194
|
|
|
204
195
|
Before wiring gtd into a repo, note the
|
|
205
196
|
[repository requirements](docs/cli.md#repository-requirements) — most
|
|
@@ -225,14 +216,10 @@ diagnostics for it, though `gtd validate` and the `gtd step` gate still format
|
|
|
225
216
|
and validate it like any other mode. Both halves are enforced by `gtd validate`
|
|
226
217
|
and the `gtd step` gate.
|
|
227
218
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
`herdr` CLI when running under Herdr (`HERDR_ENV=1`, a pane ID, and `herdr` on
|
|
233
|
-
`$PATH`); outside Herdr this is a complete no-op. See
|
|
234
|
-
[Driving the loop](docs/loop.md#herdr-integration-optional) for the full
|
|
235
|
-
reporting contract.
|
|
219
|
+
A workflow state can declare an optional `label:` — a human-readable display
|
|
220
|
+
name surfaced in `gtd next --json`/`gtd status`. The reference `bin/gtd` driver
|
|
221
|
+
uses it for its per-beat progress lines; an outer wrapper (a terminal
|
|
222
|
+
multiplexer, a notifier) can use it the same way.
|
|
236
223
|
|
|
237
224
|
## Documentation
|
|
238
225
|
|
package/bin/gtd
CHANGED
|
@@ -3,33 +3,19 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
# This file is the single published `bin` entry: `bin/gtd`. Before anything
|
|
5
5
|
# else, it dispatches on $1:
|
|
6
|
-
# - `--
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# (one human gate, one script check+step, or one agent prompt+step) before
|
|
13
|
-
# exiting, and `--no-notify` (or `GTD_NO_NOTIFY`) suppresses the loop's
|
|
14
|
-
# Herdr desktop notifications for this run while leaving its silent Herdr
|
|
15
|
-
# pane-state reporting untouched. Dispatch then continues on the
|
|
16
|
-
# remaining arguments as if none of them had ever been given.
|
|
17
|
-
# - `edit [path]` -> handled entirely here in bash, never forwarded to the
|
|
18
|
-
# bundle: opens `${VISUAL:-$EDITOR}` on `path`, or (no `path` given) on the
|
|
19
|
-
# resolved rest's `.file` (or `.` when absent). Low-level plumbing now that
|
|
20
|
-
# `--edit` covers the common "force an edit, then keep driving" case; this
|
|
21
|
-
# subcommand remains for opening a specific path, or the current rest's
|
|
22
|
-
# file, WITHOUT driving the loop afterward.
|
|
23
|
-
# - `log` -> handled entirely here in bash, never forwarded to the bundle:
|
|
24
|
-
# opens `${VISUAL:-$EDITOR}` on the current repo/worktree's loop logfile
|
|
25
|
-
# (see `resolve_log_path`), refusing rather than opening an empty buffer
|
|
26
|
-
# when no loop has produced one yet.
|
|
27
|
-
# - no arguments, or exactly `loop` with no further arguments (other than the
|
|
28
|
-
# flags above) -> fall through into the loop body below (the loop protocol
|
|
29
|
-
# comment further down).
|
|
6
|
+
# - `--once` (bare, or immediately after `loop`) -> stripped here; restricts
|
|
7
|
+
# the loop to exactly one beat (one human-gate capture, one script
|
|
8
|
+
# check+step, or one agent prompt+step) before exiting. Dispatch then
|
|
9
|
+
# continues on the remaining arguments as if it had never been given.
|
|
10
|
+
# - no arguments, or exactly `loop` with no further arguments -> fall through
|
|
11
|
+
# into the loop body below (the loop protocol comment further down).
|
|
30
12
|
# - `loop` with an unrecognised extra argument -> usage error, exit 2.
|
|
31
13
|
# - anything else -> hand off to the bundle via `run_gtd`, forwarding all
|
|
32
14
|
# arguments unchanged (e.g. `bin/gtd status`, `bin/gtd step human`).
|
|
15
|
+
# The loop's whole job is to drive the process until it rests at a
|
|
16
|
+
# NON-AUTONOMOUS state (a human gate) or settles. Everything beyond that —
|
|
17
|
+
# editors, desktop notifications, terminal-multiplexer status — is an OUTER
|
|
18
|
+
# wrapper's responsibility, deliberately not this script's.
|
|
33
19
|
# `run_gtd` is the ONE place that knows how to invoke "the bundle": by default
|
|
34
20
|
# it resolves `dist/gtd.bundle.mjs` relative to this script's own location
|
|
35
21
|
# (not cwd), but `GTD_BIN` (a dev/test override for the whole invocation
|
|
@@ -47,29 +33,11 @@ run_gtd() {
|
|
|
47
33
|
fi
|
|
48
34
|
}
|
|
49
35
|
|
|
50
|
-
# Shared editor-launch helper — used by both the `edit` subcommand below and
|
|
51
|
-
# the loop's human-gate flow further down. Resolves the editor git-style
|
|
52
|
-
# ($VISUAL, then $EDITOR — no fallback to `vi`), then launches it blocking in
|
|
53
|
-
# the foreground with the target path passed as a positional parameter
|
|
54
|
-
# (never string-spliced into the command), so a multi-word command
|
|
55
|
-
# (`code --wait`) and a path with special characters both work. A launch
|
|
56
|
-
# failure (bad/missing editor binary) is a plain non-zero return, propagating
|
|
57
|
-
# under `set -e`; the editor's own exit code is otherwise ignored entirely —
|
|
58
|
-
# the outcome is judged from tree state afterward, not from this function.
|
|
59
|
-
launch_editor() {
|
|
60
|
-
local target="$1" editor="${VISUAL:-${EDITOR:-}}"
|
|
61
|
-
if [[ -z "$editor" ]]; then
|
|
62
|
-
echo "gtd: no editor configured — set \$EDITOR (or \$VISUAL)" >&2
|
|
63
|
-
return 1
|
|
64
|
-
fi
|
|
65
|
-
bash -c "$editor \"\$1\"" gtd-edit "$target"
|
|
66
|
-
}
|
|
67
|
-
|
|
68
36
|
# ── Rendering primitives (capability detection, palette/markers, emitters) ──
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
37
|
+
# `FANCY=1` only when stdout (fd 1) is a real terminal AND `NO_COLOR` is unset
|
|
38
|
+
# — any pipe/redirect/non-tty stdout, or `NO_COLOR` set to any value, gives
|
|
39
|
+
# plain ASCII with no escape codes, per the NO_COLOR convention
|
|
40
|
+
# (https://no-color.org).
|
|
73
41
|
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then FANCY=1; else FANCY=0; fi
|
|
74
42
|
|
|
75
43
|
if [[ "$FANCY" == 1 ]]; then
|
|
@@ -157,8 +125,6 @@ emit_log_pointer() {
|
|
|
157
125
|
}
|
|
158
126
|
|
|
159
127
|
# ── Git-derivation helpers (parse_subject, report_commits) ──────────────────
|
|
160
|
-
# Additive: nothing below calls these yet. Replaces today's
|
|
161
|
-
# head_before/head_after string comparisons in a later change.
|
|
162
128
|
|
|
163
129
|
# parse_subject <subject> — sets S_ACTOR / S_FROM / S_TO / S_KIND. Patterns are
|
|
164
130
|
# held in variables (rather than inlined) so bash's [[ =~ ]] doesn't need the
|
|
@@ -183,11 +149,14 @@ parse_subject() {
|
|
|
183
149
|
fi
|
|
184
150
|
}
|
|
185
151
|
|
|
186
|
-
# report_commits
|
|
187
|
-
#
|
|
188
|
-
#
|
|
152
|
+
# report_commits — walks "$reported_head"..HEAD in order, emitting a
|
|
153
|
+
# transition/commit line per new commit, and advances $reported_head to HEAD
|
|
154
|
+
# when it reported at least one; returns non-zero (having emitted nothing,
|
|
155
|
+
# and leaving $reported_head unchanged) when there were no new commits. Global
|
|
156
|
+
# rather than per-beat so the review checkout window's HEAD rewinds between
|
|
157
|
+
# beats never cause an already-reported commit to replay.
|
|
189
158
|
report_commits() {
|
|
190
|
-
local
|
|
159
|
+
local sha subject files reported=0
|
|
191
160
|
while IFS= read -r sha; do
|
|
192
161
|
[[ -z "$sha" ]] && continue
|
|
193
162
|
reported=1
|
|
@@ -198,11 +167,14 @@ report_commits() {
|
|
|
198
167
|
transition) emit_transition "$S_FROM" "$S_TO" "$files" ;;
|
|
199
168
|
capture | squash) emit_commit "$subject" "$files" ;;
|
|
200
169
|
esac
|
|
201
|
-
done < <(git rev-list --reverse "$
|
|
170
|
+
done < <(git rev-list --reverse "$reported_head"..HEAD)
|
|
171
|
+
if [[ "$reported" == 1 ]]; then
|
|
172
|
+
reported_head="$(git rev-parse HEAD)"
|
|
173
|
+
fi
|
|
202
174
|
[[ "$reported" == 1 ]]
|
|
203
175
|
}
|
|
204
176
|
|
|
205
|
-
# ── Log file (resolve_log_path
|
|
177
|
+
# ── Log file (resolve_log_path) ─────────────────────────────────────────────
|
|
206
178
|
|
|
207
179
|
# worktree_git_dir — the git dir of the working tree gtd is operating in,
|
|
208
180
|
# derived purely from the cwd. GIT_DIR/GIT_WORK_TREE are scrubbed for this one
|
|
@@ -226,25 +198,16 @@ resolve_log_path() {
|
|
|
226
198
|
fi
|
|
227
199
|
}
|
|
228
200
|
|
|
229
|
-
# --
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
# with a real subcommand) is left untouched and falls through to the existing
|
|
239
|
-
# dispatch below, which forwards a non-flag first argument to the bundle
|
|
240
|
-
# (whose own unknown-option guard rejects it) or rejects a `loop` invocation
|
|
241
|
-
# carrying an unrecognised extra argument.
|
|
242
|
-
edit_disabled=0
|
|
243
|
-
edit_forced=0
|
|
201
|
+
# --once governs only the loop driver, recognised in exactly two positions —
|
|
202
|
+
# bare (`gtd --once`) or immediately after `loop` (`gtd loop --once`) — at
|
|
203
|
+
# most once. When recognised it is stripped here before anything below looks
|
|
204
|
+
# at "$@", so the bundle's own unknown-option guard never sees it; anything
|
|
205
|
+
# else (an unknown flag, a duplicate --once, or --once mixed with a real
|
|
206
|
+
# subcommand) is left untouched and falls through to the existing dispatch
|
|
207
|
+
# below, which forwards a non-flag first argument to the bundle (whose own
|
|
208
|
+
# unknown-option guard rejects it) or rejects a `loop` invocation carrying an
|
|
209
|
+
# unrecognised extra argument.
|
|
244
210
|
once_mode=0
|
|
245
|
-
notify_disabled=0
|
|
246
|
-
[[ -n "${GTD_NO_EDIT:-}" ]] && edit_disabled=1
|
|
247
|
-
[[ -n "${GTD_NO_NOTIFY:-}" ]] && notify_disabled=1
|
|
248
211
|
|
|
249
212
|
if [[ "${1:-}" == "loop" ]]; then
|
|
250
213
|
loop_form=1
|
|
@@ -255,31 +218,17 @@ else
|
|
|
255
218
|
fi
|
|
256
219
|
|
|
257
220
|
valid_combo=1
|
|
258
|
-
n_no_edit=0
|
|
259
|
-
n_edit=0
|
|
260
221
|
n_once=0
|
|
261
|
-
n_no_notify=0
|
|
262
222
|
for a in ${loop_flag_args[@]+"${loop_flag_args[@]}"}; do
|
|
263
223
|
case "$a" in
|
|
264
|
-
--no-edit) n_no_edit=$((n_no_edit + 1)) ;;
|
|
265
|
-
--edit | -e) n_edit=$((n_edit + 1)) ;;
|
|
266
224
|
--once) n_once=$((n_once + 1)) ;;
|
|
267
|
-
--no-notify) n_no_notify=$((n_no_notify + 1)) ;;
|
|
268
225
|
*) valid_combo=0 ;;
|
|
269
226
|
esac
|
|
270
227
|
done
|
|
271
|
-
|
|
272
|
-
valid_combo=0
|
|
273
|
-
fi
|
|
228
|
+
((n_once > 1)) && valid_combo=0
|
|
274
229
|
|
|
275
|
-
if [[ "$valid_combo" == 1 ]]; then
|
|
276
|
-
|
|
277
|
-
((n_edit >= 1)) && edit_forced=1
|
|
278
|
-
((n_once >= 1)) && once_mode=1
|
|
279
|
-
((n_no_notify >= 1)) && notify_disabled=1
|
|
280
|
-
# --edit overrides an ambient GTD_NO_EDIT/--no-edit for THIS run — the
|
|
281
|
-
# explicit ask to force the editor wins over the suppressing default.
|
|
282
|
-
((edit_forced)) && edit_disabled=0
|
|
230
|
+
if [[ "$valid_combo" == 1 && "$n_once" == 1 ]]; then
|
|
231
|
+
once_mode=1
|
|
283
232
|
if [[ "$loop_form" == 1 ]]; then
|
|
284
233
|
set -- loop
|
|
285
234
|
else
|
|
@@ -287,37 +236,6 @@ if [[ "$valid_combo" == 1 ]]; then
|
|
|
287
236
|
fi
|
|
288
237
|
fi
|
|
289
238
|
|
|
290
|
-
if [[ "${1:-}" == "edit" ]]; then
|
|
291
|
-
shift
|
|
292
|
-
if [[ $# -gt 0 ]]; then
|
|
293
|
-
launch_editor "$1"
|
|
294
|
-
else
|
|
295
|
-
if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
296
|
-
echo "gtd edit: could not determine the next step:" >&2
|
|
297
|
-
echo "$peek_json" >&2
|
|
298
|
-
exit 1
|
|
299
|
-
fi
|
|
300
|
-
file="$(jq -r '.file // empty' <<<"$peek_json")"
|
|
301
|
-
launch_editor "${file:-.}"
|
|
302
|
-
fi
|
|
303
|
-
exit 0
|
|
304
|
-
fi
|
|
305
|
-
|
|
306
|
-
if [[ "${1:-}" == "log" ]]; then
|
|
307
|
-
shift
|
|
308
|
-
if [[ $# -gt 0 ]]; then
|
|
309
|
-
echo "gtd log: takes no arguments" >&2
|
|
310
|
-
exit 2
|
|
311
|
-
fi
|
|
312
|
-
log_path="$(resolve_log_path)"
|
|
313
|
-
if [[ ! -f "$log_path" ]]; then
|
|
314
|
-
echo "gtd log: no loop log yet at $log_path" >&2
|
|
315
|
-
exit 1
|
|
316
|
-
fi
|
|
317
|
-
launch_editor "$log_path"
|
|
318
|
-
exit 0
|
|
319
|
-
fi
|
|
320
|
-
|
|
321
239
|
if [[ $# -gt 0 && "$1" != "loop" ]]; then
|
|
322
240
|
run_gtd "$@"
|
|
323
241
|
exit $?
|
|
@@ -326,16 +244,14 @@ if [[ "${1:-}" == "loop" && $# -gt 1 ]]; then
|
|
|
326
244
|
echo "gtd: 'loop' takes no arguments" >&2
|
|
327
245
|
exit 2
|
|
328
246
|
fi
|
|
329
|
-
# otherwise ($# -eq 0, or exactly "loop" with no further args, --
|
|
330
|
-
#
|
|
331
|
-
# unchanged in behavior.
|
|
247
|
+
# otherwise ($# -eq 0, or exactly "loop" with no further args, --once already
|
|
248
|
+
# stripped above): fall through into the loop body below.
|
|
332
249
|
|
|
333
250
|
# Drives the v3 pattern-machine loop (see docs/loop.md): each iteration asks
|
|
334
251
|
# `gtd next --json` who's up and what they should do, dispatching on `kind`:
|
|
335
|
-
# - "message" (a human rest) ->
|
|
336
|
-
#
|
|
337
|
-
#
|
|
338
|
-
# the user what gtd is waiting on, unchanged from before this feature.
|
|
252
|
+
# - "message" (a human rest) -> the non-autonomous halt: print the gate and
|
|
253
|
+
# exit 0. Acting on it (opening an editor, notifying, whatever) is an
|
|
254
|
+
# outer wrapper's job; this loop only ever drives the AUTONOMOUS states.
|
|
339
255
|
# - "script" (a check rest) -> execute `content` (the emitted wrapper
|
|
340
256
|
# script) verbatim via bash — exit code deliberately ignored, the outcome
|
|
341
257
|
# lives in the tree — then `gtd step <actor>` to capture it; zero new
|
|
@@ -362,118 +278,16 @@ fi
|
|
|
362
278
|
# resume-or-fresh flag are exported as $GTD_LOOP_MEMORY / $GTD_LOOP_SESSION_ID
|
|
363
279
|
# / $GTD_LOOP_MEMORY_RESUME; the default adapter turns them into
|
|
364
280
|
# `--resume`/`--session-id`. An adapter that ignores them keeps working.
|
|
365
|
-
herdr_ok() { [ "${HERDR_ENV:-}" = 1 ] && [ -n "${HERDR_PANE_ID:-}" ] && command -v herdr >/dev/null 2>&1; }
|
|
366
|
-
|
|
367
|
-
# Run a `herdr` invocation best-effort. Normally output is discarded and a
|
|
368
|
-
# failure is swallowed (`|| true`) so a missing/incompatible herdr never blocks
|
|
369
|
-
# the loop. Set GTD_HERDR_DEBUG=1 to instead surface every call and its exit
|
|
370
|
-
# code on stderr — the escape hatch that makes a silent CLI-signature mismatch
|
|
371
|
-
# (e.g. an arg herdr rejects) visible again.
|
|
372
|
-
herdr_run() {
|
|
373
|
-
if [ "${GTD_HERDR_DEBUG:-}" = 1 ]; then
|
|
374
|
-
printf 'gtd/herdr: %s\n' "$*" >&2
|
|
375
|
-
herdr "$@" >&2
|
|
376
|
-
printf 'gtd/herdr: exit=%s\n' "$?" >&2
|
|
377
|
-
else
|
|
378
|
-
herdr "$@" >/dev/null 2>&1 || true
|
|
379
|
-
fi
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
# NOTE: herdr requires the positional <PANE_ID> BEFORE the options for
|
|
383
|
-
# `pane` subcommands — passing it last makes herdr reject the first option as
|
|
384
|
-
# `unknown option` (exit 2). Keep the pane id first here.
|
|
385
|
-
herdr_report() { # $1=state (working|blocked|idle) $2=message
|
|
386
|
-
herdr_ok || return 0
|
|
387
|
-
herdr_run pane report-agent "$HERDR_PANE_ID" \
|
|
388
|
-
--source herdr:gtd --agent gtd --state "$1" --message "$2"
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
# Suppressible via GTD_NO_NOTIFY/--no-notify (set into $notify_disabled at
|
|
392
|
-
# parse time, above) — the OS popup this call makes is the only thing that
|
|
393
|
-
# opts out; herdr_report/herdr_release (silent sidebar status) are unaffected.
|
|
394
|
-
herdr_notify() { # $1=title $2=body
|
|
395
|
-
[[ "$notify_disabled" == 1 ]] && return 0
|
|
396
|
-
herdr_ok || return 0
|
|
397
|
-
herdr_run notification show "$1" --body "$2" --sound request
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
herdr_release() { # hand the pane back to Herdr's normal detection
|
|
401
|
-
herdr_ok || return 0
|
|
402
|
-
herdr_run pane release-agent "$HERDR_PANE_ID" --source herdr:gtd --agent gtd
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
# Shared human-gate handler for a "message" (human) rest — used both by the
|
|
406
|
-
# opening move (when editing is on) and by every "message" rest the main loop
|
|
407
|
-
# encounters. $1 is the rest's `next --json` blob.
|
|
408
|
-
#
|
|
409
|
-
# Editing OFF ($edit_disabled): today's behavior, unchanged — render the gate
|
|
410
|
-
# via emit_gate and exit 0 without touching an editor.
|
|
411
|
-
#
|
|
412
|
-
# Editing ON (the default): open the editor on the rest's `.file` (or `.` when
|
|
413
|
-
# absent), wait for it to exit, then run `gtd step human`:
|
|
414
|
-
# - a NEW commit was authored -> return normally so the caller keeps driving.
|
|
415
|
-
# - ZERO commits were authored -> halt, exit 0 (closing the editor without a
|
|
416
|
-
# capturable change means "done for now" — and keeps a gate with no `C`
|
|
417
|
-
# pattern, e.g. idle, from being reopened forever).
|
|
418
|
-
# - the step REFUSES (non-zero exit: malformed steering file, unfinished
|
|
419
|
-
# review) -> surface it verbatim and halt, exit non-zero — the same
|
|
420
|
-
# wording an opening-move refusal already used before this function
|
|
421
|
-
# existed.
|
|
422
|
-
handle_human_gate() {
|
|
423
|
-
local json="$1" file label state step_out head_before head_after
|
|
424
|
-
label="$(jq -r '.label // .state' <<<"$json")"
|
|
425
|
-
state="$(jq -r .state <<<"$json")"
|
|
426
|
-
|
|
427
|
-
if [[ "$edit_disabled" == 1 ]]; then
|
|
428
|
-
herdr_report blocked "$label"
|
|
429
|
-
herdr_notify "gtd needs you — $(basename "$PWD")" "$label"
|
|
430
|
-
emit_gate "$state" "$label"
|
|
431
|
-
run_gtd next
|
|
432
|
-
exit 0
|
|
433
|
-
fi
|
|
434
|
-
|
|
435
|
-
file="$(jq -r '.file // empty' <<<"$json")"
|
|
436
|
-
herdr_report blocked "$label"
|
|
437
|
-
launch_editor "${file:-.}"
|
|
438
|
-
herdr_report working "$label"
|
|
439
|
-
|
|
440
|
-
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
441
|
-
if ! step_out="$(run_gtd step human 2>&1)"; then
|
|
442
|
-
emit_error "could not capture your changes at the human gate:
|
|
443
|
-
$step_out
|
|
444
|
-
Fix the reported issue and re-run gtd-loop."
|
|
445
|
-
emit_log_pointer
|
|
446
|
-
exit 1
|
|
447
|
-
fi
|
|
448
|
-
head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
449
|
-
if [[ "$head_before" == "$head_after" ]]; then
|
|
450
|
-
herdr_report idle "$label"
|
|
451
|
-
herdr_release
|
|
452
|
-
emit_action "$M_HUMAN" "done for now (nothing captured)"
|
|
453
|
-
exit 0
|
|
454
|
-
fi
|
|
455
|
-
# A new commit was captured — render it, then return normally so the
|
|
456
|
-
# caller keeps driving, unless --once says this one human gate is the whole
|
|
457
|
-
# run.
|
|
458
|
-
report_commits "$head_before"
|
|
459
|
-
if [[ "$once_mode" == 1 ]]; then
|
|
460
|
-
exit 0
|
|
461
|
-
fi
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
trap 'code=$?; if [ "$code" -ne 0 ]; then
|
|
465
|
-
herdr_report blocked "${GTD_LAST_LABEL:-stopped}: exited $code"
|
|
466
|
-
herdr_notify "gtd stopped — $(basename "$PWD")" "${GTD_LAST_LABEL:-loop} failed (exit $code)"
|
|
467
|
-
fi' EXIT
|
|
468
281
|
|
|
469
282
|
prev_state=""
|
|
470
283
|
prev_content=""
|
|
284
|
+
prev_head=""
|
|
471
285
|
# Where the last "prompt" turn's `memory` scope + session id are remembered
|
|
472
286
|
# across iterations and across restarts. In the git dir, never the work tree.
|
|
473
287
|
memory_marker="$(worktree_git_dir 2>/dev/null || echo .git)/gtd-loop-memory"
|
|
474
288
|
|
|
475
289
|
# Truncated once here (not every iteration), so the file always reflects the
|
|
476
|
-
# current run but survives after the loop exits
|
|
290
|
+
# current run but survives after the loop exits.
|
|
477
291
|
log_path="$(resolve_log_path)"
|
|
478
292
|
: >"$log_path"
|
|
479
293
|
export GTD_LOOP_LOG="$log_path"
|
|
@@ -512,6 +326,12 @@ run_agent_turn() {
|
|
|
512
326
|
>>"${GTD_LOOP_LOG:-/dev/null}" 2>&1
|
|
513
327
|
}
|
|
514
328
|
|
|
329
|
+
# The furthest commit already reported to the terminal by report_commits (see
|
|
330
|
+
# its own comment) — a global high-water mark rather than a per-beat
|
|
331
|
+
# head_before, so a review checkout window's HEAD rewind between beats never
|
|
332
|
+
# causes an already-printed transition to replay.
|
|
333
|
+
reported_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
334
|
+
|
|
515
335
|
# Opening move: capture the human's pending edit so gtd-loop is the ONLY command
|
|
516
336
|
# a human runs. A human acts at a gate by editing files (a plan answer, a review
|
|
517
337
|
# tick, a code fix) and re-launching the loop — they should never have to run
|
|
@@ -525,16 +345,9 @@ run_agent_turn() {
|
|
|
525
345
|
# and a mid-cycle restart must simply resume driving, not fail. A clean gate is
|
|
526
346
|
# a harmless no-op; a genuine refusal there (a malformed steering file, or an
|
|
527
347
|
# edit no `on` pattern accepts) is surfaced and stops the loop rather than being
|
|
528
|
-
# driven past.
|
|
529
|
-
#
|
|
530
|
-
#
|
|
531
|
-
# (open the editor there first, THEN step) instead of blindly stepping human
|
|
532
|
-
# and moving on. Editing OFF: unchanged from before this function existed —
|
|
533
|
-
# step silently, refusal surfaced and halts, a clean/committed gate just falls
|
|
534
|
-
# into the loop below. Either way, --once treats a captured commit here as the
|
|
535
|
-
# run's one beat and exits (see handle_human_gate's own once-mode exit for the
|
|
536
|
-
# editing-ON path); a clean/no-op gate captures nothing, so it isn't a beat —
|
|
537
|
-
# it falls into the loop below exactly as it would without --once.
|
|
348
|
+
# driven past. --once treats a captured commit here as the run's one beat and
|
|
349
|
+
# exits; a clean/no-op gate captures nothing, so it isn't a beat — it falls
|
|
350
|
+
# into the loop below exactly as it would without --once.
|
|
538
351
|
if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
539
352
|
emit_error "could not determine the next step:
|
|
540
353
|
$peek_json
|
|
@@ -544,29 +357,20 @@ Run \`gtd status\` to inspect the repo."
|
|
|
544
357
|
fi
|
|
545
358
|
peek_kind="$(jq -r .kind <<<"$peek_json")"
|
|
546
359
|
if [[ "$peek_kind" == "message" ]]; then
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
emit_error "could not capture your changes at the human gate:
|
|
360
|
+
opening_head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
361
|
+
if ! step_out="$(run_gtd step human 2>&1)"; then
|
|
362
|
+
emit_error "could not capture your changes at the human gate:
|
|
551
363
|
$step_out
|
|
552
364
|
Fix the reported issue and re-run gtd-loop."
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
fi
|
|
556
|
-
if [[ "$once_mode" == 1 && "$opening_head_before" != "$(git rev-parse HEAD 2>/dev/null || echo none)" ]]; then
|
|
557
|
-
exit 0
|
|
558
|
-
fi
|
|
559
|
-
else
|
|
560
|
-
handle_human_gate "$peek_json"
|
|
365
|
+
emit_log_pointer
|
|
366
|
+
exit 1
|
|
561
367
|
fi
|
|
562
|
-
|
|
563
|
-
#
|
|
564
|
-
#
|
|
565
|
-
|
|
566
|
-
if [[ "$
|
|
567
|
-
|
|
568
|
-
else
|
|
569
|
-
printf '%s--edit: not at a human gate yet — continuing to drive%s\n' "$C_DIM" "$C_RESET"
|
|
368
|
+
# This branch reports nothing — advance the marker past whatever was just
|
|
369
|
+
# (silently) captured so the first loop-body beat below doesn't newly
|
|
370
|
+
# print it.
|
|
371
|
+
reported_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
372
|
+
if [[ "$once_mode" == 1 && "$opening_head_before" != "$reported_head" ]]; then
|
|
373
|
+
exit 0
|
|
570
374
|
fi
|
|
571
375
|
fi
|
|
572
376
|
|
|
@@ -585,20 +389,18 @@ while true; do
|
|
|
585
389
|
model="$(jq -r '.model // empty' <<<"$next_json")"
|
|
586
390
|
memory="$(jq -r '.memory // empty' <<<"$next_json")"
|
|
587
391
|
label="$(jq -r '.label // .state' <<<"$next_json")"
|
|
588
|
-
GTD_LAST_LABEL="$label"
|
|
589
392
|
|
|
590
393
|
if [[ "$kind" == "message" ]]; then
|
|
591
|
-
#
|
|
592
|
-
#
|
|
593
|
-
|
|
594
|
-
|
|
394
|
+
# The non-autonomous halt: print the gate, hand off, exit 0. The human
|
|
395
|
+
# (or an outer wrapper) edits and re-launches; the opening move above
|
|
396
|
+
# captures what they left.
|
|
397
|
+
emit_gate "$state" "$label"
|
|
398
|
+
run_gtd next
|
|
399
|
+
exit 0
|
|
595
400
|
fi
|
|
596
401
|
|
|
597
|
-
herdr_report working "$label"
|
|
598
|
-
|
|
599
402
|
if [[ "$kind" == "script" ]]; then
|
|
600
403
|
emit_action "$M_CHECK" "$label"
|
|
601
|
-
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
602
404
|
# Execute the emitted wrapper verbatim; its exit code is deliberately
|
|
603
405
|
# ignored (`|| true` under `set -e`) — the script encodes its outcome in
|
|
604
406
|
# the tree, and `gtd step` captures whatever it left there via the state's
|
|
@@ -611,9 +413,7 @@ while true; do
|
|
|
611
413
|
exit 1
|
|
612
414
|
fi
|
|
613
415
|
printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
|
|
614
|
-
if ! report_commits
|
|
615
|
-
herdr_report idle "$label"
|
|
616
|
-
herdr_release
|
|
416
|
+
if ! report_commits; then
|
|
617
417
|
emit_settled "$state"
|
|
618
418
|
exit 0
|
|
619
419
|
fi
|
|
@@ -626,9 +426,14 @@ while true; do
|
|
|
626
426
|
fi
|
|
627
427
|
|
|
628
428
|
# kind == "prompt": stall detection (skills/loop/SKILL.md) — the same
|
|
629
|
-
# agent-owned state and content repeating
|
|
630
|
-
#
|
|
631
|
-
|
|
429
|
+
# agent-owned state and content repeating, WITH NO COMMIT authored since the
|
|
430
|
+
# last time we saw this beat, means the last dispatch made no progress; stop
|
|
431
|
+
# rather than spin on it. The HEAD check is what distinguishes a genuine
|
|
432
|
+
# stall from a return lap back into the same state (e.g. checking ->
|
|
433
|
+
# fixing): a static prompt looks identical to last time, but a commit
|
|
434
|
+
# landed in between, so HEAD has moved and this isn't a stall.
|
|
435
|
+
cur_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
436
|
+
if [[ "$state" == "$prev_state" && "$content" == "$prev_content" && "$cur_head" == "$prev_head" ]]; then
|
|
632
437
|
next_out="$(run_gtd next 2>&1)" || true
|
|
633
438
|
emit_error "no progress at '$state' — the agent's last turn changed nothing. Stopping to avoid spinning.
|
|
634
439
|
$next_out"
|
|
@@ -637,6 +442,7 @@ $next_out"
|
|
|
637
442
|
fi
|
|
638
443
|
prev_state="$state"
|
|
639
444
|
prev_content="$content"
|
|
445
|
+
prev_head="$cur_head"
|
|
640
446
|
|
|
641
447
|
# Agent-memory scope: compare this turn's `memory` label against the last
|
|
642
448
|
# prompt turn's (persisted in $memory_marker). Same non-empty label AND a
|
|
@@ -673,7 +479,6 @@ $next_out"
|
|
|
673
479
|
# driver-side equivalent, so the next rest is only ever handed a well-formed
|
|
674
480
|
# file. gtd itself never validates at step time — this is the loop's job.
|
|
675
481
|
emit_action "$M_AGENT" "$label"
|
|
676
|
-
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
677
482
|
run_agent_turn "$content" "$resume"
|
|
678
483
|
|
|
679
484
|
fix_attempt=0
|
|
@@ -704,7 +509,7 @@ Stopping rather than stepping with a malformed steering file."
|
|
|
704
509
|
# A no-op step (nothing matched a declared pattern on a clean tree) reports
|
|
705
510
|
# zero commits here — report_commits returns non-zero, and that's fine: the
|
|
706
511
|
# NEXT iteration's stall check is what catches unchanged state/content.
|
|
707
|
-
report_commits
|
|
512
|
+
report_commits || true
|
|
708
513
|
if [[ "$once_mode" == 1 ]]; then
|
|
709
514
|
exit 0
|
|
710
515
|
fi
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -205450,12 +205450,9 @@ Commands:
|
|
|
205450
205450
|
(no command), loop
|
|
205451
205451
|
Launch the loop driver (bin/gtd), which repeatedly drives
|
|
205452
205452
|
an agent through gtd next/gtd step calls until the
|
|
205453
|
-
workflow
|
|
205454
|
-
invocation and gtd loop both
|
|
205455
|
-
|
|
205456
|
-
the resolved rest's declared file (or the repo directory
|
|
205457
|
-
if it declares none). Handled entirely by bin/gtd, not
|
|
205458
|
-
this bundle
|
|
205453
|
+
workflow rests at a human gate (a non-autonomous state)
|
|
205454
|
+
or settles. A bare gtd invocation and gtd loop both
|
|
205455
|
+
launch it identically
|
|
205459
205456
|
init Scaffold a minimal .gtdrc.json for this repo, seeding the
|
|
205460
205457
|
default variables you are most likely to change (the test
|
|
205461
205458
|
command) and a Prettier formatting suggestion. gtd runs its
|
package/docs/cli.md
CHANGED
|
@@ -7,8 +7,9 @@ Commands:
|
|
|
7
7
|
(no command), loop
|
|
8
8
|
Launch the loop driver (bin/gtd), which repeatedly drives
|
|
9
9
|
an agent through gtd next/gtd step calls until the
|
|
10
|
-
workflow
|
|
11
|
-
invocation and gtd loop both
|
|
10
|
+
workflow rests at a human gate (a non-autonomous state)
|
|
11
|
+
or settles. A bare gtd invocation and gtd loop both
|
|
12
|
+
launch it identically
|
|
12
13
|
init Scaffold a minimal .gtdrc.json for this repo, seeding the
|
|
13
14
|
default variables you are most likely to change (the test
|
|
14
15
|
command) and a Prettier formatting suggestion. gtd runs its
|
|
@@ -45,12 +46,6 @@ Commands:
|
|
|
45
46
|
commits that would be lost
|
|
46
47
|
next Print the resolved rest's rendered script/prompt/message
|
|
47
48
|
(no mutation)
|
|
48
|
-
edit [path] Open <path> (repo-relative) in $VISUAL/$EDITOR, blocking
|
|
49
|
-
until the editor exits. With no argument, opens the
|
|
50
|
-
resolved rest's declared file (or the repo root if it
|
|
51
|
-
declares none). Never reads --no-edit/--edit/GTD_NO_EDIT.
|
|
52
|
-
Low-level plumbing — --edit covers the common "force an
|
|
53
|
-
edit, then keep driving" case
|
|
54
49
|
status Print the resolved rest's state/actor and which declared
|
|
55
50
|
pattern (if any) each pending change matches (no mutation)
|
|
56
51
|
validate Format and validate the steering file the resolved rest
|
|
@@ -69,15 +64,9 @@ Options:
|
|
|
69
64
|
--no-open (gtd visualize only) do not open the browser
|
|
70
65
|
--cost=<n> (gtd step only) record the invocation's token cost
|
|
71
66
|
--model=<name> (gtd step only, with --cost) tag that cost's model
|
|
72
|
-
--no-edit (bare gtd or gtd loop only) disable the loop's automatic
|
|
73
|
-
editor launching at human gates
|
|
74
|
-
--edit, -e (bare gtd or gtd loop only) force the editor open at the
|
|
75
|
-
current human gate, overriding --no-edit/GTD_NO_EDIT; a
|
|
76
|
-
no-op note (not an error) when not at a human gate
|
|
77
67
|
--once (bare gtd or gtd loop only) run exactly one loop beat (one
|
|
78
|
-
human
|
|
79
|
-
prompt+step), then exit
|
|
80
|
-
--edit/--no-edit
|
|
68
|
+
human-gate capture, one script check+step, or one agent
|
|
69
|
+
prompt+step), then exit
|
|
81
70
|
--version, -v Print version and exit
|
|
82
71
|
--help, -h Print this help and exit
|
|
83
72
|
```
|
|
@@ -101,49 +90,30 @@ to plain-text mode. A bare `--cost`/`--model` with no value, a non-numeric or
|
|
|
101
90
|
negative `--cost`, an empty `--model`, `--model` without `--cost`, or either
|
|
102
91
|
flag on any command other than `gtd step` are all usage errors.
|
|
103
92
|
|
|
104
|
-
`--
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
`--edit`/`--no-edit` together, which conflict (forcing and suppressing the
|
|
116
|
-
editor in the same run is not a coherent request). Any other placement (e.g.
|
|
117
|
-
`gtd step --once`), a duplicated flag (`gtd --once --once`), or the
|
|
118
|
-
`--edit`/`--no-edit` conflict is a usage error, surfaced via whatever guard the
|
|
119
|
-
misplaced argument first reaches (the compiled bundle's own
|
|
120
|
-
unknown-option/unknown-command rejection for a misplaced flag, or
|
|
93
|
+
`--once` is a separate, bash-level flag handled entirely by `bin/gtd` itself,
|
|
94
|
+
stripped before anything reaches the bundle — see below.
|
|
95
|
+
|
|
96
|
+
## `--once`
|
|
97
|
+
|
|
98
|
+
A flag on the loop driver only, recognized bare (`gtd --once`) or immediately
|
|
99
|
+
after `loop` (`gtd loop --once`) — the two positions are equivalent, since bare
|
|
100
|
+
`gtd` and `gtd loop` are themselves equivalent. Any other placement (e.g.
|
|
101
|
+
`gtd step --once`) or a duplicated flag (`gtd --once --once`) is a usage error,
|
|
102
|
+
surfaced via whatever guard the misplaced argument first reaches (the compiled
|
|
103
|
+
bundle's own unknown-option/unknown-command rejection for a misplaced flag, or
|
|
121
104
|
`gtd: 'loop' takes no arguments` for one misplaced after `loop`) — never
|
|
122
|
-
silently ignored
|
|
123
|
-
|
|
124
|
-
**`--
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
**`--edit`** (`-e`) is `--no-edit`'s mirror: it forces the editor open at the
|
|
129
|
-
current human gate, overriding an ambient `GTD_NO_EDIT`/`--no-edit` default for
|
|
130
|
-
this one run. It is only meaningful when the machine currently rests at a human
|
|
131
|
-
gate — the honesty caveat: at any other rest, forcing an edit is undefined, so
|
|
132
|
-
`bin/gtd` prints a note that it isn't at a human gate yet and simply keeps
|
|
133
|
-
driving, rather than pretending to force something. Both flags govern only the
|
|
134
|
-
loop's own automatic launching — `gtd edit` invoked directly never reads either
|
|
135
|
-
(see [`gtd edit`](#gtd-edit-path) above).
|
|
136
|
-
|
|
137
|
-
**`--once`** restricts the loop to exactly one beat — one human gate, one script
|
|
138
|
-
check+step, or one agent prompt+step — then exits, instead of driving all the
|
|
139
|
-
way back to idle/settled. Precisely: at most one commit/transition is made;
|
|
140
|
-
whichever of the three kinds the currently-resolved rest is, that one gets
|
|
105
|
+
silently ignored.
|
|
106
|
+
|
|
107
|
+
**`--once`** restricts the loop to exactly one beat — one human-gate capture,
|
|
108
|
+
one script check+step, or one agent prompt+step — then exits, instead of driving
|
|
109
|
+
all the way back to idle/settled. Precisely: at most one commit/transition is
|
|
110
|
+
made; whichever of the three kinds the currently-resolved rest is, that one gets
|
|
141
111
|
processed (including any internal `gtd validate` fix-reprompt rounds a producing
|
|
142
112
|
agent turn needs — those are all part of finishing the ONE prompt beat, not
|
|
143
113
|
separate beats) and the loop exits 0 immediately after, without re-peeking at
|
|
144
114
|
what comes next. A clean human gate with nothing to capture (the opening move's
|
|
145
|
-
silent capture attempt
|
|
146
|
-
|
|
115
|
+
silent capture attempt) is not a beat and falls through to the driver's ordinary
|
|
116
|
+
halt, same as without `--once`.
|
|
147
117
|
|
|
148
118
|
See [Driving the loop](loop.md) and `skills/loop/SKILL.md` for the full
|
|
149
119
|
gate-flow description.
|
|
@@ -470,46 +440,6 @@ which are JSON-only. `--json` emits
|
|
|
470
440
|
when the state has no `on` (a commit state); a per-edge `describe` is likewise
|
|
471
441
|
omitted when that edge declares none.
|
|
472
442
|
|
|
473
|
-
## `gtd edit [path]`
|
|
474
|
-
|
|
475
|
-
Low-level plumbing: for the common case of "force an edit at the current human
|
|
476
|
-
gate, then keep driving the loop", use `--edit` (below) instead. `gtd edit`
|
|
477
|
-
itself only opens an editor on a path — it never drives anything afterward,
|
|
478
|
-
which makes it the right tool for opening a SPECIFIC path (or peeking at the
|
|
479
|
-
current rest's file) without starting a loop.
|
|
480
|
-
|
|
481
|
-
Handled entirely in `bin/gtd`'s own bash — unlike every other subcommand, it is
|
|
482
|
-
**never forwarded** to the compiled bundle (`dist/gtd.bundle.mjs`).
|
|
483
|
-
|
|
484
|
-
With `<path>` given (repo-relative), opens it in `${VISUAL:-$EDITOR}` (git's own
|
|
485
|
-
precedence — `$VISUAL` first, then `$EDITOR`; no fallback to `vi`), blocking in
|
|
486
|
-
the foreground until the editor exits. It creates nothing if `<path>` doesn't
|
|
487
|
-
exist — the path is handed straight to the editor command verbatim. The editor's
|
|
488
|
-
own exit code is ignored entirely; success is judged from tree state afterward,
|
|
489
|
-
not from this command.
|
|
490
|
-
|
|
491
|
-
With no argument, it peeks with `gtd next --json` to find the resolved rest's
|
|
492
|
-
declared `.file`, and opens that (repo-relative), or the repo directory (`.`)
|
|
493
|
-
when the resolved state declares no `file:`. If `gtd next --json` itself fails
|
|
494
|
-
(e.g. not in a repo), it prints an error and exits non-zero without attempting
|
|
495
|
-
to open an editor:
|
|
496
|
-
|
|
497
|
-
```
|
|
498
|
-
gtd edit: could not determine the next step:
|
|
499
|
-
<gtd next --json's own error output>
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
`gtd edit` **never** reads `--no-edit`/`--edit`/`GTD_NO_EDIT` — those govern
|
|
503
|
-
only the loop's own automatic editor launching at human gates (see
|
|
504
|
-
[Driving the loop](loop.md) and `skills/loop/SKILL.md`), not this command.
|
|
505
|
-
Invoked directly, `gtd edit` always launches, unconditionally.
|
|
506
|
-
|
|
507
|
-
If neither `$VISUAL` nor `$EDITOR` is set, no editor is launched:
|
|
508
|
-
|
|
509
|
-
```
|
|
510
|
-
gtd: no editor configured — set $EDITOR (or $VISUAL)
|
|
511
|
-
```
|
|
512
|
-
|
|
513
443
|
## Running `script` rests (no `gtd` subcommand)
|
|
514
444
|
|
|
515
445
|
There is no `gtd` subcommand that executes a workflow script — gtd never runs a
|
package/docs/loop.md
CHANGED
|
@@ -52,38 +52,17 @@ capture happens within the same run or a later one. A review nobody is going to
|
|
|
52
52
|
finish is ended with [`gtd abandon`](cli.md#gtd-abandon---json), which drops the
|
|
53
53
|
process's commits and keeps their content as uncommitted changes.
|
|
54
54
|
|
|
55
|
-
**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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).
|
|
55
|
+
**The loop drives only the autonomous states.** Reaching a `"message"` rest, it
|
|
56
|
+
prints the gate via `gtd next` and exits 0 — the human edits and re-launches the
|
|
57
|
+
loop, whose opening move captures what they left. Anything richer at that
|
|
58
|
+
boundary (opening an editor on the gate's file, desktop notifications,
|
|
59
|
+
terminal-multiplexer status) is deliberately an OUTER wrapper's responsibility,
|
|
60
|
+
not `bin/gtd`'s.
|
|
61
|
+
|
|
62
|
+
Pass `--once` — see [`cli.md`](cli.md#--once) for its exact syntax and
|
|
63
|
+
positioning — to restrict the loop to exactly one beat — one human-gate capture,
|
|
64
|
+
one script check+step, or one agent prompt+step — then exit, instead of driving
|
|
65
|
+
all the way to idle/settled.
|
|
87
66
|
|
|
88
67
|
```bash
|
|
89
68
|
gtd next --json # ask who's up and what they should do
|
|
@@ -106,21 +85,17 @@ when the machine rests at a `"message"` gate — so `gtd` is the only command a
|
|
|
106
85
|
human runs. `bin/gtd` is the packaged entry point: invoked bare, or with `loop`
|
|
107
86
|
as its first argument, it runs this exact script; any other first argument (e.g.
|
|
108
87
|
`next`, `step`, `status`) hands off to `node dist/gtd.bundle.mjs` instead. The
|
|
109
|
-
loop body it runs adds
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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.
|
|
88
|
+
loop body it runs adds five things on top of the reference script above: it can
|
|
89
|
+
restrict itself to exactly one beat via `--once` instead of driving to
|
|
90
|
+
idle/settled; it stops with a diagnostic if the same `"prompt"` state/content
|
|
91
|
+
repeat with no progress (see `skills/loop/SKILL.md`'s "Stall detection"); it
|
|
92
|
+
lets `GTD_LOOP_AGENT_CMD` swap in any coding agent CLI in place of the default
|
|
93
|
+
`claude -p`, receiving the prompt via `$GTD_LOOP_PROMPT`; it exports the
|
|
94
|
+
resolved state's optional `model` hint as `$GTD_LOOP_MODEL`, appending
|
|
95
|
+
`--model "$GTD_LOOP_MODEL"` to the default `claude -p` invocation whenever it's
|
|
96
|
+
non-empty; and it acts on the optional `memory` scope hint (see "Agent memory
|
|
97
|
+
scope" below) — continuing one agent session across consecutive same-scope turns
|
|
98
|
+
and starting fresh when the scope changes.
|
|
124
99
|
|
|
125
100
|
`bin/gtd` resolves both its bundle hand-off and the loop's own internal `gtd`
|
|
126
101
|
calls against `dist/gtd.bundle.mjs` next to its own location. Set `GTD_BIN` — a
|
|
@@ -181,11 +156,8 @@ The driver — not the prompt text — owns ending the agent's turn
|
|
|
181
156
|
(`gtd step "$actor"` right after the agent acts): every default-workflow agent
|
|
182
157
|
prompt says explicitly not to run `gtd step agent` itself.
|
|
183
158
|
|
|
184
|
-
|
|
185
|
-
`
|
|
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
|
|
159
|
+
The loop's logfile lives at `"$(worktree_git_dir)/gtd-loop.log"` by default, or
|
|
160
|
+
`$GTD_LOOP_LOG` verbatim when set. `worktree_git_dir` derives the git dir from
|
|
189
161
|
the cwd with `GIT_DIR`/`GIT_WORK_TREE` scrubbed, so an inherited `GIT_DIR`
|
|
190
162
|
(leaked from a parent git process, a hook, or another worktree's shell) can
|
|
191
163
|
never key the log — or the loop's memory marker — to a different worktree; two
|
|
@@ -205,8 +177,8 @@ by a `📄 see <log>` pointer (plain: `see <log>`).
|
|
|
205
177
|
The three previously terminal-visible subprocess streams — the agent turn's own
|
|
206
178
|
output, the check script's own output, and `gtd step`'s own output — are no
|
|
207
179
|
longer printed directly: they're appended to the per-repo/per-worktree log file
|
|
208
|
-
(truncated once at the start of the run) instead
|
|
209
|
-
|
|
180
|
+
(truncated once at the start of the run) instead — the loop prints the log's
|
|
181
|
+
path as its first line, ready to `tail -f`.
|
|
210
182
|
|
|
211
183
|
## Agent memory scope
|
|
212
184
|
|
|
@@ -245,45 +217,3 @@ example, to drive a different agent CLI:
|
|
|
245
217
|
```bash
|
|
246
218
|
GTD_LOOP_AGENT_CMD='my-agent-cli --prompt "$GTD_LOOP_PROMPT"' gtd
|
|
247
219
|
```
|
|
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.
|