bmalph 2.7.5 → 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.
- package/README.md +20 -5
- package/dist/commands/doctor-runtime-checks.js +104 -86
- package/dist/commands/doctor-runtime-checks.js.map +1 -1
- package/dist/commands/run.js +11 -2
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/watch.js +5 -0
- package/dist/commands/watch.js.map +1 -1
- package/dist/installer/bmad-assets.js +182 -0
- package/dist/installer/bmad-assets.js.map +1 -0
- package/dist/installer/commands.js +324 -0
- package/dist/installer/commands.js.map +1 -0
- package/dist/installer/install.js +42 -0
- package/dist/installer/install.js.map +1 -0
- package/dist/installer/metadata.js +56 -0
- package/dist/installer/metadata.js.map +1 -0
- package/dist/installer/project-files.js +169 -0
- package/dist/installer/project-files.js.map +1 -0
- package/dist/installer/ralph-assets.js +91 -0
- package/dist/installer/ralph-assets.js.map +1 -0
- package/dist/installer/template-files.js +187 -0
- package/dist/installer/template-files.js.map +1 -0
- package/dist/installer/types.js +2 -0
- package/dist/installer/types.js.map +1 -0
- package/dist/installer.js +5 -843
- package/dist/installer.js.map +1 -1
- package/dist/run/run-dashboard.js +20 -6
- package/dist/run/run-dashboard.js.map +1 -1
- package/dist/transition/artifact-loading.js +91 -0
- package/dist/transition/artifact-loading.js.map +1 -0
- package/dist/transition/context-output.js +85 -0
- package/dist/transition/context-output.js.map +1 -0
- package/dist/transition/context.js +11 -3
- package/dist/transition/context.js.map +1 -1
- package/dist/transition/fix-plan-sync.js +119 -0
- package/dist/transition/fix-plan-sync.js.map +1 -0
- package/dist/transition/orchestration.js +25 -362
- package/dist/transition/orchestration.js.map +1 -1
- package/dist/transition/specs-sync.js +78 -2
- package/dist/transition/specs-sync.js.map +1 -1
- package/dist/utils/ralph-runtime-state.js +222 -0
- package/dist/utils/ralph-runtime-state.js.map +1 -0
- package/dist/utils/state.js +17 -16
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/validate.js +16 -0
- package/dist/utils/validate.js.map +1 -1
- package/dist/watch/dashboard.js +25 -21
- package/dist/watch/dashboard.js.map +1 -1
- package/dist/watch/frame-writer.js +83 -0
- package/dist/watch/frame-writer.js.map +1 -0
- package/dist/watch/renderer.js +214 -49
- package/dist/watch/renderer.js.map +1 -1
- package/dist/watch/state-reader.js +87 -44
- package/dist/watch/state-reader.js.map +1 -1
- package/package.json +1 -1
- package/ralph/RALPH-REFERENCE.md +34 -14
- package/ralph/drivers/claude-code.sh +27 -0
- package/ralph/drivers/codex.sh +11 -0
- package/ralph/drivers/copilot.sh +11 -0
- package/ralph/drivers/cursor.sh +11 -0
- package/ralph/lib/circuit_breaker.sh +3 -3
- package/ralph/lib/date_utils.sh +28 -9
- package/ralph/lib/enable_core.sh +10 -2
- package/ralph/lib/response_analyzer.sh +252 -40
- package/ralph/ralph_import.sh +9 -1
- package/ralph/ralph_loop.sh +548 -128
- package/ralph/templates/PROMPT.md +20 -5
- package/ralph/templates/ralphrc.template +14 -4
|
@@ -22,6 +22,103 @@ RALPH_DIR="${RALPH_DIR:-.ralph}"
|
|
|
22
22
|
COMPLETION_KEYWORDS=("done" "complete" "finished" "all tasks complete" "project complete" "ready for review")
|
|
23
23
|
TEST_ONLY_PATTERNS=("npm test" "bats" "pytest" "jest" "cargo test" "go test" "running tests")
|
|
24
24
|
NO_WORK_PATTERNS=("nothing to do" "no changes" "already implemented" "up to date")
|
|
25
|
+
PERMISSION_DENIAL_INLINE_PATTERNS=(
|
|
26
|
+
"requires approval before it can run"
|
|
27
|
+
"requires approval before it can proceed"
|
|
28
|
+
"not allowed to use tool"
|
|
29
|
+
"not permitted to use tool"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
extract_permission_signal_text() {
|
|
33
|
+
local text=$1
|
|
34
|
+
|
|
35
|
+
if [[ -z "$text" ]]; then
|
|
36
|
+
echo ""
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Only inspect the response preamble for tool refusals. Later paragraphs and
|
|
41
|
+
# copied logs often contain old permission errors that should not halt Ralph.
|
|
42
|
+
local signal_source="${text//$'\r'/}"
|
|
43
|
+
if [[ "$signal_source" == *"---RALPH_STATUS---"* ]]; then
|
|
44
|
+
signal_source="${signal_source%%---RALPH_STATUS---*}"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
local signal_text=""
|
|
48
|
+
local non_empty_lines=0
|
|
49
|
+
local trimmed=""
|
|
50
|
+
local line=""
|
|
51
|
+
|
|
52
|
+
while IFS= read -r line; do
|
|
53
|
+
trimmed="${line#"${line%%[![:space:]]*}"}"
|
|
54
|
+
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
|
|
55
|
+
|
|
56
|
+
if [[ -z "$trimmed" ]]; then
|
|
57
|
+
if [[ $non_empty_lines -gt 0 ]]; then
|
|
58
|
+
break
|
|
59
|
+
fi
|
|
60
|
+
continue
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
signal_text+="$trimmed"$'\n'
|
|
64
|
+
((non_empty_lines += 1))
|
|
65
|
+
if [[ $non_empty_lines -ge 5 ]]; then
|
|
66
|
+
break
|
|
67
|
+
fi
|
|
68
|
+
done <<< "$signal_source"
|
|
69
|
+
|
|
70
|
+
printf '%s' "$signal_text"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
permission_denial_line_matches() {
|
|
74
|
+
local normalized=$1
|
|
75
|
+
|
|
76
|
+
case "$normalized" in
|
|
77
|
+
permission\ denied:*|denied\ permission:*)
|
|
78
|
+
[[ "$normalized" == *approval* || "$normalized" == *tool* || "$normalized" == *command* || "$normalized" == *blocked* || "$normalized" == *"not allowed"* || "$normalized" == *"not permitted"* ]]
|
|
79
|
+
return
|
|
80
|
+
;;
|
|
81
|
+
approval\ required:*)
|
|
82
|
+
[[ "$normalized" == *run* || "$normalized" == *proceed* || "$normalized" == *tool* || "$normalized" == *command* || "$normalized" == *blocked* ]]
|
|
83
|
+
return
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
|
|
87
|
+
return 1
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
contains_permission_denial_signal() {
|
|
91
|
+
local signal_text=$1
|
|
92
|
+
|
|
93
|
+
if [[ -z "$signal_text" ]]; then
|
|
94
|
+
return 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
local line
|
|
98
|
+
while IFS= read -r line; do
|
|
99
|
+
local trimmed="${line#"${line%%[![:space:]]*}"}"
|
|
100
|
+
local normalized="${trimmed,,}"
|
|
101
|
+
|
|
102
|
+
if permission_denial_line_matches "$normalized"; then
|
|
103
|
+
return 0
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
local pattern
|
|
107
|
+
for pattern in "${PERMISSION_DENIAL_INLINE_PATTERNS[@]}"; do
|
|
108
|
+
if [[ "$normalized" == *"$pattern"* ]]; then
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
done
|
|
112
|
+
done <<< "$signal_text"
|
|
113
|
+
|
|
114
|
+
return 1
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
contains_permission_denial_text() {
|
|
118
|
+
local signal_text
|
|
119
|
+
signal_text=$(extract_permission_signal_text "$1")
|
|
120
|
+
contains_permission_denial_signal "$signal_text"
|
|
121
|
+
}
|
|
25
122
|
|
|
26
123
|
# =============================================================================
|
|
27
124
|
# JSON OUTPUT FORMAT DETECTION AND PARSING
|
|
@@ -299,6 +396,68 @@ trim_shell_whitespace() {
|
|
|
299
396
|
printf '%s' "$value"
|
|
300
397
|
}
|
|
301
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
|
+
|
|
302
461
|
# Parse JSON response and extract structured fields
|
|
303
462
|
# Creates .ralph/.json_parse_result with normalized analysis data
|
|
304
463
|
# Supports FIVE JSON formats:
|
|
@@ -369,35 +528,38 @@ parse_json_response() {
|
|
|
369
528
|
# Track whether EXIT_SIGNAL was explicitly provided (vs inferred from STATUS)
|
|
370
529
|
local exit_signal=$(jq -r -j '.exit_signal // false' "$output_file" 2>/dev/null)
|
|
371
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
|
|
372
535
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
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
|
|
401
563
|
fi
|
|
402
564
|
fi
|
|
403
565
|
|
|
@@ -452,6 +614,14 @@ parse_json_response() {
|
|
|
452
614
|
denied_commands_json=$(jq -r -j '[.permission_denials[] | if .tool_name == "Bash" then "Bash(\(.tool_input.command // "?" | split("\n")[0] | .[0:60]))" else .tool_name // "unknown" end]' "$output_file" 2>/dev/null || echo "[]")
|
|
453
615
|
fi
|
|
454
616
|
|
|
617
|
+
# Heuristic permission-denial matching is limited to the refusal-shaped
|
|
618
|
+
# response preamble, not arbitrary prose or copied logs later in the body.
|
|
619
|
+
if [[ "$has_permission_denials" != "true" ]] && contains_permission_denial_text "$summary"; then
|
|
620
|
+
has_permission_denials="true"
|
|
621
|
+
permission_denial_count=1
|
|
622
|
+
denied_commands_json='["permission_denied"]'
|
|
623
|
+
fi
|
|
624
|
+
|
|
455
625
|
# Apply completion heuristics to normalized summary text when explicit structured
|
|
456
626
|
# completion markers are absent. This keeps JSONL analysis aligned with text mode.
|
|
457
627
|
local summary_has_completion_keyword="false"
|
|
@@ -535,6 +705,7 @@ parse_json_response() {
|
|
|
535
705
|
--argjson loop_number "$loop_number" \
|
|
536
706
|
--arg session_id "$session_id" \
|
|
537
707
|
--argjson confidence "$confidence" \
|
|
708
|
+
--argjson tasks_completed_this_loop "$tasks_completed_this_loop" \
|
|
538
709
|
--argjson has_permission_denials "$has_permission_denials" \
|
|
539
710
|
--argjson permission_denial_count "$permission_denial_count" \
|
|
540
711
|
--argjson denied_commands "$denied_commands_json" \
|
|
@@ -550,6 +721,7 @@ parse_json_response() {
|
|
|
550
721
|
loop_number: $loop_number,
|
|
551
722
|
session_id: $session_id,
|
|
552
723
|
confidence: $confidence,
|
|
724
|
+
tasks_completed_this_loop: $tasks_completed_this_loop,
|
|
553
725
|
has_permission_denials: $has_permission_denials,
|
|
554
726
|
permission_denial_count: $permission_denial_count,
|
|
555
727
|
denied_commands: $denied_commands,
|
|
@@ -582,6 +754,7 @@ analyze_response() {
|
|
|
582
754
|
local exit_signal=false
|
|
583
755
|
local work_summary=""
|
|
584
756
|
local files_modified=0
|
|
757
|
+
local tasks_completed_this_loop=0
|
|
585
758
|
|
|
586
759
|
# Read output file
|
|
587
760
|
if [[ ! -f "$output_file" ]]; then
|
|
@@ -607,6 +780,7 @@ analyze_response() {
|
|
|
607
780
|
is_stuck=$(jq -r -j '.is_stuck' "$json_parse_result_file" 2>/dev/null || echo "false")
|
|
608
781
|
work_summary=$(jq -r -j '.summary' "$json_parse_result_file" 2>/dev/null || echo "")
|
|
609
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")
|
|
610
784
|
local json_confidence=$(jq -r -j '.confidence' "$json_parse_result_file" 2>/dev/null || echo "0")
|
|
611
785
|
local session_id=$(jq -r -j '.session_id' "$json_parse_result_file" 2>/dev/null || echo "")
|
|
612
786
|
|
|
@@ -628,6 +802,10 @@ analyze_response() {
|
|
|
628
802
|
confidence_score=$((json_confidence + 50))
|
|
629
803
|
fi
|
|
630
804
|
|
|
805
|
+
if [[ ! "$tasks_completed_this_loop" =~ ^-?[0-9]+$ ]]; then
|
|
806
|
+
tasks_completed_this_loop=0
|
|
807
|
+
fi
|
|
808
|
+
|
|
631
809
|
# Check for file changes via git (supplements JSON data)
|
|
632
810
|
# Fix #141: Detect both uncommitted changes AND committed changes
|
|
633
811
|
if command -v git &>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
|
|
@@ -679,6 +857,7 @@ analyze_response() {
|
|
|
679
857
|
--argjson files_modified "$files_modified" \
|
|
680
858
|
--argjson confidence_score "$confidence_score" \
|
|
681
859
|
--argjson exit_signal "$exit_signal" \
|
|
860
|
+
--argjson tasks_completed_this_loop "$tasks_completed_this_loop" \
|
|
682
861
|
--arg work_summary "$work_summary" \
|
|
683
862
|
--argjson output_length "$output_length" \
|
|
684
863
|
--argjson has_permission_denials "$has_permission_denials" \
|
|
@@ -697,6 +876,9 @@ analyze_response() {
|
|
|
697
876
|
files_modified: $files_modified,
|
|
698
877
|
confidence_score: $confidence_score,
|
|
699
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,
|
|
700
882
|
work_summary: $work_summary,
|
|
701
883
|
output_length: $output_length,
|
|
702
884
|
has_permission_denials: $has_permission_denials,
|
|
@@ -718,13 +900,23 @@ analyze_response() {
|
|
|
718
900
|
local explicit_exit_signal_found=false
|
|
719
901
|
|
|
720
902
|
# 1. Check for explicit structured output (if Claude follows schema)
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
local status
|
|
724
|
-
|
|
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
|
|
725
917
|
|
|
726
918
|
# If EXIT_SIGNAL is explicitly provided, respect it
|
|
727
|
-
if [[
|
|
919
|
+
if [[ "$exit_sig_found" == "true" ]]; then
|
|
728
920
|
explicit_exit_signal_found=true
|
|
729
921
|
if [[ "$exit_sig" == "true" ]]; then
|
|
730
922
|
has_completion_signal=true
|
|
@@ -873,8 +1065,18 @@ analyze_response() {
|
|
|
873
1065
|
fi
|
|
874
1066
|
fi
|
|
875
1067
|
|
|
1068
|
+
local has_permission_denials=false
|
|
1069
|
+
local permission_denial_count=0
|
|
1070
|
+
local denied_commands_json='[]'
|
|
1071
|
+
local permission_signal_text=""
|
|
1072
|
+
permission_signal_text=$(extract_permission_signal_text "$output_content")
|
|
1073
|
+
if contains_permission_denial_text "$work_summary" || contains_permission_denial_signal "$permission_signal_text"; then
|
|
1074
|
+
has_permission_denials=true
|
|
1075
|
+
permission_denial_count=1
|
|
1076
|
+
denied_commands_json='["permission_denied"]'
|
|
1077
|
+
fi
|
|
1078
|
+
|
|
876
1079
|
# Write analysis results to file (text parsing path) using jq for safe construction
|
|
877
|
-
# Note: Permission denial fields default to false/0 since text output doesn't include this data
|
|
878
1080
|
jq -n \
|
|
879
1081
|
--argjson loop_number "$loop_number" \
|
|
880
1082
|
--arg timestamp "$(get_iso_timestamp)" \
|
|
@@ -887,8 +1089,12 @@ analyze_response() {
|
|
|
887
1089
|
--argjson files_modified "$files_modified" \
|
|
888
1090
|
--argjson confidence_score "$confidence_score" \
|
|
889
1091
|
--argjson exit_signal "$exit_signal" \
|
|
1092
|
+
--argjson tasks_completed_this_loop "$tasks_completed_this_loop" \
|
|
890
1093
|
--arg work_summary "$work_summary" \
|
|
891
1094
|
--argjson output_length "$output_length" \
|
|
1095
|
+
--argjson has_permission_denials "$has_permission_denials" \
|
|
1096
|
+
--argjson permission_denial_count "$permission_denial_count" \
|
|
1097
|
+
--argjson denied_commands "$denied_commands_json" \
|
|
892
1098
|
'{
|
|
893
1099
|
loop_number: $loop_number,
|
|
894
1100
|
timestamp: $timestamp,
|
|
@@ -902,11 +1108,14 @@ analyze_response() {
|
|
|
902
1108
|
files_modified: $files_modified,
|
|
903
1109
|
confidence_score: $confidence_score,
|
|
904
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,
|
|
905
1114
|
work_summary: $work_summary,
|
|
906
1115
|
output_length: $output_length,
|
|
907
|
-
has_permission_denials:
|
|
908
|
-
permission_denial_count:
|
|
909
|
-
denied_commands:
|
|
1116
|
+
has_permission_denials: $has_permission_denials,
|
|
1117
|
+
permission_denial_count: $permission_denial_count,
|
|
1118
|
+
denied_commands: $denied_commands
|
|
910
1119
|
}
|
|
911
1120
|
}' > "$analysis_result_file"
|
|
912
1121
|
|
|
@@ -930,6 +1139,8 @@ update_exit_signals() {
|
|
|
930
1139
|
local has_completion_signal=$(jq -r -j '.analysis.has_completion_signal' "$analysis_file")
|
|
931
1140
|
local loop_number=$(jq -r -j '.loop_number' "$analysis_file")
|
|
932
1141
|
local has_progress=$(jq -r -j '.analysis.has_progress' "$analysis_file")
|
|
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")
|
|
933
1144
|
|
|
934
1145
|
# Read current exit signals
|
|
935
1146
|
local signals=$(cat "$exit_signals_file" 2>/dev/null || echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}')
|
|
@@ -944,8 +1155,9 @@ update_exit_signals() {
|
|
|
944
1155
|
fi
|
|
945
1156
|
fi
|
|
946
1157
|
|
|
947
|
-
#
|
|
948
|
-
|
|
1158
|
+
# Permission denials are handled in the same loop, so they must not become
|
|
1159
|
+
# completion state that can halt the next loop.
|
|
1160
|
+
if [[ "$has_permission_denials" != "true" && "$has_progress_tracking_mismatch" != "true" && "$has_completion_signal" == "true" ]]; then
|
|
949
1161
|
signals=$(echo "$signals" | jq ".done_signals += [$loop_number]")
|
|
950
1162
|
fi
|
|
951
1163
|
|
|
@@ -954,7 +1166,7 @@ update_exit_signals() {
|
|
|
954
1166
|
# due to deterministic scoring (+50 for JSON format, +20 for result field).
|
|
955
1167
|
# This caused premature exits after 5 loops. Now we respect Claude's explicit intent.
|
|
956
1168
|
local exit_signal=$(jq -r -j '.analysis.exit_signal // false' "$analysis_file")
|
|
957
|
-
if [[ "$exit_signal" == "true" ]]; then
|
|
1169
|
+
if [[ "$has_permission_denials" != "true" && "$has_progress_tracking_mismatch" != "true" && "$exit_signal" == "true" ]]; then
|
|
958
1170
|
signals=$(echo "$signals" | jq ".completion_indicators += [$loop_number]")
|
|
959
1171
|
fi
|
|
960
1172
|
|
package/ralph/ralph_import.sh
CHANGED
|
@@ -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
|
-
-
|
|
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
|