@pmelab/gtd 7.4.2 → 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 +13 -3
- package/bin/gtd +352 -34
- package/dist/gtd.bundle.mjs +4 -0
- package/docs/cli.md +66 -6
- package/docs/loop.md +62 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,6 +106,9 @@ handful of commands drive it:
|
|
|
106
106
|
pending changes, and which action leads where. The panel and diagram highlight
|
|
107
107
|
refresh live (~every 3s) while the page is open, so advancing the process
|
|
108
108
|
elsewhere shows up without a manual refresh.
|
|
109
|
+
- **`gtd edit [path]`** — open `<path>` (or, with no argument, the current
|
|
110
|
+
resting state's steering file, or the repo dir) in `${VISUAL:-$EDITOR}`,
|
|
111
|
+
blocking until it exits.
|
|
109
112
|
|
|
110
113
|
`gtd version` (or `gtd --version`/`-v`) prints the installed version and exits;
|
|
111
114
|
`gtd help` (or `gtd --help`/`-h`) prints the command list. Both short-circuit
|
|
@@ -172,9 +175,16 @@ the file.
|
|
|
172
175
|
|
|
173
176
|
Bare `gtd` (or `gtd loop`) is a ready-to-run driver for the whole protocol —
|
|
174
177
|
point it at a repo and it runs the loop until it's your turn. It is the only
|
|
175
|
-
command you run: at a gate
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
command you run: at a human gate it now opens `${VISUAL:-$EDITOR}` on the gate's
|
|
179
|
+
file for you automatically (answer a plan question, tick a review box, fix
|
|
180
|
+
code), waits for you to save and exit, then captures your edit itself and keeps
|
|
181
|
+
driving — or halts if you left nothing changed — so you never run
|
|
182
|
+
`gtd step human` by hand. Pass `--no-edit` (or set `GTD_NO_EDIT`) to fall back
|
|
183
|
+
to halting and printing the gate instead, if you'd rather edit and re-launch it
|
|
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
|
|
178
188
|
[Driving the loop](docs/loop.md).
|
|
179
189
|
|
|
180
190
|
Before wiring gtd into a repo, note the
|
package/bin/gtd
CHANGED
|
@@ -3,9 +3,21 @@ 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
|
+
# - `--no-edit` (bare, or immediately after `loop`) -> stripped here and
|
|
7
|
+
# disables the loop's automatic editor launching for this run (see
|
|
8
|
+
# `edit_disabled`/`GTD_NO_EDIT` below); dispatch then continues on the
|
|
9
|
+
# remaining arguments as if it had never been given.
|
|
10
|
+
# - `edit [path]` -> handled entirely here in bash, never forwarded to the
|
|
11
|
+
# bundle: opens `${VISUAL:-$EDITOR}` on `path`, or (no `path` given) on the
|
|
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.
|
|
6
17
|
# - no arguments, or exactly `loop` with no further arguments -> fall through
|
|
7
18
|
# into the loop body below (the loop protocol comment further down).
|
|
8
|
-
# - `loop` with additional arguments
|
|
19
|
+
# - `loop` with additional arguments (other than a lone `--no-edit`) ->
|
|
20
|
+
# usage error, exit 2.
|
|
9
21
|
# - anything else -> hand off to the bundle via `run_gtd`, forwarding all
|
|
10
22
|
# arguments unchanged (e.g. `bin/gtd status`, `bin/gtd step human`).
|
|
11
23
|
# `run_gtd` is the ONE place that knows how to invoke "the bundle": by default
|
|
@@ -25,6 +37,211 @@ run_gtd() {
|
|
|
25
37
|
fi
|
|
26
38
|
}
|
|
27
39
|
|
|
40
|
+
# Shared editor-launch helper — used by both the `edit` subcommand below and
|
|
41
|
+
# the loop's human-gate flow further down. Resolves the editor git-style
|
|
42
|
+
# ($VISUAL, then $EDITOR — no fallback to `vi`), then launches it blocking in
|
|
43
|
+
# the foreground with the target path passed as a positional parameter
|
|
44
|
+
# (never string-spliced into the command), so a multi-word command
|
|
45
|
+
# (`code --wait`) and a path with special characters both work. A launch
|
|
46
|
+
# failure (bad/missing editor binary) is a plain non-zero return, propagating
|
|
47
|
+
# under `set -e`; the editor's own exit code is otherwise ignored entirely —
|
|
48
|
+
# the outcome is judged from tree state afterward, not from this function.
|
|
49
|
+
launch_editor() {
|
|
50
|
+
local target="$1" editor="${VISUAL:-${EDITOR:-}}"
|
|
51
|
+
if [[ -z "$editor" ]]; then
|
|
52
|
+
echo "gtd: no editor configured — set \$EDITOR (or \$VISUAL)" >&2
|
|
53
|
+
return 1
|
|
54
|
+
fi
|
|
55
|
+
bash -c "$editor \"\$1\"" gtd-edit "$target"
|
|
56
|
+
}
|
|
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
|
+
|
|
199
|
+
# --no-edit / GTD_NO_EDIT govern only the loop's automatic editor launching at
|
|
200
|
+
# human gates (see handle_human_gate further down) — `gtd edit` itself never
|
|
201
|
+
# reads either. `--no-edit` is recognised in exactly the two sanctioned
|
|
202
|
+
# positions (bare, or right after `loop`) and stripped before anything below
|
|
203
|
+
# looks at "$@", so the bundle's own unknown-option guard never sees it.
|
|
204
|
+
edit_disabled=0
|
|
205
|
+
[[ -n "${GTD_NO_EDIT:-}" ]] && edit_disabled=1
|
|
206
|
+
if [[ "${1:-}" == "--no-edit" && $# -eq 1 ]]; then
|
|
207
|
+
edit_disabled=1
|
|
208
|
+
shift
|
|
209
|
+
elif [[ "${1:-}" == "loop" && "${2:-}" == "--no-edit" && $# -eq 2 ]]; then
|
|
210
|
+
edit_disabled=1
|
|
211
|
+
set -- loop
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
if [[ "${1:-}" == "edit" ]]; then
|
|
215
|
+
shift
|
|
216
|
+
if [[ $# -gt 0 ]]; then
|
|
217
|
+
launch_editor "$1"
|
|
218
|
+
else
|
|
219
|
+
if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
220
|
+
echo "gtd edit: could not determine the next step:" >&2
|
|
221
|
+
echo "$peek_json" >&2
|
|
222
|
+
exit 1
|
|
223
|
+
fi
|
|
224
|
+
file="$(jq -r '.file // empty' <<<"$peek_json")"
|
|
225
|
+
launch_editor "${file:-.}"
|
|
226
|
+
fi
|
|
227
|
+
exit 0
|
|
228
|
+
fi
|
|
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
|
+
|
|
28
245
|
if [[ $# -gt 0 && "$1" != "loop" ]]; then
|
|
29
246
|
run_gtd "$@"
|
|
30
247
|
exit $?
|
|
@@ -33,12 +250,16 @@ if [[ "${1:-}" == "loop" && $# -gt 1 ]]; then
|
|
|
33
250
|
echo "gtd: 'loop' takes no arguments" >&2
|
|
34
251
|
exit 2
|
|
35
252
|
fi
|
|
36
|
-
# otherwise ($# -eq 0, or exactly "loop" with no further args
|
|
37
|
-
# into the existing loop body below,
|
|
253
|
+
# otherwise ($# -eq 0, or exactly "loop" with no further args, --no-edit
|
|
254
|
+
# already stripped above): fall through into the existing loop body below,
|
|
255
|
+
# unchanged in behavior.
|
|
38
256
|
|
|
39
257
|
# Drives the v3 pattern-machine loop (see docs/loop.md): each iteration asks
|
|
40
258
|
# `gtd next --json` who's up and what they should do, dispatching on `kind`:
|
|
41
|
-
# - "message" (a human rest) ->
|
|
259
|
+
# - "message" (a human rest) -> see handle_human_gate above: with editing on
|
|
260
|
+
# (the default) open the editor at the gate and step human, driving on a
|
|
261
|
+
# captured commit; with editing off (--no-edit/GTD_NO_EDIT), halt and tell
|
|
262
|
+
# the user what gtd is waiting on, unchanged from before this feature.
|
|
42
263
|
# - "script" (a check rest) -> execute `content` (the emitted wrapper
|
|
43
264
|
# script) verbatim via bash — exit code deliberately ignored, the outcome
|
|
44
265
|
# lives in the tree — then `gtd step <actor>` to capture it; zero new
|
|
@@ -101,6 +322,60 @@ herdr_release() { # hand the pane back to Herdr's normal detection
|
|
|
101
322
|
herdr_run pane release-agent "$HERDR_PANE_ID" --source herdr:gtd --agent gtd
|
|
102
323
|
}
|
|
103
324
|
|
|
325
|
+
# Shared human-gate handler for a "message" (human) rest — used both by the
|
|
326
|
+
# opening move (when editing is on) and by every "message" rest the main loop
|
|
327
|
+
# encounters. $1 is the rest's `next --json` blob.
|
|
328
|
+
#
|
|
329
|
+
# Editing OFF ($edit_disabled): today's behavior, unchanged — render the gate
|
|
330
|
+
# via emit_gate and exit 0 without touching an editor.
|
|
331
|
+
#
|
|
332
|
+
# Editing ON (the default): open the editor on the rest's `.file` (or `.` when
|
|
333
|
+
# absent), wait for it to exit, then run `gtd step human`:
|
|
334
|
+
# - a NEW commit was authored -> return normally so the caller keeps driving.
|
|
335
|
+
# - ZERO commits were authored -> halt, exit 0 (closing the editor without a
|
|
336
|
+
# capturable change means "done for now" — and keeps a gate with no `C`
|
|
337
|
+
# pattern, e.g. idle, from being reopened forever).
|
|
338
|
+
# - the step REFUSES (non-zero exit: malformed steering file, unfinished
|
|
339
|
+
# review) -> surface it verbatim and halt, exit non-zero — the same
|
|
340
|
+
# wording an opening-move refusal already used before this function
|
|
341
|
+
# existed.
|
|
342
|
+
handle_human_gate() {
|
|
343
|
+
local json="$1" file label state step_out head_before head_after
|
|
344
|
+
label="$(jq -r '.label // .state' <<<"$json")"
|
|
345
|
+
state="$(jq -r .state <<<"$json")"
|
|
346
|
+
|
|
347
|
+
if [[ "$edit_disabled" == 1 ]]; then
|
|
348
|
+
herdr_report blocked "$label"
|
|
349
|
+
herdr_notify "gtd needs you — $(basename "$PWD")" "$label"
|
|
350
|
+
emit_gate "$state" "$label"
|
|
351
|
+
run_gtd next
|
|
352
|
+
exit 0
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
file="$(jq -r '.file // empty' <<<"$json")"
|
|
356
|
+
herdr_report working "$label"
|
|
357
|
+
launch_editor "${file:-.}"
|
|
358
|
+
|
|
359
|
+
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:
|
|
362
|
+
$step_out
|
|
363
|
+
Fix the reported issue and re-run gtd-loop."
|
|
364
|
+
emit_log_pointer
|
|
365
|
+
exit 1
|
|
366
|
+
fi
|
|
367
|
+
head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
368
|
+
if [[ "$head_before" == "$head_after" ]]; then
|
|
369
|
+
herdr_report idle "$label"
|
|
370
|
+
herdr_release
|
|
371
|
+
emit_action "$M_HUMAN" "done for now (nothing captured)"
|
|
372
|
+
exit 0
|
|
373
|
+
fi
|
|
374
|
+
# A new commit was captured — render it, then return normally so the
|
|
375
|
+
# caller keeps driving.
|
|
376
|
+
report_commits "$head_before"
|
|
377
|
+
}
|
|
378
|
+
|
|
104
379
|
trap 'code=$?; if [ "$code" -ne 0 ]; then
|
|
105
380
|
herdr_report blocked "${GTD_LAST_LABEL:-stopped}: exited $code"
|
|
106
381
|
herdr_notify "gtd stopped — $(basename "$PWD")" "${GTD_LAST_LABEL:-loop} failed (exit $code)"
|
|
@@ -112,6 +387,17 @@ prev_content=""
|
|
|
112
387
|
# across iterations and across restarts. In the git dir, never the work tree.
|
|
113
388
|
memory_marker="$(git rev-parse --git-dir 2>/dev/null || echo .git)/gtd-loop-memory"
|
|
114
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
|
+
|
|
115
401
|
# Swappable agent adapter: GTD_LOOP_AGENT_CMD lets any coding agent CLI stand
|
|
116
402
|
# in for the default, receiving the prompt via $GTD_LOOP_PROMPT, the state's
|
|
117
403
|
# opaque model hint (if any) via $GTD_LOOP_MODEL, and the memory scope via
|
|
@@ -137,7 +423,8 @@ run_agent_turn() {
|
|
|
137
423
|
cmd="$cmd"' --dangerously-skip-permissions'
|
|
138
424
|
fi
|
|
139
425
|
GTD_LOOP_PROMPT="$prompt" GTD_LOOP_MODEL="$model" GTD_LOOP_MEMORY="$memory" \
|
|
140
|
-
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
|
|
141
428
|
}
|
|
142
429
|
|
|
143
430
|
# Opening move: capture the human's pending edit so gtd-loop is the ONLY command
|
|
@@ -154,18 +441,30 @@ run_agent_turn() {
|
|
|
154
441
|
# a harmless no-op; a genuine refusal there (a malformed steering file, or an
|
|
155
442
|
# edit no `on` pattern accepts) is surfaced and stops the loop rather than being
|
|
156
443
|
# driven past.
|
|
444
|
+
#
|
|
445
|
+
# Editing ON: route through the exact same gate logic the main loop uses below
|
|
446
|
+
# (open the editor there first, THEN step) instead of blindly stepping human
|
|
447
|
+
# and moving on. Editing OFF: unchanged from before this function existed —
|
|
448
|
+
# step silently, refusal surfaced and halts, a clean/committed gate just falls
|
|
449
|
+
# into the loop below.
|
|
157
450
|
if ! peek_json="$(run_gtd next --json 2>&1)"; then
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
451
|
+
emit_error "could not determine the next step:
|
|
452
|
+
$peek_json
|
|
453
|
+
Run \`gtd status\` to inspect the repo."
|
|
454
|
+
emit_log_pointer
|
|
161
455
|
exit 1
|
|
162
456
|
fi
|
|
163
457
|
if [[ "$(jq -r .kind <<<"$peek_json")" == "message" ]]; then
|
|
164
|
-
if
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
458
|
+
if [[ "$edit_disabled" == 1 ]]; then
|
|
459
|
+
if ! step_out="$(run_gtd step human 2>&1)"; then
|
|
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
|
|
464
|
+
exit 1
|
|
465
|
+
fi
|
|
466
|
+
else
|
|
467
|
+
handle_human_gate "$peek_json"
|
|
169
468
|
fi
|
|
170
469
|
fi
|
|
171
470
|
|
|
@@ -186,31 +485,34 @@ while true; do
|
|
|
186
485
|
label="$(jq -r '.label // .state' <<<"$next_json")"
|
|
187
486
|
GTD_LAST_LABEL="$label"
|
|
188
487
|
|
|
189
|
-
if [[ "$kind" != "message" ]]; then
|
|
190
|
-
herdr_report working "$label"
|
|
191
|
-
fi
|
|
192
|
-
|
|
193
488
|
if [[ "$kind" == "message" ]]; then
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
exit 0
|
|
489
|
+
# handle_human_gate exits 0/non-zero itself on a halt/refusal; it only
|
|
490
|
+
# returns here when a new commit was captured, so we keep driving.
|
|
491
|
+
handle_human_gate "$next_json"
|
|
492
|
+
continue
|
|
199
493
|
fi
|
|
200
494
|
|
|
495
|
+
herdr_report working "$label"
|
|
496
|
+
|
|
201
497
|
if [[ "$kind" == "script" ]]; then
|
|
498
|
+
emit_action "$M_CHECK" "$label"
|
|
202
499
|
head_before="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
203
500
|
# Execute the emitted wrapper verbatim; its exit code is deliberately
|
|
204
501
|
# ignored (`|| true` under `set -e`) — the script encodes its outcome in
|
|
205
502
|
# the tree, and `gtd step` captures whatever it left there via the state's
|
|
206
|
-
# own `on` patterns.
|
|
207
|
-
bash -c "$content" || true
|
|
208
|
-
run_gtd step "$actor"
|
|
209
|
-
|
|
210
|
-
|
|
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
|
|
211
513
|
herdr_report idle "$label"
|
|
212
514
|
herdr_release
|
|
213
|
-
|
|
515
|
+
emit_settled "$state"
|
|
214
516
|
exit 0
|
|
215
517
|
fi
|
|
216
518
|
prev_state=""
|
|
@@ -222,8 +524,10 @@ while true; do
|
|
|
222
524
|
# agent-owned state and content repeating means the last dispatch made no
|
|
223
525
|
# progress; stop rather than spin on it.
|
|
224
526
|
if [[ "$state" == "$prev_state" && "$content" == "$prev_content" ]]; then
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
227
531
|
exit 1
|
|
228
532
|
fi
|
|
229
533
|
prev_state="$state"
|
|
@@ -263,23 +567,37 @@ while true; do
|
|
|
263
567
|
# cap) before stepping — the plain-`gtd next` self-validation instruction's
|
|
264
568
|
# driver-side equivalent, so the next rest is only ever handed a well-formed
|
|
265
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)"
|
|
266
572
|
run_agent_turn "$content" "$resume"
|
|
267
573
|
|
|
268
574
|
fix_attempt=0
|
|
269
575
|
while ! validate_out="$(run_gtd validate 2>&1)"; do
|
|
270
576
|
fix_attempt=$((fix_attempt + 1))
|
|
271
577
|
if ((fix_attempt > 3)); then
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
275
582
|
exit 1
|
|
276
583
|
fi
|
|
277
584
|
# Resume the same session when there is one (the agent just wrote the file);
|
|
278
585
|
# otherwise a fresh invocation still gets the findings via the prompt.
|
|
279
586
|
fix_resume=0
|
|
280
587
|
[[ -n "$session_id" ]] && fix_resume=1
|
|
588
|
+
emit_action "$M_FIX" "attempt $fix_attempt"
|
|
281
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"
|
|
282
590
|
done
|
|
283
591
|
|
|
284
|
-
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
|
|
285
603
|
done
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -205428,6 +205428,10 @@ Commands:
|
|
|
205428
205428
|
an agent through gtd next/gtd step calls until the
|
|
205429
205429
|
workflow returns to its initial state again. A bare gtd
|
|
205430
205430
|
invocation and gtd loop both launch it identically
|
|
205431
|
+
edit [path] Open \${VISUAL:-$EDITOR} on <path>, or — with no path — on
|
|
205432
|
+
the resolved rest's declared file (or the repo directory
|
|
205433
|
+
if it declares none). Handled entirely by bin/gtd, not
|
|
205434
|
+
this bundle
|
|
205431
205435
|
init Scaffold a minimal .gtdrc.json for this repo, seeding the
|
|
205432
205436
|
default variables you are most likely to change (the test
|
|
205433
205437
|
command) and a Prettier formatting suggestion. gtd runs its
|
package/docs/cli.md
CHANGED
|
@@ -45,6 +45,10 @@ Commands:
|
|
|
45
45
|
commits that would be lost
|
|
46
46
|
next Print the resolved rest's rendered script/prompt/message
|
|
47
47
|
(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/GTD_NO_EDIT
|
|
48
52
|
status Print the resolved rest's state/actor and which declared
|
|
49
53
|
pattern (if any) each pending change matches (no mutation)
|
|
50
54
|
validate Format and validate the steering file the resolved rest
|
|
@@ -63,6 +67,8 @@ Options:
|
|
|
63
67
|
--no-open (gtd visualize only) do not open the browser
|
|
64
68
|
--cost=<n> (gtd step only) record the invocation's token cost
|
|
65
69
|
--model=<name> (gtd step only, with --cost) tag that cost's model
|
|
70
|
+
--no-edit (bare gtd or gtd loop only) disable the loop's automatic
|
|
71
|
+
editor launching at human gates
|
|
66
72
|
--version, -v Print version and exit
|
|
67
73
|
--help, -h Print this help and exit
|
|
68
74
|
```
|
|
@@ -79,12 +85,32 @@ history relative to cwd, so it refuses with a clear error if invoked from a
|
|
|
79
85
|
subdirectory.
|
|
80
86
|
|
|
81
87
|
`--json`, `--cost=<n>`, and `--model=<name>` (the latter two only for
|
|
82
|
-
`gtd step`) are the only long options
|
|
83
|
-
like `--jsn`) is rejected with a usage error
|
|
84
|
-
mistyped flag can never degrade a JSON caller
|
|
85
|
-
`--cost`/`--model` with no value, a non-numeric or
|
|
86
|
-
`--model`, `--model` without `--cost`, or either
|
|
87
|
-
`gtd step` are all usage errors.
|
|
88
|
+
`gtd step`) are the only long options the compiled bundle recognizes. Any other
|
|
89
|
+
`--` option (including a typo like `--jsn`) is rejected with a usage error
|
|
90
|
+
rather than silently ignored, so a mistyped flag can never degrade a JSON caller
|
|
91
|
+
to plain-text mode. A bare `--cost`/`--model` with no value, a non-numeric or
|
|
92
|
+
negative `--cost`, an empty `--model`, `--model` without `--cost`, or either
|
|
93
|
+
flag on any command other than `gtd step` are all usage errors.
|
|
94
|
+
|
|
95
|
+
`--no-edit` is a separate, bash-level flag handled entirely by `bin/gtd` itself,
|
|
96
|
+
stripped before anything reaches the bundle — see [`--no-edit`](#--no-edit)
|
|
97
|
+
below.
|
|
98
|
+
|
|
99
|
+
## `--no-edit`
|
|
100
|
+
|
|
101
|
+
A flag on the loop driver only — bare `gtd --no-edit` or `gtd loop --no-edit`
|
|
102
|
+
(the two are equivalent, since bare `gtd` and `gtd loop` are themselves
|
|
103
|
+
equivalent). Recognized in exactly those two positions; any other
|
|
104
|
+
placement/combination (e.g. `gtd step --no-edit`, or combined with any other
|
|
105
|
+
argument) is a usage error. The `GTD_NO_EDIT` environment variable (any
|
|
106
|
+
non-empty value) does the same thing.
|
|
107
|
+
|
|
108
|
+
Disables the loop's default behavior of launching an editor at human gates,
|
|
109
|
+
restoring the previous halt-and-print-and-exit behavior with no editor involved.
|
|
110
|
+
It governs only the loop's own automatic launching — `gtd edit` invoked directly
|
|
111
|
+
never reads it (see [`gtd edit`](#gtd-edit-path) above). See
|
|
112
|
+
[Driving the loop](loop.md) and `skills/loop/SKILL.md` for the full gate-flow
|
|
113
|
+
description.
|
|
88
114
|
|
|
89
115
|
## `gtd init`
|
|
90
116
|
|
|
@@ -408,6 +434,40 @@ which are JSON-only. `--json` emits
|
|
|
408
434
|
when the state has no `on` (a commit state); a per-edge `describe` is likewise
|
|
409
435
|
omitted when that edge declares none.
|
|
410
436
|
|
|
437
|
+
## `gtd edit [path]`
|
|
438
|
+
|
|
439
|
+
Handled entirely in `bin/gtd`'s own bash — unlike every other subcommand, it is
|
|
440
|
+
**never forwarded** to the compiled bundle (`dist/gtd.bundle.mjs`).
|
|
441
|
+
|
|
442
|
+
With `<path>` given (repo-relative), opens it in `${VISUAL:-$EDITOR}` (git's own
|
|
443
|
+
precedence — `$VISUAL` first, then `$EDITOR`; no fallback to `vi`), blocking in
|
|
444
|
+
the foreground until the editor exits. It creates nothing if `<path>` doesn't
|
|
445
|
+
exist — the path is handed straight to the editor command verbatim. The editor's
|
|
446
|
+
own exit code is ignored entirely; success is judged from tree state afterward,
|
|
447
|
+
not from this command.
|
|
448
|
+
|
|
449
|
+
With no argument, it peeks with `gtd next --json` to find the resolved rest's
|
|
450
|
+
declared `.file`, and opens that (repo-relative), or the repo directory (`.`)
|
|
451
|
+
when the resolved state declares no `file:`. If `gtd next --json` itself fails
|
|
452
|
+
(e.g. not in a repo), it prints an error and exits non-zero without attempting
|
|
453
|
+
to open an editor:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
gtd edit: could not determine the next step:
|
|
457
|
+
<gtd next --json's own error output>
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
`gtd edit` **never** reads `--no-edit`/`GTD_NO_EDIT` — those two govern only the
|
|
461
|
+
loop's own automatic editor launching at human gates (see
|
|
462
|
+
[Driving the loop](loop.md) and `skills/loop/SKILL.md`), not this command.
|
|
463
|
+
Invoked directly, `gtd edit` always launches, unconditionally.
|
|
464
|
+
|
|
465
|
+
If neither `$VISUAL` nor `$EDITOR` is set, no editor is launched:
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
gtd: no editor configured — set $EDITOR (or $VISUAL)
|
|
469
|
+
```
|
|
470
|
+
|
|
411
471
|
## Running `script` rests (no `gtd` subcommand)
|
|
412
472
|
|
|
413
473
|
There is no `gtd` subcommand that executes a workflow script — gtd never runs a
|
package/docs/loop.md
CHANGED
|
@@ -41,16 +41,35 @@ loop picks their change up.
|
|
|
41
41
|
|
|
42
42
|
**A `"message"` rest is a real handoff, not a slow beat.** No driver signs off
|
|
43
43
|
for the human: in the bundled workflow the loop runs the whole cycle unattended
|
|
44
|
-
up to `await-review` and then
|
|
44
|
+
up to `await-review` and then rests there with the review checkout window open
|
|
45
45
|
(the reviewable diff surfaced as uncommitted changes — see
|
|
46
46
|
[STATES.md §11](../STATES.md#11-the-review-checkout-window)). Advancing takes a
|
|
47
47
|
human edit to `.gtd/REVIEW.md`: ticking every box with **no** comment signs off
|
|
48
48
|
into the squash finale, while any comment (a note, or a code edit of your own)
|
|
49
|
-
is feedback that routes into another build + re-review round.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
ended with [`gtd abandon`](cli.md#gtd-abandon---json), which drops the
|
|
53
|
-
commits and keeps their content as uncommitted changes.
|
|
49
|
+
is feedback that routes into another build + re-review round. That gate is the
|
|
50
|
+
contract, not a stall — nothing advances until the edit is captured, whether the
|
|
51
|
+
capture happens within the same run or a later one. A review nobody is going to
|
|
52
|
+
finish is ended with [`gtd abandon`](cli.md#gtd-abandon---json), which drops the
|
|
53
|
+
process's commits and keeps their content as uncommitted changes.
|
|
54
|
+
|
|
55
|
+
**Editing is on by default.** Reaching a `"message"` rest, `bin/gtd` opens
|
|
56
|
+
`${VISUAL:-$EDITOR}` on the rest's declared `.file` (or the repo root `.` when
|
|
57
|
+
the state declares none), blocking until the editor exits, then runs
|
|
58
|
+
`gtd step human` to capture whatever changed:
|
|
59
|
+
|
|
60
|
+
- a new commit was captured → the loop keeps driving on its own; re-launching is
|
|
61
|
+
never necessary;
|
|
62
|
+
- zero commits were captured (a clean tree once the editor closed) → the loop
|
|
63
|
+
halts, exit 0 — "done for now";
|
|
64
|
+
- the step refuses (a malformed steering file, or an edit matching no declared
|
|
65
|
+
pattern) → the refusal is surfaced verbatim and the loop halts non-zero,
|
|
66
|
+
exactly as a manual `gtd step human` refusal would.
|
|
67
|
+
|
|
68
|
+
Set `GTD_NO_EDIT` (any non-empty value), or pass `--no-edit` — see
|
|
69
|
+
[`cli.md`'s `--no-edit`](cli.md#--no-edit) 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.
|
|
54
73
|
|
|
55
74
|
```bash
|
|
56
75
|
gtd next --json # ask who's up and what they should do
|
|
@@ -73,16 +92,18 @@ when the machine rests at a `"message"` gate — so `gtd` is the only command a
|
|
|
73
92
|
human runs. `bin/gtd` is the packaged entry point: invoked bare, or with `loop`
|
|
74
93
|
as its first argument, it runs this exact script; any other first argument (e.g.
|
|
75
94
|
`next`, `step`, `status`) hands off to `node dist/gtd.bundle.mjs` instead. The
|
|
76
|
-
loop body it runs adds
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
and
|
|
95
|
+
loop body it runs adds five things on top of the reference script above: it
|
|
96
|
+
opens an editor at every `"message"` gate instead of just printing it (see
|
|
97
|
+
"Editing is on by default" above; `--no-edit`/`GTD_NO_EDIT` fall back to the
|
|
98
|
+
reference script's plain print-and-exit); it stops with a diagnostic if the same
|
|
99
|
+
`"prompt"` state/content repeat with no progress (see `skills/loop/SKILL.md`'s
|
|
100
|
+
"Stall detection"); it lets `GTD_LOOP_AGENT_CMD` swap in any coding agent CLI in
|
|
101
|
+
place of the default `claude -p`, receiving the prompt via `$GTD_LOOP_PROMPT`;
|
|
102
|
+
it exports the resolved state's optional `model` hint as `$GTD_LOOP_MODEL`,
|
|
103
|
+
appending `--model "$GTD_LOOP_MODEL"` to the default `claude -p` invocation
|
|
104
|
+
whenever it's non-empty; and it acts on the optional `memory` scope hint (see
|
|
105
|
+
"Agent memory scope" below) — continuing one agent session across consecutive
|
|
106
|
+
same-scope turns and starting fresh when the scope changes.
|
|
86
107
|
|
|
87
108
|
`bin/gtd` resolves both its bundle hand-off and the loop's own internal `gtd`
|
|
88
109
|
calls against `dist/gtd.bundle.mjs` next to its own location. Set `GTD_BIN` — a
|
|
@@ -116,7 +137,7 @@ while true; do
|
|
|
116
137
|
content="$(jq -r .content <<<"$next_json")"
|
|
117
138
|
|
|
118
139
|
if [[ "$kind" == "message" ]]; then
|
|
119
|
-
echo "
|
|
140
|
+
echo "your turn ($state)" # bin/gtd renders this via emit_gate instead
|
|
120
141
|
gtd next
|
|
121
142
|
exit 0
|
|
122
143
|
fi
|
|
@@ -127,7 +148,7 @@ while true; do
|
|
|
127
148
|
gtd step "$actor" >/dev/null # capture whatever it left in the tree
|
|
128
149
|
head_after="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
129
150
|
if [[ "$head_before" == "$head_after" ]]; then
|
|
130
|
-
echo "
|
|
151
|
+
echo "settled ($state: check passed, nothing to do)" # bin/gtd renders this via emit_settled instead
|
|
131
152
|
exit 0
|
|
132
153
|
fi
|
|
133
154
|
continue
|
|
@@ -143,6 +164,29 @@ The driver — not the prompt text — owns ending the agent's turn
|
|
|
143
164
|
(`gtd step "$actor"` right after the agent acts): every default-workflow agent
|
|
144
165
|
prompt says explicitly not to run `gtd step agent` itself.
|
|
145
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
|
+
|
|
146
190
|
## Agent memory scope
|
|
147
191
|
|
|
148
192
|
A `"prompt"` beat may carry an optional `memory` key — an opaque scope label the
|