@pmelab/gtd 8.2.1 → 8.3.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 +7 -0
- package/bin/gtd +68 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -308,6 +308,13 @@ name surfaced in `gtd next --json`/`gtd status`. The driver uses it for its
|
|
|
308
308
|
per-beat progress lines; an outer wrapper (a terminal multiplexer, a notifier)
|
|
309
309
|
can use it the same way.
|
|
310
310
|
|
|
311
|
+
A state can also declare an optional `memory:` scope label: consecutive agent
|
|
312
|
+
turns sharing the same label continue one agent session (remembered in the git
|
|
313
|
+
dir, so it survives restarts across the same worktree), while a changed or
|
|
314
|
+
absent label starts a fresh one. If the remembered session no longer exists
|
|
315
|
+
(retention expired, `~/.claude/projects` wiped, a machine change), the driver
|
|
316
|
+
degrades to a fresh session with a warning instead of stopping the loop.
|
|
317
|
+
|
|
311
318
|
### Terminal-multiplexer status: a herdr wrapper
|
|
312
319
|
|
|
313
320
|
[herdr](https://herdr.dev) shows a per-pane status in its sidebar. This wrapper
|
package/bin/gtd
CHANGED
|
@@ -23,7 +23,16 @@ set -euo pipefail
|
|
|
23
23
|
# Every call the loop body makes below goes through `run_gtd` too, so the loop
|
|
24
24
|
# always drives the exact same build/source it was launched from — no
|
|
25
25
|
# dependence on a `gtd` binary being on PATH.
|
|
26
|
-
|
|
26
|
+
# Follow symlinks first: npm's global install exposes this script as a
|
|
27
|
+
# symlink in the node prefix's bin/, and dist/ only exists next to the REAL
|
|
28
|
+
# script inside the package.
|
|
29
|
+
SCRIPT_SOURCE="${BASH_SOURCE[0]}"
|
|
30
|
+
while [[ -L "$SCRIPT_SOURCE" ]]; do
|
|
31
|
+
SCRIPT_DIR="$(cd -- "$(dirname -- "$SCRIPT_SOURCE")" &>/dev/null && pwd)"
|
|
32
|
+
SCRIPT_SOURCE="$(readlink "$SCRIPT_SOURCE")"
|
|
33
|
+
[[ "$SCRIPT_SOURCE" != /* ]] && SCRIPT_SOURCE="$SCRIPT_DIR/$SCRIPT_SOURCE"
|
|
34
|
+
done
|
|
35
|
+
SCRIPT_DIR="$(cd -- "$(dirname -- "$SCRIPT_SOURCE")" &>/dev/null && pwd)"
|
|
27
36
|
|
|
28
37
|
run_gtd() {
|
|
29
38
|
if [[ -n "${GTD_BIN:-}" ]]; then
|
|
@@ -172,6 +181,23 @@ emit_output_since() {
|
|
|
172
181
|
emit_block "$bytes"
|
|
173
182
|
}
|
|
174
183
|
|
|
184
|
+
# A resume that failed because the session it names no longer exists — claude
|
|
185
|
+
# prints "No conversation found with session ID: <uuid>". Loose on purpose: any
|
|
186
|
+
# adapter whose CLI says something session-shaped gets the same recovery
|
|
187
|
+
# instead of stopping the loop.
|
|
188
|
+
SESSION_GONE_RE='no conversation found with session id|no such session|session (id )?[^ ]*( was)? not found|session [^ ]* does not exist'
|
|
189
|
+
|
|
190
|
+
# session_gone_since <offset> — true when the log bytes appended since <offset>
|
|
191
|
+
# carry the missing-session signature above. Blind (returns false) when no log
|
|
192
|
+
# is configured, so recovery is best-effort, never a hard dependency.
|
|
193
|
+
session_gone_since() {
|
|
194
|
+
local offset="$1" bytes=""
|
|
195
|
+
if [[ -n "${GTD_LOOP_LOG:-}" && -f "$GTD_LOOP_LOG" ]]; then
|
|
196
|
+
bytes="$(tail -c "+$((offset + 1))" "$GTD_LOOP_LOG" 2>/dev/null || true)"
|
|
197
|
+
fi
|
|
198
|
+
[[ -n "$bytes" ]] && grep -qiE "$SESSION_GONE_RE" <<<"$bytes"
|
|
199
|
+
}
|
|
200
|
+
|
|
175
201
|
# emit_warn <message> — a surfaced but NON-fatal failure (a check script that
|
|
176
202
|
# exited non-zero).
|
|
177
203
|
emit_warn() {
|
|
@@ -330,7 +356,18 @@ fi
|
|
|
330
356
|
# still resumes one planning session. The scope, session id, and a
|
|
331
357
|
# resume-or-fresh flag are exported as $GTD_LOOP_MEMORY / $GTD_LOOP_SESSION_ID
|
|
332
358
|
# / $GTD_LOOP_MEMORY_RESUME; the default adapter turns them into
|
|
333
|
-
# `--resume`/`--session-id`. An adapter that ignores them keeps working.
|
|
359
|
+
# `--resume`/`--session-id`. An adapter that ignores them keeps working. A
|
|
360
|
+
# remembered session that has since vanished (the retention window elapsed, a
|
|
361
|
+
# wiped `~/.claude/projects`, a machine change) degrades to a fresh session
|
|
362
|
+
# with a warning rather than failing the turn — see run_agent_turn's own
|
|
363
|
+
# comment.
|
|
364
|
+
|
|
365
|
+
# new_session_id — mints an opaque session id for a fresh memory scope
|
|
366
|
+
# (uuidgen, falling back to the kernel's random uuid, or empty when neither is
|
|
367
|
+
# available — the default adapter then omits --session-id entirely).
|
|
368
|
+
new_session_id() {
|
|
369
|
+
uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || echo ""
|
|
370
|
+
}
|
|
334
371
|
|
|
335
372
|
prev_state=""
|
|
336
373
|
prev_content=""
|
|
@@ -362,7 +399,7 @@ fi
|
|
|
362
399
|
# the resume flag; the memory scope ($memory/$session_id) is read from the
|
|
363
400
|
# enclosing iteration, so a same-turn re-prompt (the validate gate) resumes the
|
|
364
401
|
# same session.
|
|
365
|
-
|
|
402
|
+
invoke_agent() {
|
|
366
403
|
local prompt="$1" do_resume="$2" cmd="${GTD_LOOP_AGENT_CMD:-}"
|
|
367
404
|
if [[ -z "$cmd" ]]; then
|
|
368
405
|
cmd='claude -p "$GTD_LOOP_PROMPT"'
|
|
@@ -379,6 +416,33 @@ run_agent_turn() {
|
|
|
379
416
|
>>"${GTD_LOOP_LOG:-/dev/null}" 2>&1
|
|
380
417
|
}
|
|
381
418
|
|
|
419
|
+
# run_agent_turn <prompt> <resume> — invoke_agent, with one recovery: a
|
|
420
|
+
# resumed turn (do_resume == 1) that fails AND whose output carries the
|
|
421
|
+
# missing-session signature (session_gone_since) means the marker's session no
|
|
422
|
+
# longer exists on disk. Rather than failing the turn (and every turn after
|
|
423
|
+
# it, since the dead id would just be resumed again), mint a fresh session,
|
|
424
|
+
# rewrite $memory_marker with it — the self-healing part, so a later launch
|
|
425
|
+
# doesn't repeat this — and retry the SAME prompt once with resume off. $resume
|
|
426
|
+
# and $session_id are the enclosing iteration's globals; recovery rewrites them
|
|
427
|
+
# too, so a later validate-gate re-prompt in the same turn resumes the newly
|
|
428
|
+
# pinned session. Blind to the failure mode when session_gone_since can't tell
|
|
429
|
+
# (no log configured): a genuine agent failure then correctly propagates.
|
|
430
|
+
run_agent_turn() {
|
|
431
|
+
local prompt="$1" do_resume="$2" offset status=0
|
|
432
|
+
offset="$(log_offset)"
|
|
433
|
+
invoke_agent "$prompt" "$do_resume" && return 0
|
|
434
|
+
status=$?
|
|
435
|
+
if [[ "$do_resume" == "1" ]] && session_gone_since "$offset"; then
|
|
436
|
+
emit_warn "the remembered agent session for '$memory' is gone — continuing in a fresh session (this turn starts without its previous context)"
|
|
437
|
+
session_id="$(new_session_id)"
|
|
438
|
+
resume=0
|
|
439
|
+
printf '%s\n%s\n' "$memory" "$session_id" >"$memory_marker"
|
|
440
|
+
invoke_agent "$prompt" 0
|
|
441
|
+
return $?
|
|
442
|
+
fi
|
|
443
|
+
return $status
|
|
444
|
+
}
|
|
445
|
+
|
|
382
446
|
# The furthest commit already reported to the terminal by report_commits (see
|
|
383
447
|
# its own comment) — a global high-water mark rather than a per-beat
|
|
384
448
|
# head_before, so a review checkout window's HEAD rewind between beats never
|
|
@@ -528,7 +592,7 @@ $next_out"
|
|
|
528
592
|
resume=1
|
|
529
593
|
session_id="$prev_session"
|
|
530
594
|
else
|
|
531
|
-
session_id="$(
|
|
595
|
+
session_id="$(new_session_id)"
|
|
532
596
|
fi
|
|
533
597
|
printf '%s\n%s\n' "$memory" "$session_id" >"$memory_marker"
|
|
534
598
|
else
|