@pmelab/gtd 3.4.0 → 3.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/bin/gtd-loop CHANGED
@@ -7,9 +7,10 @@ set -euo pipefail
7
7
  # - "script" (a check rest) -> `gtd run` executes it and steps the check
8
8
  # actor in one command; zero new commits = green with nothing owed (the
9
9
  # terminal signal at idle).
10
- # - "prompt" (an agent rest) -> feed `content` to the agent, then
11
- # `gtd step <actor>` ourselves — the harness owns ending the turn, not
12
- # the prompt text.
10
+ # - "prompt" (an agent rest) -> feed `content` to the agent, run the
11
+ # self-validation gate (`gtd validate`, re-prompting on findings — see the
12
+ # gate's own comment below), then `gtd step <actor>` ourselves — the
13
+ # harness owns ending the turn, not the prompt text.
13
14
  # `.model` (optional, opaque) is exported as $GTD_LOOP_MODEL for the agent
14
15
  # adapter invoked at a "prompt" rest; the default adapter appends `--model
15
16
  # "$GTD_LOOP_MODEL"` only when it's non-empty.
@@ -34,6 +35,34 @@ prev_content=""
34
35
  # across iterations and across restarts. In the git dir, never the work tree.
35
36
  memory_marker="$(git rev-parse --git-dir 2>/dev/null || echo .git)/gtd-loop-memory"
36
37
 
38
+ # Swappable agent adapter: GTD_LOOP_AGENT_CMD lets any coding agent CLI stand
39
+ # in for the default, receiving the prompt via $GTD_LOOP_PROMPT, the state's
40
+ # opaque model hint (if any) via $GTD_LOOP_MODEL, and the memory scope via
41
+ # $GTD_LOOP_MEMORY / $GTD_LOOP_SESSION_ID / $GTD_LOOP_MEMORY_RESUME ("1" when
42
+ # this turn should continue the prior session). An adapter that ignores any of
43
+ # these keeps working — they're exported alongside $GTD_LOOP_PROMPT, never
44
+ # required. The default adapter maps the memory signal onto claude's own
45
+ # session flags: --resume to continue the scope's session, --session-id to pin
46
+ # a fresh one so the next same-scope turn can resume it. $1 is the prompt, $2
47
+ # the resume flag; the memory scope ($memory/$session_id) is read from the
48
+ # enclosing iteration, so a same-turn re-prompt (the validate gate) resumes the
49
+ # same session.
50
+ run_agent_turn() {
51
+ local prompt="$1" do_resume="$2" cmd="${GTD_LOOP_AGENT_CMD:-}"
52
+ if [[ -z "$cmd" ]]; then
53
+ cmd='claude -p "$GTD_LOOP_PROMPT"'
54
+ [[ -n "$model" ]] && cmd="$cmd"' --model "$GTD_LOOP_MODEL"'
55
+ if [[ "$do_resume" == "1" ]]; then
56
+ cmd="$cmd"' --resume "$GTD_LOOP_SESSION_ID"'
57
+ elif [[ -n "$session_id" ]]; then
58
+ cmd="$cmd"' --session-id "$GTD_LOOP_SESSION_ID"'
59
+ fi
60
+ cmd="$cmd"' --dangerously-skip-permissions'
61
+ fi
62
+ GTD_LOOP_PROMPT="$prompt" GTD_LOOP_MODEL="$model" GTD_LOOP_MEMORY="$memory" \
63
+ GTD_LOOP_SESSION_ID="$session_id" GTD_LOOP_MEMORY_RESUME="$do_resume" bash -c "$cmd"
64
+ }
65
+
37
66
  while true; do
38
67
  if ! next_json="$(gtd next --json 2>&1)"; then
39
68
  echo "gtd-loop: could not determine the next step:" >&2
@@ -104,28 +133,32 @@ while true; do
104
133
  rm -f "$memory_marker"
105
134
  fi
106
135
 
107
- # Swappable agent adapter: GTD_LOOP_AGENT_CMD lets any coding agent CLI
108
- # stand in for the default, receiving the prompt via $GTD_LOOP_PROMPT, the
109
- # state's opaque model hint (if any) via $GTD_LOOP_MODEL, and the memory
110
- # scope via $GTD_LOOP_MEMORY / $GTD_LOOP_SESSION_ID / $GTD_LOOP_MEMORY_RESUME
111
- # ("1" when this turn should continue the prior session). An adapter that
112
- # ignores any of these keeps working — they're exported alongside
113
- # $GTD_LOOP_PROMPT, never required. The default adapter maps the memory
114
- # signal onto claude's own session flags: --resume to continue the scope's
115
- # session, --session-id to pin a fresh one so the next same-scope turn can
116
- # resume it.
117
- cmd="${GTD_LOOP_AGENT_CMD:-}"
118
- if [[ -z "$cmd" ]]; then
119
- cmd='claude -p "$GTD_LOOP_PROMPT"'
120
- [[ -n "$model" ]] && cmd="$cmd"' --model "$GTD_LOOP_MODEL"'
121
- if [[ "$resume" == "1" ]]; then
122
- cmd="$cmd"' --resume "$GTD_LOOP_SESSION_ID"'
123
- elif [[ -n "$session_id" ]]; then
124
- cmd="$cmd"' --session-id "$GTD_LOOP_SESSION_ID"'
136
+ # Run the agent turn (see run_agent_turn's own comment for the adapter
137
+ # contract). Then the self-validation gate: a producing state may declare a
138
+ # `file:`/`mode:` pair (e.g. the default workflow's grilling/reviewing),
139
+ # making its output's format checkable. `gtd validate` is a no-op (exit 0)
140
+ # when the resolved rest has nothing to validate, so run it after EVERY agent
141
+ # turn; on findings, re-prompt the SAME agent session to fix them (up to a
142
+ # cap) before stepping — the plain-`gtd next` self-validation instruction's
143
+ # driver-side equivalent, so the next rest is only ever handed a well-formed
144
+ # file. gtd itself never validates at step time — this is the loop's job.
145
+ run_agent_turn "$content" "$resume"
146
+
147
+ fix_attempt=0
148
+ while ! validate_out="$(gtd validate 2>&1)"; do
149
+ fix_attempt=$((fix_attempt + 1))
150
+ if ((fix_attempt > 3)); then
151
+ echo "gtd-loop: '$state' still fails \`gtd validate\` after 3 fix attempts:" >&2
152
+ echo "$validate_out" >&2
153
+ echo "Stopping rather than stepping with a malformed steering file." >&2
154
+ exit 1
125
155
  fi
126
- cmd="$cmd"' --dangerously-skip-permissions'
127
- fi
128
- GTD_LOOP_PROMPT="$content" GTD_LOOP_MODEL="$model" GTD_LOOP_MEMORY="$memory" \
129
- GTD_LOOP_SESSION_ID="$session_id" GTD_LOOP_MEMORY_RESUME="$resume" bash -c "$cmd"
156
+ # Resume the same session when there is one (the agent just wrote the file);
157
+ # otherwise a fresh invocation still gets the findings via the prompt.
158
+ fix_resume=0
159
+ [[ -n "$session_id" ]] && fix_resume=1
160
+ 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"
161
+ done
162
+
130
163
  gtd step "$actor" >/dev/null
131
164
  done