@pmelab/gtd 7.5.0 → 7.6.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 +5 -1
- package/bin/gtd +226 -27
- package/docs/loop.md +25 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -181,7 +181,11 @@ code), waits for you to save and exit, then captures your edit itself and keeps
|
|
|
181
181
|
driving — or halts if you left nothing changed — so you never run
|
|
182
182
|
`gtd step human` by hand. Pass `--no-edit` (or set `GTD_NO_EDIT`) to fall back
|
|
183
183
|
to halting and printing the gate instead, if you'd rather edit and re-launch it
|
|
184
|
-
yourself.
|
|
184
|
+
yourself. Bare `gtd` prints one line per event — colored and emoji on a real
|
|
185
|
+
terminal, plain ASCII under `NO_COLOR` or when piped — and redirects the noisier
|
|
186
|
+
agent/check/step subprocess output to a per-repo/per-worktree log file.
|
|
187
|
+
`gtd log` opens that logfile in your editor. See
|
|
188
|
+
[Driving the loop](docs/loop.md).
|
|
185
189
|
|
|
186
190
|
Before wiring gtd into a repo, note the
|
|
187
191
|
[repository requirements](docs/cli.md#repository-requirements) — most
|
package/bin/gtd
CHANGED
|
@@ -10,6 +10,10 @@ set -euo pipefail
|
|
|
10
10
|
# - `edit [path]` -> handled entirely here in bash, never forwarded to the
|
|
11
11
|
# bundle: opens `${VISUAL:-$EDITOR}` on `path`, or (no `path` given) on the
|
|
12
12
|
# resolved rest's `.file` (or `.` when absent).
|
|
13
|
+
# - `log` -> handled entirely here in bash, never forwarded to the bundle:
|
|
14
|
+
# opens `${VISUAL:-$EDITOR}` on the current repo/worktree's loop logfile
|
|
15
|
+
# (see `resolve_log_path`), refusing rather than opening an empty buffer
|
|
16
|
+
# when no loop has produced one yet.
|
|
13
17
|
# - no arguments, or exactly `loop` with no further arguments -> fall through
|
|
14
18
|
# into the loop body below (the loop protocol comment further down).
|
|
15
19
|
# - `loop` with additional arguments (other than a lone `--no-edit`) ->
|
|
@@ -51,6 +55,147 @@ launch_editor() {
|
|
|
51
55
|
bash -c "$editor \"\$1\"" gtd-edit "$target"
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
# ── Rendering primitives (capability detection, palette/markers, emitters) ──
|
|
59
|
+
# Additive: nothing below calls these yet. `FANCY=1` only when stdout (fd 1)
|
|
60
|
+
# is a real terminal AND `NO_COLOR` is unset — any pipe/redirect/non-tty
|
|
61
|
+
# stdout, or `NO_COLOR` set to any value, gives plain ASCII with no escape
|
|
62
|
+
# codes, per the NO_COLOR convention (https://no-color.org).
|
|
63
|
+
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then FANCY=1; else FANCY=0; fi
|
|
64
|
+
|
|
65
|
+
if [[ "$FANCY" == 1 ]]; then
|
|
66
|
+
C_RESET=$'\e[0m'; C_BOLD=$'\e[1m'; C_DIM=$'\e[2m'
|
|
67
|
+
C_CYAN=$'\e[36m'; C_GREEN=$'\e[32m'; C_YELLOW=$'\e[33m'; C_RED=$'\e[31m'
|
|
68
|
+
M_TRANS="➡️"; M_AGENT="🤖"; M_CHECK="🔧"; M_HUMAN="✍️"
|
|
69
|
+
M_FIX="🔁"; M_COMMIT="✅"; M_SETTLE="🎉"; M_ERROR="❌"
|
|
70
|
+
else
|
|
71
|
+
C_RESET=""; C_BOLD=""; C_DIM=""
|
|
72
|
+
C_CYAN=""; C_GREEN=""; C_YELLOW=""; C_RED=""
|
|
73
|
+
M_TRANS="->"; M_AGENT="[agent]"; M_CHECK="[check]"; M_HUMAN="[you]"
|
|
74
|
+
M_FIX="[fix]"; M_COMMIT="[commit]"; M_SETTLE="[done]"; M_ERROR="[err]"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# emit_transition <from> <to> <files> — a captured state-to-state move. The
|
|
78
|
+
# "→" separator is literal (not FANCY-dependent) — the same separator the
|
|
79
|
+
# engine's commit grammar itself uses, per the package spec.
|
|
80
|
+
emit_transition() {
|
|
81
|
+
local from="$1" to="$2" files="$3"
|
|
82
|
+
if [[ -n "$files" ]]; then
|
|
83
|
+
printf '%s %s%s → %s%s (%s%s%s)\n' \
|
|
84
|
+
"$M_TRANS" "$C_BOLD$C_CYAN" "$from" "$to" "$C_RESET" \
|
|
85
|
+
"$C_DIM" "$files" "$C_RESET"
|
|
86
|
+
else
|
|
87
|
+
printf '%s %s%s → %s%s\n' \
|
|
88
|
+
"$M_TRANS" "$C_BOLD$C_CYAN" "$from" "$to" "$C_RESET"
|
|
89
|
+
fi
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# emit_action <marker> <label> — the heartbeat printed before a turn runs.
|
|
93
|
+
emit_action() {
|
|
94
|
+
local marker="$1" label="$2"
|
|
95
|
+
printf '%s %s%s\n' "$marker" "$label" "$C_RESET"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# emit_commit <subject> <files> — a non-transition capture (a bare-subject
|
|
99
|
+
# self-loop, or the squash's agent-authored final commit).
|
|
100
|
+
emit_commit() {
|
|
101
|
+
local subject="$1" files="$2"
|
|
102
|
+
if [[ -n "$files" ]]; then
|
|
103
|
+
printf '%s %s%s%s (%s%s%s)\n' "$M_COMMIT" "$C_GREEN" "$subject" "$C_RESET" \
|
|
104
|
+
"$C_DIM" "$files" "$C_RESET"
|
|
105
|
+
else
|
|
106
|
+
printf '%s %s%s%s\n' "$M_COMMIT" "$C_GREEN" "$subject" "$C_RESET"
|
|
107
|
+
fi
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# emit_gate <state> <message> — the human-instruction line at a message rest.
|
|
111
|
+
emit_gate() {
|
|
112
|
+
local message="$2"
|
|
113
|
+
printf '%s %s%s%s\n' "$M_HUMAN" "$C_BOLD$C_YELLOW" "$message" "$C_RESET"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# emit_settled <state> — the terminal "nothing left to do" signal.
|
|
117
|
+
emit_settled() {
|
|
118
|
+
printf '%s %ssettled — nothing left to do%s\n' "$M_SETTLE" "$C_GREEN" "$C_RESET"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# emit_error <message> — refusals/stalls/failures, to stderr; <message> may be
|
|
122
|
+
# multi-line and is passed through verbatim.
|
|
123
|
+
emit_error() {
|
|
124
|
+
local message="$1"
|
|
125
|
+
printf '%s %s%s%s\n' "$M_ERROR" "$C_RED" "$message" "$C_RESET" >&2
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# emit_log_pointer — the "see the log" line printed right after every
|
|
129
|
+
# emit_error call, to stderr: suppression must never hide a failure, so every
|
|
130
|
+
# error points at where the redirected subprocess output actually went.
|
|
131
|
+
emit_log_pointer() {
|
|
132
|
+
if [[ "$FANCY" == 1 ]]; then
|
|
133
|
+
printf '%s📄 see %s%s\n' "$C_DIM" "$GTD_LOOP_LOG" "$C_RESET" >&2
|
|
134
|
+
else
|
|
135
|
+
printf '%ssee %s%s\n' "$C_DIM" "$GTD_LOOP_LOG" "$C_RESET" >&2
|
|
136
|
+
fi
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# ── Git-derivation helpers (parse_subject, report_commits) ──────────────────
|
|
140
|
+
# Additive: nothing below calls these yet. Replaces today's
|
|
141
|
+
# head_before/head_after string comparisons in a later change.
|
|
142
|
+
|
|
143
|
+
# parse_subject <subject> — sets S_ACTOR / S_FROM / S_TO / S_KIND. Patterns are
|
|
144
|
+
# held in variables (rather than inlined) so bash's [[ =~ ]] doesn't need the
|
|
145
|
+
# literal parens/spaces quoted inline.
|
|
146
|
+
parse_subject() {
|
|
147
|
+
local subject="$1"
|
|
148
|
+
local transition_re='^gtd\(([^)]+)\): (.+) → (.+)$'
|
|
149
|
+
local capture_re='^gtd\(([^)]+)\): (.+)$'
|
|
150
|
+
S_ACTOR="" S_FROM="" S_TO="" S_KIND=""
|
|
151
|
+
if [[ "$subject" =~ $transition_re ]]; then
|
|
152
|
+
S_KIND=transition
|
|
153
|
+
S_ACTOR="${BASH_REMATCH[1]}"
|
|
154
|
+
S_FROM="${BASH_REMATCH[2]}"
|
|
155
|
+
S_TO="${BASH_REMATCH[3]}"
|
|
156
|
+
elif [[ "$subject" =~ $capture_re ]]; then
|
|
157
|
+
S_KIND=capture
|
|
158
|
+
S_ACTOR="${BASH_REMATCH[1]}"
|
|
159
|
+
S_TO="${BASH_REMATCH[2]}"
|
|
160
|
+
else
|
|
161
|
+
S_KIND=squash
|
|
162
|
+
S_TO="$subject"
|
|
163
|
+
fi
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# report_commits <head_before> — walks <head_before>..HEAD in order, emitting
|
|
167
|
+
# a transition/commit line per new commit; returns non-zero (having emitted
|
|
168
|
+
# nothing) when there were no new commits.
|
|
169
|
+
report_commits() {
|
|
170
|
+
local head_before="$1" sha subject files reported=0
|
|
171
|
+
while IFS= read -r sha; do
|
|
172
|
+
[[ -z "$sha" ]] && continue
|
|
173
|
+
reported=1
|
|
174
|
+
subject="$(git log -1 --format=%s "$sha")"
|
|
175
|
+
files="$(git diff-tree --no-commit-id --name-only -r "$sha" | paste -sd, - | sed 's/,/, /g')"
|
|
176
|
+
parse_subject "$subject"
|
|
177
|
+
case "$S_KIND" in
|
|
178
|
+
transition) emit_transition "$S_FROM" "$S_TO" "$files" ;;
|
|
179
|
+
capture | squash) emit_commit "$subject" "$files" ;;
|
|
180
|
+
esac
|
|
181
|
+
done < <(git rev-list --reverse "$head_before"..HEAD)
|
|
182
|
+
[[ "$reported" == 1 ]]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# ── Log file (resolve_log_path, gtd log) ────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
# resolve_log_path — echoes the loop's logfile path: $GTD_LOOP_LOG verbatim if
|
|
188
|
+
# set, else "$(git rev-parse --git-dir)/gtd-loop.log" (per-repository AND
|
|
189
|
+
# per-worktree automatically, since a linked worktree's git-dir is its own
|
|
190
|
+
# private .git/worktrees/<name>).
|
|
191
|
+
resolve_log_path() {
|
|
192
|
+
if [[ -n "${GTD_LOOP_LOG:-}" ]]; then
|
|
193
|
+
echo "$GTD_LOOP_LOG"
|
|
194
|
+
else
|
|
195
|
+
echo "$(git rev-parse --git-dir)/gtd-loop.log"
|
|
196
|
+
fi
|
|
197
|
+
}
|
|
198
|
+
|
|
54
199
|
# --no-edit / GTD_NO_EDIT govern only the loop's automatic editor launching at
|
|
55
200
|
# human gates (see handle_human_gate further down) — `gtd edit` itself never
|
|
56
201
|
# reads either. `--no-edit` is recognised in exactly the two sanctioned
|
|
@@ -82,6 +227,21 @@ if [[ "${1:-}" == "edit" ]]; then
|
|
|
82
227
|
exit 0
|
|
83
228
|
fi
|
|
84
229
|
|
|
230
|
+
if [[ "${1:-}" == "log" ]]; then
|
|
231
|
+
shift
|
|
232
|
+
if [[ $# -gt 0 ]]; then
|
|
233
|
+
echo "gtd log: takes no arguments" >&2
|
|
234
|
+
exit 2
|
|
235
|
+
fi
|
|
236
|
+
log_path="$(resolve_log_path)"
|
|
237
|
+
if [[ ! -f "$log_path" ]]; then
|
|
238
|
+
echo "gtd log: no loop log yet at $log_path" >&2
|
|
239
|
+
exit 1
|
|
240
|
+
fi
|
|
241
|
+
launch_editor "$log_path"
|
|
242
|
+
exit 0
|
|
243
|
+
fi
|
|
244
|
+
|
|
85
245
|
if [[ $# -gt 0 && "$1" != "loop" ]]; then
|
|
86
246
|
run_gtd "$@"
|
|
87
247
|
exit $?
|
|
@@ -166,8 +326,8 @@ herdr_release() { # hand the pane back to Herdr's normal detection
|
|
|
166
326
|
# opening move (when editing is on) and by every "message" rest the main loop
|
|
167
327
|
# encounters. $1 is the rest's `next --json` blob.
|
|
168
328
|
#
|
|
169
|
-
# Editing OFF ($edit_disabled): today's behavior, unchanged —
|
|
170
|
-
# and exit 0 without touching an editor.
|
|
329
|
+
# Editing OFF ($edit_disabled): today's behavior, unchanged — render the gate
|
|
330
|
+
# via emit_gate and exit 0 without touching an editor.
|
|
171
331
|
#
|
|
172
332
|
# Editing ON (the default): open the editor on the rest's `.file` (or `.` when
|
|
173
333
|
# absent), wait for it to exit, then run `gtd step human`:
|
|
@@ -187,7 +347,7 @@ handle_human_gate() {
|
|
|
187
347
|
if [[ "$edit_disabled" == 1 ]]; then
|
|
188
348
|
herdr_report blocked "$label"
|
|
189
349
|
herdr_notify "gtd needs you — $(basename "$PWD")" "$label"
|
|
190
|
-
|
|
350
|
+
emit_gate "$state" "$label"
|
|
191
351
|
run_gtd next
|
|
192
352
|
exit 0
|
|
193
353
|
fi
|
|
@@ -198,19 +358,22 @@ handle_human_gate() {
|
|
|
198
358
|
|
|
199
359
|
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
200
360
|
if ! step_out="$(run_gtd step human 2>&1)"; then
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
361
|
+
emit_error "could not capture your changes at the human gate:
|
|
362
|
+
$step_out
|
|
363
|
+
Fix the reported issue and re-run gtd-loop."
|
|
364
|
+
emit_log_pointer
|
|
204
365
|
exit 1
|
|
205
366
|
fi
|
|
206
367
|
head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
207
368
|
if [[ "$head_before" == "$head_after" ]]; then
|
|
208
369
|
herdr_report idle "$label"
|
|
209
370
|
herdr_release
|
|
210
|
-
|
|
371
|
+
emit_action "$M_HUMAN" "done for now (nothing captured)"
|
|
211
372
|
exit 0
|
|
212
373
|
fi
|
|
213
|
-
#
|
|
374
|
+
# A new commit was captured — render it, then return normally so the
|
|
375
|
+
# caller keeps driving.
|
|
376
|
+
report_commits "$head_before"
|
|
214
377
|
}
|
|
215
378
|
|
|
216
379
|
trap 'code=$?; if [ "$code" -ne 0 ]; then
|
|
@@ -224,6 +387,17 @@ prev_content=""
|
|
|
224
387
|
# across iterations and across restarts. In the git dir, never the work tree.
|
|
225
388
|
memory_marker="$(git rev-parse --git-dir 2>/dev/null || echo .git)/gtd-loop-memory"
|
|
226
389
|
|
|
390
|
+
# Truncated once here (not every iteration), so the file always reflects the
|
|
391
|
+
# current run but survives after the loop exits for a later `gtd log`.
|
|
392
|
+
log_path="$(resolve_log_path)"
|
|
393
|
+
: >"$log_path"
|
|
394
|
+
export GTD_LOOP_LOG="$log_path"
|
|
395
|
+
if [[ "$FANCY" == 1 ]]; then
|
|
396
|
+
printf '%s📄 log: %s (tail -f to follow)%s\n' "$C_DIM" "$log_path" "$C_RESET"
|
|
397
|
+
else
|
|
398
|
+
printf '%slog: %s (tail -f to follow)%s\n' "$C_DIM" "$log_path" "$C_RESET"
|
|
399
|
+
fi
|
|
400
|
+
|
|
227
401
|
# Swappable agent adapter: GTD_LOOP_AGENT_CMD lets any coding agent CLI stand
|
|
228
402
|
# in for the default, receiving the prompt via $GTD_LOOP_PROMPT, the state's
|
|
229
403
|
# opaque model hint (if any) via $GTD_LOOP_MODEL, and the memory scope via
|
|
@@ -249,7 +423,8 @@ run_agent_turn() {
|
|
|
249
423
|
cmd="$cmd"' --dangerously-skip-permissions'
|
|
250
424
|
fi
|
|
251
425
|
GTD_LOOP_PROMPT="$prompt" GTD_LOOP_MODEL="$model" GTD_LOOP_MEMORY="$memory" \
|
|
252
|
-
GTD_LOOP_SESSION_ID="$session_id" GTD_LOOP_MEMORY_RESUME="$do_resume" bash -c "$cmd"
|
|
426
|
+
GTD_LOOP_SESSION_ID="$session_id" GTD_LOOP_MEMORY_RESUME="$do_resume" bash -c "$cmd" \
|
|
427
|
+
>>"${GTD_LOOP_LOG:-/dev/null}" 2>&1
|
|
253
428
|
}
|
|
254
429
|
|
|
255
430
|
# Opening move: capture the human's pending edit so gtd-loop is the ONLY command
|
|
@@ -273,17 +448,19 @@ run_agent_turn() {
|
|
|
273
448
|
# step silently, refusal surfaced and halts, a clean/committed gate just falls
|
|
274
449
|
# into the loop below.
|
|
275
450
|
if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
451
|
+
emit_error "could not determine the next step:
|
|
452
|
+
$peek_json
|
|
453
|
+
Run \`gtd status\` to inspect the repo."
|
|
454
|
+
emit_log_pointer
|
|
279
455
|
exit 1
|
|
280
456
|
fi
|
|
281
457
|
if [[ "$(jq -r .kind <<<"$peek_json")" == "message" ]]; then
|
|
282
458
|
if [[ "$edit_disabled" == 1 ]]; then
|
|
283
459
|
if ! step_out="$(run_gtd step human 2>&1)"; then
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
460
|
+
emit_error "could not capture your changes at the human gate:
|
|
461
|
+
$step_out
|
|
462
|
+
Fix the reported issue and re-run gtd-loop."
|
|
463
|
+
emit_log_pointer
|
|
287
464
|
exit 1
|
|
288
465
|
fi
|
|
289
466
|
else
|
|
@@ -318,18 +495,24 @@ while true; do
|
|
|
318
495
|
herdr_report working "$label"
|
|
319
496
|
|
|
320
497
|
if [[ "$kind" == "script" ]]; then
|
|
498
|
+
emit_action "$M_CHECK" "$label"
|
|
321
499
|
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
322
500
|
# Execute the emitted wrapper verbatim; its exit code is deliberately
|
|
323
501
|
# ignored (`|| true` under `set -e`) — the script encodes its outcome in
|
|
324
502
|
# the tree, and `gtd step` captures whatever it left there via the state's
|
|
325
|
-
# own `on` patterns.
|
|
326
|
-
bash -c "$content" || true
|
|
327
|
-
run_gtd step "$actor"
|
|
328
|
-
|
|
329
|
-
|
|
503
|
+
# own `on` patterns. Its own output goes to the log, not the terminal.
|
|
504
|
+
bash -c "$content" >>"$GTD_LOOP_LOG" 2>&1 || true
|
|
505
|
+
if ! step_out="$(run_gtd step "$actor" 2>&1)"; then
|
|
506
|
+
printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
|
|
507
|
+
emit_error "$step_out"
|
|
508
|
+
emit_log_pointer
|
|
509
|
+
exit 1
|
|
510
|
+
fi
|
|
511
|
+
printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
|
|
512
|
+
if ! report_commits "$head_before"; then
|
|
330
513
|
herdr_report idle "$label"
|
|
331
514
|
herdr_release
|
|
332
|
-
|
|
515
|
+
emit_settled "$state"
|
|
333
516
|
exit 0
|
|
334
517
|
fi
|
|
335
518
|
prev_state=""
|
|
@@ -341,8 +524,10 @@ while true; do
|
|
|
341
524
|
# agent-owned state and content repeating means the last dispatch made no
|
|
342
525
|
# progress; stop rather than spin on it.
|
|
343
526
|
if [[ "$state" == "$prev_state" && "$content" == "$prev_content" ]]; then
|
|
344
|
-
|
|
345
|
-
|
|
527
|
+
next_out="$(run_gtd next 2>&1)" || true
|
|
528
|
+
emit_error "no progress at '$state' — the agent's last turn changed nothing. Stopping to avoid spinning.
|
|
529
|
+
$next_out"
|
|
530
|
+
emit_log_pointer
|
|
346
531
|
exit 1
|
|
347
532
|
fi
|
|
348
533
|
prev_state="$state"
|
|
@@ -382,23 +567,37 @@ while true; do
|
|
|
382
567
|
# cap) before stepping — the plain-`gtd next` self-validation instruction's
|
|
383
568
|
# driver-side equivalent, so the next rest is only ever handed a well-formed
|
|
384
569
|
# file. gtd itself never validates at step time — this is the loop's job.
|
|
570
|
+
emit_action "$M_AGENT" "$label"
|
|
571
|
+
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
385
572
|
run_agent_turn "$content" "$resume"
|
|
386
573
|
|
|
387
574
|
fix_attempt=0
|
|
388
575
|
while ! validate_out="$(run_gtd validate 2>&1)"; do
|
|
389
576
|
fix_attempt=$((fix_attempt + 1))
|
|
390
577
|
if ((fix_attempt > 3)); then
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
578
|
+
emit_error "'$state' still fails \`gtd validate\` after 3 fix attempts:
|
|
579
|
+
$validate_out
|
|
580
|
+
Stopping rather than stepping with a malformed steering file."
|
|
581
|
+
emit_log_pointer
|
|
394
582
|
exit 1
|
|
395
583
|
fi
|
|
396
584
|
# Resume the same session when there is one (the agent just wrote the file);
|
|
397
585
|
# otherwise a fresh invocation still gets the findings via the prompt.
|
|
398
586
|
fix_resume=0
|
|
399
587
|
[[ -n "$session_id" ]] && fix_resume=1
|
|
588
|
+
emit_action "$M_FIX" "attempt $fix_attempt"
|
|
400
589
|
run_agent_turn "$content"$'\n\n'"Your last turn does not pass \`gtd validate\`. Fix these format violations, then finish:"$'\n'"$validate_out" "$fix_resume"
|
|
401
590
|
done
|
|
402
591
|
|
|
403
|
-
run_gtd step "$actor"
|
|
592
|
+
if ! step_out="$(run_gtd step "$actor" 2>&1)"; then
|
|
593
|
+
printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
|
|
594
|
+
emit_error "$step_out"
|
|
595
|
+
emit_log_pointer
|
|
596
|
+
exit 1
|
|
597
|
+
fi
|
|
598
|
+
printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
|
|
599
|
+
# A no-op step (nothing matched a declared pattern on a clean tree) reports
|
|
600
|
+
# zero commits here — report_commits returns non-zero, and that's fine: the
|
|
601
|
+
# NEXT iteration's stall check is what catches unchanged state/content.
|
|
602
|
+
report_commits "$head_before" || true
|
|
404
603
|
done
|
package/docs/loop.md
CHANGED
|
@@ -137,7 +137,7 @@ while true; do
|
|
|
137
137
|
content="$(jq -r .content <<<"$next_json")"
|
|
138
138
|
|
|
139
139
|
if [[ "$kind" == "message" ]]; then
|
|
140
|
-
echo "
|
|
140
|
+
echo "your turn ($state)" # bin/gtd renders this via emit_gate instead
|
|
141
141
|
gtd next
|
|
142
142
|
exit 0
|
|
143
143
|
fi
|
|
@@ -148,7 +148,7 @@ while true; do
|
|
|
148
148
|
gtd step "$actor" >/dev/null # capture whatever it left in the tree
|
|
149
149
|
head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
150
150
|
if [[ "$head_before" == "$head_after" ]]; then
|
|
151
|
-
echo "
|
|
151
|
+
echo "settled ($state: check passed, nothing to do)" # bin/gtd renders this via emit_settled instead
|
|
152
152
|
exit 0
|
|
153
153
|
fi
|
|
154
154
|
continue
|
|
@@ -164,6 +164,29 @@ The driver — not the prompt text — owns ending the agent's turn
|
|
|
164
164
|
(`gtd step "$actor"` right after the agent acts): every default-workflow agent
|
|
165
165
|
prompt says explicitly not to run `gtd step agent` itself.
|
|
166
166
|
|
|
167
|
+
`bin/gtd log` opens the current repo/worktree's loop logfile —
|
|
168
|
+
`"$(git rev-parse --git-dir)/gtd-loop.log"` by default, or `$GTD_LOOP_LOG`
|
|
169
|
+
verbatim when set — in `${VISUAL:-$EDITOR}`, the same editor resolution
|
|
170
|
+
`bin/gtd edit` uses. It takes no arguments and errors out (rather than opening
|
|
171
|
+
an empty buffer) if no loop has produced a log yet.
|
|
172
|
+
|
|
173
|
+
## Rendered output and logging
|
|
174
|
+
|
|
175
|
+
`bin/gtd` detects its output capability once at startup (`FANCY`, true when
|
|
176
|
+
stdout is a real terminal AND `NO_COLOR` is unset): on a real terminal it prints
|
|
177
|
+
one colored/emoji line per event, and under `NO_COLOR` or a piped/redirected
|
|
178
|
+
stdout it prints the same events as plain ASCII with no escape codes, per the
|
|
179
|
+
[NO_COLOR convention](https://no-color.org). Normal events (a state transition,
|
|
180
|
+
a captured commit, an agent/check turn starting, settling at idle) render to
|
|
181
|
+
stdout; refusals, stalls, and validate-cap halts render to stderr, each followed
|
|
182
|
+
by a `📄 see <log>` pointer (plain: `see <log>`).
|
|
183
|
+
|
|
184
|
+
The three previously terminal-visible subprocess streams — the agent turn's own
|
|
185
|
+
output, the check script's own output, and `gtd step`'s own output — are no
|
|
186
|
+
longer printed directly: they're appended to the per-repo/per-worktree log file
|
|
187
|
+
(truncated once at the start of the run) instead, and surfaced with
|
|
188
|
+
`bin/gtd log` above.
|
|
189
|
+
|
|
167
190
|
## Agent memory scope
|
|
168
191
|
|
|
169
192
|
A `"prompt"` beat may carry an optional `memory` key — an opaque scope label the
|