@pmelab/gtd 8.2.0 → 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 +29 -4
- package/bin/gtd +68 -4
- package/dist/gtd.bundle.mjs +27 -827
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -115,10 +115,12 @@ steering-file entries are chosen by which file you create:
|
|
|
115
115
|
per-package **agentic review** that verifies the package against its spec.
|
|
116
116
|
|
|
117
117
|
Both flows converge on the same tail: an agent hands you a `.gtd/REVIEW.md`
|
|
118
|
-
checkbox review of the diff —
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
checkbox review of the diff — the prompt never inlines the diff itself; it names
|
|
119
|
+
the commit the changes are based at and the agent runs `git diff` to read the
|
|
120
|
+
range before writing the review. Tick a box as you review each hunk (ticking
|
|
121
|
+
just records "I read this"), and leave a **comment** to request changes: a note
|
|
122
|
+
on a line, an inline `// TODO`-style comment in the code, or a direct code edit.
|
|
123
|
+
Any comment sends a build + re-review round — an agent first turns your comments
|
|
122
124
|
into an explicit instruction list, then a build turn implements it (a re-review
|
|
123
125
|
then covers only the follow-through, and a hand-edit is treated as your own fix
|
|
124
126
|
the agent completes without reverting your lines; a comment can't be silently
|
|
@@ -306,6 +308,13 @@ name surfaced in `gtd next --json`/`gtd status`. The driver uses it for its
|
|
|
306
308
|
per-beat progress lines; an outer wrapper (a terminal multiplexer, a notifier)
|
|
307
309
|
can use it the same way.
|
|
308
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
|
+
|
|
309
318
|
### Terminal-multiplexer status: a herdr wrapper
|
|
310
319
|
|
|
311
320
|
[herdr](https://herdr.dev) shows a per-pane status in its sidebar. This wrapper
|
|
@@ -470,6 +479,22 @@ workflow:
|
|
|
470
479
|
mode: <modeName> # optional, requires "file" — a built-in (qa/review/prose) or a `modes:` entry
|
|
471
480
|
```
|
|
472
481
|
|
|
482
|
+
Besides `it.vars` (below), a `script`/`prompt`/`message`/`commit` template sees:
|
|
483
|
+
|
|
484
|
+
- **`it.startCommit`** — the process's diff base (the commit the current cycle
|
|
485
|
+
started from, or a `gtd review <commitish>` entry's resolved base).
|
|
486
|
+
- **`it.reviewBase`** — the previous review round's boundary, falling back to
|
|
487
|
+
`it.startCommit` on a first review.
|
|
488
|
+
- **`it.retainedBase`** — the process's trace/retry boundary, what a squash
|
|
489
|
+
actually keeps (never moved by a `gtd review` entry).
|
|
490
|
+
- **`it.currentCommit`** / **`it.previousCommit`** — HEAD's hash and its parent,
|
|
491
|
+
at render time.
|
|
492
|
+
|
|
493
|
+
A template never sees rendered diff CONTENT — no field carries a diff. It names
|
|
494
|
+
a base and leaves the agent to run `git diff <base>` itself; this keeps every
|
|
495
|
+
render cheap (no diff computed on `gtd next`/`gtd status`/`gtd lsp`) and the
|
|
496
|
+
prompt small and cacheable.
|
|
497
|
+
|
|
473
498
|
Authoring or editing a workflow with a coding agent? `skills/authoring/SKILL.md`
|
|
474
499
|
is the agent-facing contract for producing a valid `workflow:` — the state
|
|
475
500
|
model, pattern grammar, load-time rules, and how to verify a change compiles.
|
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
|