wiggum-cli 0.14.0 → 0.16.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.
Files changed (59) hide show
  1. package/README.md +30 -5
  2. package/dist/ai/providers.js +19 -14
  3. package/dist/commands/run.d.ts +1 -1
  4. package/dist/commands/run.js +2 -2
  5. package/dist/generator/templates.d.ts +1 -0
  6. package/dist/generator/templates.js +14 -1
  7. package/dist/index.d.ts +14 -1
  8. package/dist/index.js +229 -41
  9. package/dist/repl/session-state.d.ts +2 -0
  10. package/dist/templates/config/ralph.config.cjs.tmpl +1 -1
  11. package/dist/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
  12. package/dist/templates/prompts/PROMPT_feature.md.tmpl +23 -0
  13. package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +13 -42
  14. package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +168 -0
  15. package/dist/templates/scripts/feature-loop-actions.test.ts +92 -0
  16. package/dist/templates/scripts/feature-loop.sh.tmpl +334 -40
  17. package/dist/tui/app.d.ts +16 -1
  18. package/dist/tui/app.js +31 -5
  19. package/dist/tui/components/ActivityFeed.d.ts +18 -0
  20. package/dist/tui/components/ActivityFeed.js +31 -0
  21. package/dist/tui/components/ChatInput.d.ts +3 -1
  22. package/dist/tui/components/ChatInput.js +23 -4
  23. package/dist/tui/components/CommandDropdown.d.ts +3 -1
  24. package/dist/tui/components/CommandDropdown.js +10 -7
  25. package/dist/tui/components/RunCompletionSummary.d.ts +27 -1
  26. package/dist/tui/components/RunCompletionSummary.js +97 -7
  27. package/dist/tui/components/SpecCompletionSummary.d.ts +3 -2
  28. package/dist/tui/components/SpecCompletionSummary.js +26 -9
  29. package/dist/tui/components/SummaryBox.d.ts +4 -3
  30. package/dist/tui/components/SummaryBox.js +7 -3
  31. package/dist/tui/hooks/useBackgroundRuns.js +1 -1
  32. package/dist/tui/orchestration/interview-orchestrator.js +35 -5
  33. package/dist/tui/screens/MainShell.js +2 -1
  34. package/dist/tui/screens/RunScreen.d.ts +15 -15
  35. package/dist/tui/screens/RunScreen.js +139 -17
  36. package/dist/tui/utils/action-inbox.d.ts +43 -0
  37. package/dist/tui/utils/action-inbox.js +109 -0
  38. package/dist/tui/utils/build-run-summary.js +4 -1
  39. package/dist/tui/utils/git-summary.d.ts +13 -0
  40. package/dist/tui/utils/git-summary.js +30 -0
  41. package/dist/tui/utils/loop-status.d.ts +54 -0
  42. package/dist/tui/utils/loop-status.js +213 -1
  43. package/dist/tui/utils/polishGoal.d.ts +37 -0
  44. package/dist/tui/utils/polishGoal.js +170 -0
  45. package/dist/utils/ci.d.ts +8 -0
  46. package/dist/utils/ci.js +13 -0
  47. package/dist/utils/config.d.ts +1 -1
  48. package/dist/utils/fuzzy-match.d.ts +5 -0
  49. package/dist/utils/fuzzy-match.js +16 -0
  50. package/dist/utils/spec-names.d.ts +6 -0
  51. package/dist/utils/spec-names.js +27 -0
  52. package/package.json +15 -5
  53. package/src/templates/config/ralph.config.cjs.tmpl +1 -1
  54. package/src/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
  55. package/src/templates/prompts/PROMPT_feature.md.tmpl +23 -0
  56. package/src/templates/prompts/PROMPT_review_auto.md.tmpl +13 -42
  57. package/src/templates/prompts/PROMPT_review_merge.md.tmpl +168 -0
  58. package/src/templates/scripts/feature-loop-actions.test.ts +92 -0
  59. package/src/templates/scripts/feature-loop.sh.tmpl +334 -40
@@ -6,8 +6,8 @@
6
6
  # Options:
7
7
  # --worktree Use git worktree for isolation (enables parallel execution)
8
8
  # --resume Resume an interrupted loop (reuses existing branch/worktree)
9
- # --model MODEL Claude model to use (e.g., opus, sonnet, claude-sonnet-4-5-20250514)
10
- # --review-mode MODE Review mode: 'manual' (stop at PR) or 'auto' (review + merge). Default: 'manual'
9
+ # --model MODEL Claude model to use (e.g., opus, sonnet, claude-sonnet-4-5-20250929)
10
+ # --review-mode MODE Review mode: 'manual' (stop at PR), 'auto' (review, no merge), or 'merge' (review + merge). Default: 'manual'
11
11
 
12
12
  set -e
13
13
  set -o pipefail
@@ -91,14 +91,14 @@ if [ -z "$REVIEW_MODE" ]; then
91
91
  fi
92
92
 
93
93
  # Validate review mode
94
- if [ "$REVIEW_MODE" != "manual" ] && [ "$REVIEW_MODE" != "auto" ]; then
95
- echo "ERROR: Invalid review mode: '$REVIEW_MODE'. Allowed values are 'manual' or 'auto'." >&2
94
+ if [ "$REVIEW_MODE" != "manual" ] && [ "$REVIEW_MODE" != "auto" ] && [ "$REVIEW_MODE" != "merge" ]; then
95
+ echo "ERROR: Invalid review mode: '$REVIEW_MODE'. Allowed values are 'manual', 'auto', or 'merge'." >&2
96
96
  exit 1
97
97
  fi
98
98
 
99
99
  # Build claude commands
100
- CLAUDE_CMD_OPUS="claude -p --dangerously-skip-permissions --model ${PLANNING_MODEL}"
101
- CLAUDE_CMD_IMPL="claude -p --dangerously-skip-permissions --model ${MODEL:-$DEFAULT_MODEL}"
100
+ CLAUDE_CMD_OPUS="claude -p --output-format json --dangerously-skip-permissions --model ${PLANNING_MODEL}"
101
+ CLAUDE_CMD_IMPL="claude -p --output-format json --dangerously-skip-permissions --model ${MODEL:-$DEFAULT_MODEL}"
102
102
 
103
103
  # Token tracking
104
104
  TOKENS_FILE="/tmp/ralph-loop-${1}.tokens"
@@ -107,35 +107,192 @@ STATUS_FILE="/tmp/ralph-loop-${1}.status"
107
107
  FINAL_STATUS_FILE="/tmp/ralph-loop-${1}.final"
108
108
  PHASES_FILE="/tmp/ralph-loop-${1}.phases"
109
109
  BASELINE_FILE="/tmp/ralph-loop-${1}.baseline"
110
+ SESSIONS_FILE="/tmp/ralph-loop-${1}.sessions"
111
+ LOG_FILE="/tmp/ralph-loop-${1}.log"
110
112
 
111
- # Initialize token tracking
113
+ # Initialize token tracking (4-field format: input|output|cache_create|cache_read)
112
114
  init_tokens() {
113
- echo "0|0" > "$TOKENS_FILE"
115
+ echo "0|0|0|0" > "$TOKENS_FILE"
116
+ > "$SESSIONS_FILE"
117
+ > "$LOG_FILE"
114
118
  }
115
119
 
116
- # Parse and accumulate tokens
117
- parse_and_accumulate_tokens() {
118
- local output_file="$1"
119
- local input_tokens=$({ grep -oE "input[^0-9]*([0-9,]+)" "$output_file" 2>/dev/null || true; } | { grep -oE "[0-9,]+" || true; } | tr -d ',' | tail -1)
120
- local output_tokens=$({ grep -oE "output[^0-9]*([0-9,]+)" "$output_file" 2>/dev/null || true; } | { grep -oE "[0-9,]+" || true; } | tr -d ',' | tail -1)
120
+ # Extract session result from JSON output.
121
+ # Writes human-readable result text to the .log file and captures session_id.
122
+ # Usage: extract_session_result <raw_json_file>
123
+ # Sets: LAST_SESSION_ID variable
124
+ extract_session_result() {
125
+ local raw_file="$1"
126
+ LAST_SESSION_ID=""
127
+ if [ ! -f "$raw_file" ]; then return; fi
128
+
129
+ local result
130
+ result=$(python3 -c "
131
+ import json, sys
132
+ try:
133
+ data = json.load(open(sys.argv[1]))
134
+ if not isinstance(data, list): data = [data]
135
+ for entry in reversed(data):
136
+ if isinstance(entry, dict) and entry.get('type') == 'result':
137
+ print(entry.get('session_id', ''))
138
+ break
139
+ except Exception:
140
+ pass
141
+ " "$raw_file" 2>/dev/null) || true
121
142
 
122
- input_tokens=${input_tokens:-0}
123
- output_tokens=${output_tokens:-0}
143
+ LAST_SESSION_ID="$result"
144
+
145
+ if [ -n "$LAST_SESSION_ID" ]; then
146
+ echo "$LAST_SESSION_ID" >> "$SESSIONS_FILE"
147
+ fi
124
148
 
149
+ # Extract human-readable result text and append to log
150
+ python3 -c "
151
+ import json, sys
152
+ try:
153
+ data = json.load(open(sys.argv[1]))
154
+ if not isinstance(data, list): data = [data]
155
+ for entry in data:
156
+ if isinstance(entry, dict) and entry.get('type') == 'result':
157
+ text = entry.get('result', '')
158
+ if text:
159
+ print(text)
160
+ except Exception:
161
+ pass
162
+ " "$raw_file" >> "$LOG_FILE" 2>/dev/null || true
163
+ }
164
+
165
+ # Accumulate tokens from a session JSONL file into the .tokens file.
166
+ # Usage: accumulate_tokens_from_session <session_id>
167
+ accumulate_tokens_from_session() {
168
+ local session_id="$1"
169
+ if [ -z "$session_id" ]; then return; fi
170
+
171
+ # Find the JSONL file for this session
172
+ local jsonl_file=""
173
+ for f in ~/.claude/projects/*/"${session_id}.jsonl"; do
174
+ if [ -f "$f" ]; then
175
+ jsonl_file="$f"
176
+ break
177
+ fi
178
+ done
179
+
180
+ if [ -z "$jsonl_file" ]; then
181
+ echo "WARNING: Could not find JSONL for session $session_id" >&2
182
+ return
183
+ fi
184
+
185
+ # Extract and sum token usage from all assistant messages
186
+ local session_tokens
187
+ session_tokens=$(python3 -c "
188
+ import json, sys
189
+ totals = {'input': 0, 'output': 0, 'cache_create': 0, 'cache_read': 0}
190
+ for line in open(sys.argv[1]):
191
+ try:
192
+ obj = json.loads(line)
193
+ if obj.get('type') != 'assistant':
194
+ continue
195
+ usage = obj.get('message', {}).get('usage', {})
196
+ if not usage:
197
+ continue
198
+ totals['input'] += usage.get('input_tokens', 0)
199
+ totals['output'] += usage.get('output_tokens', 0)
200
+ totals['cache_create'] += usage.get('cache_creation_input_tokens', 0)
201
+ totals['cache_read'] += usage.get('cache_read_input_tokens', 0)
202
+ except (json.JSONDecodeError, AttributeError):
203
+ continue
204
+ print(f\"{totals['input']}|{totals['output']}|{totals['cache_create']}|{totals['cache_read']}\")
205
+ " "$jsonl_file" 2>/dev/null) || true
206
+
207
+ if [ -z "$session_tokens" ]; then return; fi
208
+
209
+ # Parse session tokens
210
+ local s_input s_output s_cache_create s_cache_read
211
+ s_input=$(echo "$session_tokens" | cut -d'|' -f1)
212
+ s_output=$(echo "$session_tokens" | cut -d'|' -f2)
213
+ s_cache_create=$(echo "$session_tokens" | cut -d'|' -f3)
214
+ s_cache_read=$(echo "$session_tokens" | cut -d'|' -f4)
215
+
216
+ # Read current totals
217
+ local current c_input c_output c_cache_create c_cache_read
125
218
  if [ -f "$TOKENS_FILE" ]; then
126
- local current=$(cat "$TOKENS_FILE")
127
- local current_input=$(echo "$current" | cut -d'|' -f1)
128
- local current_output=$(echo "$current" | cut -d'|' -f2)
129
- [[ "$current_input" =~ ^[0-9]+$ ]] || current_input=0
130
- [[ "$current_output" =~ ^[0-9]+$ ]] || current_output=0
219
+ current=$(cat "$TOKENS_FILE")
220
+ c_input=$(echo "$current" | cut -d'|' -f1)
221
+ c_output=$(echo "$current" | cut -d'|' -f2)
222
+ c_cache_create=$(echo "$current" | cut -d'|' -f3)
223
+ c_cache_read=$(echo "$current" | cut -d'|' -f4)
224
+ [[ "$c_input" =~ ^[0-9]+$ ]] || c_input=0
225
+ [[ "$c_output" =~ ^[0-9]+$ ]] || c_output=0
226
+ [[ "$c_cache_create" =~ ^[0-9]+$ ]] || c_cache_create=0
227
+ [[ "$c_cache_read" =~ ^[0-9]+$ ]] || c_cache_read=0
131
228
  else
132
- current_input=0
133
- current_output=0
229
+ c_input=0; c_output=0; c_cache_create=0; c_cache_read=0
230
+ fi
231
+
232
+ # Accumulate
233
+ echo "$((c_input + s_input))|$((c_output + s_output))|$((c_cache_create + s_cache_create))|$((c_cache_read + s_cache_read))" > "$TOKENS_FILE"
234
+ }
235
+
236
+ # Action inbox: write request file if not already present
237
+ write_action_request() {
238
+ local action_file="/tmp/ralph-loop-${FEATURE}.action.json"
239
+ if [ -f "$action_file" ]; then
240
+ echo "WARNING: Action request file already exists, skipping write: $action_file" >&2
241
+ return 0
134
242
  fi
243
+ cat > "$action_file" << 'EOF'
244
+ {
245
+ "id": "post_pr_choice",
246
+ "prompt": "Loop complete. What would you like to do?",
247
+ "choices": [
248
+ {"id": "done", "label": "Done — end loop"},
249
+ {"id": "merge_local", "label": "Merge back to main locally"},
250
+ {"id": "keep_branch", "label": "Keep branch as-is"},
251
+ {"id": "discard", "label": "Discard this work"}
252
+ ],
253
+ "default": "done"
254
+ }
255
+ EOF
256
+ echo "Action request written: $action_file"
257
+ }
135
258
 
136
- local new_input=$((current_input + input_tokens))
137
- local new_output=$((current_output + output_tokens))
138
- echo "${new_input}|${new_output}" > "$TOKENS_FILE"
259
+ # Action inbox: poll for reply file, fallback to default after 15 minutes
260
+ poll_action_reply() {
261
+ local action_file="/tmp/ralph-loop-${FEATURE}.action.json"
262
+ local reply_file="/tmp/ralph-loop-${FEATURE}.action.reply.json"
263
+ local default_choice="keep_branch"
264
+ local timeout=900 # 15 minutes in seconds
265
+ local elapsed=0
266
+
267
+ # Read default from action file if present
268
+ if [ -f "$action_file" ]; then
269
+ local parsed_default
270
+ parsed_default=$(node -e "try { const d=require('fs').readFileSync(process.argv[1],'utf8'); console.log(JSON.parse(d).default||'keep_branch'); } catch(e) { console.log('keep_branch'); }" "$action_file" 2>/dev/null || echo "keep_branch")
271
+ if [ -n "$parsed_default" ]; then
272
+ default_choice="$parsed_default"
273
+ fi
274
+ fi
275
+
276
+ while [ $elapsed -lt $timeout ]; do
277
+ if [ -f "$reply_file" ]; then
278
+ local choice
279
+ choice=$(node -e "try { const d=require('fs').readFileSync(process.argv[1],'utf8'); console.log(JSON.parse(d).choice||''); } catch(e) { console.log(''); }" "$reply_file" 2>/dev/null || echo "")
280
+ if [ -n "$choice" ]; then
281
+ echo "User selected: $choice" >&2
282
+ # Cleanup both files
283
+ rm -f "$action_file" "$reply_file" 2>/dev/null || true
284
+ echo "$choice"
285
+ return 0
286
+ fi
287
+ fi
288
+ sleep 1
289
+ elapsed=$((elapsed + 1))
290
+ done
291
+
292
+ # Timeout: use default
293
+ echo "Action reply timeout after ${timeout}s, using default: $default_choice" >&2
294
+ rm -f "$action_file" "$reply_file" 2>/dev/null || true
295
+ echo "$default_choice"
139
296
  }
140
297
 
141
298
  # Initialize tokens
@@ -237,12 +394,13 @@ if [ ! -f "$PLAN_FILE" ]; then
237
394
  echo "======================== PLANNING PHASE ========================"
238
395
  write_phase_start "planning"
239
396
  export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
240
- cat "$PROMPTS_DIR/PROMPT_feature.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT" || {
397
+ cat "$PROMPTS_DIR/PROMPT_feature.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || {
241
398
  echo "ERROR: Planning phase failed"
242
399
  write_phase_end "planning" "failed"
243
400
  exit 1
244
401
  }
245
- parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
402
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
403
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
246
404
  write_phase_end "planning" "success"
247
405
  else
248
406
  echo "Plan file exists, skipping planning phase"
@@ -275,8 +433,9 @@ while true; do
275
433
 
276
434
  echo "Pending implementation tasks: $PENDING_IMPL"
277
435
  export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
278
- cat "$PROMPTS_DIR/PROMPT.md" | envsubst | $CLAUDE_CMD_IMPL 2>&1 | tee "$CLAUDE_OUTPUT" || true
279
- parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
436
+ cat "$PROMPTS_DIR/PROMPT.md" | envsubst | $CLAUDE_CMD_IMPL 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
437
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
438
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
280
439
 
281
440
  sleep 2
282
441
  done
@@ -300,8 +459,9 @@ else
300
459
  echo "------------------------ E2E Attempt $E2E_ATTEMPT of $MAX_E2E_ATTEMPTS ------------------------"
301
460
 
302
461
  export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
303
- cat "$PROMPTS_DIR/PROMPT_e2e.md" | envsubst | $CLAUDE_CMD_IMPL 2>&1 | tee "$CLAUDE_OUTPUT" || true
304
- parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
462
+ cat "$PROMPTS_DIR/PROMPT_e2e.md" | envsubst | $CLAUDE_CMD_IMPL 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
463
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
464
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
305
465
 
306
466
  # Check if all E2E tests passed
307
467
  E2E_FAILED=$({ grep "^- \[ \].*E2E:.*FAILED" "$PLAN_FILE" 2>/dev/null || true; } | wc -l | tr -d ' ')
@@ -315,8 +475,9 @@ else
315
475
 
316
476
  if [ $E2E_ATTEMPT -lt $MAX_E2E_ATTEMPTS ]; then
317
477
  echo "E2E tests have failures. Running fix iteration..."
318
- cat "$PROMPTS_DIR/PROMPT.md" | envsubst | $CLAUDE_CMD_IMPL 2>&1 | tee "$CLAUDE_OUTPUT" || true
319
- parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
478
+ cat "$PROMPTS_DIR/PROMPT.md" | envsubst | $CLAUDE_CMD_IMPL 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
479
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
480
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
320
481
  fi
321
482
  done
322
483
 
@@ -332,10 +493,11 @@ echo "======================== SPEC VERIFICATION PHASE ========================"
332
493
  write_phase_start "verification"
333
494
  export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
334
495
  VERIFY_STATUS="success"
335
- if ! cat "$PROMPTS_DIR/PROMPT_verify.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
496
+ if ! cat "$PROMPTS_DIR/PROMPT_verify.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "${CLAUDE_OUTPUT}.raw"; then
336
497
  VERIFY_STATUS="failed"
337
498
  fi
338
- parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
499
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
500
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
339
501
  write_phase_end "verification" "$VERIFY_STATUS"
340
502
 
341
503
  # Phase 7: PR and Review
@@ -343,18 +505,136 @@ echo "======================== PR & REVIEW PHASE ========================"
343
505
  write_phase_start "pr_review"
344
506
  export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
345
507
  PR_STATUS="success"
508
+ MAX_REVIEW_ATTEMPTS=3
509
+
510
+ # Check for review approval in stdout or latest PR comment.
511
+ # Returns 0 (true) if approved, 1 (false) otherwise.
512
+ check_review_approved() {
513
+ local output_file="$1"
514
+
515
+ # Primary: check stdout for explicit verdict line
516
+ if grep -qi "VERDICT:.*APPROVED" "$output_file" 2>/dev/null; then
517
+ # Make sure it's not "NOT APPROVED"
518
+ if ! grep -qi "VERDICT:.*NOT APPROVED" "$output_file" 2>/dev/null; then
519
+ return 0
520
+ fi
521
+ fi
522
+
523
+ # Fallback: check the latest PR comment for approval signal
524
+ local latest_comment
525
+ latest_comment=$(gh pr view "$BRANCH" --json comments --jq '.comments[-1].body' 2>/dev/null || echo "")
526
+ if echo "$latest_comment" | grep -qi "VERDICT:.*APPROVED\|Verdict:.*APPROVED" 2>/dev/null; then
527
+ if ! echo "$latest_comment" | grep -qi "NOT APPROVED" 2>/dev/null; then
528
+ return 0
529
+ fi
530
+ fi
531
+
532
+ return 1
533
+ }
534
+
346
535
  if [ "$REVIEW_MODE" = "manual" ]; then
347
- if ! cat "$PROMPTS_DIR/PROMPT_review_manual.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
536
+ if ! cat "$PROMPTS_DIR/PROMPT_review_manual.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "${CLAUDE_OUTPUT}.raw"; then
348
537
  PR_STATUS="failed"
349
538
  fi
350
- else
351
- if ! cat "$PROMPTS_DIR/PROMPT_review_auto.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
539
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
540
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
541
+
542
+ elif [ "$REVIEW_MODE" = "merge" ]; then
543
+ # Merge mode: create PR, iterate review+fixes until approved, then merge
544
+ REVIEW_ATTEMPT=0
545
+ REVIEW_APPROVED=false
546
+ while [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; do
547
+ REVIEW_ATTEMPT=$((REVIEW_ATTEMPT + 1))
548
+ echo "--- Review attempt $REVIEW_ATTEMPT of $MAX_REVIEW_ATTEMPTS ---"
549
+ cat "$PROMPTS_DIR/PROMPT_review_merge.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
550
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
551
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
552
+
553
+ # Check stdout and PR comment for approval
554
+ if check_review_approved "${CLAUDE_OUTPUT}.raw"; then
555
+ echo "Review approved!"
556
+ REVIEW_APPROVED=true
557
+ break
558
+ fi
559
+
560
+ if [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; then
561
+ echo "Review found issues. Running fix iteration..."
562
+ echo "Fix the issues found in the code review above. Run git diff main to see the current changes, then:
563
+ 1. Fix each issue referenced in the review
564
+ 2. Run tests: npm test
565
+ 3. Commit and push the fixes
566
+ Do NOT propose completion options or ask interactive questions. Just fix, test, commit, push." | $CLAUDE_CMD_IMPL 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
567
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
568
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
569
+ fi
570
+ done
571
+ if [ "$REVIEW_APPROVED" != true ]; then
572
+ echo "Review not approved after $MAX_REVIEW_ATTEMPTS attempts."
352
573
  PR_STATUS="failed"
353
574
  fi
575
+
576
+ else
577
+ # Auto mode: create PR, iterate review+fixes until approved (no merge)
578
+ REVIEW_ATTEMPT=0
579
+ REVIEW_APPROVED=false
580
+ while [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; do
581
+ REVIEW_ATTEMPT=$((REVIEW_ATTEMPT + 1))
582
+ echo "--- Review attempt $REVIEW_ATTEMPT of $MAX_REVIEW_ATTEMPTS ---"
583
+ cat "$PROMPTS_DIR/PROMPT_review_auto.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
584
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
585
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
586
+
587
+ # Check stdout and PR comment for approval
588
+ if check_review_approved "${CLAUDE_OUTPUT}.raw"; then
589
+ echo "Review approved!"
590
+ REVIEW_APPROVED=true
591
+ break
592
+ fi
593
+
594
+ if [ $REVIEW_ATTEMPT -lt $MAX_REVIEW_ATTEMPTS ]; then
595
+ echo "Review found issues. Running fix iteration..."
596
+ echo "Fix the issues found in the code review above. Run git diff main to see the current changes, then:
597
+ 1. Fix each issue referenced in the review
598
+ 2. Run tests: npm test
599
+ 3. Commit and push the fixes
600
+ Do NOT propose completion options or ask interactive questions. Just fix, test, commit, push." | $CLAUDE_CMD_IMPL 2>&1 | tee "${CLAUDE_OUTPUT}.raw" || true
601
+ extract_session_result "${CLAUDE_OUTPUT}.raw"
602
+ accumulate_tokens_from_session "$LAST_SESSION_ID"
603
+ fi
604
+ done
605
+ if [ "$REVIEW_APPROVED" != true ]; then
606
+ echo "Review not approved after $MAX_REVIEW_ATTEMPTS attempts. PR ready for manual review."
607
+ fi
354
608
  fi
355
- parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
356
609
  write_phase_end "pr_review" "$PR_STATUS"
357
610
 
611
+ # Phase 7.5: Post-completion action request
612
+ echo "======================== ACTION REQUEST PHASE ========================"
613
+ write_action_request
614
+ CHOSEN_ACTION=$(poll_action_reply)
615
+ echo "User chose: $CHOSEN_ACTION"
616
+
617
+ # Dispatch based on user choice
618
+ case "$CHOSEN_ACTION" in
619
+ done)
620
+ echo "Loop complete. Exiting."
621
+ ;;
622
+ merge_local)
623
+ echo "Merging back to main locally..."
624
+ git checkout main 2>/dev/null || git checkout master
625
+ git merge --squash "$BRANCH" && git commit -m "feat($FEATURE): squash merge from $BRANCH"
626
+ echo "Merged. You can delete the branch with: git branch -D $BRANCH"
627
+ ;;
628
+ discard)
629
+ echo "Discarding work on branch $BRANCH..."
630
+ git checkout main 2>/dev/null || git checkout master
631
+ git branch -D "$BRANCH" 2>/dev/null || echo "Branch $BRANCH not found locally."
632
+ ;;
633
+ keep_branch|*)
634
+ echo "Keeping branch $BRANCH as-is."
635
+ ;;
636
+ esac
637
+
358
638
  # Persist final status for TUI summaries
359
639
  if ! echo "$ITERATION|$MAX_ITERATIONS|$(date +%s)|done" > "$FINAL_STATUS_FILE"; then
360
640
  echo "WARNING: Failed to write final status file: $FINAL_STATUS_FILE" >&2
@@ -363,16 +643,30 @@ fi
363
643
  # Cleanup temp files
364
644
  rm -f "$STATUS_FILE" 2>/dev/null || true
365
645
  rm -f "/tmp/ralph-loop-${FEATURE}.output" 2>/dev/null || true
646
+ rm -f "/tmp/ralph-loop-${FEATURE}.output.raw" 2>/dev/null || true
366
647
 
367
648
  # Print final token usage
368
649
  if [ -f "/tmp/ralph-loop-${FEATURE}.tokens" ]; then
369
650
  echo ""
370
651
  echo "=========================================="
371
652
  echo "Final Token Usage:"
372
- cat "/tmp/ralph-loop-${FEATURE}.tokens" | awk -F'|' '{printf " Input: %s tokens\n Output: %s tokens\n Total: %s tokens\n", $1, $2, $1+$2}'
653
+ awk -F'|' '{
654
+ printf " Input: %d tokens\n", $1
655
+ printf " Output: %d tokens\n", $2
656
+ printf " Cache create: %d tokens\n", $3
657
+ printf " Cache read: %d tokens\n", $4
658
+ printf " Total: %d tokens\n", $1+$2+$3+$4
659
+ }' "/tmp/ralph-loop-${FEATURE}.tokens"
373
660
  echo "=========================================="
374
661
  fi
375
662
 
663
+ # Print session IDs
664
+ if [ -f "/tmp/ralph-loop-${FEATURE}.sessions" ]; then
665
+ SESSION_COUNT=$(wc -l < "/tmp/ralph-loop-${FEATURE}.sessions" | tr -d ' ')
666
+ echo "Sessions: $SESSION_COUNT"
667
+ cat "/tmp/ralph-loop-${FEATURE}.sessions"
668
+ fi
669
+
376
670
  echo "=========================================="
377
671
  echo "Ralph loop completed: $FEATURE"
378
672
  echo "=========================================="