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
package/ralph/ralph_loop.sh
CHANGED
|
@@ -40,9 +40,32 @@ _env_MAX_CALLS_PER_HOUR="${MAX_CALLS_PER_HOUR:-}"
|
|
|
40
40
|
_env_CLAUDE_TIMEOUT_MINUTES="${CLAUDE_TIMEOUT_MINUTES:-}"
|
|
41
41
|
_env_CLAUDE_OUTPUT_FORMAT="${CLAUDE_OUTPUT_FORMAT:-}"
|
|
42
42
|
_env_CLAUDE_ALLOWED_TOOLS="${CLAUDE_ALLOWED_TOOLS:-}"
|
|
43
|
+
_env_has_CLAUDE_PERMISSION_MODE="${CLAUDE_PERMISSION_MODE+x}"
|
|
44
|
+
_env_CLAUDE_PERMISSION_MODE="${CLAUDE_PERMISSION_MODE:-}"
|
|
43
45
|
_env_CLAUDE_USE_CONTINUE="${CLAUDE_USE_CONTINUE:-}"
|
|
44
46
|
_env_CLAUDE_SESSION_EXPIRY_HOURS="${CLAUDE_SESSION_EXPIRY_HOURS:-}"
|
|
47
|
+
_env_ALLOWED_TOOLS="${ALLOWED_TOOLS:-}"
|
|
48
|
+
_env_SESSION_CONTINUITY="${SESSION_CONTINUITY:-}"
|
|
49
|
+
_env_SESSION_EXPIRY_HOURS="${SESSION_EXPIRY_HOURS:-}"
|
|
50
|
+
_env_PERMISSION_DENIAL_MODE="${PERMISSION_DENIAL_MODE:-}"
|
|
51
|
+
_env_RALPH_VERBOSE="${RALPH_VERBOSE:-}"
|
|
45
52
|
_env_VERBOSE_PROGRESS="${VERBOSE_PROGRESS:-}"
|
|
53
|
+
|
|
54
|
+
# CLI flags are parsed before main() runs, so capture explicit values separately.
|
|
55
|
+
_CLI_MAX_CALLS_PER_HOUR="${_CLI_MAX_CALLS_PER_HOUR:-}"
|
|
56
|
+
_CLI_CLAUDE_TIMEOUT_MINUTES="${_CLI_CLAUDE_TIMEOUT_MINUTES:-}"
|
|
57
|
+
_CLI_CLAUDE_OUTPUT_FORMAT="${_CLI_CLAUDE_OUTPUT_FORMAT:-}"
|
|
58
|
+
_CLI_ALLOWED_TOOLS="${_CLI_ALLOWED_TOOLS:-}"
|
|
59
|
+
_CLI_SESSION_CONTINUITY="${_CLI_SESSION_CONTINUITY:-}"
|
|
60
|
+
_CLI_SESSION_EXPIRY_HOURS="${_CLI_SESSION_EXPIRY_HOURS:-}"
|
|
61
|
+
_CLI_VERBOSE_PROGRESS="${_CLI_VERBOSE_PROGRESS:-}"
|
|
62
|
+
_cli_MAX_CALLS_PER_HOUR="${MAX_CALLS_PER_HOUR:-}"
|
|
63
|
+
_cli_CLAUDE_TIMEOUT_MINUTES="${CLAUDE_TIMEOUT_MINUTES:-}"
|
|
64
|
+
_cli_CLAUDE_OUTPUT_FORMAT="${CLAUDE_OUTPUT_FORMAT:-}"
|
|
65
|
+
_cli_CLAUDE_ALLOWED_TOOLS="${CLAUDE_ALLOWED_TOOLS:-}"
|
|
66
|
+
_cli_CLAUDE_USE_CONTINUE="${CLAUDE_USE_CONTINUE:-}"
|
|
67
|
+
_cli_CLAUDE_SESSION_EXPIRY_HOURS="${CLAUDE_SESSION_EXPIRY_HOURS:-}"
|
|
68
|
+
_cli_VERBOSE_PROGRESS="${VERBOSE_PROGRESS:-}"
|
|
46
69
|
_env_CB_COOLDOWN_MINUTES="${CB_COOLDOWN_MINUTES:-}"
|
|
47
70
|
_env_CB_AUTO_RESET="${CB_AUTO_RESET:-}"
|
|
48
71
|
|
|
@@ -50,11 +73,15 @@ _env_CB_AUTO_RESET="${CB_AUTO_RESET:-}"
|
|
|
50
73
|
MAX_CALLS_PER_HOUR="${MAX_CALLS_PER_HOUR:-100}"
|
|
51
74
|
VERBOSE_PROGRESS="${VERBOSE_PROGRESS:-false}"
|
|
52
75
|
CLAUDE_TIMEOUT_MINUTES="${CLAUDE_TIMEOUT_MINUTES:-15}"
|
|
76
|
+
DEFAULT_CLAUDE_ALLOWED_TOOLS="Write,Read,Edit,MultiEdit,Glob,Grep,Task,TodoWrite,WebFetch,WebSearch,EnterPlanMode,ExitPlanMode,NotebookEdit,Bash"
|
|
77
|
+
DEFAULT_PERMISSION_DENIAL_MODE="continue"
|
|
53
78
|
|
|
54
79
|
# Modern Claude CLI configuration (Phase 1.1)
|
|
55
80
|
CLAUDE_OUTPUT_FORMAT="${CLAUDE_OUTPUT_FORMAT:-json}"
|
|
56
|
-
CLAUDE_ALLOWED_TOOLS="${CLAUDE_ALLOWED_TOOLS
|
|
81
|
+
CLAUDE_ALLOWED_TOOLS="${CLAUDE_ALLOWED_TOOLS:-$DEFAULT_CLAUDE_ALLOWED_TOOLS}"
|
|
82
|
+
CLAUDE_PERMISSION_MODE="${CLAUDE_PERMISSION_MODE:-bypassPermissions}"
|
|
57
83
|
CLAUDE_USE_CONTINUE="${CLAUDE_USE_CONTINUE:-true}"
|
|
84
|
+
PERMISSION_DENIAL_MODE="${PERMISSION_DENIAL_MODE:-$DEFAULT_PERMISSION_DENIAL_MODE}"
|
|
58
85
|
CLAUDE_SESSION_FILE="$RALPH_DIR/.claude_session_id" # Session ID persistence file
|
|
59
86
|
CLAUDE_MIN_VERSION="2.0.76" # Minimum required Claude CLI version
|
|
60
87
|
|
|
@@ -80,6 +107,9 @@ VALID_TOOL_PATTERNS=(
|
|
|
80
107
|
"TodoWrite"
|
|
81
108
|
"WebFetch"
|
|
82
109
|
"WebSearch"
|
|
110
|
+
"AskUserQuestion"
|
|
111
|
+
"EnterPlanMode"
|
|
112
|
+
"ExitPlanMode"
|
|
83
113
|
"Bash"
|
|
84
114
|
"Bash(git *)"
|
|
85
115
|
"Bash(npm *)"
|
|
@@ -88,6 +118,8 @@ VALID_TOOL_PATTERNS=(
|
|
|
88
118
|
"Bash(node *)"
|
|
89
119
|
"NotebookEdit"
|
|
90
120
|
)
|
|
121
|
+
ALLOWED_TOOLS_IGNORED_WARNED=false
|
|
122
|
+
PERMISSION_DENIAL_ACTION=""
|
|
91
123
|
|
|
92
124
|
# Exit detection configuration
|
|
93
125
|
EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
|
|
@@ -131,7 +163,9 @@ resolve_ralphrc_file() {
|
|
|
131
163
|
# - MAX_CALLS_PER_HOUR
|
|
132
164
|
# - CLAUDE_TIMEOUT_MINUTES
|
|
133
165
|
# - CLAUDE_OUTPUT_FORMAT
|
|
134
|
-
# -
|
|
166
|
+
# - CLAUDE_PERMISSION_MODE
|
|
167
|
+
# - ALLOWED_TOOLS (mapped to CLAUDE_ALLOWED_TOOLS for Claude Code only)
|
|
168
|
+
# - PERMISSION_DENIAL_MODE
|
|
135
169
|
# - SESSION_CONTINUITY (mapped to CLAUDE_USE_CONTINUE)
|
|
136
170
|
# - SESSION_EXPIRY_HOURS (mapped to CLAUDE_SESSION_EXPIRY_HOURS)
|
|
137
171
|
# - CB_NO_PROGRESS_THRESHOLD
|
|
@@ -155,6 +189,9 @@ load_ralphrc() {
|
|
|
155
189
|
if [[ -n "${ALLOWED_TOOLS:-}" ]]; then
|
|
156
190
|
CLAUDE_ALLOWED_TOOLS="$ALLOWED_TOOLS"
|
|
157
191
|
fi
|
|
192
|
+
if [[ -n "${PERMISSION_DENIAL_MODE:-}" ]]; then
|
|
193
|
+
PERMISSION_DENIAL_MODE="$PERMISSION_DENIAL_MODE"
|
|
194
|
+
fi
|
|
158
195
|
if [[ -n "${SESSION_CONTINUITY:-}" ]]; then
|
|
159
196
|
CLAUDE_USE_CONTINUE="$SESSION_CONTINUITY"
|
|
160
197
|
fi
|
|
@@ -167,22 +204,57 @@ load_ralphrc() {
|
|
|
167
204
|
|
|
168
205
|
# Restore ONLY values that were explicitly set via environment variables
|
|
169
206
|
# (not script defaults). The _env_* variables were captured BEFORE defaults were set.
|
|
170
|
-
#
|
|
207
|
+
# Internal CLAUDE_* variables are kept for backward compatibility.
|
|
171
208
|
[[ -n "$_env_MAX_CALLS_PER_HOUR" ]] && MAX_CALLS_PER_HOUR="$_env_MAX_CALLS_PER_HOUR"
|
|
172
209
|
[[ -n "$_env_CLAUDE_TIMEOUT_MINUTES" ]] && CLAUDE_TIMEOUT_MINUTES="$_env_CLAUDE_TIMEOUT_MINUTES"
|
|
173
210
|
[[ -n "$_env_CLAUDE_OUTPUT_FORMAT" ]] && CLAUDE_OUTPUT_FORMAT="$_env_CLAUDE_OUTPUT_FORMAT"
|
|
174
211
|
[[ -n "$_env_CLAUDE_ALLOWED_TOOLS" ]] && CLAUDE_ALLOWED_TOOLS="$_env_CLAUDE_ALLOWED_TOOLS"
|
|
212
|
+
if [[ "$_env_has_CLAUDE_PERMISSION_MODE" == "x" ]]; then
|
|
213
|
+
CLAUDE_PERMISSION_MODE="$_env_CLAUDE_PERMISSION_MODE"
|
|
214
|
+
fi
|
|
175
215
|
[[ -n "$_env_CLAUDE_USE_CONTINUE" ]] && CLAUDE_USE_CONTINUE="$_env_CLAUDE_USE_CONTINUE"
|
|
176
216
|
[[ -n "$_env_CLAUDE_SESSION_EXPIRY_HOURS" ]] && CLAUDE_SESSION_EXPIRY_HOURS="$_env_CLAUDE_SESSION_EXPIRY_HOURS"
|
|
217
|
+
[[ -n "$_env_PERMISSION_DENIAL_MODE" ]] && PERMISSION_DENIAL_MODE="$_env_PERMISSION_DENIAL_MODE"
|
|
177
218
|
[[ -n "$_env_VERBOSE_PROGRESS" ]] && VERBOSE_PROGRESS="$_env_VERBOSE_PROGRESS"
|
|
219
|
+
|
|
220
|
+
# Public aliases are the preferred external interface and win over the
|
|
221
|
+
# legacy internal environment variables when both are explicitly set.
|
|
222
|
+
[[ -n "$_env_ALLOWED_TOOLS" ]] && CLAUDE_ALLOWED_TOOLS="$_env_ALLOWED_TOOLS"
|
|
223
|
+
[[ -n "$_env_SESSION_CONTINUITY" ]] && CLAUDE_USE_CONTINUE="$_env_SESSION_CONTINUITY"
|
|
224
|
+
[[ -n "$_env_SESSION_EXPIRY_HOURS" ]] && CLAUDE_SESSION_EXPIRY_HOURS="$_env_SESSION_EXPIRY_HOURS"
|
|
225
|
+
[[ -n "$_env_RALPH_VERBOSE" ]] && VERBOSE_PROGRESS="$_env_RALPH_VERBOSE"
|
|
226
|
+
|
|
227
|
+
# CLI flags are the highest-priority runtime inputs because they are
|
|
228
|
+
# parsed before main() and would otherwise be overwritten by .ralphrc.
|
|
229
|
+
# Keep every config-backed CLI flag here so the precedence contract stays
|
|
230
|
+
# consistent: CLI > public env aliases > internal env vars > config.
|
|
231
|
+
[[ "$_CLI_MAX_CALLS_PER_HOUR" == "true" ]] && MAX_CALLS_PER_HOUR="$_cli_MAX_CALLS_PER_HOUR"
|
|
232
|
+
[[ "$_CLI_CLAUDE_TIMEOUT_MINUTES" == "true" ]] && CLAUDE_TIMEOUT_MINUTES="$_cli_CLAUDE_TIMEOUT_MINUTES"
|
|
233
|
+
[[ "$_CLI_CLAUDE_OUTPUT_FORMAT" == "true" ]] && CLAUDE_OUTPUT_FORMAT="$_cli_CLAUDE_OUTPUT_FORMAT"
|
|
234
|
+
[[ "$_CLI_ALLOWED_TOOLS" == "true" ]] && CLAUDE_ALLOWED_TOOLS="$_cli_CLAUDE_ALLOWED_TOOLS"
|
|
235
|
+
[[ "$_CLI_SESSION_CONTINUITY" == "true" ]] && CLAUDE_USE_CONTINUE="$_cli_CLAUDE_USE_CONTINUE"
|
|
236
|
+
[[ "$_CLI_SESSION_EXPIRY_HOURS" == "true" ]] && CLAUDE_SESSION_EXPIRY_HOURS="$_cli_CLAUDE_SESSION_EXPIRY_HOURS"
|
|
237
|
+
[[ "$_CLI_VERBOSE_PROGRESS" == "true" ]] && VERBOSE_PROGRESS="$_cli_VERBOSE_PROGRESS"
|
|
178
238
|
[[ -n "$_env_CB_COOLDOWN_MINUTES" ]] && CB_COOLDOWN_MINUTES="$_env_CB_COOLDOWN_MINUTES"
|
|
179
239
|
[[ -n "$_env_CB_AUTO_RESET" ]] && CB_AUTO_RESET="$_env_CB_AUTO_RESET"
|
|
180
240
|
|
|
241
|
+
normalize_claude_permission_mode
|
|
181
242
|
RALPHRC_FILE="$config_file"
|
|
182
243
|
RALPHRC_LOADED=true
|
|
183
244
|
return 0
|
|
184
245
|
}
|
|
185
246
|
|
|
247
|
+
driver_supports_tool_allowlist() {
|
|
248
|
+
return 1
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
driver_permission_denial_help() {
|
|
252
|
+
echo " - Review the active driver's permission or approval settings."
|
|
253
|
+
echo " - ALLOWED_TOOLS in $RALPHRC_FILE only applies to the Claude Code driver."
|
|
254
|
+
echo " - Keep CLAUDE_PERMISSION_MODE=bypassPermissions for unattended Claude Code loops."
|
|
255
|
+
echo " - After updating permissions, reset the session and restart the loop."
|
|
256
|
+
}
|
|
257
|
+
|
|
186
258
|
# Source platform driver
|
|
187
259
|
load_platform_driver() {
|
|
188
260
|
local driver_file="$SCRIPT_DIR/drivers/${PLATFORM_DRIVER}.sh"
|
|
@@ -323,8 +395,8 @@ setup_tmux_session() {
|
|
|
323
395
|
if [[ "$CLAUDE_TIMEOUT_MINUTES" != "15" ]]; then
|
|
324
396
|
ralph_cmd="$ralph_cmd --timeout $CLAUDE_TIMEOUT_MINUTES"
|
|
325
397
|
fi
|
|
326
|
-
# Forward --allowed-tools
|
|
327
|
-
if [[ "$CLAUDE_ALLOWED_TOOLS" != "
|
|
398
|
+
# Forward --allowed-tools only for drivers that support tool allowlists
|
|
399
|
+
if driver_supports_tool_allowlist && [[ "$CLAUDE_ALLOWED_TOOLS" != "$DEFAULT_CLAUDE_ALLOWED_TOOLS" ]]; then
|
|
328
400
|
ralph_cmd="$ralph_cmd --allowed-tools '$CLAUDE_ALLOWED_TOOLS'"
|
|
329
401
|
fi
|
|
330
402
|
# Forward --no-continue if session continuity disabled
|
|
@@ -443,6 +515,175 @@ update_status() {
|
|
|
443
515
|
}' > "$STATUS_FILE"
|
|
444
516
|
}
|
|
445
517
|
|
|
518
|
+
validate_permission_denial_mode() {
|
|
519
|
+
local mode=$1
|
|
520
|
+
|
|
521
|
+
case "$mode" in
|
|
522
|
+
continue|halt|threshold)
|
|
523
|
+
return 0
|
|
524
|
+
;;
|
|
525
|
+
*)
|
|
526
|
+
echo "Error: Invalid PERMISSION_DENIAL_MODE: '$mode'"
|
|
527
|
+
echo "Valid modes: continue halt threshold"
|
|
528
|
+
return 1
|
|
529
|
+
;;
|
|
530
|
+
esac
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
normalize_claude_permission_mode() {
|
|
534
|
+
if [[ -z "${CLAUDE_PERMISSION_MODE:-}" ]]; then
|
|
535
|
+
CLAUDE_PERMISSION_MODE="bypassPermissions"
|
|
536
|
+
fi
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
validate_claude_permission_mode() {
|
|
540
|
+
local mode=$1
|
|
541
|
+
|
|
542
|
+
case "$mode" in
|
|
543
|
+
auto|acceptEdits|bypassPermissions|default|dontAsk|plan)
|
|
544
|
+
return 0
|
|
545
|
+
;;
|
|
546
|
+
*)
|
|
547
|
+
echo "Error: Invalid CLAUDE_PERMISSION_MODE: '$mode'"
|
|
548
|
+
echo "Valid modes: auto acceptEdits bypassPermissions default dontAsk plan"
|
|
549
|
+
return 1
|
|
550
|
+
;;
|
|
551
|
+
esac
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
warn_if_allowed_tools_ignored() {
|
|
555
|
+
if driver_supports_tool_allowlist; then
|
|
556
|
+
return 0
|
|
557
|
+
fi
|
|
558
|
+
|
|
559
|
+
if [[ "$ALLOWED_TOOLS_IGNORED_WARNED" == "true" ]]; then
|
|
560
|
+
return 0
|
|
561
|
+
fi
|
|
562
|
+
|
|
563
|
+
if [[ "${_CLI_ALLOWED_TOOLS:-}" == "true" || "$CLAUDE_ALLOWED_TOOLS" != "$DEFAULT_CLAUDE_ALLOWED_TOOLS" ]]; then
|
|
564
|
+
log_status "WARN" "ALLOWED_TOOLS/--allowed-tools is ignored by $DRIVER_DISPLAY_NAME."
|
|
565
|
+
ALLOWED_TOOLS_IGNORED_WARNED=true
|
|
566
|
+
fi
|
|
567
|
+
|
|
568
|
+
return 0
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
show_current_allowed_tools() {
|
|
572
|
+
if ! driver_supports_tool_allowlist; then
|
|
573
|
+
return 0
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
if [[ -f "$RALPHRC_FILE" ]]; then
|
|
577
|
+
local current_tools=$(grep "^ALLOWED_TOOLS=" "$RALPHRC_FILE" 2>/dev/null | cut -d= -f2- | tr -d '"')
|
|
578
|
+
if [[ -n "$current_tools" ]]; then
|
|
579
|
+
echo -e "${BLUE}Current ALLOWED_TOOLS:${NC} $current_tools"
|
|
580
|
+
echo ""
|
|
581
|
+
fi
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
return 0
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
response_analysis_has_permission_denials() {
|
|
588
|
+
if [[ ! -f "$RESPONSE_ANALYSIS_FILE" ]]; then
|
|
589
|
+
return 1
|
|
590
|
+
fi
|
|
591
|
+
|
|
592
|
+
local has_permission_denials
|
|
593
|
+
has_permission_denials=$(jq -r '.analysis.has_permission_denials // false' "$RESPONSE_ANALYSIS_FILE" 2>/dev/null || echo "false")
|
|
594
|
+
|
|
595
|
+
[[ "$has_permission_denials" == "true" ]]
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
get_response_analysis_denied_commands() {
|
|
599
|
+
if [[ ! -f "$RESPONSE_ANALYSIS_FILE" ]]; then
|
|
600
|
+
echo "unknown"
|
|
601
|
+
return 0
|
|
602
|
+
fi
|
|
603
|
+
|
|
604
|
+
jq -r '.analysis.denied_commands | join(", ")' "$RESPONSE_ANALYSIS_FILE" 2>/dev/null || echo "unknown"
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
clear_response_analysis_permission_denials() {
|
|
608
|
+
if [[ ! -f "$RESPONSE_ANALYSIS_FILE" ]]; then
|
|
609
|
+
return 0
|
|
610
|
+
fi
|
|
611
|
+
|
|
612
|
+
local tmp_file="$RESPONSE_ANALYSIS_FILE.tmp"
|
|
613
|
+
if jq '
|
|
614
|
+
(.analysis //= {}) |
|
|
615
|
+
.analysis.has_completion_signal = false |
|
|
616
|
+
.analysis.exit_signal = false |
|
|
617
|
+
.analysis.has_permission_denials = false |
|
|
618
|
+
.analysis.permission_denial_count = 0 |
|
|
619
|
+
.analysis.denied_commands = []
|
|
620
|
+
' "$RESPONSE_ANALYSIS_FILE" > "$tmp_file" 2>/dev/null; then
|
|
621
|
+
mv "$tmp_file" "$RESPONSE_ANALYSIS_FILE"
|
|
622
|
+
return 0
|
|
623
|
+
fi
|
|
624
|
+
|
|
625
|
+
rm -f "$tmp_file" 2>/dev/null
|
|
626
|
+
return 1
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
handle_permission_denial() {
|
|
630
|
+
local loop_count=$1
|
|
631
|
+
local denied_cmds=${2:-unknown}
|
|
632
|
+
local calls_made
|
|
633
|
+
calls_made=$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")
|
|
634
|
+
PERMISSION_DENIAL_ACTION=""
|
|
635
|
+
|
|
636
|
+
case "$PERMISSION_DENIAL_MODE" in
|
|
637
|
+
continue|threshold)
|
|
638
|
+
log_status "WARN" "🚫 Permission denied in loop #$loop_count: $denied_cmds"
|
|
639
|
+
log_status "WARN" "PERMISSION_DENIAL_MODE=$PERMISSION_DENIAL_MODE - continuing execution"
|
|
640
|
+
update_status "$loop_count" "$calls_made" "permission_denied" "running"
|
|
641
|
+
PERMISSION_DENIAL_ACTION="continue"
|
|
642
|
+
return 0
|
|
643
|
+
;;
|
|
644
|
+
halt)
|
|
645
|
+
log_status "ERROR" "🚫 Permission denied - halting loop"
|
|
646
|
+
reset_session "permission_denied"
|
|
647
|
+
update_status "$loop_count" "$calls_made" "permission_denied" "halted" "permission_denied"
|
|
648
|
+
|
|
649
|
+
echo ""
|
|
650
|
+
echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}"
|
|
651
|
+
echo -e "${RED}║ PERMISSION DENIED - Loop Halted ║${NC}"
|
|
652
|
+
echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
|
|
653
|
+
echo ""
|
|
654
|
+
echo -e "${YELLOW}$DRIVER_DISPLAY_NAME was denied permission to execute commands.${NC}"
|
|
655
|
+
echo ""
|
|
656
|
+
echo -e "${YELLOW}To fix this:${NC}"
|
|
657
|
+
driver_permission_denial_help
|
|
658
|
+
echo ""
|
|
659
|
+
show_current_allowed_tools
|
|
660
|
+
PERMISSION_DENIAL_ACTION="halt"
|
|
661
|
+
return 0
|
|
662
|
+
;;
|
|
663
|
+
esac
|
|
664
|
+
|
|
665
|
+
return 1
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
consume_current_loop_permission_denial() {
|
|
669
|
+
local loop_count=$1
|
|
670
|
+
PERMISSION_DENIAL_ACTION=""
|
|
671
|
+
|
|
672
|
+
if ! response_analysis_has_permission_denials; then
|
|
673
|
+
return 1
|
|
674
|
+
fi
|
|
675
|
+
|
|
676
|
+
local denied_cmds
|
|
677
|
+
denied_cmds=$(get_response_analysis_denied_commands)
|
|
678
|
+
|
|
679
|
+
if ! clear_response_analysis_permission_denials; then
|
|
680
|
+
log_status "WARN" "Failed to clear permission denial markers from response analysis"
|
|
681
|
+
fi
|
|
682
|
+
|
|
683
|
+
handle_permission_denial "$loop_count" "$denied_cmds"
|
|
684
|
+
return 0
|
|
685
|
+
}
|
|
686
|
+
|
|
446
687
|
# Check if we can make another call
|
|
447
688
|
can_make_call() {
|
|
448
689
|
local calls_made=0
|
|
@@ -487,6 +728,74 @@ wait_for_reset() {
|
|
|
487
728
|
log_status "SUCCESS" "Rate limit reset! Ready for new calls."
|
|
488
729
|
}
|
|
489
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
|
+
|
|
490
799
|
# Check if we should gracefully exit
|
|
491
800
|
should_exit_gracefully() {
|
|
492
801
|
|
|
@@ -508,21 +817,6 @@ should_exit_gracefully() {
|
|
|
508
817
|
|
|
509
818
|
# Check for exit conditions
|
|
510
819
|
|
|
511
|
-
# 0. Permission denials (highest priority - Issue #101)
|
|
512
|
-
# When Claude Code is denied permission to run commands, halt immediately
|
|
513
|
-
# to allow user to update .ralphrc ALLOWED_TOOLS configuration
|
|
514
|
-
if [[ -f "$RESPONSE_ANALYSIS_FILE" ]]; then
|
|
515
|
-
local has_permission_denials=$(jq -r '.analysis.has_permission_denials // false' "$RESPONSE_ANALYSIS_FILE" 2>/dev/null || echo "false")
|
|
516
|
-
if [[ "$has_permission_denials" == "true" ]]; then
|
|
517
|
-
local denied_count=$(jq -r '.analysis.permission_denial_count // 0' "$RESPONSE_ANALYSIS_FILE" 2>/dev/null || echo "0")
|
|
518
|
-
local denied_cmds=$(jq -r '.analysis.denied_commands | join(", ")' "$RESPONSE_ANALYSIS_FILE" 2>/dev/null || echo "unknown")
|
|
519
|
-
log_status "WARN" "🚫 Permission denied for $denied_count command(s): $denied_cmds"
|
|
520
|
-
log_status "WARN" "Update ALLOWED_TOOLS in .ralphrc to include the required tools"
|
|
521
|
-
echo "permission_denied"
|
|
522
|
-
return 0
|
|
523
|
-
fi
|
|
524
|
-
fi
|
|
525
|
-
|
|
526
820
|
# 1. Too many consecutive test-only loops
|
|
527
821
|
if [[ $recent_test_loops -ge $MAX_CONSECUTIVE_TEST_LOOPS ]]; then
|
|
528
822
|
log_status "WARN" "Exit condition: Too many test-focused loops ($recent_test_loops >= $MAX_CONSECUTIVE_TEST_LOOPS)"
|
|
@@ -568,11 +862,10 @@ should_exit_gracefully() {
|
|
|
568
862
|
# Fix #144: Only match valid markdown checkboxes, not date entries like [2026-01-29]
|
|
569
863
|
# Valid patterns: "- [ ]" (uncompleted) and "- [x]" or "- [X]" (completed)
|
|
570
864
|
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
|
571
|
-
local
|
|
572
|
-
|
|
573
|
-
local
|
|
574
|
-
|
|
575
|
-
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")
|
|
576
869
|
|
|
577
870
|
if [[ $total_items -gt 0 ]] && [[ $completed_items -eq $total_items ]]; then
|
|
578
871
|
log_status "WARN" "Exit condition: All @fix_plan.md items completed ($completed_items/$total_items)" >&2
|
|
@@ -677,8 +970,10 @@ build_loop_context() {
|
|
|
677
970
|
# Extract incomplete tasks from @fix_plan.md
|
|
678
971
|
# Bug #3 Fix: Support indented markdown checkboxes with [[:space:]]* pattern
|
|
679
972
|
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
|
680
|
-
local
|
|
681
|
-
|
|
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")
|
|
682
977
|
context+="Remaining tasks: ${incomplete_tasks}. "
|
|
683
978
|
fi
|
|
684
979
|
|
|
@@ -810,6 +1105,7 @@ save_claude_session() {
|
|
|
810
1105
|
session_id=$(extract_session_id_from_output "$output_file" 2>/dev/null || echo "")
|
|
811
1106
|
if [[ -n "$session_id" && "$session_id" != "null" ]]; then
|
|
812
1107
|
echo "$session_id" > "$CLAUDE_SESSION_FILE"
|
|
1108
|
+
sync_ralph_session_with_driver "$session_id"
|
|
813
1109
|
log_status "INFO" "Saved session: ${session_id:0:20}..."
|
|
814
1110
|
fi
|
|
815
1111
|
fi
|
|
@@ -819,6 +1115,101 @@ save_claude_session() {
|
|
|
819
1115
|
# SESSION LIFECYCLE MANAGEMENT FUNCTIONS (Phase 1.2)
|
|
820
1116
|
# =============================================================================
|
|
821
1117
|
|
|
1118
|
+
write_active_ralph_session() {
|
|
1119
|
+
local session_id=$1
|
|
1120
|
+
local created_at=$2
|
|
1121
|
+
local last_used=${3:-$created_at}
|
|
1122
|
+
|
|
1123
|
+
jq -n \
|
|
1124
|
+
--arg session_id "$session_id" \
|
|
1125
|
+
--arg created_at "$created_at" \
|
|
1126
|
+
--arg last_used "$last_used" \
|
|
1127
|
+
'{
|
|
1128
|
+
session_id: $session_id,
|
|
1129
|
+
created_at: $created_at,
|
|
1130
|
+
last_used: $last_used
|
|
1131
|
+
}' > "$RALPH_SESSION_FILE"
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
write_inactive_ralph_session() {
|
|
1135
|
+
local reset_at=$1
|
|
1136
|
+
local reset_reason=$2
|
|
1137
|
+
|
|
1138
|
+
jq -n \
|
|
1139
|
+
--arg session_id "" \
|
|
1140
|
+
--arg reset_at "$reset_at" \
|
|
1141
|
+
--arg reset_reason "$reset_reason" \
|
|
1142
|
+
'{
|
|
1143
|
+
session_id: $session_id,
|
|
1144
|
+
reset_at: $reset_at,
|
|
1145
|
+
reset_reason: $reset_reason
|
|
1146
|
+
}' > "$RALPH_SESSION_FILE"
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
get_ralph_session_state() {
|
|
1150
|
+
if [[ ! -f "$RALPH_SESSION_FILE" ]]; then
|
|
1151
|
+
echo "missing"
|
|
1152
|
+
return 0
|
|
1153
|
+
fi
|
|
1154
|
+
|
|
1155
|
+
if ! jq empty "$RALPH_SESSION_FILE" 2>/dev/null; then
|
|
1156
|
+
echo "invalid"
|
|
1157
|
+
return 0
|
|
1158
|
+
fi
|
|
1159
|
+
|
|
1160
|
+
local session_id_type
|
|
1161
|
+
session_id_type=$(
|
|
1162
|
+
jq -r 'if has("session_id") then (.session_id | type) else "missing" end' \
|
|
1163
|
+
"$RALPH_SESSION_FILE" 2>/dev/null
|
|
1164
|
+
) || {
|
|
1165
|
+
echo "invalid"
|
|
1166
|
+
return 0
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if [[ "$session_id_type" != "string" ]]; then
|
|
1170
|
+
echo "invalid"
|
|
1171
|
+
return 0
|
|
1172
|
+
fi
|
|
1173
|
+
|
|
1174
|
+
local session_id
|
|
1175
|
+
session_id=$(jq -r '.session_id' "$RALPH_SESSION_FILE" 2>/dev/null) || {
|
|
1176
|
+
echo "invalid"
|
|
1177
|
+
return 0
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
if [[ "$session_id" == "" ]]; then
|
|
1181
|
+
echo "inactive"
|
|
1182
|
+
return 0
|
|
1183
|
+
fi
|
|
1184
|
+
|
|
1185
|
+
local created_at_type
|
|
1186
|
+
created_at_type=$(
|
|
1187
|
+
jq -r 'if has("created_at") then (.created_at | type) else "missing" end' \
|
|
1188
|
+
"$RALPH_SESSION_FILE" 2>/dev/null
|
|
1189
|
+
) || {
|
|
1190
|
+
echo "invalid"
|
|
1191
|
+
return 0
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if [[ "$created_at_type" != "string" ]]; then
|
|
1195
|
+
echo "invalid"
|
|
1196
|
+
return 0
|
|
1197
|
+
fi
|
|
1198
|
+
|
|
1199
|
+
local created_at
|
|
1200
|
+
created_at=$(jq -r '.created_at' "$RALPH_SESSION_FILE" 2>/dev/null) || {
|
|
1201
|
+
echo "invalid"
|
|
1202
|
+
return 0
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if ! is_usable_ralph_session_created_at "$created_at"; then
|
|
1206
|
+
echo "invalid"
|
|
1207
|
+
return 0
|
|
1208
|
+
fi
|
|
1209
|
+
|
|
1210
|
+
echo "active"
|
|
1211
|
+
}
|
|
1212
|
+
|
|
822
1213
|
# Get current session ID from Ralph session file
|
|
823
1214
|
# Returns: session ID string or empty if not found
|
|
824
1215
|
get_session_id() {
|
|
@@ -840,6 +1231,65 @@ get_session_id() {
|
|
|
840
1231
|
return 0
|
|
841
1232
|
}
|
|
842
1233
|
|
|
1234
|
+
is_usable_ralph_session_created_at() {
|
|
1235
|
+
local created_at=$1
|
|
1236
|
+
if [[ -z "$created_at" || "$created_at" == "null" ]]; then
|
|
1237
|
+
return 1
|
|
1238
|
+
fi
|
|
1239
|
+
|
|
1240
|
+
local created_at_epoch
|
|
1241
|
+
created_at_epoch=$(parse_iso_to_epoch_strict "$created_at") || return 1
|
|
1242
|
+
|
|
1243
|
+
local now_epoch
|
|
1244
|
+
now_epoch=$(get_epoch_seconds)
|
|
1245
|
+
|
|
1246
|
+
[[ "$created_at_epoch" -le "$now_epoch" ]]
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
get_active_session_created_at() {
|
|
1250
|
+
if [[ "$(get_ralph_session_state)" != "active" ]]; then
|
|
1251
|
+
echo ""
|
|
1252
|
+
return 0
|
|
1253
|
+
fi
|
|
1254
|
+
|
|
1255
|
+
local created_at
|
|
1256
|
+
created_at=$(jq -r '.created_at // ""' "$RALPH_SESSION_FILE" 2>/dev/null)
|
|
1257
|
+
if [[ "$created_at" == "null" ]]; then
|
|
1258
|
+
created_at=""
|
|
1259
|
+
fi
|
|
1260
|
+
|
|
1261
|
+
if ! is_usable_ralph_session_created_at "$created_at"; then
|
|
1262
|
+
echo ""
|
|
1263
|
+
return 0
|
|
1264
|
+
fi
|
|
1265
|
+
|
|
1266
|
+
echo "$created_at"
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
sync_ralph_session_with_driver() {
|
|
1270
|
+
local driver_session_id=$1
|
|
1271
|
+
if [[ -z "$driver_session_id" || "$driver_session_id" == "null" ]]; then
|
|
1272
|
+
return 0
|
|
1273
|
+
fi
|
|
1274
|
+
|
|
1275
|
+
local ts
|
|
1276
|
+
ts=$(get_iso_timestamp)
|
|
1277
|
+
|
|
1278
|
+
if [[ "$(get_ralph_session_state)" == "active" ]]; then
|
|
1279
|
+
local current_session_id
|
|
1280
|
+
current_session_id=$(get_session_id)
|
|
1281
|
+
local current_created_at
|
|
1282
|
+
current_created_at=$(get_active_session_created_at)
|
|
1283
|
+
|
|
1284
|
+
if [[ "$current_session_id" == "$driver_session_id" && -n "$current_created_at" ]]; then
|
|
1285
|
+
write_active_ralph_session "$driver_session_id" "$current_created_at" "$ts"
|
|
1286
|
+
return 0
|
|
1287
|
+
fi
|
|
1288
|
+
fi
|
|
1289
|
+
|
|
1290
|
+
write_active_ralph_session "$driver_session_id" "$ts" "$ts"
|
|
1291
|
+
}
|
|
1292
|
+
|
|
843
1293
|
# Reset session with reason logging
|
|
844
1294
|
# Usage: reset_session "reason_for_reset"
|
|
845
1295
|
reset_session() {
|
|
@@ -849,20 +1299,7 @@ reset_session() {
|
|
|
849
1299
|
local reset_timestamp
|
|
850
1300
|
reset_timestamp=$(get_iso_timestamp)
|
|
851
1301
|
|
|
852
|
-
|
|
853
|
-
jq -n \
|
|
854
|
-
--arg session_id "" \
|
|
855
|
-
--arg created_at "" \
|
|
856
|
-
--arg last_used "" \
|
|
857
|
-
--arg reset_at "$reset_timestamp" \
|
|
858
|
-
--arg reset_reason "$reason" \
|
|
859
|
-
'{
|
|
860
|
-
session_id: $session_id,
|
|
861
|
-
created_at: $created_at,
|
|
862
|
-
last_used: $last_used,
|
|
863
|
-
reset_at: $reset_at,
|
|
864
|
-
reset_reason: $reset_reason
|
|
865
|
-
}' > "$RALPH_SESSION_FILE"
|
|
1302
|
+
write_inactive_ralph_session "$reset_timestamp" "$reason"
|
|
866
1303
|
|
|
867
1304
|
# Also clear the Claude session file for consistency
|
|
868
1305
|
rm -f "$CLAUDE_SESSION_FILE" 2>/dev/null
|
|
@@ -951,67 +1388,39 @@ init_session_tracking() {
|
|
|
951
1388
|
local ts
|
|
952
1389
|
ts=$(get_iso_timestamp)
|
|
953
1390
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
new_session_id=$(generate_session_id)
|
|
958
|
-
|
|
959
|
-
jq -n \
|
|
960
|
-
--arg session_id "$new_session_id" \
|
|
961
|
-
--arg created_at "$ts" \
|
|
962
|
-
--arg last_used "$ts" \
|
|
963
|
-
--arg reset_at "" \
|
|
964
|
-
--arg reset_reason "" \
|
|
965
|
-
'{
|
|
966
|
-
session_id: $session_id,
|
|
967
|
-
created_at: $created_at,
|
|
968
|
-
last_used: $last_used,
|
|
969
|
-
reset_at: $reset_at,
|
|
970
|
-
reset_reason: $reset_reason
|
|
971
|
-
}' > "$RALPH_SESSION_FILE"
|
|
972
|
-
|
|
973
|
-
log_status "INFO" "Initialized session tracking (session: $new_session_id)"
|
|
1391
|
+
local session_state
|
|
1392
|
+
session_state=$(get_ralph_session_state)
|
|
1393
|
+
if [[ "$session_state" == "active" ]]; then
|
|
974
1394
|
return 0
|
|
975
1395
|
fi
|
|
976
1396
|
|
|
977
|
-
|
|
978
|
-
if ! jq empty "$RALPH_SESSION_FILE" 2>/dev/null; then
|
|
1397
|
+
if [[ "$session_state" == "invalid" ]]; then
|
|
979
1398
|
log_status "WARN" "Corrupted session file detected, recreating..."
|
|
980
|
-
local new_session_id
|
|
981
|
-
new_session_id=$(generate_session_id)
|
|
982
|
-
|
|
983
|
-
jq -n \
|
|
984
|
-
--arg session_id "$new_session_id" \
|
|
985
|
-
--arg created_at "$ts" \
|
|
986
|
-
--arg last_used "$ts" \
|
|
987
|
-
--arg reset_at "$ts" \
|
|
988
|
-
--arg reset_reason "corrupted_file_recovery" \
|
|
989
|
-
'{
|
|
990
|
-
session_id: $session_id,
|
|
991
|
-
created_at: $created_at,
|
|
992
|
-
last_used: $last_used,
|
|
993
|
-
reset_at: $reset_at,
|
|
994
|
-
reset_reason: $reset_reason
|
|
995
|
-
}' > "$RALPH_SESSION_FILE"
|
|
996
1399
|
fi
|
|
1400
|
+
|
|
1401
|
+
local new_session_id
|
|
1402
|
+
new_session_id=$(generate_session_id)
|
|
1403
|
+
write_active_ralph_session "$new_session_id" "$ts" "$ts"
|
|
1404
|
+
|
|
1405
|
+
log_status "INFO" "Initialized session tracking (session: $new_session_id)"
|
|
997
1406
|
}
|
|
998
1407
|
|
|
999
1408
|
# Update last_used timestamp in session file (called on each loop iteration)
|
|
1000
1409
|
update_session_last_used() {
|
|
1001
|
-
if [[
|
|
1410
|
+
if [[ "$(get_ralph_session_state)" != "active" ]]; then
|
|
1002
1411
|
return 0
|
|
1003
1412
|
fi
|
|
1004
1413
|
|
|
1005
1414
|
local ts
|
|
1006
1415
|
ts=$(get_iso_timestamp)
|
|
1007
1416
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1417
|
+
local session_id
|
|
1418
|
+
session_id=$(get_session_id)
|
|
1419
|
+
local created_at
|
|
1420
|
+
created_at=$(get_active_session_created_at)
|
|
1012
1421
|
|
|
1013
|
-
if [[
|
|
1014
|
-
|
|
1422
|
+
if [[ -n "$session_id" && -n "$created_at" ]]; then
|
|
1423
|
+
write_active_ralph_session "$session_id" "$created_at" "$ts"
|
|
1015
1424
|
fi
|
|
1016
1425
|
}
|
|
1017
1426
|
|
|
@@ -1072,6 +1481,8 @@ execute_claude_code() {
|
|
|
1072
1481
|
local loop_count=$1
|
|
1073
1482
|
local calls_made=$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")
|
|
1074
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")
|
|
1075
1486
|
|
|
1076
1487
|
# Fix #141: Capture git HEAD SHA at loop start to detect commits as progress
|
|
1077
1488
|
# Store in file for access by progress detection after Claude execution
|
|
@@ -1326,6 +1737,10 @@ EOF
|
|
|
1326
1737
|
analyze_response "$output_file" "$loop_count"
|
|
1327
1738
|
local analysis_exit_code=$?
|
|
1328
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
|
+
|
|
1329
1744
|
# Update exit signals based on analysis
|
|
1330
1745
|
update_exit_signals
|
|
1331
1746
|
|
|
@@ -1435,11 +1850,31 @@ loop_count=0
|
|
|
1435
1850
|
main() {
|
|
1436
1851
|
initialize_runtime_context
|
|
1437
1852
|
|
|
1438
|
-
|
|
1439
|
-
if [[ "${_CLI_ALLOWED_TOOLS:-}" == "true" ]] && ! validate_allowed_tools "$CLAUDE_ALLOWED_TOOLS"; then
|
|
1853
|
+
if ! validate_permission_denial_mode "$PERMISSION_DENIAL_MODE"; then
|
|
1440
1854
|
exit 1
|
|
1441
1855
|
fi
|
|
1442
1856
|
|
|
1857
|
+
if [[ "$(driver_name)" == "claude-code" ]]; then
|
|
1858
|
+
normalize_claude_permission_mode
|
|
1859
|
+
|
|
1860
|
+
if ! validate_claude_permission_mode "$CLAUDE_PERMISSION_MODE"; then
|
|
1861
|
+
exit 1
|
|
1862
|
+
fi
|
|
1863
|
+
fi
|
|
1864
|
+
|
|
1865
|
+
if driver_supports_tool_allowlist; then
|
|
1866
|
+
# Validate --allowed-tools now that platform-specific VALID_TOOL_PATTERNS are loaded
|
|
1867
|
+
if [[ "${_CLI_ALLOWED_TOOLS:-}" == "true" ]] && ! validate_allowed_tools "$CLAUDE_ALLOWED_TOOLS"; then
|
|
1868
|
+
exit 1
|
|
1869
|
+
fi
|
|
1870
|
+
else
|
|
1871
|
+
warn_if_allowed_tools_ignored
|
|
1872
|
+
fi
|
|
1873
|
+
|
|
1874
|
+
if [[ "${_CLI_ALLOWED_TOOLS:-}" == "true" ]] && ! driver_supports_tool_allowlist; then
|
|
1875
|
+
_CLI_ALLOWED_TOOLS=""
|
|
1876
|
+
fi
|
|
1877
|
+
|
|
1443
1878
|
log_status "SUCCESS" "🚀 Ralph loop starting with $DRIVER_DISPLAY_NAME"
|
|
1444
1879
|
log_status "INFO" "Max calls per hour: $MAX_CALLS_PER_HOUR"
|
|
1445
1880
|
log_status "INFO" "Logs: $LOG_DIR/ | Docs: $DOCS_DIR/ | Status: $STATUS_FILE"
|
|
@@ -1531,45 +1966,6 @@ main() {
|
|
|
1531
1966
|
# Check for graceful exit conditions
|
|
1532
1967
|
local exit_reason=$(should_exit_gracefully)
|
|
1533
1968
|
if [[ "$exit_reason" != "" ]]; then
|
|
1534
|
-
# Handle permission_denied specially (Issue #101)
|
|
1535
|
-
if [[ "$exit_reason" == "permission_denied" ]]; then
|
|
1536
|
-
log_status "ERROR" "🚫 Permission denied - halting loop"
|
|
1537
|
-
reset_session "permission_denied"
|
|
1538
|
-
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "permission_denied" "halted" "permission_denied"
|
|
1539
|
-
|
|
1540
|
-
# Display helpful guidance for resolving permission issues
|
|
1541
|
-
echo ""
|
|
1542
|
-
echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}"
|
|
1543
|
-
echo -e "${RED}║ PERMISSION DENIED - Loop Halted ║${NC}"
|
|
1544
|
-
echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
|
|
1545
|
-
echo ""
|
|
1546
|
-
echo -e "${YELLOW}$DRIVER_DISPLAY_NAME was denied permission to execute commands.${NC}"
|
|
1547
|
-
echo ""
|
|
1548
|
-
echo -e "${YELLOW}To fix this:${NC}"
|
|
1549
|
-
echo " 1. Edit .ralphrc and update ALLOWED_TOOLS to include the required tools"
|
|
1550
|
-
echo " 2. Common patterns:"
|
|
1551
|
-
echo " - Bash(npm *) - All npm commands"
|
|
1552
|
-
echo " - Bash(npm install) - Only npm install"
|
|
1553
|
-
echo " - Bash(pnpm *) - All pnpm commands"
|
|
1554
|
-
echo " - Bash(yarn *) - All yarn commands"
|
|
1555
|
-
echo ""
|
|
1556
|
-
echo -e "${YELLOW}After updating .ralphrc:${NC}"
|
|
1557
|
-
echo " bash .ralph/ralph_loop.sh --reset-session # Clear stale session state"
|
|
1558
|
-
echo " bmalph run # Restart the loop"
|
|
1559
|
-
echo ""
|
|
1560
|
-
|
|
1561
|
-
# Show current ALLOWED_TOOLS if .ralphrc exists
|
|
1562
|
-
if [[ -f ".ralphrc" ]]; then
|
|
1563
|
-
local current_tools=$(grep "^ALLOWED_TOOLS=" ".ralphrc" 2>/dev/null | cut -d= -f2- | tr -d '"')
|
|
1564
|
-
if [[ -n "$current_tools" ]]; then
|
|
1565
|
-
echo -e "${BLUE}Current ALLOWED_TOOLS:${NC} $current_tools"
|
|
1566
|
-
echo ""
|
|
1567
|
-
fi
|
|
1568
|
-
fi
|
|
1569
|
-
|
|
1570
|
-
break
|
|
1571
|
-
fi
|
|
1572
|
-
|
|
1573
1969
|
log_status "SUCCESS" "🏁 Graceful exit triggered: $exit_reason"
|
|
1574
1970
|
reset_session "project_complete"
|
|
1575
1971
|
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "graceful_exit" "completed" "$exit_reason"
|
|
@@ -1591,6 +1987,17 @@ main() {
|
|
|
1591
1987
|
local exec_result=$?
|
|
1592
1988
|
|
|
1593
1989
|
if [ $exec_result -eq 0 ]; then
|
|
1990
|
+
if consume_current_loop_permission_denial "$loop_count"; then
|
|
1991
|
+
if [[ "$PERMISSION_DENIAL_ACTION" == "halt" ]]; then
|
|
1992
|
+
break
|
|
1993
|
+
fi
|
|
1994
|
+
|
|
1995
|
+
# Brief pause between loops when the denial was recorded but
|
|
1996
|
+
# policy allows Ralph to continue.
|
|
1997
|
+
sleep 5
|
|
1998
|
+
continue
|
|
1999
|
+
fi
|
|
2000
|
+
|
|
1594
2001
|
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "completed" "success"
|
|
1595
2002
|
|
|
1596
2003
|
# Brief pause between successful executions
|
|
@@ -1676,7 +2083,7 @@ Options:
|
|
|
1676
2083
|
Modern CLI Options (Phase 1.1):
|
|
1677
2084
|
--output-format FORMAT Set driver output format: json or text (default: $CLAUDE_OUTPUT_FORMAT)
|
|
1678
2085
|
Note: --live mode requires JSON and will auto-switch
|
|
1679
|
-
--allowed-tools TOOLS
|
|
2086
|
+
--allowed-tools TOOLS Claude Code only. Ignored by codex, cursor, and copilot
|
|
1680
2087
|
--no-continue Disable session continuity across loops
|
|
1681
2088
|
--session-expiry HOURS Set session expiration time in hours (default: $CLAUDE_SESSION_EXPIRY_HOURS)
|
|
1682
2089
|
|
|
@@ -1722,6 +2129,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
1722
2129
|
;;
|
|
1723
2130
|
-c|--calls)
|
|
1724
2131
|
MAX_CALLS_PER_HOUR="$2"
|
|
2132
|
+
_cli_MAX_CALLS_PER_HOUR="$MAX_CALLS_PER_HOUR"
|
|
2133
|
+
_CLI_MAX_CALLS_PER_HOUR=true
|
|
1725
2134
|
shift 2
|
|
1726
2135
|
;;
|
|
1727
2136
|
-p|--prompt)
|
|
@@ -1743,6 +2152,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
1743
2152
|
;;
|
|
1744
2153
|
-v|--verbose)
|
|
1745
2154
|
VERBOSE_PROGRESS=true
|
|
2155
|
+
_cli_VERBOSE_PROGRESS="$VERBOSE_PROGRESS"
|
|
2156
|
+
_CLI_VERBOSE_PROGRESS=true
|
|
1746
2157
|
shift
|
|
1747
2158
|
;;
|
|
1748
2159
|
-l|--live)
|
|
@@ -1752,6 +2163,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
1752
2163
|
-t|--timeout)
|
|
1753
2164
|
if [[ "$2" =~ ^[1-9][0-9]*$ ]] && [[ "$2" -le 120 ]]; then
|
|
1754
2165
|
CLAUDE_TIMEOUT_MINUTES="$2"
|
|
2166
|
+
_cli_CLAUDE_TIMEOUT_MINUTES="$CLAUDE_TIMEOUT_MINUTES"
|
|
2167
|
+
_CLI_CLAUDE_TIMEOUT_MINUTES=true
|
|
1755
2168
|
else
|
|
1756
2169
|
echo "Error: Timeout must be a positive integer between 1 and 120 minutes"
|
|
1757
2170
|
exit 1
|
|
@@ -1785,6 +2198,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
1785
2198
|
--output-format)
|
|
1786
2199
|
if [[ "$2" == "json" || "$2" == "text" ]]; then
|
|
1787
2200
|
CLAUDE_OUTPUT_FORMAT="$2"
|
|
2201
|
+
_cli_CLAUDE_OUTPUT_FORMAT="$CLAUDE_OUTPUT_FORMAT"
|
|
2202
|
+
_CLI_CLAUDE_OUTPUT_FORMAT=true
|
|
1788
2203
|
else
|
|
1789
2204
|
echo "Error: --output-format must be 'json' or 'text'"
|
|
1790
2205
|
exit 1
|
|
@@ -1793,11 +2208,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
1793
2208
|
;;
|
|
1794
2209
|
--allowed-tools)
|
|
1795
2210
|
CLAUDE_ALLOWED_TOOLS="$2"
|
|
2211
|
+
_cli_CLAUDE_ALLOWED_TOOLS="$2"
|
|
1796
2212
|
_CLI_ALLOWED_TOOLS=true
|
|
1797
2213
|
shift 2
|
|
1798
2214
|
;;
|
|
1799
2215
|
--no-continue)
|
|
1800
2216
|
CLAUDE_USE_CONTINUE=false
|
|
2217
|
+
_cli_CLAUDE_USE_CONTINUE="$CLAUDE_USE_CONTINUE"
|
|
2218
|
+
_CLI_SESSION_CONTINUITY=true
|
|
1801
2219
|
shift
|
|
1802
2220
|
;;
|
|
1803
2221
|
--session-expiry)
|
|
@@ -1806,6 +2224,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
1806
2224
|
exit 1
|
|
1807
2225
|
fi
|
|
1808
2226
|
CLAUDE_SESSION_EXPIRY_HOURS="$2"
|
|
2227
|
+
_cli_CLAUDE_SESSION_EXPIRY_HOURS="$2"
|
|
2228
|
+
_CLI_SESSION_EXPIRY_HOURS=true
|
|
1809
2229
|
shift 2
|
|
1810
2230
|
;;
|
|
1811
2231
|
--auto-reset-circuit)
|