bmalph 2.7.6 → 2.7.7

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.
@@ -396,6 +396,68 @@ trim_shell_whitespace() {
396
396
  printf '%s' "$value"
397
397
  }
398
398
 
399
+ extract_ralph_status_block_json() {
400
+ local text=$1
401
+ local normalized="${text//$'\r'/}"
402
+
403
+ if [[ "$normalized" != *"---RALPH_STATUS---"* ]]; then
404
+ return 1
405
+ fi
406
+
407
+ local block="${normalized#*---RALPH_STATUS---}"
408
+ if [[ "$block" == "$normalized" ]]; then
409
+ return 1
410
+ fi
411
+
412
+ if [[ "$block" == *"---END_RALPH_STATUS---"* ]]; then
413
+ block="${block%%---END_RALPH_STATUS---*}"
414
+ fi
415
+
416
+ local status=""
417
+ local exit_signal="false"
418
+ local exit_signal_found="false"
419
+ local tasks_completed_this_loop=0
420
+ local line=""
421
+ local trimmed=""
422
+ local value=""
423
+
424
+ while IFS= read -r line; do
425
+ trimmed=$(trim_shell_whitespace "$line")
426
+
427
+ case "$trimmed" in
428
+ STATUS:*)
429
+ value=$(trim_shell_whitespace "${trimmed#STATUS:}")
430
+ [[ -n "$value" ]] && status="$value"
431
+ ;;
432
+ EXIT_SIGNAL:*)
433
+ value=$(trim_shell_whitespace "${trimmed#EXIT_SIGNAL:}")
434
+ if [[ "$value" == "true" || "$value" == "false" ]]; then
435
+ exit_signal="$value"
436
+ exit_signal_found="true"
437
+ fi
438
+ ;;
439
+ TASKS_COMPLETED_THIS_LOOP:*)
440
+ value=$(trim_shell_whitespace "${trimmed#TASKS_COMPLETED_THIS_LOOP:}")
441
+ if [[ "$value" =~ ^-?[0-9]+$ ]]; then
442
+ tasks_completed_this_loop=$value
443
+ fi
444
+ ;;
445
+ esac
446
+ done <<< "$block"
447
+
448
+ jq -n \
449
+ --arg status "$status" \
450
+ --argjson exit_signal_found "$exit_signal_found" \
451
+ --argjson exit_signal "$exit_signal" \
452
+ --argjson tasks_completed_this_loop "$tasks_completed_this_loop" \
453
+ '{
454
+ status: $status,
455
+ exit_signal_found: $exit_signal_found,
456
+ exit_signal: $exit_signal,
457
+ tasks_completed_this_loop: $tasks_completed_this_loop
458
+ }'
459
+ }
460
+
399
461
  # Parse JSON response and extract structured fields
400
462
  # Creates .ralph/.json_parse_result with normalized analysis data
401
463
  # Supports FIVE JSON formats:
@@ -466,35 +528,38 @@ parse_json_response() {
466
528
  # Track whether EXIT_SIGNAL was explicitly provided (vs inferred from STATUS)
467
529
  local exit_signal=$(jq -r -j '.exit_signal // false' "$output_file" 2>/dev/null)
468
530
  local explicit_exit_signal_found=$(jq -r -j 'has("exit_signal")' "$output_file" 2>/dev/null)
531
+ local tasks_completed_this_loop=$(jq -r -j '.tasks_completed_this_loop // 0' "$output_file" 2>/dev/null)
532
+ if [[ ! "$tasks_completed_this_loop" =~ ^-?[0-9]+$ ]]; then
533
+ tasks_completed_this_loop=0
534
+ fi
469
535
 
470
- # Bug #1 Fix: If exit_signal is still false, check for RALPH_STATUS block in .result field
471
- # Claude CLI JSON format embeds the RALPH_STATUS block within the .result text field
472
- if [[ "$exit_signal" == "false" && "$has_result_field" == "true" ]]; then
473
- local result_text=$(jq -r -j '.result // ""' "$output_file" 2>/dev/null)
474
- if [[ -n "$result_text" ]] && echo "$result_text" | grep -q -- "---RALPH_STATUS---"; then
475
- # Extract EXIT_SIGNAL value from RALPH_STATUS block within result text
476
- local embedded_exit_sig
477
- embedded_exit_sig=$(trim_shell_whitespace "$(printf '%s\n' "$result_text" | grep "EXIT_SIGNAL:" | cut -d: -f2)")
478
- if [[ -n "$embedded_exit_sig" ]]; then
479
- # Explicit EXIT_SIGNAL found in RALPH_STATUS block
480
- explicit_exit_signal_found="true"
481
- if [[ "$embedded_exit_sig" == "true" ]]; then
482
- exit_signal="true"
483
- [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Extracted EXIT_SIGNAL=true from .result RALPH_STATUS block" >&2
484
- else
485
- exit_signal="false"
486
- [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Extracted EXIT_SIGNAL=false from .result RALPH_STATUS block (respecting explicit intent)" >&2
487
- fi
488
- fi
489
- # Also check STATUS field as fallback ONLY when EXIT_SIGNAL was not specified
490
- # This respects explicit EXIT_SIGNAL: false which means "task complete, continue working"
491
- local embedded_status
492
- embedded_status=$(trim_shell_whitespace "$(printf '%s\n' "$result_text" | grep "STATUS:" | cut -d: -f2)")
493
- if [[ "$embedded_status" == "COMPLETE" && "$explicit_exit_signal_found" != "true" ]]; then
494
- # STATUS: COMPLETE without any EXIT_SIGNAL field implies completion
495
- exit_signal="true"
496
- [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Inferred EXIT_SIGNAL=true from .result STATUS=COMPLETE (no explicit EXIT_SIGNAL found)" >&2
497
- fi
536
+ local result_text=""
537
+ if [[ "$has_result_field" == "true" ]]; then
538
+ result_text=$(jq -r -j '.result // ""' "$output_file" 2>/dev/null)
539
+ fi
540
+
541
+ local ralph_status_json=""
542
+ if [[ -n "$result_text" ]] && ralph_status_json=$(extract_ralph_status_block_json "$result_text" 2>/dev/null); then
543
+ local embedded_exit_signal_found
544
+ embedded_exit_signal_found=$(printf '%s' "$ralph_status_json" | jq -r -j '.exit_signal_found' 2>/dev/null)
545
+ local embedded_exit_sig
546
+ embedded_exit_sig=$(printf '%s' "$ralph_status_json" | jq -r -j '.exit_signal' 2>/dev/null)
547
+ local embedded_status
548
+ embedded_status=$(printf '%s' "$ralph_status_json" | jq -r -j '.status' 2>/dev/null)
549
+ local embedded_tasks_completed
550
+ embedded_tasks_completed=$(printf '%s' "$ralph_status_json" | jq -r -j '.tasks_completed_this_loop' 2>/dev/null)
551
+
552
+ if [[ "$embedded_tasks_completed" =~ ^-?[0-9]+$ ]]; then
553
+ tasks_completed_this_loop=$embedded_tasks_completed
554
+ fi
555
+
556
+ if [[ "$embedded_exit_signal_found" == "true" ]]; then
557
+ explicit_exit_signal_found="true"
558
+ exit_signal="$embedded_exit_sig"
559
+ [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Extracted EXIT_SIGNAL=$embedded_exit_sig from .result RALPH_STATUS block" >&2
560
+ elif [[ "$embedded_status" == "COMPLETE" && "$explicit_exit_signal_found" != "true" ]]; then
561
+ exit_signal="true"
562
+ [[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Inferred EXIT_SIGNAL=true from .result STATUS=COMPLETE (no explicit EXIT_SIGNAL found)" >&2
498
563
  fi
499
564
  fi
500
565
 
@@ -640,6 +705,7 @@ parse_json_response() {
640
705
  --argjson loop_number "$loop_number" \
641
706
  --arg session_id "$session_id" \
642
707
  --argjson confidence "$confidence" \
708
+ --argjson tasks_completed_this_loop "$tasks_completed_this_loop" \
643
709
  --argjson has_permission_denials "$has_permission_denials" \
644
710
  --argjson permission_denial_count "$permission_denial_count" \
645
711
  --argjson denied_commands "$denied_commands_json" \
@@ -655,6 +721,7 @@ parse_json_response() {
655
721
  loop_number: $loop_number,
656
722
  session_id: $session_id,
657
723
  confidence: $confidence,
724
+ tasks_completed_this_loop: $tasks_completed_this_loop,
658
725
  has_permission_denials: $has_permission_denials,
659
726
  permission_denial_count: $permission_denial_count,
660
727
  denied_commands: $denied_commands,
@@ -687,6 +754,7 @@ analyze_response() {
687
754
  local exit_signal=false
688
755
  local work_summary=""
689
756
  local files_modified=0
757
+ local tasks_completed_this_loop=0
690
758
 
691
759
  # Read output file
692
760
  if [[ ! -f "$output_file" ]]; then
@@ -712,6 +780,7 @@ analyze_response() {
712
780
  is_stuck=$(jq -r -j '.is_stuck' "$json_parse_result_file" 2>/dev/null || echo "false")
713
781
  work_summary=$(jq -r -j '.summary' "$json_parse_result_file" 2>/dev/null || echo "")
714
782
  files_modified=$(jq -r -j '.files_modified' "$json_parse_result_file" 2>/dev/null || echo "0")
783
+ tasks_completed_this_loop=$(jq -r -j '.tasks_completed_this_loop // 0' "$json_parse_result_file" 2>/dev/null || echo "0")
715
784
  local json_confidence=$(jq -r -j '.confidence' "$json_parse_result_file" 2>/dev/null || echo "0")
716
785
  local session_id=$(jq -r -j '.session_id' "$json_parse_result_file" 2>/dev/null || echo "")
717
786
 
@@ -733,6 +802,10 @@ analyze_response() {
733
802
  confidence_score=$((json_confidence + 50))
734
803
  fi
735
804
 
805
+ if [[ ! "$tasks_completed_this_loop" =~ ^-?[0-9]+$ ]]; then
806
+ tasks_completed_this_loop=0
807
+ fi
808
+
736
809
  # Check for file changes via git (supplements JSON data)
737
810
  # Fix #141: Detect both uncommitted changes AND committed changes
738
811
  if command -v git &>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
@@ -784,6 +857,7 @@ analyze_response() {
784
857
  --argjson files_modified "$files_modified" \
785
858
  --argjson confidence_score "$confidence_score" \
786
859
  --argjson exit_signal "$exit_signal" \
860
+ --argjson tasks_completed_this_loop "$tasks_completed_this_loop" \
787
861
  --arg work_summary "$work_summary" \
788
862
  --argjson output_length "$output_length" \
789
863
  --argjson has_permission_denials "$has_permission_denials" \
@@ -802,6 +876,9 @@ analyze_response() {
802
876
  files_modified: $files_modified,
803
877
  confidence_score: $confidence_score,
804
878
  exit_signal: $exit_signal,
879
+ tasks_completed_this_loop: $tasks_completed_this_loop,
880
+ fix_plan_completed_delta: 0,
881
+ has_progress_tracking_mismatch: false,
805
882
  work_summary: $work_summary,
806
883
  output_length: $output_length,
807
884
  has_permission_denials: $has_permission_denials,
@@ -823,13 +900,23 @@ analyze_response() {
823
900
  local explicit_exit_signal_found=false
824
901
 
825
902
  # 1. Check for explicit structured output (if Claude follows schema)
826
- if grep -q -- "---RALPH_STATUS---" "$output_file"; then
827
- # Parse structured output
828
- local status=$(trim_shell_whitespace "$(grep "STATUS:" "$output_file" | cut -d: -f2)")
829
- local exit_sig=$(trim_shell_whitespace "$(grep "EXIT_SIGNAL:" "$output_file" | cut -d: -f2)")
903
+ local ralph_status_json=""
904
+ if ralph_status_json=$(extract_ralph_status_block_json "$output_content" 2>/dev/null); then
905
+ local status
906
+ status=$(printf '%s' "$ralph_status_json" | jq -r -j '.status' 2>/dev/null)
907
+ local exit_sig_found
908
+ exit_sig_found=$(printf '%s' "$ralph_status_json" | jq -r -j '.exit_signal_found' 2>/dev/null)
909
+ local exit_sig
910
+ exit_sig=$(printf '%s' "$ralph_status_json" | jq -r -j '.exit_signal' 2>/dev/null)
911
+ local parsed_tasks_completed
912
+ parsed_tasks_completed=$(printf '%s' "$ralph_status_json" | jq -r -j '.tasks_completed_this_loop' 2>/dev/null)
913
+
914
+ if [[ "$parsed_tasks_completed" =~ ^-?[0-9]+$ ]]; then
915
+ tasks_completed_this_loop=$parsed_tasks_completed
916
+ fi
830
917
 
831
918
  # If EXIT_SIGNAL is explicitly provided, respect it
832
- if [[ -n "$exit_sig" ]]; then
919
+ if [[ "$exit_sig_found" == "true" ]]; then
833
920
  explicit_exit_signal_found=true
834
921
  if [[ "$exit_sig" == "true" ]]; then
835
922
  has_completion_signal=true
@@ -1002,6 +1089,7 @@ analyze_response() {
1002
1089
  --argjson files_modified "$files_modified" \
1003
1090
  --argjson confidence_score "$confidence_score" \
1004
1091
  --argjson exit_signal "$exit_signal" \
1092
+ --argjson tasks_completed_this_loop "$tasks_completed_this_loop" \
1005
1093
  --arg work_summary "$work_summary" \
1006
1094
  --argjson output_length "$output_length" \
1007
1095
  --argjson has_permission_denials "$has_permission_denials" \
@@ -1020,6 +1108,9 @@ analyze_response() {
1020
1108
  files_modified: $files_modified,
1021
1109
  confidence_score: $confidence_score,
1022
1110
  exit_signal: $exit_signal,
1111
+ tasks_completed_this_loop: $tasks_completed_this_loop,
1112
+ fix_plan_completed_delta: 0,
1113
+ has_progress_tracking_mismatch: false,
1023
1114
  work_summary: $work_summary,
1024
1115
  output_length: $output_length,
1025
1116
  has_permission_denials: $has_permission_denials,
@@ -1049,6 +1140,7 @@ update_exit_signals() {
1049
1140
  local loop_number=$(jq -r -j '.loop_number' "$analysis_file")
1050
1141
  local has_progress=$(jq -r -j '.analysis.has_progress' "$analysis_file")
1051
1142
  local has_permission_denials=$(jq -r -j '.analysis.has_permission_denials // false' "$analysis_file")
1143
+ local has_progress_tracking_mismatch=$(jq -r -j '.analysis.has_progress_tracking_mismatch // false' "$analysis_file")
1052
1144
 
1053
1145
  # Read current exit signals
1054
1146
  local signals=$(cat "$exit_signals_file" 2>/dev/null || echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}')
@@ -1065,7 +1157,7 @@ update_exit_signals() {
1065
1157
 
1066
1158
  # Permission denials are handled in the same loop, so they must not become
1067
1159
  # completion state that can halt the next loop.
1068
- if [[ "$has_permission_denials" != "true" && "$has_completion_signal" == "true" ]]; then
1160
+ if [[ "$has_permission_denials" != "true" && "$has_progress_tracking_mismatch" != "true" && "$has_completion_signal" == "true" ]]; then
1069
1161
  signals=$(echo "$signals" | jq ".done_signals += [$loop_number]")
1070
1162
  fi
1071
1163
 
@@ -1074,7 +1166,7 @@ update_exit_signals() {
1074
1166
  # due to deterministic scoring (+50 for JSON format, +20 for result field).
1075
1167
  # This caused premature exits after 5 loops. Now we respect Claude's explicit intent.
1076
1168
  local exit_signal=$(jq -r -j '.analysis.exit_signal // false' "$analysis_file")
1077
- if [[ "$has_permission_denials" != "true" && "$exit_signal" == "true" ]]; then
1169
+ if [[ "$has_permission_denials" != "true" && "$has_progress_tracking_mismatch" != "true" && "$exit_signal" == "true" ]]; then
1078
1170
  signals=$(echo "$signals" | jq ".completion_indicators += [$loop_number]")
1079
1171
  fi
1080
1172
 
@@ -353,9 +353,17 @@ You are Ralph, an autonomous AI development agent working on a [PROJECT NAME] pr
353
353
  - Search the codebase before assuming something isn't implemented
354
354
  - Use subagents for expensive operations (file searching, analysis)
355
355
  - Write comprehensive tests with clear documentation
356
- - Update @fix_plan.md with your learnings
356
+ - Toggle completed story checkboxes in @fix_plan.md without rewriting story lines
357
357
  - Commit working changes with descriptive messages
358
358
 
359
+ ## Progress Tracking (CRITICAL)
360
+ - Ralph tracks progress by counting story checkboxes in @fix_plan.md
361
+ - When you complete a story, change `- [ ]` to `- [x]` on that exact story line
362
+ - Do NOT remove, rewrite, or reorder story lines in @fix_plan.md
363
+ - Update the checkbox before committing so the monitor updates immediately
364
+ - Set `TASKS_COMPLETED_THIS_LOOP` to the exact number of story checkboxes toggled this loop
365
+ - Only valid values: 0 or 1
366
+
359
367
  ## 🧪 Testing Guidelines (CRITICAL)
360
368
  - LIMIT testing to ~20% of your total effort per loop
361
369
  - PRIORITIZE: Implementation > Documentation > Tests
@@ -73,13 +73,13 @@ _env_CB_AUTO_RESET="${CB_AUTO_RESET:-}"
73
73
  MAX_CALLS_PER_HOUR="${MAX_CALLS_PER_HOUR:-100}"
74
74
  VERBOSE_PROGRESS="${VERBOSE_PROGRESS:-false}"
75
75
  CLAUDE_TIMEOUT_MINUTES="${CLAUDE_TIMEOUT_MINUTES:-15}"
76
- DEFAULT_CLAUDE_ALLOWED_TOOLS="Write,Read,Edit,MultiEdit,Glob,Grep,Task,TodoWrite,WebFetch,WebSearch,NotebookEdit,Bash"
76
+ DEFAULT_CLAUDE_ALLOWED_TOOLS="Write,Read,Edit,MultiEdit,Glob,Grep,Task,TodoWrite,WebFetch,WebSearch,EnterPlanMode,ExitPlanMode,NotebookEdit,Bash"
77
77
  DEFAULT_PERMISSION_DENIAL_MODE="continue"
78
78
 
79
79
  # Modern Claude CLI configuration (Phase 1.1)
80
80
  CLAUDE_OUTPUT_FORMAT="${CLAUDE_OUTPUT_FORMAT:-json}"
81
81
  CLAUDE_ALLOWED_TOOLS="${CLAUDE_ALLOWED_TOOLS:-$DEFAULT_CLAUDE_ALLOWED_TOOLS}"
82
- CLAUDE_PERMISSION_MODE="${CLAUDE_PERMISSION_MODE:-auto}"
82
+ CLAUDE_PERMISSION_MODE="${CLAUDE_PERMISSION_MODE:-bypassPermissions}"
83
83
  CLAUDE_USE_CONTINUE="${CLAUDE_USE_CONTINUE:-true}"
84
84
  PERMISSION_DENIAL_MODE="${PERMISSION_DENIAL_MODE:-$DEFAULT_PERMISSION_DENIAL_MODE}"
85
85
  CLAUDE_SESSION_FILE="$RALPH_DIR/.claude_session_id" # Session ID persistence file
@@ -108,6 +108,8 @@ VALID_TOOL_PATTERNS=(
108
108
  "WebFetch"
109
109
  "WebSearch"
110
110
  "AskUserQuestion"
111
+ "EnterPlanMode"
112
+ "ExitPlanMode"
111
113
  "Bash"
112
114
  "Bash(git *)"
113
115
  "Bash(npm *)"
@@ -249,7 +251,7 @@ driver_supports_tool_allowlist() {
249
251
  driver_permission_denial_help() {
250
252
  echo " - Review the active driver's permission or approval settings."
251
253
  echo " - ALLOWED_TOOLS in $RALPHRC_FILE only applies to the Claude Code driver."
252
- echo " - Keep CLAUDE_PERMISSION_MODE=auto for unattended Claude Code loops."
254
+ echo " - Keep CLAUDE_PERMISSION_MODE=bypassPermissions for unattended Claude Code loops."
253
255
  echo " - After updating permissions, reset the session and restart the loop."
254
256
  }
255
257
 
@@ -530,7 +532,7 @@ validate_permission_denial_mode() {
530
532
 
531
533
  normalize_claude_permission_mode() {
532
534
  if [[ -z "${CLAUDE_PERMISSION_MODE:-}" ]]; then
533
- CLAUDE_PERMISSION_MODE="auto"
535
+ CLAUDE_PERMISSION_MODE="bypassPermissions"
534
536
  fi
535
537
  }
536
538
 
@@ -726,6 +728,74 @@ wait_for_reset() {
726
728
  log_status "SUCCESS" "Rate limit reset! Ready for new calls."
727
729
  }
728
730
 
731
+ count_fix_plan_checkboxes() {
732
+ local fix_plan_file="${1:-$RALPH_DIR/@fix_plan.md}"
733
+ local completed_items=0
734
+ local uncompleted_items=0
735
+ local total_items=0
736
+
737
+ if [[ -f "$fix_plan_file" ]]; then
738
+ uncompleted_items=$(grep -cE "^[[:space:]]*- \[ \]" "$fix_plan_file" 2>/dev/null || true)
739
+ [[ -z "$uncompleted_items" ]] && uncompleted_items=0
740
+ completed_items=$(grep -cE "^[[:space:]]*- \[[xX]\]" "$fix_plan_file" 2>/dev/null || true)
741
+ [[ -z "$completed_items" ]] && completed_items=0
742
+ fi
743
+
744
+ total_items=$((completed_items + uncompleted_items))
745
+ printf '%s %s %s\n' "$completed_items" "$uncompleted_items" "$total_items"
746
+ }
747
+
748
+ enforce_fix_plan_progress_tracking() {
749
+ local analysis_file=$1
750
+ local completed_before=$2
751
+ local completed_after=$3
752
+
753
+ if [[ ! -f "$analysis_file" ]]; then
754
+ return 0
755
+ fi
756
+
757
+ local claimed_tasks
758
+ claimed_tasks=$(jq -r '.analysis.tasks_completed_this_loop // 0' "$analysis_file" 2>/dev/null || echo "0")
759
+ if [[ ! "$claimed_tasks" =~ ^-?[0-9]+$ ]]; then
760
+ claimed_tasks=0
761
+ fi
762
+
763
+ local fix_plan_completed_delta=$((completed_after - completed_before))
764
+ local has_progress_tracking_mismatch=false
765
+ if [[ $claimed_tasks -ne $fix_plan_completed_delta || $claimed_tasks -gt 1 || $fix_plan_completed_delta -gt 1 || $fix_plan_completed_delta -lt 0 ]]; then
766
+ has_progress_tracking_mismatch=true
767
+ fi
768
+
769
+ local tmp_file="$analysis_file.tmp"
770
+ if jq \
771
+ --argjson claimed_tasks "$claimed_tasks" \
772
+ --argjson fix_plan_completed_delta "$fix_plan_completed_delta" \
773
+ --argjson has_progress_tracking_mismatch "$has_progress_tracking_mismatch" \
774
+ '
775
+ (.analysis //= {}) |
776
+ .analysis.tasks_completed_this_loop = $claimed_tasks |
777
+ .analysis.fix_plan_completed_delta = $fix_plan_completed_delta |
778
+ .analysis.has_progress_tracking_mismatch = $has_progress_tracking_mismatch |
779
+ if $has_progress_tracking_mismatch then
780
+ .analysis.has_completion_signal = false |
781
+ .analysis.exit_signal = false
782
+ else
783
+ .
784
+ end
785
+ ' "$analysis_file" > "$tmp_file" 2>/dev/null; then
786
+ mv "$tmp_file" "$analysis_file"
787
+ else
788
+ rm -f "$tmp_file" 2>/dev/null
789
+ return 0
790
+ fi
791
+
792
+ if [[ "$has_progress_tracking_mismatch" == "true" ]]; then
793
+ log_status "WARN" "Progress tracking mismatch: claimed $claimed_tasks completed task(s) but checkbox delta was $fix_plan_completed_delta. Completion signals suppressed for this loop."
794
+ fi
795
+
796
+ return 0
797
+ }
798
+
729
799
  # Check if we should gracefully exit
730
800
  should_exit_gracefully() {
731
801
 
@@ -792,11 +862,10 @@ should_exit_gracefully() {
792
862
  # Fix #144: Only match valid markdown checkboxes, not date entries like [2026-01-29]
793
863
  # Valid patterns: "- [ ]" (uncompleted) and "- [x]" or "- [X]" (completed)
794
864
  if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
795
- local uncompleted_items=$(grep -cE "^[[:space:]]*- \[ \]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || true)
796
- [[ -z "$uncompleted_items" ]] && uncompleted_items=0
797
- local completed_items=$(grep -cE "^[[:space:]]*- \[[xX]\]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || true)
798
- [[ -z "$completed_items" ]] && completed_items=0
799
- local total_items=$((uncompleted_items + completed_items))
865
+ local completed_items=0
866
+ local uncompleted_items=0
867
+ local total_items=0
868
+ read -r completed_items uncompleted_items total_items < <(count_fix_plan_checkboxes "$RALPH_DIR/@fix_plan.md")
800
869
 
801
870
  if [[ $total_items -gt 0 ]] && [[ $completed_items -eq $total_items ]]; then
802
871
  log_status "WARN" "Exit condition: All @fix_plan.md items completed ($completed_items/$total_items)" >&2
@@ -901,8 +970,10 @@ build_loop_context() {
901
970
  # Extract incomplete tasks from @fix_plan.md
902
971
  # Bug #3 Fix: Support indented markdown checkboxes with [[:space:]]* pattern
903
972
  if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
904
- local incomplete_tasks=$(grep -cE "^[[:space:]]*- \[ \]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || true)
905
- [[ -z "$incomplete_tasks" ]] && incomplete_tasks=0
973
+ local completed_tasks=0
974
+ local incomplete_tasks=0
975
+ local total_tasks=0
976
+ read -r completed_tasks incomplete_tasks total_tasks < <(count_fix_plan_checkboxes "$RALPH_DIR/@fix_plan.md")
906
977
  context+="Remaining tasks: ${incomplete_tasks}. "
907
978
  fi
908
979
 
@@ -1410,6 +1481,8 @@ execute_claude_code() {
1410
1481
  local loop_count=$1
1411
1482
  local calls_made=$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")
1412
1483
  calls_made=$((calls_made + 1))
1484
+ local fix_plan_completed_before=0
1485
+ read -r fix_plan_completed_before _ _ < <(count_fix_plan_checkboxes "$RALPH_DIR/@fix_plan.md")
1413
1486
 
1414
1487
  # Fix #141: Capture git HEAD SHA at loop start to detect commits as progress
1415
1488
  # Store in file for access by progress detection after Claude execution
@@ -1664,6 +1737,10 @@ EOF
1664
1737
  analyze_response "$output_file" "$loop_count"
1665
1738
  local analysis_exit_code=$?
1666
1739
 
1740
+ local fix_plan_completed_after=0
1741
+ read -r fix_plan_completed_after _ _ < <(count_fix_plan_checkboxes "$RALPH_DIR/@fix_plan.md")
1742
+ enforce_fix_plan_progress_tracking "$RESPONSE_ANALYSIS_FILE" "$fix_plan_completed_before" "$fix_plan_completed_after"
1743
+
1667
1744
  # Update exit signals based on analysis
1668
1745
  update_exit_signals
1669
1746
 
@@ -9,16 +9,24 @@ You are Ralph, an autonomous AI development agent working on a [YOUR PROJECT NAM
9
9
  3. Implement the highest priority item using best practices
10
10
  4. Use parallel subagents for complex tasks (max 100 concurrent)
11
11
  5. Run tests after each implementation
12
- 6. Update documentation and @fix_plan.md
12
+ 6. Update documentation and the completed story checkbox in @fix_plan.md
13
13
 
14
14
  ## Key Principles
15
15
  - ONE task per loop - focus on the most important thing
16
16
  - Search the codebase before assuming something isn't implemented
17
17
  - Use subagents for expensive operations (file searching, analysis)
18
18
  - Write comprehensive tests with clear documentation
19
- - Update .ralph/@fix_plan.md with your learnings
19
+ - Toggle completed story checkboxes in .ralph/@fix_plan.md without rewriting story lines
20
20
  - Commit working changes with descriptive messages
21
21
 
22
+ ## Progress Tracking (CRITICAL)
23
+ - Ralph tracks progress by counting story checkboxes in .ralph/@fix_plan.md
24
+ - When you complete a story, change `- [ ]` to `- [x]` on that exact story line
25
+ - Do NOT remove, rewrite, or reorder story lines in .ralph/@fix_plan.md
26
+ - Update the checkbox before committing so the monitor updates immediately
27
+ - Set `TASKS_COMPLETED_THIS_LOOP` to the exact number of story checkboxes toggled this loop
28
+ - Only valid values: 0 or 1
29
+
22
30
  ## 🧪 Testing Guidelines (CRITICAL)
23
31
  - LIMIT testing to ~20% of your total effort per loop
24
32
  - PRIORITIZE: Implementation > Documentation > Tests
@@ -34,6 +42,8 @@ You are Ralph, an autonomous AI development agent working on a [YOUR PROJECT NAM
34
42
  - Keep .ralph/@AGENT.md updated with build/run instructions
35
43
  - Document the WHY behind tests and implementations
36
44
  - No placeholder implementations - build it properly
45
+
46
+ ## Autonomous Mode (CRITICAL)
37
47
  - do not ask the user questions during loop execution
38
48
  - do not use AskUserQuestion, EnterPlanMode, or ExitPlanMode during loop execution
39
49
  - make the safest reasonable assumption and continue
@@ -47,7 +57,7 @@ You are Ralph, an autonomous AI development agent working on a [YOUR PROJECT NAM
47
57
  ```
48
58
  ---RALPH_STATUS---
49
59
  STATUS: IN_PROGRESS | COMPLETE | BLOCKED
50
- TASKS_COMPLETED_THIS_LOOP: <number>
60
+ TASKS_COMPLETED_THIS_LOOP: 0 | 1
51
61
  FILES_MODIFIED: <number>
52
62
  TESTS_STATUS: PASSING | FAILING | NOT_RUN
53
63
  WORK_TYPE: IMPLEMENTATION | TESTING | DOCUMENTATION | REFACTORING
@@ -71,7 +81,7 @@ Set EXIT_SIGNAL to **true** when ALL of these conditions are met:
71
81
  ```
72
82
  ---RALPH_STATUS---
73
83
  STATUS: IN_PROGRESS
74
- TASKS_COMPLETED_THIS_LOOP: 2
84
+ TASKS_COMPLETED_THIS_LOOP: 1
75
85
  FILES_MODIFIED: 5
76
86
  TESTS_STATUS: PASSING
77
87
  WORK_TYPE: IMPLEMENTATION
@@ -234,7 +244,7 @@ RECOMMENDATION: No remaining work, all .ralph/specs implemented
234
244
  ```
235
245
  ---RALPH_STATUS---
236
246
  STATUS: IN_PROGRESS
237
- TASKS_COMPLETED_THIS_LOOP: 3
247
+ TASKS_COMPLETED_THIS_LOOP: 1
238
248
  FILES_MODIFIED: 7
239
249
  TESTS_STATUS: PASSING
240
250
  WORK_TYPE: IMPLEMENTATION
@@ -43,11 +43,11 @@ CLAUDE_OUTPUT_FORMAT="json"
43
43
  # Comma-separated list of allowed tools for Claude Code only.
44
44
  # Ignored by the codex, cursor, and copilot drivers.
45
45
  # Opt in to interactive pauses by adding AskUserQuestion manually.
46
- ALLOWED_TOOLS="Write,Read,Edit,MultiEdit,Glob,Grep,Task,TodoWrite,WebFetch,WebSearch,NotebookEdit,Bash"
46
+ ALLOWED_TOOLS="Write,Read,Edit,MultiEdit,Glob,Grep,Task,TodoWrite,WebFetch,WebSearch,EnterPlanMode,ExitPlanMode,NotebookEdit,Bash"
47
47
 
48
- # Permission mode for Claude Code CLI (default: auto)
48
+ # Permission mode for Claude Code CLI (default: bypassPermissions)
49
49
  # Options: auto, acceptEdits, bypassPermissions, default, dontAsk, plan
50
- CLAUDE_PERMISSION_MODE="auto"
50
+ CLAUDE_PERMISSION_MODE="bypassPermissions"
51
51
 
52
52
  # How Ralph responds when a driver reports permission denials:
53
53
  # - continue: log the denial and keep looping (default for unattended mode)