bmalph 2.7.5 → 2.7.6
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/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 +168 -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/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/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/renderer.js +48 -6
- package/dist/watch/renderer.js.map +1 -1
- package/dist/watch/state-reader.js +79 -44
- package/dist/watch/state-reader.js.map +1 -1
- package/package.json +1 -1
- package/ralph/RALPH-REFERENCE.md +33 -13
- package/ralph/drivers/claude-code.sh +25 -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/response_analyzer.sh +127 -7
- package/ralph/ralph_loop.sh +464 -121
- package/ralph/templates/PROMPT.md +5 -0
- 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
|
|
@@ -452,6 +549,14 @@ parse_json_response() {
|
|
|
452
549
|
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
550
|
fi
|
|
454
551
|
|
|
552
|
+
# Heuristic permission-denial matching is limited to the refusal-shaped
|
|
553
|
+
# response preamble, not arbitrary prose or copied logs later in the body.
|
|
554
|
+
if [[ "$has_permission_denials" != "true" ]] && contains_permission_denial_text "$summary"; then
|
|
555
|
+
has_permission_denials="true"
|
|
556
|
+
permission_denial_count=1
|
|
557
|
+
denied_commands_json='["permission_denied"]'
|
|
558
|
+
fi
|
|
559
|
+
|
|
455
560
|
# Apply completion heuristics to normalized summary text when explicit structured
|
|
456
561
|
# completion markers are absent. This keeps JSONL analysis aligned with text mode.
|
|
457
562
|
local summary_has_completion_keyword="false"
|
|
@@ -873,8 +978,18 @@ analyze_response() {
|
|
|
873
978
|
fi
|
|
874
979
|
fi
|
|
875
980
|
|
|
981
|
+
local has_permission_denials=false
|
|
982
|
+
local permission_denial_count=0
|
|
983
|
+
local denied_commands_json='[]'
|
|
984
|
+
local permission_signal_text=""
|
|
985
|
+
permission_signal_text=$(extract_permission_signal_text "$output_content")
|
|
986
|
+
if contains_permission_denial_text "$work_summary" || contains_permission_denial_signal "$permission_signal_text"; then
|
|
987
|
+
has_permission_denials=true
|
|
988
|
+
permission_denial_count=1
|
|
989
|
+
denied_commands_json='["permission_denied"]'
|
|
990
|
+
fi
|
|
991
|
+
|
|
876
992
|
# 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
993
|
jq -n \
|
|
879
994
|
--argjson loop_number "$loop_number" \
|
|
880
995
|
--arg timestamp "$(get_iso_timestamp)" \
|
|
@@ -889,6 +1004,9 @@ analyze_response() {
|
|
|
889
1004
|
--argjson exit_signal "$exit_signal" \
|
|
890
1005
|
--arg work_summary "$work_summary" \
|
|
891
1006
|
--argjson output_length "$output_length" \
|
|
1007
|
+
--argjson has_permission_denials "$has_permission_denials" \
|
|
1008
|
+
--argjson permission_denial_count "$permission_denial_count" \
|
|
1009
|
+
--argjson denied_commands "$denied_commands_json" \
|
|
892
1010
|
'{
|
|
893
1011
|
loop_number: $loop_number,
|
|
894
1012
|
timestamp: $timestamp,
|
|
@@ -904,9 +1022,9 @@ analyze_response() {
|
|
|
904
1022
|
exit_signal: $exit_signal,
|
|
905
1023
|
work_summary: $work_summary,
|
|
906
1024
|
output_length: $output_length,
|
|
907
|
-
has_permission_denials:
|
|
908
|
-
permission_denial_count:
|
|
909
|
-
denied_commands:
|
|
1025
|
+
has_permission_denials: $has_permission_denials,
|
|
1026
|
+
permission_denial_count: $permission_denial_count,
|
|
1027
|
+
denied_commands: $denied_commands
|
|
910
1028
|
}
|
|
911
1029
|
}' > "$analysis_result_file"
|
|
912
1030
|
|
|
@@ -930,6 +1048,7 @@ update_exit_signals() {
|
|
|
930
1048
|
local has_completion_signal=$(jq -r -j '.analysis.has_completion_signal' "$analysis_file")
|
|
931
1049
|
local loop_number=$(jq -r -j '.loop_number' "$analysis_file")
|
|
932
1050
|
local has_progress=$(jq -r -j '.analysis.has_progress' "$analysis_file")
|
|
1051
|
+
local has_permission_denials=$(jq -r -j '.analysis.has_permission_denials // false' "$analysis_file")
|
|
933
1052
|
|
|
934
1053
|
# Read current exit signals
|
|
935
1054
|
local signals=$(cat "$exit_signals_file" 2>/dev/null || echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}')
|
|
@@ -944,8 +1063,9 @@ update_exit_signals() {
|
|
|
944
1063
|
fi
|
|
945
1064
|
fi
|
|
946
1065
|
|
|
947
|
-
#
|
|
948
|
-
|
|
1066
|
+
# Permission denials are handled in the same loop, so they must not become
|
|
1067
|
+
# completion state that can halt the next loop.
|
|
1068
|
+
if [[ "$has_permission_denials" != "true" && "$has_completion_signal" == "true" ]]; then
|
|
949
1069
|
signals=$(echo "$signals" | jq ".done_signals += [$loop_number]")
|
|
950
1070
|
fi
|
|
951
1071
|
|
|
@@ -954,7 +1074,7 @@ update_exit_signals() {
|
|
|
954
1074
|
# due to deterministic scoring (+50 for JSON format, +20 for result field).
|
|
955
1075
|
# This caused premature exits after 5 loops. Now we respect Claude's explicit intent.
|
|
956
1076
|
local exit_signal=$(jq -r -j '.analysis.exit_signal // false' "$analysis_file")
|
|
957
|
-
if [[ "$exit_signal" == "true" ]]; then
|
|
1077
|
+
if [[ "$has_permission_denials" != "true" && "$exit_signal" == "true" ]]; then
|
|
958
1078
|
signals=$(echo "$signals" | jq ".completion_indicators += [$loop_number]")
|
|
959
1079
|
fi
|
|
960
1080
|
|