@pmelab/gtd 8.3.1 → 8.5.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 +320 -118
- package/bin/gtd +80 -29
- package/dist/gtd.bundle.mjs +4684 -3247
- package/package.json +11 -9
- package/schema.json +147 -265
package/bin/gtd
CHANGED
|
@@ -369,11 +369,63 @@ new_session_id() {
|
|
|
369
369
|
uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || echo ""
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
# scope_of <key> — the scope part of a "<scope>#<hash>" memory key: everything
|
|
373
|
+
# before the first "#". Used to decide which existing table row a fresh entry
|
|
374
|
+
# into a scope displaces (see write_scope_session).
|
|
375
|
+
scope_of() {
|
|
376
|
+
printf '%s' "${1%%#*}"
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
# lookup_scope_session <key> — print the session id remembered for EXACTLY
|
|
380
|
+
# this key (a row whose first field string-equals it), or fail with no output
|
|
381
|
+
# if no row matches (a lookup miss -> the caller starts fresh). $memory_marker
|
|
382
|
+
# absent counts as no rows at all.
|
|
383
|
+
lookup_scope_session() {
|
|
384
|
+
local key="$1" row_key row_sid
|
|
385
|
+
[[ -f "$memory_marker" ]] || return 1
|
|
386
|
+
while read -r row_key row_sid; do
|
|
387
|
+
if [[ "$row_key" == "$key" ]]; then
|
|
388
|
+
printf '%s' "$row_sid"
|
|
389
|
+
return 0
|
|
390
|
+
fi
|
|
391
|
+
done <"$memory_marker"
|
|
392
|
+
return 1
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
# write_scope_session <key> <session_id> — replace the ONE row for this key's
|
|
396
|
+
# SCOPE (scope_of) with a fresh row for <key>, leaving every OTHER scope's row
|
|
397
|
+
# in the table untouched. A scope has at most one live conversation at any
|
|
398
|
+
# moment, so an existing row for this scope is dead the instant a new entry
|
|
399
|
+
# into it begins — this is the entire pruning story, no process concept
|
|
400
|
+
# needed (see the module's own memory-scope doc comment below). The table is
|
|
401
|
+
# rewritten via a sibling temp file + rename, so a crash mid-write never
|
|
402
|
+
# leaves a half-written table.
|
|
403
|
+
write_scope_session() {
|
|
404
|
+
local key="$1" session_id="$2" scope tmp row_key row_sid
|
|
405
|
+
scope="$(scope_of "$key")"
|
|
406
|
+
tmp="$(mktemp "${memory_marker}.XXXXXX")"
|
|
407
|
+
if [[ -f "$memory_marker" ]]; then
|
|
408
|
+
while read -r row_key row_sid; do
|
|
409
|
+
[[ "$(scope_of "$row_key")" == "$scope" ]] && continue
|
|
410
|
+
printf '%s %s\n' "$row_key" "$row_sid" >>"$tmp"
|
|
411
|
+
done <"$memory_marker"
|
|
412
|
+
fi
|
|
413
|
+
printf '%s %s\n' "$key" "$session_id" >>"$tmp"
|
|
414
|
+
mv "$tmp" "$memory_marker"
|
|
415
|
+
}
|
|
416
|
+
|
|
372
417
|
prev_state=""
|
|
373
418
|
prev_content=""
|
|
374
419
|
prev_head=""
|
|
375
|
-
# Where
|
|
376
|
-
#
|
|
420
|
+
# Where each scope's remembered agent session is persisted across iterations
|
|
421
|
+
# and across restarts, as a TABLE — one "<key> <session_id>" row per scope
|
|
422
|
+
# (keys contain no whitespace, so plain field-splitting is safe) — never a
|
|
423
|
+
# single last-label value: with machine-scoped memory, a scope's key can
|
|
424
|
+
# legitimately reappear AFTER the loop has run turns in a completely
|
|
425
|
+
# different (or nested) scope — e.g. build.fix -> into a nested health-gate
|
|
426
|
+
# child's own agent turn -> back to build.fix, still the same builder
|
|
427
|
+
# conversation. "The last label" can't represent that; a table where each
|
|
428
|
+
# scope owns exactly one row can. In the git dir, never the work tree.
|
|
377
429
|
memory_marker="$(worktree_git_dir 2>/dev/null || echo .git)/gtd-loop-memory"
|
|
378
430
|
|
|
379
431
|
# Truncated once here (not every iteration), so the file always reflects the
|
|
@@ -418,15 +470,17 @@ invoke_agent() {
|
|
|
418
470
|
|
|
419
471
|
# run_agent_turn <prompt> <resume> — invoke_agent, with one recovery: a
|
|
420
472
|
# resumed turn (do_resume == 1) that fails AND whose output carries the
|
|
421
|
-
# missing-session signature (session_gone_since) means the
|
|
422
|
-
# longer exists on disk. Rather than failing the turn
|
|
423
|
-
# it, since the dead id would just be resumed again),
|
|
424
|
-
#
|
|
425
|
-
#
|
|
426
|
-
#
|
|
427
|
-
#
|
|
428
|
-
#
|
|
429
|
-
#
|
|
473
|
+
# missing-session signature (session_gone_since) means the CURRENT scope's
|
|
474
|
+
# remembered session no longer exists on disk. Rather than failing the turn
|
|
475
|
+
# (and every turn after it, since the dead id would just be resumed again),
|
|
476
|
+
# mint a fresh session and rewrite ONLY this scope's row in $memory_marker
|
|
477
|
+
# (write_scope_session — every other scope's row survives untouched) — the
|
|
478
|
+
# self-healing part, so a later launch doesn't repeat this — and retry the
|
|
479
|
+
# SAME prompt once with resume off. $resume and $session_id are the enclosing
|
|
480
|
+
# iteration's globals; recovery rewrites them too, so a later validate-gate
|
|
481
|
+
# re-prompt in the same turn resumes the newly pinned session. Blind to the
|
|
482
|
+
# failure mode when session_gone_since can't tell (no log configured): a
|
|
483
|
+
# genuine agent failure then correctly propagates.
|
|
430
484
|
run_agent_turn() {
|
|
431
485
|
local prompt="$1" do_resume="$2" offset status=0
|
|
432
486
|
offset="$(log_offset)"
|
|
@@ -436,7 +490,7 @@ run_agent_turn() {
|
|
|
436
490
|
emit_warn "the remembered agent session for '$memory' is gone — continuing in a fresh session (this turn starts without its previous context)"
|
|
437
491
|
session_id="$(new_session_id)"
|
|
438
492
|
resume=0
|
|
439
|
-
|
|
493
|
+
write_scope_session "$memory" "$session_id"
|
|
440
494
|
invoke_agent "$prompt" 0
|
|
441
495
|
return $?
|
|
442
496
|
fi
|
|
@@ -459,7 +513,7 @@ reported_head="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
|
|
459
513
|
# We peek with the pure, mutation-free `gtd next --json` and step ONLY when it
|
|
460
514
|
# reports a human rest (`kind == "message"`): at an agent/check rest
|
|
461
515
|
# `gtd step human` would refuse out-of-turn (non-zero, aborting under `set -e`),
|
|
462
|
-
# and a mid-
|
|
516
|
+
# and a mid-process restart must simply resume driving, not fail. A clean gate is
|
|
463
517
|
# a harmless no-op; a genuine refusal there (a malformed steering file, or an
|
|
464
518
|
# edit no `on` pattern accepts) is surfaced and stops the loop rather than being
|
|
465
519
|
# driven past. --once treats a captured commit here as the run's one beat and
|
|
@@ -574,29 +628,26 @@ $next_out"
|
|
|
574
628
|
prev_content="$content"
|
|
575
629
|
prev_head="$cur_head"
|
|
576
630
|
|
|
577
|
-
# Agent-memory scope:
|
|
578
|
-
#
|
|
579
|
-
#
|
|
580
|
-
#
|
|
581
|
-
#
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
fi
|
|
631
|
+
# Agent-memory scope: look this turn's `memory` key up in the per-scope
|
|
632
|
+
# table (lookup_scope_session — exact string equality against a row's full
|
|
633
|
+
# key, "<scope>#<hash>", not just its scope part). A hit -> resume that
|
|
634
|
+
# session; a miss -> start fresh with a new session id, then write ONLY this
|
|
635
|
+
# key's scope's row (write_scope_session), leaving every other scope's row
|
|
636
|
+
# untouched — a scope can legitimately go quiet and reappear later (see the
|
|
637
|
+
# $memory_marker declaration's own comment) without losing its place. An
|
|
638
|
+
# absent/empty key carries no constraint (harness default) and leaves the
|
|
639
|
+
# table completely untouched — it may hold live sessions for OTHER scopes
|
|
640
|
+
# this turn knows nothing about, so it's neither read nor written here.
|
|
588
641
|
resume=0
|
|
589
642
|
session_id=""
|
|
590
643
|
if [[ -n "$memory" ]]; then
|
|
591
|
-
if
|
|
644
|
+
if found_session_id="$(lookup_scope_session "$memory")"; then
|
|
592
645
|
resume=1
|
|
593
|
-
session_id="$
|
|
646
|
+
session_id="$found_session_id"
|
|
594
647
|
else
|
|
595
648
|
session_id="$(new_session_id)"
|
|
596
649
|
fi
|
|
597
|
-
|
|
598
|
-
else
|
|
599
|
-
rm -f "$memory_marker"
|
|
650
|
+
write_scope_session "$memory" "$session_id"
|
|
600
651
|
fi
|
|
601
652
|
|
|
602
653
|
# Run the agent turn (see run_agent_turn's own comment for the adapter
|