@the-bearded-bear/claude-craft 3.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dev/i18n/de/Common/agents/ralph-conductor.md +146 -0
- package/Dev/i18n/de/Common/commands/ralph-run.md +171 -0
- package/Dev/i18n/de/Common/commands/setup-project-context.md +286 -0
- package/Dev/i18n/en/Common/agents/ralph-conductor.md +146 -0
- package/Dev/i18n/en/Common/commands/ralph-run.md +171 -0
- package/Dev/i18n/en/Common/commands/setup-project-context.md +286 -0
- package/Dev/i18n/es/Common/agents/ralph-conductor.md +146 -0
- package/Dev/i18n/es/Common/commands/ralph-run.md +171 -0
- package/Dev/i18n/es/Common/commands/setup-project-context.md +286 -0
- package/Dev/i18n/fr/Common/agents/ralph-conductor.md +146 -0
- package/Dev/i18n/fr/Common/commands/ralph-run.md +171 -0
- package/Dev/i18n/fr/Common/commands/setup-project-context.md +286 -0
- package/Dev/i18n/pt/Common/agents/ralph-conductor.md +146 -0
- package/Dev/i18n/pt/Common/commands/ralph-run.md +171 -0
- package/Dev/i18n/pt/Common/commands/setup-project-context.md +286 -0
- package/Tools/Ralph/README.md +303 -0
- package/Tools/Ralph/lib/checkpoint.sh +238 -0
- package/Tools/Ralph/lib/circuit-breaker.sh +172 -0
- package/Tools/Ralph/lib/dod-validator.sh +306 -0
- package/Tools/Ralph/lib/loop.sh +232 -0
- package/Tools/Ralph/lib/session.sh +234 -0
- package/Tools/Ralph/ralph.sh +491 -0
- package/Tools/Ralph/templates/ralph.yml.template +178 -0
- package/Tools/i18n/ralph/de.sh +147 -0
- package/Tools/i18n/ralph/en.sh +147 -0
- package/Tools/i18n/ralph/es.sh +147 -0
- package/Tools/i18n/ralph/fr.sh +147 -0
- package/Tools/i18n/ralph/pt.sh +147 -0
- package/cli/index.js +90 -0
- package/package.json +1 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Ralph Wiggum - Circuit Breaker Module
|
|
4
|
+
# Safety mechanism to prevent infinite loops and detect stalls
|
|
5
|
+
# =============================================================================
|
|
6
|
+
|
|
7
|
+
# Circuit breaker thresholds (defaults)
|
|
8
|
+
CB_NO_CHANGES_THRESHOLD="${CB_NO_CHANGES_THRESHOLD:-3}"
|
|
9
|
+
CB_REPEATED_ERROR_THRESHOLD="${CB_REPEATED_ERROR_THRESHOLD:-5}"
|
|
10
|
+
CB_OUTPUT_DECLINE_THRESHOLD="${CB_OUTPUT_DECLINE_THRESHOLD:-70}"
|
|
11
|
+
|
|
12
|
+
# Circuit breaker state
|
|
13
|
+
CB_ITERATIONS_WITHOUT_CHANGES=0
|
|
14
|
+
CB_CONSECUTIVE_ERRORS=0
|
|
15
|
+
CB_PEAK_OUTPUT_LENGTH=0
|
|
16
|
+
CB_LAST_OUTPUT_LENGTH=0
|
|
17
|
+
CB_TRIGGERED=false
|
|
18
|
+
CB_TRIGGER_REASON=""
|
|
19
|
+
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# Initialization
|
|
22
|
+
# =============================================================================
|
|
23
|
+
|
|
24
|
+
init_circuit_breaker() {
|
|
25
|
+
CB_ITERATIONS_WITHOUT_CHANGES=0
|
|
26
|
+
CB_CONSECUTIVE_ERRORS=0
|
|
27
|
+
CB_PEAK_OUTPUT_LENGTH=0
|
|
28
|
+
CB_LAST_OUTPUT_LENGTH=0
|
|
29
|
+
CB_TRIGGERED=false
|
|
30
|
+
CB_TRIGGER_REASON=""
|
|
31
|
+
|
|
32
|
+
# Load thresholds from config if available
|
|
33
|
+
if [[ -n "$CONFIG_FILE" && -f "$CONFIG_FILE" ]] && command -v yq &> /dev/null; then
|
|
34
|
+
local no_changes=$(yq e '.circuit_breaker.no_file_changes_threshold // ""' "$CONFIG_FILE" 2>/dev/null)
|
|
35
|
+
[[ -n "$no_changes" ]] && CB_NO_CHANGES_THRESHOLD=$no_changes
|
|
36
|
+
|
|
37
|
+
local errors=$(yq e '.circuit_breaker.repeated_error_threshold // ""' "$CONFIG_FILE" 2>/dev/null)
|
|
38
|
+
[[ -n "$errors" ]] && CB_REPEATED_ERROR_THRESHOLD=$errors
|
|
39
|
+
|
|
40
|
+
local decline=$(yq e '.circuit_breaker.output_decline_threshold // ""' "$CONFIG_FILE" 2>/dev/null)
|
|
41
|
+
[[ -n "$decline" ]] && CB_OUTPUT_DECLINE_THRESHOLD=$decline
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
print_verbose "Circuit breaker initialized:"
|
|
45
|
+
print_verbose " - No changes threshold: $CB_NO_CHANGES_THRESHOLD"
|
|
46
|
+
print_verbose " - Error threshold: $CB_REPEATED_ERROR_THRESHOLD"
|
|
47
|
+
print_verbose " - Output decline threshold: ${CB_OUTPUT_DECLINE_THRESHOLD}%"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# =============================================================================
|
|
51
|
+
# Circuit Breaker Check
|
|
52
|
+
# =============================================================================
|
|
53
|
+
|
|
54
|
+
check_circuit_breaker() {
|
|
55
|
+
# Return 0 (true) if triggered, 1 (false) if OK to continue
|
|
56
|
+
|
|
57
|
+
# Check no progress (no file changes)
|
|
58
|
+
if [[ $CB_ITERATIONS_WITHOUT_CHANGES -ge $CB_NO_CHANGES_THRESHOLD ]]; then
|
|
59
|
+
CB_TRIGGERED=true
|
|
60
|
+
CB_TRIGGER_REASON="no_progress"
|
|
61
|
+
print_warning "${MSG_CB_TRIGGERED}: ${MSG_CB_NO_CHANGES} $CB_ITERATIONS_WITHOUT_CHANGES ${MSG_CB_ITERATIONS}"
|
|
62
|
+
return 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Check repeated errors
|
|
66
|
+
if [[ $CB_CONSECUTIVE_ERRORS -ge $CB_REPEATED_ERROR_THRESHOLD ]]; then
|
|
67
|
+
CB_TRIGGERED=true
|
|
68
|
+
CB_TRIGGER_REASON="repeated_errors"
|
|
69
|
+
print_warning "${MSG_CB_TRIGGERED}: ${MSG_CB_REPEATED_ERRORS}"
|
|
70
|
+
return 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Check output decline
|
|
74
|
+
if [[ $CB_PEAK_OUTPUT_LENGTH -gt 0 && $CB_LAST_OUTPUT_LENGTH -gt 0 ]]; then
|
|
75
|
+
local decline_percent=$(( (CB_PEAK_OUTPUT_LENGTH - CB_LAST_OUTPUT_LENGTH) * 100 / CB_PEAK_OUTPUT_LENGTH ))
|
|
76
|
+
if [[ $decline_percent -ge $CB_OUTPUT_DECLINE_THRESHOLD ]]; then
|
|
77
|
+
CB_TRIGGERED=true
|
|
78
|
+
CB_TRIGGER_REASON="output_decline"
|
|
79
|
+
print_warning "${MSG_CB_TRIGGERED}: ${MSG_CB_OUTPUT_DECLINE} ${decline_percent}${MSG_CB_PERCENT}"
|
|
80
|
+
return 0
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
return 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# =============================================================================
|
|
88
|
+
# Update Circuit Breaker State
|
|
89
|
+
# =============================================================================
|
|
90
|
+
|
|
91
|
+
update_circuit_breaker() {
|
|
92
|
+
local event_type="$1"
|
|
93
|
+
local output="$2"
|
|
94
|
+
|
|
95
|
+
case "$event_type" in
|
|
96
|
+
progress)
|
|
97
|
+
# Reset counters on progress
|
|
98
|
+
CB_ITERATIONS_WITHOUT_CHANGES=0
|
|
99
|
+
CB_CONSECUTIVE_ERRORS=0
|
|
100
|
+
|
|
101
|
+
# Update output tracking
|
|
102
|
+
local output_length=${#output}
|
|
103
|
+
CB_LAST_OUTPUT_LENGTH=$output_length
|
|
104
|
+
if [[ $output_length -gt $CB_PEAK_OUTPUT_LENGTH ]]; then
|
|
105
|
+
CB_PEAK_OUTPUT_LENGTH=$output_length
|
|
106
|
+
fi
|
|
107
|
+
;;
|
|
108
|
+
|
|
109
|
+
no_progress)
|
|
110
|
+
CB_ITERATIONS_WITHOUT_CHANGES=$((CB_ITERATIONS_WITHOUT_CHANGES + 1))
|
|
111
|
+
print_verbose "No progress: $CB_ITERATIONS_WITHOUT_CHANGES / $CB_NO_CHANGES_THRESHOLD"
|
|
112
|
+
;;
|
|
113
|
+
|
|
114
|
+
error)
|
|
115
|
+
CB_CONSECUTIVE_ERRORS=$((CB_CONSECUTIVE_ERRORS + 1))
|
|
116
|
+
print_verbose "Error count: $CB_CONSECUTIVE_ERRORS / $CB_REPEATED_ERROR_THRESHOLD"
|
|
117
|
+
;;
|
|
118
|
+
|
|
119
|
+
reset)
|
|
120
|
+
CB_ITERATIONS_WITHOUT_CHANGES=0
|
|
121
|
+
CB_CONSECUTIVE_ERRORS=0
|
|
122
|
+
CB_TRIGGERED=false
|
|
123
|
+
CB_TRIGGER_REASON=""
|
|
124
|
+
print_verbose "${MSG_CB_RESET}"
|
|
125
|
+
;;
|
|
126
|
+
esac
|
|
127
|
+
|
|
128
|
+
# Update session state if available
|
|
129
|
+
if [[ -n "$SESSION_ID" ]]; then
|
|
130
|
+
update_session_state "$SESSION_ID" "circuit_breaker" "{
|
|
131
|
+
\"iterations_without_changes\": $CB_ITERATIONS_WITHOUT_CHANGES,
|
|
132
|
+
\"consecutive_errors\": $CB_CONSECUTIVE_ERRORS,
|
|
133
|
+
\"peak_output_length\": $CB_PEAK_OUTPUT_LENGTH,
|
|
134
|
+
\"last_output_length\": $CB_LAST_OUTPUT_LENGTH,
|
|
135
|
+
\"triggered\": $CB_TRIGGERED,
|
|
136
|
+
\"trigger_reason\": \"$CB_TRIGGER_REASON\"
|
|
137
|
+
}"
|
|
138
|
+
fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# =============================================================================
|
|
142
|
+
# Circuit Breaker Status
|
|
143
|
+
# =============================================================================
|
|
144
|
+
|
|
145
|
+
get_circuit_breaker_status() {
|
|
146
|
+
cat <<EOF
|
|
147
|
+
{
|
|
148
|
+
"iterations_without_changes": $CB_ITERATIONS_WITHOUT_CHANGES,
|
|
149
|
+
"consecutive_errors": $CB_CONSECUTIVE_ERRORS,
|
|
150
|
+
"peak_output_length": $CB_PEAK_OUTPUT_LENGTH,
|
|
151
|
+
"last_output_length": $CB_LAST_OUTPUT_LENGTH,
|
|
152
|
+
"triggered": $CB_TRIGGERED,
|
|
153
|
+
"trigger_reason": "$CB_TRIGGER_REASON",
|
|
154
|
+
"thresholds": {
|
|
155
|
+
"no_changes": $CB_NO_CHANGES_THRESHOLD,
|
|
156
|
+
"errors": $CB_REPEATED_ERROR_THRESHOLD,
|
|
157
|
+
"output_decline": $CB_OUTPUT_DECLINE_THRESHOLD
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
EOF
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# =============================================================================
|
|
164
|
+
# Force Trip
|
|
165
|
+
# =============================================================================
|
|
166
|
+
|
|
167
|
+
trip_circuit_breaker() {
|
|
168
|
+
local reason="$1"
|
|
169
|
+
CB_TRIGGERED=true
|
|
170
|
+
CB_TRIGGER_REASON="${reason:-manual}"
|
|
171
|
+
print_warning "${MSG_CB_TRIGGERED}: $CB_TRIGGER_REASON"
|
|
172
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Ralph Wiggum - Definition of Done Validator Module
|
|
4
|
+
# Validates completion criteria using configurable checklist
|
|
5
|
+
# =============================================================================
|
|
6
|
+
|
|
7
|
+
# Default completion marker
|
|
8
|
+
DOD_COMPLETION_MARKER="${DOD_COMPLETION_MARKER:-<promise>COMPLETE</promise>}"
|
|
9
|
+
|
|
10
|
+
# DoD validator types
|
|
11
|
+
declare -A DOD_VALIDATORS=(
|
|
12
|
+
["command"]="validate_command"
|
|
13
|
+
["output_contains"]="validate_output_contains"
|
|
14
|
+
["file_changed"]="validate_file_changed"
|
|
15
|
+
["hook"]="validate_hook"
|
|
16
|
+
["human"]="validate_human"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# =============================================================================
|
|
20
|
+
# Main DoD Validation
|
|
21
|
+
# =============================================================================
|
|
22
|
+
|
|
23
|
+
validate_dod() {
|
|
24
|
+
local output="$1"
|
|
25
|
+
local config_file="$2"
|
|
26
|
+
|
|
27
|
+
# If no config, use simple completion marker check
|
|
28
|
+
if [[ -z "$config_file" || ! -f "$config_file" ]]; then
|
|
29
|
+
return $(validate_simple_completion "$output")
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Check if yq is available for YAML parsing
|
|
33
|
+
if ! command -v yq &> /dev/null; then
|
|
34
|
+
return $(validate_simple_completion "$output")
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Get DoD checklist from config
|
|
38
|
+
local checklist=$(yq e '.definition_of_done.checklist // []' "$config_file" 2>/dev/null)
|
|
39
|
+
|
|
40
|
+
# If no checklist, use simple completion marker
|
|
41
|
+
if [[ "$checklist" == "[]" || -z "$checklist" ]]; then
|
|
42
|
+
local marker=$(yq e '.definition_of_done.completion_marker // ""' "$config_file" 2>/dev/null)
|
|
43
|
+
if [[ -n "$marker" ]]; then
|
|
44
|
+
DOD_COMPLETION_MARKER="$marker"
|
|
45
|
+
fi
|
|
46
|
+
return $(validate_simple_completion "$output")
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Process checklist items
|
|
50
|
+
local all_required_passed=true
|
|
51
|
+
local results=()
|
|
52
|
+
|
|
53
|
+
# Get count of checklist items
|
|
54
|
+
local count=$(echo "$checklist" | yq e 'length' -)
|
|
55
|
+
|
|
56
|
+
for ((i=0; i<count; i++)); do
|
|
57
|
+
local item=$(yq e ".definition_of_done.checklist[$i]" "$config_file")
|
|
58
|
+
local id=$(echo "$item" | yq e '.id' -)
|
|
59
|
+
local name=$(echo "$item" | yq e '.name' -)
|
|
60
|
+
local type=$(echo "$item" | yq e '.type' -)
|
|
61
|
+
local required=$(echo "$item" | yq e '.required // true' -)
|
|
62
|
+
|
|
63
|
+
# Validate based on type
|
|
64
|
+
local passed=false
|
|
65
|
+
local message=""
|
|
66
|
+
|
|
67
|
+
case "$type" in
|
|
68
|
+
command)
|
|
69
|
+
local cmd=$(echo "$item" | yq e '.command' -)
|
|
70
|
+
if validate_command "$cmd"; then
|
|
71
|
+
passed=true
|
|
72
|
+
message="Command passed"
|
|
73
|
+
else
|
|
74
|
+
message="Command failed"
|
|
75
|
+
fi
|
|
76
|
+
;;
|
|
77
|
+
output_contains)
|
|
78
|
+
local pattern=$(echo "$item" | yq e '.pattern' -)
|
|
79
|
+
if validate_output_contains "$output" "$pattern"; then
|
|
80
|
+
passed=true
|
|
81
|
+
message="Pattern found"
|
|
82
|
+
else
|
|
83
|
+
message="Pattern not found"
|
|
84
|
+
fi
|
|
85
|
+
;;
|
|
86
|
+
file_changed)
|
|
87
|
+
local pattern=$(echo "$item" | yq e '.pattern' -)
|
|
88
|
+
if validate_file_changed "$pattern"; then
|
|
89
|
+
passed=true
|
|
90
|
+
message="Files changed"
|
|
91
|
+
else
|
|
92
|
+
message="No files changed"
|
|
93
|
+
fi
|
|
94
|
+
;;
|
|
95
|
+
hook)
|
|
96
|
+
local script=$(echo "$item" | yq e '.script' -)
|
|
97
|
+
if validate_hook "$script" "$output"; then
|
|
98
|
+
passed=true
|
|
99
|
+
message="Hook passed"
|
|
100
|
+
else
|
|
101
|
+
message="Hook failed"
|
|
102
|
+
fi
|
|
103
|
+
;;
|
|
104
|
+
human)
|
|
105
|
+
local prompt=$(echo "$item" | yq e '.prompt // ""' -)
|
|
106
|
+
if validate_human "$name" "$prompt"; then
|
|
107
|
+
passed=true
|
|
108
|
+
message="Human approved"
|
|
109
|
+
else
|
|
110
|
+
message="Human rejected"
|
|
111
|
+
fi
|
|
112
|
+
;;
|
|
113
|
+
*)
|
|
114
|
+
message="Unknown validator type: $type"
|
|
115
|
+
;;
|
|
116
|
+
esac
|
|
117
|
+
|
|
118
|
+
# Print result
|
|
119
|
+
if [[ "$passed" == "true" ]]; then
|
|
120
|
+
echo -e " ${C_GREEN}✓${C_RESET} [$id] $name - ${C_GREEN}${MSG_DOD_ITEM_PASSED}${C_RESET}"
|
|
121
|
+
else
|
|
122
|
+
if [[ "$required" == "true" ]]; then
|
|
123
|
+
echo -e " ${C_RED}✗${C_RESET} [$id] $name - ${C_RED}${MSG_DOD_ITEM_FAILED}${C_RESET} (${MSG_DOD_REQUIRED})"
|
|
124
|
+
all_required_passed=false
|
|
125
|
+
else
|
|
126
|
+
echo -e " ${C_YELLOW}○${C_RESET} [$id] $name - ${C_YELLOW}${MSG_DOD_ITEM_SKIPPED}${C_RESET} (${MSG_DOD_OPTIONAL})"
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Store result
|
|
131
|
+
results+=("{\"id\":\"$id\",\"passed\":$passed,\"required\":$required,\"message\":\"$message\"}")
|
|
132
|
+
done
|
|
133
|
+
|
|
134
|
+
# Return overall result
|
|
135
|
+
if [[ "$all_required_passed" == "true" ]]; then
|
|
136
|
+
echo -e "\n ${C_GREEN}${MSG_DOD_ALL_REQUIRED_PASSED}${C_RESET}"
|
|
137
|
+
return 0
|
|
138
|
+
else
|
|
139
|
+
echo -e "\n ${C_RED}${MSG_DOD_MISSING_REQUIRED}${C_RESET}"
|
|
140
|
+
return 1
|
|
141
|
+
fi
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# =============================================================================
|
|
145
|
+
# Simple Completion Check
|
|
146
|
+
# =============================================================================
|
|
147
|
+
|
|
148
|
+
validate_simple_completion() {
|
|
149
|
+
local output="$1"
|
|
150
|
+
|
|
151
|
+
if echo "$output" | grep -qF "$DOD_COMPLETION_MARKER"; then
|
|
152
|
+
echo -e " ${C_GREEN}✓${C_RESET} Completion marker found"
|
|
153
|
+
return 0
|
|
154
|
+
else
|
|
155
|
+
echo -e " ${C_YELLOW}○${C_RESET} Completion marker not found"
|
|
156
|
+
return 1
|
|
157
|
+
fi
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# =============================================================================
|
|
161
|
+
# Validator: Command
|
|
162
|
+
# =============================================================================
|
|
163
|
+
|
|
164
|
+
validate_command() {
|
|
165
|
+
local cmd="$1"
|
|
166
|
+
|
|
167
|
+
echo -e " ${C_DIM}${MSG_VALIDATOR_COMMAND}: $cmd${C_RESET}"
|
|
168
|
+
|
|
169
|
+
# Execute the command
|
|
170
|
+
if eval "$cmd" > /dev/null 2>&1; then
|
|
171
|
+
return 0
|
|
172
|
+
else
|
|
173
|
+
return 1
|
|
174
|
+
fi
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# =============================================================================
|
|
178
|
+
# Validator: Output Contains Pattern
|
|
179
|
+
# =============================================================================
|
|
180
|
+
|
|
181
|
+
validate_output_contains() {
|
|
182
|
+
local output="$1"
|
|
183
|
+
local pattern="$2"
|
|
184
|
+
|
|
185
|
+
echo -e " ${C_DIM}${MSG_VALIDATOR_OUTPUT}: $pattern${C_RESET}"
|
|
186
|
+
|
|
187
|
+
if echo "$output" | grep -qE "$pattern"; then
|
|
188
|
+
return 0
|
|
189
|
+
else
|
|
190
|
+
return 1
|
|
191
|
+
fi
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# =============================================================================
|
|
195
|
+
# Validator: File Changed
|
|
196
|
+
# =============================================================================
|
|
197
|
+
|
|
198
|
+
validate_file_changed() {
|
|
199
|
+
local pattern="$1"
|
|
200
|
+
|
|
201
|
+
echo -e " ${C_DIM}${MSG_VALIDATOR_FILE}: $pattern${C_RESET}"
|
|
202
|
+
|
|
203
|
+
# Use git to check for changes matching pattern
|
|
204
|
+
if command -v git &> /dev/null && git rev-parse --git-dir &> /dev/null; then
|
|
205
|
+
local changes=$(git status --porcelain 2>/dev/null | grep -E "$pattern" | wc -l)
|
|
206
|
+
if [[ $changes -gt 0 ]]; then
|
|
207
|
+
return 0
|
|
208
|
+
fi
|
|
209
|
+
else
|
|
210
|
+
# Fallback: check for recently modified files
|
|
211
|
+
local changes=$(find . -name "$pattern" -mmin -5 2>/dev/null | wc -l)
|
|
212
|
+
if [[ $changes -gt 0 ]]; then
|
|
213
|
+
return 0
|
|
214
|
+
fi
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
return 1
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# =============================================================================
|
|
221
|
+
# Validator: Hook
|
|
222
|
+
# =============================================================================
|
|
223
|
+
|
|
224
|
+
validate_hook() {
|
|
225
|
+
local script="$1"
|
|
226
|
+
local output="$2"
|
|
227
|
+
|
|
228
|
+
echo -e " ${C_DIM}${MSG_VALIDATOR_HOOK}: $script${C_RESET}"
|
|
229
|
+
|
|
230
|
+
# Check if script exists
|
|
231
|
+
if [[ ! -f "$script" ]]; then
|
|
232
|
+
echo -e " ${C_RED}Hook script not found: $script${C_RESET}"
|
|
233
|
+
return 1
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
# Prepare input JSON (same format as Claude hooks)
|
|
237
|
+
local input_json=$(jq -n \
|
|
238
|
+
--arg cwd "$PWD" \
|
|
239
|
+
--arg output "$output" \
|
|
240
|
+
'{cwd: $cwd, output: $output}')
|
|
241
|
+
|
|
242
|
+
# Execute hook with JSON input
|
|
243
|
+
local result
|
|
244
|
+
result=$(echo "$input_json" | bash "$script" 2>&1)
|
|
245
|
+
local exit_code=$?
|
|
246
|
+
|
|
247
|
+
# Check exit code (0 = continue/pass, 2 = block/fail)
|
|
248
|
+
if [[ $exit_code -eq 0 ]]; then
|
|
249
|
+
return 0
|
|
250
|
+
elif [[ $exit_code -eq 2 ]]; then
|
|
251
|
+
return 1
|
|
252
|
+
else
|
|
253
|
+
# Other exit codes treated as errors
|
|
254
|
+
echo -e " ${C_YELLOW}Hook exited with code $exit_code${C_RESET}"
|
|
255
|
+
return 1
|
|
256
|
+
fi
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# =============================================================================
|
|
260
|
+
# Validator: Human
|
|
261
|
+
# =============================================================================
|
|
262
|
+
|
|
263
|
+
validate_human() {
|
|
264
|
+
local name="$1"
|
|
265
|
+
local prompt="$2"
|
|
266
|
+
|
|
267
|
+
echo -e "\n ${C_MAGENTA}${MSG_DOD_HUMAN_GATE}: $name${C_RESET}"
|
|
268
|
+
|
|
269
|
+
# Default prompt if not provided
|
|
270
|
+
if [[ -z "$prompt" ]]; then
|
|
271
|
+
prompt="${MSG_DOD_HUMAN_PROMPT}"
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
# Ask for human input
|
|
275
|
+
echo -ne " ${C_BOLD}$prompt ${C_RESET}"
|
|
276
|
+
read -r response
|
|
277
|
+
|
|
278
|
+
# Check response
|
|
279
|
+
case "${response,,}" in
|
|
280
|
+
y|yes|o|oui|s|si|j|ja|sim)
|
|
281
|
+
return 0
|
|
282
|
+
;;
|
|
283
|
+
*)
|
|
284
|
+
return 1
|
|
285
|
+
;;
|
|
286
|
+
esac
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# =============================================================================
|
|
290
|
+
# DoD Result Storage
|
|
291
|
+
# =============================================================================
|
|
292
|
+
|
|
293
|
+
store_dod_results() {
|
|
294
|
+
local session_id="$1"
|
|
295
|
+
local results="$2"
|
|
296
|
+
|
|
297
|
+
local session_dir="$RALPH_SESSION_BASE/sessions/$session_id"
|
|
298
|
+
local state_file="$session_dir/state.json"
|
|
299
|
+
|
|
300
|
+
if [[ -f "$state_file" ]]; then
|
|
301
|
+
local tmp_file=$(mktemp)
|
|
302
|
+
echo "$results" | jq -s '.' > "$session_dir/dod_results.json"
|
|
303
|
+
jq --slurpfile results "$session_dir/dod_results.json" '.dod_results = $results[0]' "$state_file" > "$tmp_file"
|
|
304
|
+
mv "$tmp_file" "$state_file"
|
|
305
|
+
fi
|
|
306
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Ralph Wiggum - Main Loop Module
|
|
4
|
+
# Core iteration loop logic and Claude invocation
|
|
5
|
+
# =============================================================================
|
|
6
|
+
|
|
7
|
+
# Claude command configuration
|
|
8
|
+
CLAUDE_COMMAND="${CLAUDE_COMMAND:-claude}"
|
|
9
|
+
CLAUDE_ARGS="${CLAUDE_ARGS:---dangerously-skip-permissions}"
|
|
10
|
+
|
|
11
|
+
# =============================================================================
|
|
12
|
+
# Claude Invocation
|
|
13
|
+
# =============================================================================
|
|
14
|
+
|
|
15
|
+
invoke_claude() {
|
|
16
|
+
local session_id="$1"
|
|
17
|
+
local prompt="$2"
|
|
18
|
+
local timeout="$3"
|
|
19
|
+
|
|
20
|
+
# Build the command
|
|
21
|
+
local cmd="$CLAUDE_COMMAND"
|
|
22
|
+
|
|
23
|
+
# Add continue flag if we have a session
|
|
24
|
+
if [[ -n "$session_id" ]]; then
|
|
25
|
+
cmd="$cmd --continue"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Add prompt
|
|
29
|
+
cmd="$cmd -p"
|
|
30
|
+
|
|
31
|
+
# Log the invocation
|
|
32
|
+
log_session "$session_id" "INFO" "Invoking Claude: $cmd \"${prompt:0:100}...\""
|
|
33
|
+
|
|
34
|
+
# Execute with timeout
|
|
35
|
+
local timeout_sec=$((timeout / 1000))
|
|
36
|
+
local output
|
|
37
|
+
local exit_code
|
|
38
|
+
|
|
39
|
+
# Use timeout command if available
|
|
40
|
+
if command -v timeout &> /dev/null; then
|
|
41
|
+
output=$(timeout "${timeout_sec}s" $cmd "$prompt" 2>&1)
|
|
42
|
+
exit_code=$?
|
|
43
|
+
|
|
44
|
+
# Check for timeout (exit code 124)
|
|
45
|
+
if [[ $exit_code -eq 124 ]]; then
|
|
46
|
+
log_session "$session_id" "ERROR" "Claude invocation timed out after ${timeout_sec}s"
|
|
47
|
+
return 124
|
|
48
|
+
fi
|
|
49
|
+
else
|
|
50
|
+
# Fallback without timeout
|
|
51
|
+
output=$($cmd "$prompt" 2>&1)
|
|
52
|
+
exit_code=$?
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# Log result
|
|
56
|
+
if [[ $exit_code -eq 0 ]]; then
|
|
57
|
+
log_session "$session_id" "INFO" "Claude responded (${#output} chars)"
|
|
58
|
+
else
|
|
59
|
+
log_session "$session_id" "ERROR" "Claude failed with exit code $exit_code"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Output the response
|
|
63
|
+
echo "$output"
|
|
64
|
+
return $exit_code
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# =============================================================================
|
|
68
|
+
# Output Processing
|
|
69
|
+
# =============================================================================
|
|
70
|
+
|
|
71
|
+
process_output() {
|
|
72
|
+
local output="$1"
|
|
73
|
+
local session_id="$2"
|
|
74
|
+
|
|
75
|
+
# Store the raw output
|
|
76
|
+
local session_dir="$RALPH_SESSION_BASE/sessions/$session_id"
|
|
77
|
+
local output_file="$session_dir/last_output.txt"
|
|
78
|
+
|
|
79
|
+
echo "$output" > "$output_file"
|
|
80
|
+
|
|
81
|
+
# Extract any structured data if present
|
|
82
|
+
# Look for JSON blocks, completion markers, etc.
|
|
83
|
+
|
|
84
|
+
# Check for completion marker
|
|
85
|
+
if echo "$output" | grep -q "$DEFAULT_COMPLETION_MARKER"; then
|
|
86
|
+
log_session "$session_id" "INFO" "Completion marker found in output"
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
return 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# =============================================================================
|
|
94
|
+
# File Change Detection
|
|
95
|
+
# =============================================================================
|
|
96
|
+
|
|
97
|
+
detect_file_changes() {
|
|
98
|
+
local session_id="$1"
|
|
99
|
+
|
|
100
|
+
# Use git to detect changes if available
|
|
101
|
+
if command -v git &> /dev/null && git rev-parse --git-dir &> /dev/null; then
|
|
102
|
+
local changes=$(git status --porcelain 2>/dev/null | wc -l)
|
|
103
|
+
log_session "$session_id" "DEBUG" "Detected $changes file changes"
|
|
104
|
+
echo "$changes"
|
|
105
|
+
else
|
|
106
|
+
# Fallback: check modification times in common source directories
|
|
107
|
+
local changes=0
|
|
108
|
+
for dir in src lib app tests; do
|
|
109
|
+
if [[ -d "$dir" ]]; then
|
|
110
|
+
local recent=$(find "$dir" -type f -mmin -1 2>/dev/null | wc -l)
|
|
111
|
+
changes=$((changes + recent))
|
|
112
|
+
fi
|
|
113
|
+
done
|
|
114
|
+
echo "$changes"
|
|
115
|
+
fi
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# =============================================================================
|
|
119
|
+
# Progress Detection
|
|
120
|
+
# =============================================================================
|
|
121
|
+
|
|
122
|
+
check_progress() {
|
|
123
|
+
local session_id="$1"
|
|
124
|
+
local output="$2"
|
|
125
|
+
local previous_length="$3"
|
|
126
|
+
|
|
127
|
+
local current_length=${#output}
|
|
128
|
+
local file_changes=$(detect_file_changes "$session_id")
|
|
129
|
+
|
|
130
|
+
# Progress indicators
|
|
131
|
+
local has_progress=false
|
|
132
|
+
|
|
133
|
+
# 1. Files were changed
|
|
134
|
+
if [[ $file_changes -gt 0 ]]; then
|
|
135
|
+
has_progress=true
|
|
136
|
+
log_session "$session_id" "INFO" "Progress: $file_changes files changed"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# 2. Output length is significant (not just errors)
|
|
140
|
+
if [[ $current_length -gt 100 ]]; then
|
|
141
|
+
# Check if output contains actual work indicators
|
|
142
|
+
if echo "$output" | grep -qE "(created|modified|fixed|implemented|added|updated)" ; then
|
|
143
|
+
has_progress=true
|
|
144
|
+
log_session "$session_id" "INFO" "Progress: Work indicators found in output"
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# Update metrics
|
|
149
|
+
update_session_state "$session_id" "metrics.file_changes" "$file_changes"
|
|
150
|
+
|
|
151
|
+
if [[ "$has_progress" == "true" ]]; then
|
|
152
|
+
return 0
|
|
153
|
+
else
|
|
154
|
+
return 1
|
|
155
|
+
fi
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# =============================================================================
|
|
159
|
+
# Error Detection
|
|
160
|
+
# =============================================================================
|
|
161
|
+
|
|
162
|
+
detect_errors() {
|
|
163
|
+
local output="$1"
|
|
164
|
+
local errors=()
|
|
165
|
+
|
|
166
|
+
# Common error patterns
|
|
167
|
+
local error_patterns=(
|
|
168
|
+
"Error:"
|
|
169
|
+
"error:"
|
|
170
|
+
"FAILED"
|
|
171
|
+
"Exception"
|
|
172
|
+
"fatal:"
|
|
173
|
+
"Cannot"
|
|
174
|
+
"could not"
|
|
175
|
+
"Permission denied"
|
|
176
|
+
"No such file"
|
|
177
|
+
"command not found"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
for pattern in "${error_patterns[@]}"; do
|
|
181
|
+
if echo "$output" | grep -q "$pattern"; then
|
|
182
|
+
errors+=("$pattern")
|
|
183
|
+
fi
|
|
184
|
+
done
|
|
185
|
+
|
|
186
|
+
if [[ ${#errors[@]} -gt 0 ]]; then
|
|
187
|
+
printf '%s\n' "${errors[@]}"
|
|
188
|
+
return 1
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
return 0
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# =============================================================================
|
|
195
|
+
# Loop State
|
|
196
|
+
# =============================================================================
|
|
197
|
+
|
|
198
|
+
get_loop_state() {
|
|
199
|
+
local session_id="$1"
|
|
200
|
+
local state=$(get_session_state "$session_id")
|
|
201
|
+
|
|
202
|
+
echo "$state" | jq '{
|
|
203
|
+
iteration: .current_iteration,
|
|
204
|
+
status: .status,
|
|
205
|
+
circuit_breaker: .circuit_breaker,
|
|
206
|
+
dod_results: .dod_results
|
|
207
|
+
}'
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
should_continue_loop() {
|
|
211
|
+
local session_id="$1"
|
|
212
|
+
local iteration="$2"
|
|
213
|
+
local max_iterations="$3"
|
|
214
|
+
|
|
215
|
+
# Check max iterations
|
|
216
|
+
if [[ $iteration -ge $max_iterations ]]; then
|
|
217
|
+
return 1
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# Check circuit breaker
|
|
221
|
+
if check_circuit_breaker; then
|
|
222
|
+
return 1
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# Check session status
|
|
226
|
+
local status=$(get_session_state "$session_id" | jq -r '.status')
|
|
227
|
+
if [[ "$status" == "completed" || "$status" == "failed" ]]; then
|
|
228
|
+
return 1
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
return 0
|
|
232
|
+
}
|