@pmelab/gtd 7.11.1 → 7.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/gtd +58 -26
- package/docs/cli.md +6 -4
- package/package.json +1 -1
package/bin/gtd
CHANGED
|
@@ -8,7 +8,8 @@ set -euo pipefail
|
|
|
8
8
|
# flag-parsing block below) -> stripped here; `--no-edit` disables the
|
|
9
9
|
# loop's automatic editor launching for this run, `--edit` forces it on
|
|
10
10
|
# for this run (overriding an ambient `GTD_NO_EDIT`/`--no-edit` — the two
|
|
11
|
-
# are mutually exclusive
|
|
11
|
+
# are mutually exclusive: naming both together is a dedicated usage error,
|
|
12
|
+
# exit 2), `--once` restricts the loop to exactly one beat
|
|
12
13
|
# (one human gate, one script check+step, or one agent prompt+step) before
|
|
13
14
|
# exiting, and `--no-notify` (or `GTD_NO_NOTIFY`) suppresses the loop's
|
|
14
15
|
# Herdr desktop notifications for this run while leaving its silent Herdr
|
|
@@ -183,11 +184,14 @@ parse_subject() {
|
|
|
183
184
|
fi
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
# report_commits
|
|
187
|
-
#
|
|
188
|
-
#
|
|
187
|
+
# report_commits — walks "$reported_head"..HEAD in order, emitting a
|
|
188
|
+
# transition/commit line per new commit, and advances $reported_head to HEAD
|
|
189
|
+
# when it reported at least one; returns non-zero (having emitted nothing,
|
|
190
|
+
# and leaving $reported_head unchanged) when there were no new commits. Global
|
|
191
|
+
# rather than per-beat so the review checkout window's HEAD rewinds between
|
|
192
|
+
# beats never cause an already-reported commit to replay.
|
|
189
193
|
report_commits() {
|
|
190
|
-
local
|
|
194
|
+
local sha subject files reported=0
|
|
191
195
|
while IFS= read -r sha; do
|
|
192
196
|
[[ -z "$sha" ]] && continue
|
|
193
197
|
reported=1
|
|
@@ -198,7 +202,10 @@ report_commits() {
|
|
|
198
202
|
transition) emit_transition "$S_FROM" "$S_TO" "$files" ;;
|
|
199
203
|
capture | squash) emit_commit "$subject" "$files" ;;
|
|
200
204
|
esac
|
|
201
|
-
done < <(git rev-list --reverse "$
|
|
205
|
+
done < <(git rev-list --reverse "$reported_head"..HEAD)
|
|
206
|
+
if [[ "$reported" == 1 ]]; then
|
|
207
|
+
reported_head="$(git rev-parse HEAD)"
|
|
208
|
+
fi
|
|
202
209
|
[[ "$reported" == 1 ]]
|
|
203
210
|
}
|
|
204
211
|
|
|
@@ -230,15 +237,17 @@ resolve_log_path() {
|
|
|
230
237
|
# recognised in exactly two positions — bare (e.g. `gtd --edit --once`) or
|
|
231
238
|
# immediately after `loop` (e.g. `gtd loop --once`) — in any order, each at
|
|
232
239
|
# most once. `--edit` and `--no-edit` are mutually exclusive (forcing and
|
|
233
|
-
# suppressing the editor in the same run is not a coherent request)
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
240
|
+
# suppressing the editor in the same run is not a coherent request): naming
|
|
241
|
+
# both, with no other unrecognised token present, is rejected right here with
|
|
242
|
+
# a dedicated usage error (exit 2) — it never reaches the bundle's own
|
|
243
|
+
# unknown-option guard. `--no-notify` conflicts with nothing (it's orthogonal
|
|
244
|
+
# to editing and to `--once`). A recognised, valid combination is stripped
|
|
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.
|
|
242
251
|
edit_disabled=0
|
|
243
252
|
edit_forced=0
|
|
244
253
|
once_mode=0
|
|
@@ -255,6 +264,7 @@ else
|
|
|
255
264
|
fi
|
|
256
265
|
|
|
257
266
|
valid_combo=1
|
|
267
|
+
saw_unknown=0
|
|
258
268
|
n_no_edit=0
|
|
259
269
|
n_edit=0
|
|
260
270
|
n_once=0
|
|
@@ -265,10 +275,17 @@ for a in ${loop_flag_args[@]+"${loop_flag_args[@]}"}; do
|
|
|
265
275
|
--edit | -e) n_edit=$((n_edit + 1)) ;;
|
|
266
276
|
--once) n_once=$((n_once + 1)) ;;
|
|
267
277
|
--no-notify) n_no_notify=$((n_no_notify + 1)) ;;
|
|
268
|
-
*)
|
|
278
|
+
*) saw_unknown=1 ;;
|
|
269
279
|
esac
|
|
270
280
|
done
|
|
271
|
-
|
|
281
|
+
# A pure loop-flag invocation (no unrecognised token) naming both --edit and
|
|
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
|
|
272
289
|
valid_combo=0
|
|
273
290
|
fi
|
|
274
291
|
|
|
@@ -455,7 +472,7 @@ Fix the reported issue and re-run gtd-loop."
|
|
|
455
472
|
# A new commit was captured — render it, then return normally so the
|
|
456
473
|
# caller keeps driving, unless --once says this one human gate is the whole
|
|
457
474
|
# run.
|
|
458
|
-
report_commits
|
|
475
|
+
report_commits
|
|
459
476
|
if [[ "$once_mode" == 1 ]]; then
|
|
460
477
|
exit 0
|
|
461
478
|
fi
|
|
@@ -468,6 +485,7 @@ trap 'code=$?; if [ "$code" -ne 0 ]; then
|
|
|
468
485
|
|
|
469
486
|
prev_state=""
|
|
470
487
|
prev_content=""
|
|
488
|
+
prev_head=""
|
|
471
489
|
# Where the last "prompt" turn's `memory` scope + session id are remembered
|
|
472
490
|
# across iterations and across restarts. In the git dir, never the work tree.
|
|
473
491
|
memory_marker="$(worktree_git_dir 2>/dev/null || echo .git)/gtd-loop-memory"
|
|
@@ -512,6 +530,12 @@ run_agent_turn() {
|
|
|
512
530
|
>>"${GTD_LOOP_LOG:-/dev/null}" 2>&1
|
|
513
531
|
}
|
|
514
532
|
|
|
533
|
+
# The furthest commit already reported to the terminal by report_commits (see
|
|
534
|
+
# its own comment) — a global high-water mark rather than a per-beat
|
|
535
|
+
# head_before, so a review checkout window's HEAD rewind between beats never
|
|
536
|
+
# causes an already-printed transition to replay.
|
|
537
|
+
reported_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
538
|
+
|
|
515
539
|
# Opening move: capture the human's pending edit so gtd-loop is the ONLY command
|
|
516
540
|
# a human runs. A human acts at a gate by editing files (a plan answer, a review
|
|
517
541
|
# tick, a code fix) and re-launching the loop — they should never have to run
|
|
@@ -553,7 +577,11 @@ Fix the reported issue and re-run gtd-loop."
|
|
|
553
577
|
emit_log_pointer
|
|
554
578
|
exit 1
|
|
555
579
|
fi
|
|
556
|
-
|
|
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
|
|
557
585
|
exit 0
|
|
558
586
|
fi
|
|
559
587
|
else
|
|
@@ -598,7 +626,6 @@ while true; do
|
|
|
598
626
|
|
|
599
627
|
if [[ "$kind" == "script" ]]; then
|
|
600
628
|
emit_action "$M_CHECK" "$label"
|
|
601
|
-
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
602
629
|
# Execute the emitted wrapper verbatim; its exit code is deliberately
|
|
603
630
|
# ignored (`|| true` under `set -e`) — the script encodes its outcome in
|
|
604
631
|
# the tree, and `gtd step` captures whatever it left there via the state's
|
|
@@ -611,7 +638,7 @@ while true; do
|
|
|
611
638
|
exit 1
|
|
612
639
|
fi
|
|
613
640
|
printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
|
|
614
|
-
if ! report_commits
|
|
641
|
+
if ! report_commits; then
|
|
615
642
|
herdr_report idle "$label"
|
|
616
643
|
herdr_release
|
|
617
644
|
emit_settled "$state"
|
|
@@ -626,9 +653,14 @@ while true; do
|
|
|
626
653
|
fi
|
|
627
654
|
|
|
628
655
|
# kind == "prompt": stall detection (skills/loop/SKILL.md) — the same
|
|
629
|
-
# agent-owned state and content repeating
|
|
630
|
-
#
|
|
631
|
-
|
|
656
|
+
# agent-owned state and content repeating, WITH NO COMMIT authored since the
|
|
657
|
+
# last time we saw this beat, means the last dispatch made no progress; stop
|
|
658
|
+
# rather than spin on it. The HEAD check is what distinguishes a genuine
|
|
659
|
+
# stall from a return lap through a human gate (e.g. plan-review ->
|
|
660
|
+
# planning): a static prompt looks identical to last time, but a commit
|
|
661
|
+
# landed in between, so HEAD has moved and this isn't a stall.
|
|
662
|
+
cur_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
663
|
+
if [[ "$state" == "$prev_state" && "$content" == "$prev_content" && "$cur_head" == "$prev_head" ]]; then
|
|
632
664
|
next_out="$(run_gtd next 2>&1)" || true
|
|
633
665
|
emit_error "no progress at '$state' — the agent's last turn changed nothing. Stopping to avoid spinning.
|
|
634
666
|
$next_out"
|
|
@@ -637,6 +669,7 @@ $next_out"
|
|
|
637
669
|
fi
|
|
638
670
|
prev_state="$state"
|
|
639
671
|
prev_content="$content"
|
|
672
|
+
prev_head="$cur_head"
|
|
640
673
|
|
|
641
674
|
# Agent-memory scope: compare this turn's `memory` label against the last
|
|
642
675
|
# prompt turn's (persisted in $memory_marker). Same non-empty label AND a
|
|
@@ -673,7 +706,6 @@ $next_out"
|
|
|
673
706
|
# driver-side equivalent, so the next rest is only ever handed a well-formed
|
|
674
707
|
# file. gtd itself never validates at step time — this is the loop's job.
|
|
675
708
|
emit_action "$M_AGENT" "$label"
|
|
676
|
-
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
677
709
|
run_agent_turn "$content" "$resume"
|
|
678
710
|
|
|
679
711
|
fix_attempt=0
|
|
@@ -704,7 +736,7 @@ Stopping rather than stepping with a malformed steering file."
|
|
|
704
736
|
# A no-op step (nothing matched a declared pattern on a clean tree) reports
|
|
705
737
|
# zero commits here — report_commits returns non-zero, and that's fine: the
|
|
706
738
|
# NEXT iteration's stall check is what catches unchanged state/content.
|
|
707
|
-
report_commits
|
|
739
|
+
report_commits || true
|
|
708
740
|
if [[ "$once_mode" == 1 ]]; then
|
|
709
741
|
exit 0
|
|
710
742
|
fi
|
package/docs/cli.md
CHANGED
|
@@ -113,10 +113,12 @@ each recognized bare (`gtd --no-edit`) or immediately after `loop`
|
|
|
113
113
|
`gtd loop` are themselves equivalent. All three combine freely with each other,
|
|
114
114
|
in any order (e.g. `gtd --edit --once`, `gtd loop --once --edit`), except
|
|
115
115
|
`--edit`/`--no-edit` together, which conflict (forcing and suppressing the
|
|
116
|
-
editor in the same run is not a coherent request)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
editor in the same run is not a coherent request): naming both, in either
|
|
117
|
+
position, is rejected right in `bin/gtd` with a dedicated usage error
|
|
118
|
+
(`gtd: --edit and --no-edit are mutually exclusive`, exit 2) — it never reaches
|
|
119
|
+
the compiled bundle. Any other placement (e.g. `gtd step --once`) or a
|
|
120
|
+
duplicated flag (`gtd --once --once`) is still a usage error, but surfaced via
|
|
121
|
+
whatever guard the misplaced argument first reaches (the compiled bundle's own
|
|
120
122
|
unknown-option/unknown-command rejection for a misplaced flag, or
|
|
121
123
|
`gtd: 'loop' takes no arguments` for one misplaced after `loop`) — never
|
|
122
124
|
silently ignored or silently resolved one way.
|