@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/bin/gtd
CHANGED
|
@@ -3,34 +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
|
-
# exit 2), `--once` restricts the loop to exactly one beat
|
|
13
|
-
# (one human gate, one script check+step, or one agent prompt+step) before
|
|
14
|
-
# exiting, and `--no-notify` (or `GTD_NO_NOTIFY`) suppresses the loop's
|
|
15
|
-
# Herdr desktop notifications for this run while leaving its silent Herdr
|
|
16
|
-
# pane-state reporting untouched. Dispatch then continues on the
|
|
17
|
-
# remaining arguments as if none of them had ever been given.
|
|
18
|
-
# - `edit [path]` -> handled entirely here in bash, never forwarded to the
|
|
19
|
-
# bundle: opens `${VISUAL:-$EDITOR}` on `path`, or (no `path` given) on the
|
|
20
|
-
# resolved rest's `.file` (or `.` when absent). Low-level plumbing now that
|
|
21
|
-
# `--edit` covers the common "force an edit, then keep driving" case; this
|
|
22
|
-
# subcommand remains for opening a specific path, or the current rest's
|
|
23
|
-
# file, WITHOUT driving the loop afterward.
|
|
24
|
-
# - `log` -> handled entirely here in bash, never forwarded to the bundle:
|
|
25
|
-
# opens `${VISUAL:-$EDITOR}` on the current repo/worktree's loop logfile
|
|
26
|
-
# (see `resolve_log_path`), refusing rather than opening an empty buffer
|
|
27
|
-
# when no loop has produced one yet.
|
|
28
|
-
# - no arguments, or exactly `loop` with no further arguments (other than the
|
|
29
|
-
# flags above) -> fall through into the loop body below (the loop protocol
|
|
30
|
-
# 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).
|
|
31
12
|
# - `loop` with an unrecognised extra argument -> usage error, exit 2.
|
|
32
13
|
# - anything else -> hand off to the bundle via `run_gtd`, forwarding all
|
|
33
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.
|
|
34
19
|
# `run_gtd` is the ONE place that knows how to invoke "the bundle": by default
|
|
35
20
|
# it resolves `dist/gtd.bundle.mjs` relative to this script's own location
|
|
36
21
|
# (not cwd), but `GTD_BIN` (a dev/test override for the whole invocation
|
|
@@ -48,29 +33,11 @@ run_gtd() {
|
|
|
48
33
|
fi
|
|
49
34
|
}
|
|
50
35
|
|
|
51
|
-
# Shared editor-launch helper — used by both the `edit` subcommand below and
|
|
52
|
-
# the loop's human-gate flow further down. Resolves the editor git-style
|
|
53
|
-
# ($VISUAL, then $EDITOR — no fallback to `vi`), then launches it blocking in
|
|
54
|
-
# the foreground with the target path passed as a positional parameter
|
|
55
|
-
# (never string-spliced into the command), so a multi-word command
|
|
56
|
-
# (`code --wait`) and a path with special characters both work. A launch
|
|
57
|
-
# failure (bad/missing editor binary) is a plain non-zero return, propagating
|
|
58
|
-
# under `set -e`; the editor's own exit code is otherwise ignored entirely —
|
|
59
|
-
# the outcome is judged from tree state afterward, not from this function.
|
|
60
|
-
launch_editor() {
|
|
61
|
-
local target="$1" editor="${VISUAL:-${EDITOR:-}}"
|
|
62
|
-
if [[ -z "$editor" ]]; then
|
|
63
|
-
echo "gtd: no editor configured — set \$EDITOR (or \$VISUAL)" >&2
|
|
64
|
-
return 1
|
|
65
|
-
fi
|
|
66
|
-
bash -c "$editor \"\$1\"" gtd-edit "$target"
|
|
67
|
-
}
|
|
68
|
-
|
|
69
36
|
# ── Rendering primitives (capability detection, palette/markers, emitters) ──
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
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).
|
|
74
41
|
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then FANCY=1; else FANCY=0; fi
|
|
75
42
|
|
|
76
43
|
if [[ "$FANCY" == 1 ]]; then
|
|
@@ -158,8 +125,6 @@ emit_log_pointer() {
|
|
|
158
125
|
}
|
|
159
126
|
|
|
160
127
|
# ── Git-derivation helpers (parse_subject, report_commits) ──────────────────
|
|
161
|
-
# Additive: nothing below calls these yet. Replaces today's
|
|
162
|
-
# head_before/head_after string comparisons in a later change.
|
|
163
128
|
|
|
164
129
|
# parse_subject <subject> — sets S_ACTOR / S_FROM / S_TO / S_KIND. Patterns are
|
|
165
130
|
# held in variables (rather than inlined) so bash's [[ =~ ]] doesn't need the
|
|
@@ -209,7 +174,7 @@ report_commits() {
|
|
|
209
174
|
[[ "$reported" == 1 ]]
|
|
210
175
|
}
|
|
211
176
|
|
|
212
|
-
# ── Log file (resolve_log_path
|
|
177
|
+
# ── Log file (resolve_log_path) ─────────────────────────────────────────────
|
|
213
178
|
|
|
214
179
|
# worktree_git_dir — the git dir of the working tree gtd is operating in,
|
|
215
180
|
# derived purely from the cwd. GIT_DIR/GIT_WORK_TREE are scrubbed for this one
|
|
@@ -233,27 +198,16 @@ resolve_log_path() {
|
|
|
233
198
|
fi
|
|
234
199
|
}
|
|
235
200
|
|
|
236
|
-
# --
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
# unknown-option guard
|
|
244
|
-
#
|
|
245
|
-
# here before anything below looks at "$@", so the bundle's own unknown-option
|
|
246
|
-
# guard never sees it; anything else (an unknown flag, a duplicate flag, or a
|
|
247
|
-
# flag mixed with a real subcommand) is left untouched and falls through to
|
|
248
|
-
# the existing dispatch below, which forwards a non-flag first argument to the
|
|
249
|
-
# bundle (whose own unknown-option guard rejects it) or rejects a `loop`
|
|
250
|
-
# invocation carrying an unrecognised extra argument.
|
|
251
|
-
edit_disabled=0
|
|
252
|
-
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.
|
|
253
210
|
once_mode=0
|
|
254
|
-
notify_disabled=0
|
|
255
|
-
[[ -n "${GTD_NO_EDIT:-}" ]] && edit_disabled=1
|
|
256
|
-
[[ -n "${GTD_NO_NOTIFY:-}" ]] && notify_disabled=1
|
|
257
211
|
|
|
258
212
|
if [[ "${1:-}" == "loop" ]]; then
|
|
259
213
|
loop_form=1
|
|
@@ -264,39 +218,17 @@ else
|
|
|
264
218
|
fi
|
|
265
219
|
|
|
266
220
|
valid_combo=1
|
|
267
|
-
saw_unknown=0
|
|
268
|
-
n_no_edit=0
|
|
269
|
-
n_edit=0
|
|
270
221
|
n_once=0
|
|
271
|
-
n_no_notify=0
|
|
272
222
|
for a in ${loop_flag_args[@]+"${loop_flag_args[@]}"}; do
|
|
273
223
|
case "$a" in
|
|
274
|
-
--no-edit) n_no_edit=$((n_no_edit + 1)) ;;
|
|
275
|
-
--edit | -e) n_edit=$((n_edit + 1)) ;;
|
|
276
224
|
--once) n_once=$((n_once + 1)) ;;
|
|
277
|
-
|
|
278
|
-
*) saw_unknown=1 ;;
|
|
225
|
+
*) valid_combo=0 ;;
|
|
279
226
|
esac
|
|
280
227
|
done
|
|
281
|
-
|
|
282
|
-
# --no-edit gets a dedicated usage error here, rather than falling through to
|
|
283
|
-
# the bundle's own unknown-option rejection below.
|
|
284
|
-
if [[ "$saw_unknown" == 0 ]] && ((n_edit >= 1 && n_no_edit >= 1)); then
|
|
285
|
-
echo "gtd: --edit and --no-edit are mutually exclusive" >&2
|
|
286
|
-
exit 2
|
|
287
|
-
fi
|
|
288
|
-
if ((saw_unknown == 1 || n_no_edit > 1 || n_edit > 1 || n_once > 1 || n_no_notify > 1)); then
|
|
289
|
-
valid_combo=0
|
|
290
|
-
fi
|
|
228
|
+
((n_once > 1)) && valid_combo=0
|
|
291
229
|
|
|
292
|
-
if [[ "$valid_combo" == 1 ]]; then
|
|
293
|
-
|
|
294
|
-
((n_edit >= 1)) && edit_forced=1
|
|
295
|
-
((n_once >= 1)) && once_mode=1
|
|
296
|
-
((n_no_notify >= 1)) && notify_disabled=1
|
|
297
|
-
# --edit overrides an ambient GTD_NO_EDIT/--no-edit for THIS run — the
|
|
298
|
-
# explicit ask to force the editor wins over the suppressing default.
|
|
299
|
-
((edit_forced)) && edit_disabled=0
|
|
230
|
+
if [[ "$valid_combo" == 1 && "$n_once" == 1 ]]; then
|
|
231
|
+
once_mode=1
|
|
300
232
|
if [[ "$loop_form" == 1 ]]; then
|
|
301
233
|
set -- loop
|
|
302
234
|
else
|
|
@@ -304,37 +236,6 @@ if [[ "$valid_combo" == 1 ]]; then
|
|
|
304
236
|
fi
|
|
305
237
|
fi
|
|
306
238
|
|
|
307
|
-
if [[ "${1:-}" == "edit" ]]; then
|
|
308
|
-
shift
|
|
309
|
-
if [[ $# -gt 0 ]]; then
|
|
310
|
-
launch_editor "$1"
|
|
311
|
-
else
|
|
312
|
-
if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
313
|
-
echo "gtd edit: could not determine the next step:" >&2
|
|
314
|
-
echo "$peek_json" >&2
|
|
315
|
-
exit 1
|
|
316
|
-
fi
|
|
317
|
-
file="$(jq -r '.file // empty' <<<"$peek_json")"
|
|
318
|
-
launch_editor "${file:-.}"
|
|
319
|
-
fi
|
|
320
|
-
exit 0
|
|
321
|
-
fi
|
|
322
|
-
|
|
323
|
-
if [[ "${1:-}" == "log" ]]; then
|
|
324
|
-
shift
|
|
325
|
-
if [[ $# -gt 0 ]]; then
|
|
326
|
-
echo "gtd log: takes no arguments" >&2
|
|
327
|
-
exit 2
|
|
328
|
-
fi
|
|
329
|
-
log_path="$(resolve_log_path)"
|
|
330
|
-
if [[ ! -f "$log_path" ]]; then
|
|
331
|
-
echo "gtd log: no loop log yet at $log_path" >&2
|
|
332
|
-
exit 1
|
|
333
|
-
fi
|
|
334
|
-
launch_editor "$log_path"
|
|
335
|
-
exit 0
|
|
336
|
-
fi
|
|
337
|
-
|
|
338
239
|
if [[ $# -gt 0 && "$1" != "loop" ]]; then
|
|
339
240
|
run_gtd "$@"
|
|
340
241
|
exit $?
|
|
@@ -343,16 +244,14 @@ if [[ "${1:-}" == "loop" && $# -gt 1 ]]; then
|
|
|
343
244
|
echo "gtd: 'loop' takes no arguments" >&2
|
|
344
245
|
exit 2
|
|
345
246
|
fi
|
|
346
|
-
# otherwise ($# -eq 0, or exactly "loop" with no further args, --
|
|
347
|
-
#
|
|
348
|
-
# 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.
|
|
349
249
|
|
|
350
|
-
# Drives the v3 pattern-machine loop
|
|
250
|
+
# Drives the v3 pattern-machine loop: each iteration asks
|
|
351
251
|
# `gtd next --json` who's up and what they should do, dispatching on `kind`:
|
|
352
|
-
# - "message" (a human rest) ->
|
|
353
|
-
#
|
|
354
|
-
#
|
|
355
|
-
# 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.
|
|
356
255
|
# - "script" (a check rest) -> execute `content` (the emitted wrapper
|
|
357
256
|
# script) verbatim via bash — exit code deliberately ignored, the outcome
|
|
358
257
|
# lives in the tree — then `gtd step <actor>` to capture it; zero new
|
|
@@ -365,8 +264,7 @@ fi
|
|
|
365
264
|
# adapter invoked at a "prompt" rest; the default adapter appends `--model
|
|
366
265
|
# "$GTD_LOOP_MODEL"` only when it's non-empty.
|
|
367
266
|
#
|
|
368
|
-
# `.memory` (optional, opaque
|
|
369
|
-
# is the agent-memory scope label. gtd never interprets it; the driver does:
|
|
267
|
+
# `.memory` (optional, opaque) is the agent-memory scope label. gtd never interprets it; the driver does:
|
|
370
268
|
# consecutive "prompt" turns emitting the SAME label continue ONE agent
|
|
371
269
|
# session (so the agent still remembers its previous turn), and a changed
|
|
372
270
|
# label — or the first turn, or an unset label — starts fresh. That is what
|
|
@@ -379,109 +277,6 @@ fi
|
|
|
379
277
|
# resume-or-fresh flag are exported as $GTD_LOOP_MEMORY / $GTD_LOOP_SESSION_ID
|
|
380
278
|
# / $GTD_LOOP_MEMORY_RESUME; the default adapter turns them into
|
|
381
279
|
# `--resume`/`--session-id`. An adapter that ignores them keeps working.
|
|
382
|
-
herdr_ok() { [ "${HERDR_ENV:-}" = 1 ] && [ -n "${HERDR_PANE_ID:-}" ] && command -v herdr >/dev/null 2>&1; }
|
|
383
|
-
|
|
384
|
-
# Run a `herdr` invocation best-effort. Normally output is discarded and a
|
|
385
|
-
# failure is swallowed (`|| true`) so a missing/incompatible herdr never blocks
|
|
386
|
-
# the loop. Set GTD_HERDR_DEBUG=1 to instead surface every call and its exit
|
|
387
|
-
# code on stderr — the escape hatch that makes a silent CLI-signature mismatch
|
|
388
|
-
# (e.g. an arg herdr rejects) visible again.
|
|
389
|
-
herdr_run() {
|
|
390
|
-
if [ "${GTD_HERDR_DEBUG:-}" = 1 ]; then
|
|
391
|
-
printf 'gtd/herdr: %s\n' "$*" >&2
|
|
392
|
-
herdr "$@" >&2
|
|
393
|
-
printf 'gtd/herdr: exit=%s\n' "$?" >&2
|
|
394
|
-
else
|
|
395
|
-
herdr "$@" >/dev/null 2>&1 || true
|
|
396
|
-
fi
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
# NOTE: herdr requires the positional <PANE_ID> BEFORE the options for
|
|
400
|
-
# `pane` subcommands — passing it last makes herdr reject the first option as
|
|
401
|
-
# `unknown option` (exit 2). Keep the pane id first here.
|
|
402
|
-
herdr_report() { # $1=state (working|blocked|idle) $2=message
|
|
403
|
-
herdr_ok || return 0
|
|
404
|
-
herdr_run pane report-agent "$HERDR_PANE_ID" \
|
|
405
|
-
--source herdr:gtd --agent gtd --state "$1" --message "$2"
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
# Suppressible via GTD_NO_NOTIFY/--no-notify (set into $notify_disabled at
|
|
409
|
-
# parse time, above) — the OS popup this call makes is the only thing that
|
|
410
|
-
# opts out; herdr_report/herdr_release (silent sidebar status) are unaffected.
|
|
411
|
-
herdr_notify() { # $1=title $2=body
|
|
412
|
-
[[ "$notify_disabled" == 1 ]] && return 0
|
|
413
|
-
herdr_ok || return 0
|
|
414
|
-
herdr_run notification show "$1" --body "$2" --sound request
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
herdr_release() { # hand the pane back to Herdr's normal detection
|
|
418
|
-
herdr_ok || return 0
|
|
419
|
-
herdr_run pane release-agent "$HERDR_PANE_ID" --source herdr:gtd --agent gtd
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
# Shared human-gate handler for a "message" (human) rest — used both by the
|
|
423
|
-
# opening move (when editing is on) and by every "message" rest the main loop
|
|
424
|
-
# encounters. $1 is the rest's `next --json` blob.
|
|
425
|
-
#
|
|
426
|
-
# Editing OFF ($edit_disabled): today's behavior, unchanged — render the gate
|
|
427
|
-
# via emit_gate and exit 0 without touching an editor.
|
|
428
|
-
#
|
|
429
|
-
# Editing ON (the default): open the editor on the rest's `.file` (or `.` when
|
|
430
|
-
# absent), wait for it to exit, then run `gtd step human`:
|
|
431
|
-
# - a NEW commit was authored -> return normally so the caller keeps driving.
|
|
432
|
-
# - ZERO commits were authored -> halt, exit 0 (closing the editor without a
|
|
433
|
-
# capturable change means "done for now" — and keeps a gate with no `C`
|
|
434
|
-
# pattern, e.g. idle, from being reopened forever).
|
|
435
|
-
# - the step REFUSES (non-zero exit: malformed steering file, unfinished
|
|
436
|
-
# review) -> surface it verbatim and halt, exit non-zero — the same
|
|
437
|
-
# wording an opening-move refusal already used before this function
|
|
438
|
-
# existed.
|
|
439
|
-
handle_human_gate() {
|
|
440
|
-
local json="$1" file label state step_out head_before head_after
|
|
441
|
-
label="$(jq -r '.label // .state' <<<"$json")"
|
|
442
|
-
state="$(jq -r .state <<<"$json")"
|
|
443
|
-
|
|
444
|
-
if [[ "$edit_disabled" == 1 ]]; then
|
|
445
|
-
herdr_report blocked "$label"
|
|
446
|
-
herdr_notify "gtd needs you — $(basename "$PWD")" "$label"
|
|
447
|
-
emit_gate "$state" "$label"
|
|
448
|
-
run_gtd next
|
|
449
|
-
exit 0
|
|
450
|
-
fi
|
|
451
|
-
|
|
452
|
-
file="$(jq -r '.file // empty' <<<"$json")"
|
|
453
|
-
herdr_report blocked "$label"
|
|
454
|
-
launch_editor "${file:-.}"
|
|
455
|
-
herdr_report working "$label"
|
|
456
|
-
|
|
457
|
-
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
458
|
-
if ! step_out="$(run_gtd step human 2>&1)"; then
|
|
459
|
-
emit_error "could not capture your changes at the human gate:
|
|
460
|
-
$step_out
|
|
461
|
-
Fix the reported issue and re-run gtd-loop."
|
|
462
|
-
emit_log_pointer
|
|
463
|
-
exit 1
|
|
464
|
-
fi
|
|
465
|
-
head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
466
|
-
if [[ "$head_before" == "$head_after" ]]; then
|
|
467
|
-
herdr_report idle "$label"
|
|
468
|
-
herdr_release
|
|
469
|
-
emit_action "$M_HUMAN" "done for now (nothing captured)"
|
|
470
|
-
exit 0
|
|
471
|
-
fi
|
|
472
|
-
# A new commit was captured — render it, then return normally so the
|
|
473
|
-
# caller keeps driving, unless --once says this one human gate is the whole
|
|
474
|
-
# run.
|
|
475
|
-
report_commits
|
|
476
|
-
if [[ "$once_mode" == 1 ]]; then
|
|
477
|
-
exit 0
|
|
478
|
-
fi
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
trap 'code=$?; if [ "$code" -ne 0 ]; then
|
|
482
|
-
herdr_report blocked "${GTD_LAST_LABEL:-stopped}: exited $code"
|
|
483
|
-
herdr_notify "gtd stopped — $(basename "$PWD")" "${GTD_LAST_LABEL:-loop} failed (exit $code)"
|
|
484
|
-
fi' EXIT
|
|
485
280
|
|
|
486
281
|
prev_state=""
|
|
487
282
|
prev_content=""
|
|
@@ -491,7 +286,7 @@ prev_head=""
|
|
|
491
286
|
memory_marker="$(worktree_git_dir 2>/dev/null || echo .git)/gtd-loop-memory"
|
|
492
287
|
|
|
493
288
|
# Truncated once here (not every iteration), so the file always reflects the
|
|
494
|
-
# current run but survives after the loop exits
|
|
289
|
+
# current run but survives after the loop exits.
|
|
495
290
|
log_path="$(resolve_log_path)"
|
|
496
291
|
: >"$log_path"
|
|
497
292
|
export GTD_LOOP_LOG="$log_path"
|
|
@@ -549,16 +344,9 @@ reported_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
|
549
344
|
# and a mid-cycle restart must simply resume driving, not fail. A clean gate is
|
|
550
345
|
# a harmless no-op; a genuine refusal there (a malformed steering file, or an
|
|
551
346
|
# edit no `on` pattern accepts) is surfaced and stops the loop rather than being
|
|
552
|
-
# driven past.
|
|
553
|
-
#
|
|
554
|
-
#
|
|
555
|
-
# (open the editor there first, THEN step) instead of blindly stepping human
|
|
556
|
-
# and moving on. Editing OFF: unchanged from before this function existed —
|
|
557
|
-
# step silently, refusal surfaced and halts, a clean/committed gate just falls
|
|
558
|
-
# into the loop below. Either way, --once treats a captured commit here as the
|
|
559
|
-
# run's one beat and exits (see handle_human_gate's own once-mode exit for the
|
|
560
|
-
# editing-ON path); a clean/no-op gate captures nothing, so it isn't a beat —
|
|
561
|
-
# it falls into the loop below exactly as it would without --once.
|
|
347
|
+
# driven past. --once treats a captured commit here as the run's one beat and
|
|
348
|
+
# exits; a clean/no-op gate captures nothing, so it isn't a beat — it falls
|
|
349
|
+
# into the loop below exactly as it would without --once.
|
|
562
350
|
if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
563
351
|
emit_error "could not determine the next step:
|
|
564
352
|
$peek_json
|
|
@@ -568,33 +356,20 @@ Run \`gtd status\` to inspect the repo."
|
|
|
568
356
|
fi
|
|
569
357
|
peek_kind="$(jq -r .kind <<<"$peek_json")"
|
|
570
358
|
if [[ "$peek_kind" == "message" ]]; then
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
emit_error "could not capture your changes at the human gate:
|
|
359
|
+
opening_head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
360
|
+
if ! step_out="$(run_gtd step human 2>&1)"; then
|
|
361
|
+
emit_error "could not capture your changes at the human gate:
|
|
575
362
|
$step_out
|
|
576
363
|
Fix the reported issue and re-run gtd-loop."
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
fi
|
|
580
|
-
# This branch reports nothing today — advance the marker past whatever was
|
|
581
|
-
# just (silently) captured so the first loop-body beat below doesn't newly
|
|
582
|
-
# print it.
|
|
583
|
-
reported_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
584
|
-
if [[ "$once_mode" == 1 && "$opening_head_before" != "$reported_head" ]]; then
|
|
585
|
-
exit 0
|
|
586
|
-
fi
|
|
587
|
-
else
|
|
588
|
-
handle_human_gate "$peek_json"
|
|
364
|
+
emit_log_pointer
|
|
365
|
+
exit 1
|
|
589
366
|
fi
|
|
590
|
-
|
|
591
|
-
#
|
|
592
|
-
#
|
|
593
|
-
|
|
594
|
-
if [[ "$
|
|
595
|
-
|
|
596
|
-
else
|
|
597
|
-
printf '%s--edit: not at a human gate yet — continuing to drive%s\n' "$C_DIM" "$C_RESET"
|
|
367
|
+
# This branch reports nothing — advance the marker past whatever was just
|
|
368
|
+
# (silently) captured so the first loop-body beat below doesn't newly
|
|
369
|
+
# print it.
|
|
370
|
+
reported_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
371
|
+
if [[ "$once_mode" == 1 && "$opening_head_before" != "$reported_head" ]]; then
|
|
372
|
+
exit 0
|
|
598
373
|
fi
|
|
599
374
|
fi
|
|
600
375
|
|
|
@@ -613,17 +388,16 @@ while true; do
|
|
|
613
388
|
model="$(jq -r '.model // empty' <<<"$next_json")"
|
|
614
389
|
memory="$(jq -r '.memory // empty' <<<"$next_json")"
|
|
615
390
|
label="$(jq -r '.label // .state' <<<"$next_json")"
|
|
616
|
-
GTD_LAST_LABEL="$label"
|
|
617
391
|
|
|
618
392
|
if [[ "$kind" == "message" ]]; then
|
|
619
|
-
#
|
|
620
|
-
#
|
|
621
|
-
|
|
622
|
-
|
|
393
|
+
# The non-autonomous halt: print the gate, hand off, exit 0. The human
|
|
394
|
+
# (or an outer wrapper) edits and re-launches; the opening move above
|
|
395
|
+
# captures what they left.
|
|
396
|
+
emit_gate "$state" "$label"
|
|
397
|
+
run_gtd next
|
|
398
|
+
exit 0
|
|
623
399
|
fi
|
|
624
400
|
|
|
625
|
-
herdr_report working "$label"
|
|
626
|
-
|
|
627
401
|
if [[ "$kind" == "script" ]]; then
|
|
628
402
|
emit_action "$M_CHECK" "$label"
|
|
629
403
|
# Execute the emitted wrapper verbatim; its exit code is deliberately
|
|
@@ -639,8 +413,6 @@ while true; do
|
|
|
639
413
|
fi
|
|
640
414
|
printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
|
|
641
415
|
if ! report_commits; then
|
|
642
|
-
herdr_report idle "$label"
|
|
643
|
-
herdr_release
|
|
644
416
|
emit_settled "$state"
|
|
645
417
|
exit 0
|
|
646
418
|
fi
|
|
@@ -652,12 +424,12 @@ while true; do
|
|
|
652
424
|
continue
|
|
653
425
|
fi
|
|
654
426
|
|
|
655
|
-
# kind == "prompt": stall detection
|
|
427
|
+
# kind == "prompt": stall detection — the same
|
|
656
428
|
# agent-owned state and content repeating, WITH NO COMMIT authored since the
|
|
657
429
|
# last time we saw this beat, means the last dispatch made no progress; stop
|
|
658
430
|
# rather than spin on it. The HEAD check is what distinguishes a genuine
|
|
659
|
-
# stall from a return lap
|
|
660
|
-
#
|
|
431
|
+
# stall from a return lap back into the same state (e.g. checking ->
|
|
432
|
+
# fixing): a static prompt looks identical to last time, but a commit
|
|
661
433
|
# landed in between, so HEAD has moved and this isn't a stall.
|
|
662
434
|
cur_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
663
435
|
if [[ "$state" == "$prev_state" && "$content" == "$prev_content" && "$cur_head" == "$prev_head" ]]; then
|