claude-evolve 1.3.38 → 1.3.40
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 +46 -5
- package/bin/claude-evolve-analyze +34 -5
- package/bin/claude-evolve-cleanup +297 -0
- package/bin/claude-evolve-edit +293 -0
- package/bin/claude-evolve-ideate +6 -6
- package/bin/claude-evolve-main +35 -15
- package/bin/{claude-evolve-run-unified → claude-evolve-run} +143 -8
- package/bin/claude-evolve-status +220 -0
- package/bin/claude-evolve-worker +73 -11
- package/lib/config.sh +5 -3
- package/lib/csv-lock.sh +26 -4
- package/lib/csv_helper.py +1 -1
- package/package.json +1 -2
- package/templates/config.yaml +8 -7
- package/bin/claude-evolve-run-parallel.OLD +0 -389
- package/bin/claude-evolve-run.OLD +0 -662
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Parallel evolution dispatcher - manages worker pool
|
|
3
|
-
|
|
4
|
-
set -e
|
|
5
|
-
|
|
6
|
-
# Load configuration
|
|
7
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
-
# shellcheck source=../lib/config.sh
|
|
9
|
-
source "$SCRIPT_DIR/../lib/config.sh"
|
|
10
|
-
# shellcheck source=../lib/csv-lock.sh
|
|
11
|
-
source "$SCRIPT_DIR/../lib/csv-lock.sh"
|
|
12
|
-
|
|
13
|
-
# Use CLAUDE_EVOLVE_CONFIG if set, otherwise default
|
|
14
|
-
if [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
|
|
15
|
-
load_config "$CLAUDE_EVOLVE_CONFIG"
|
|
16
|
-
else
|
|
17
|
-
load_config
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
# Parse arguments
|
|
21
|
-
timeout_seconds=""
|
|
22
|
-
|
|
23
|
-
while [[ $# -gt 0 ]]; do
|
|
24
|
-
case $1 in
|
|
25
|
-
--timeout)
|
|
26
|
-
timeout_seconds="$2"
|
|
27
|
-
shift 2
|
|
28
|
-
;;
|
|
29
|
-
*)
|
|
30
|
-
echo "[ERROR] Unknown option: $1" >&2
|
|
31
|
-
exit 1
|
|
32
|
-
;;
|
|
33
|
-
esac
|
|
34
|
-
done
|
|
35
|
-
|
|
36
|
-
echo "[DISPATCHER] Starting parallel evolution with $MAX_WORKERS max workers"
|
|
37
|
-
[[ -n $timeout_seconds ]] && echo "[DISPATCHER] Using timeout: ${timeout_seconds} seconds per worker"
|
|
38
|
-
|
|
39
|
-
# Validate workspace
|
|
40
|
-
if [[ ! -d "$FULL_EVOLUTION_DIR" ]]; then
|
|
41
|
-
echo "[ERROR] Evolution directory not found: $FULL_EVOLUTION_DIR" >&2
|
|
42
|
-
exit 1
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
if [[ ! -f "$FULL_CSV_PATH" ]]; then
|
|
46
|
-
echo "[ERROR] CSV file not found: $FULL_CSV_PATH" >&2
|
|
47
|
-
exit 1
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Prepare logging
|
|
51
|
-
mkdir -p logs
|
|
52
|
-
|
|
53
|
-
# Track active workers (using regular array for compatibility)
|
|
54
|
-
worker_pids=()
|
|
55
|
-
consecutive_failures=0
|
|
56
|
-
MAX_FAILURES=10
|
|
57
|
-
rate_limit_hit=false
|
|
58
|
-
|
|
59
|
-
# Count pending candidates
|
|
60
|
-
count_pending() {
|
|
61
|
-
if ! read_csv_with_lock csv_content; then
|
|
62
|
-
echo "0"
|
|
63
|
-
return
|
|
64
|
-
fi
|
|
65
|
-
|
|
66
|
-
# Use Python for proper CSV parsing with quoted fields
|
|
67
|
-
echo "$csv_content" | "$PYTHON_CMD" -c "
|
|
68
|
-
import csv
|
|
69
|
-
import sys
|
|
70
|
-
reader = csv.reader(sys.stdin)
|
|
71
|
-
next(reader) # Skip header
|
|
72
|
-
count = 0
|
|
73
|
-
for row in reader:
|
|
74
|
-
# If row has fewer than 5 fields, treat as pending
|
|
75
|
-
if len(row) < 5:
|
|
76
|
-
count += 1
|
|
77
|
-
elif len(row) >= 5 and (row[4] == 'pending' or row[4] == ''):
|
|
78
|
-
count += 1
|
|
79
|
-
print(count)
|
|
80
|
-
"
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
# Start a worker
|
|
84
|
-
start_worker() {
|
|
85
|
-
local worker_cmd="$SCRIPT_DIR/claude-evolve-worker"
|
|
86
|
-
[[ -n $timeout_seconds ]] && worker_cmd="$worker_cmd --timeout $timeout_seconds"
|
|
87
|
-
|
|
88
|
-
echo "[DISPATCHER] Starting worker..."
|
|
89
|
-
$worker_cmd &
|
|
90
|
-
local pid=$!
|
|
91
|
-
|
|
92
|
-
# Verify worker started successfully
|
|
93
|
-
sleep 0.1
|
|
94
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
95
|
-
worker_pids+=($pid)
|
|
96
|
-
echo "[DISPATCHER] Worker $pid started"
|
|
97
|
-
else
|
|
98
|
-
echo "[ERROR] Worker failed to start" >&2
|
|
99
|
-
((consecutive_failures++))
|
|
100
|
-
fi
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
# Check worker exit status and handle accordingly
|
|
104
|
-
handle_worker_exit() {
|
|
105
|
-
local pid="$1"
|
|
106
|
-
local exit_code="$2"
|
|
107
|
-
|
|
108
|
-
case $exit_code in
|
|
109
|
-
0)
|
|
110
|
-
echo "[DISPATCHER] Worker $pid completed successfully"
|
|
111
|
-
consecutive_failures=0
|
|
112
|
-
;;
|
|
113
|
-
1)
|
|
114
|
-
echo "[DISPATCHER] Worker $pid failed"
|
|
115
|
-
((consecutive_failures++))
|
|
116
|
-
;;
|
|
117
|
-
2)
|
|
118
|
-
echo "[DISPATCHER] Worker $pid hit rate limit"
|
|
119
|
-
rate_limit_hit=true
|
|
120
|
-
;;
|
|
121
|
-
130)
|
|
122
|
-
echo "[DISPATCHER] Worker $pid interrupted"
|
|
123
|
-
;;
|
|
124
|
-
*)
|
|
125
|
-
echo "[DISPATCHER] Worker $pid exited with code $exit_code"
|
|
126
|
-
((consecutive_failures++))
|
|
127
|
-
;;
|
|
128
|
-
esac
|
|
129
|
-
|
|
130
|
-
# Remove from tracking
|
|
131
|
-
local new_pids=()
|
|
132
|
-
for p in "${worker_pids[@]}"; do
|
|
133
|
-
[[ $p != "$pid" ]] && new_pids+=($p)
|
|
134
|
-
done
|
|
135
|
-
worker_pids=("${new_pids[@]}")
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
# Signal handler for graceful shutdown
|
|
139
|
-
shutdown_workers() {
|
|
140
|
-
echo "[DISPATCHER] Shutting down workers..."
|
|
141
|
-
for pid in "${worker_pids[@]}"; do
|
|
142
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
143
|
-
echo "[DISPATCHER] Stopping worker $pid"
|
|
144
|
-
kill -TERM "$pid" 2>/dev/null || true
|
|
145
|
-
fi
|
|
146
|
-
done
|
|
147
|
-
|
|
148
|
-
# Wait for workers to exit
|
|
149
|
-
local timeout=10
|
|
150
|
-
while [[ ${#worker_pids[@]} -gt 0 && $timeout -gt 0 ]]; do
|
|
151
|
-
sleep 1
|
|
152
|
-
((timeout--))
|
|
153
|
-
|
|
154
|
-
local new_pids=()
|
|
155
|
-
for pid in "${worker_pids[@]}"; do
|
|
156
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
157
|
-
new_pids+=($pid)
|
|
158
|
-
fi
|
|
159
|
-
done
|
|
160
|
-
worker_pids=("${new_pids[@]}")
|
|
161
|
-
done
|
|
162
|
-
|
|
163
|
-
# Force kill remaining workers
|
|
164
|
-
for pid in "${worker_pids[@]}"; do
|
|
165
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
166
|
-
echo "[DISPATCHER] Force killing worker $pid"
|
|
167
|
-
kill -KILL "$pid" 2>/dev/null || true
|
|
168
|
-
fi
|
|
169
|
-
done
|
|
170
|
-
|
|
171
|
-
echo "[DISPATCHER] Shutdown complete"
|
|
172
|
-
exit 0
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
# Better signal handling with logging
|
|
176
|
-
handle_signal() {
|
|
177
|
-
local signal="$1"
|
|
178
|
-
echo "[DISPATCHER] Received signal: $signal" >&2
|
|
179
|
-
echo "[DISPATCHER] Active workers: ${#worker_pids[@]}" >&2
|
|
180
|
-
|
|
181
|
-
# For expensive workers, give option to continue
|
|
182
|
-
if [[ ${#worker_pids[@]} -gt 0 ]]; then
|
|
183
|
-
echo "[DISPATCHER] Warning: ${#worker_pids[@]} expensive workers are still running!" >&2
|
|
184
|
-
echo "[DISPATCHER] Press Ctrl+C again within 5 seconds to force shutdown, or wait..." >&2
|
|
185
|
-
|
|
186
|
-
# Give 5 seconds to reconsider
|
|
187
|
-
local count=5
|
|
188
|
-
while [[ $count -gt 0 ]]; do
|
|
189
|
-
sleep 1
|
|
190
|
-
((count--))
|
|
191
|
-
# Check if we got another signal
|
|
192
|
-
if [[ -f /tmp/evolve-force-shutdown-$$ ]]; then
|
|
193
|
-
echo "[DISPATCHER] Force shutdown requested" >&2
|
|
194
|
-
rm -f /tmp/evolve-force-shutdown-$$
|
|
195
|
-
shutdown_workers
|
|
196
|
-
exit 1
|
|
197
|
-
fi
|
|
198
|
-
done
|
|
199
|
-
|
|
200
|
-
echo "[DISPATCHER] Continuing with active workers..." >&2
|
|
201
|
-
return
|
|
202
|
-
fi
|
|
203
|
-
|
|
204
|
-
shutdown_workers
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
# Set up signal handlers
|
|
208
|
-
trap 'handle_signal INT' INT
|
|
209
|
-
trap 'handle_signal TERM' TERM
|
|
210
|
-
trap 'echo "[DISPATCHER] Exiting with code $?" >&2' EXIT
|
|
211
|
-
|
|
212
|
-
# Check for stuck "running" candidates from previous runs
|
|
213
|
-
check_stuck_candidates() {
|
|
214
|
-
if read_csv_with_lock csv_content; then
|
|
215
|
-
local stuck_count=$(echo "$csv_content" | "$PYTHON_CMD" -c "
|
|
216
|
-
import csv
|
|
217
|
-
import sys
|
|
218
|
-
reader = csv.reader(sys.stdin)
|
|
219
|
-
next(reader) # Skip header
|
|
220
|
-
count = 0
|
|
221
|
-
for row in reader:
|
|
222
|
-
if len(row) >= 5 and row[4] == 'running':
|
|
223
|
-
count += 1
|
|
224
|
-
print(count)
|
|
225
|
-
")
|
|
226
|
-
if [[ $stuck_count -gt 0 ]]; then
|
|
227
|
-
echo "[DISPATCHER] Found $stuck_count candidates stuck in 'running' status"
|
|
228
|
-
echo "[DISPATCHER] Resetting them to 'pending' for retry..."
|
|
229
|
-
|
|
230
|
-
# Reset stuck candidates
|
|
231
|
-
if acquire_csv_lock; then
|
|
232
|
-
"$PYTHON_CMD" -c "
|
|
233
|
-
import csv
|
|
234
|
-
import sys
|
|
235
|
-
|
|
236
|
-
# Read CSV
|
|
237
|
-
with open('$FULL_CSV_PATH', 'r') as f:
|
|
238
|
-
reader = csv.reader(f)
|
|
239
|
-
rows = list(reader)
|
|
240
|
-
|
|
241
|
-
# Reset running to pending
|
|
242
|
-
for i in range(1, len(rows)):
|
|
243
|
-
if len(rows[i]) >= 5 and rows[i][4] == 'running':
|
|
244
|
-
rows[i][4] = 'pending'
|
|
245
|
-
|
|
246
|
-
# Write back
|
|
247
|
-
with open('${FULL_CSV_PATH}.tmp', 'w', newline='') as f:
|
|
248
|
-
writer = csv.writer(f)
|
|
249
|
-
writer.writerows(rows)
|
|
250
|
-
" && mv -f "${FULL_CSV_PATH}.tmp" "$FULL_CSV_PATH"
|
|
251
|
-
release_csv_lock
|
|
252
|
-
fi
|
|
253
|
-
fi
|
|
254
|
-
fi
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
# Main dispatcher loop
|
|
258
|
-
echo "[DISPATCHER] Starting main dispatch loop"
|
|
259
|
-
|
|
260
|
-
# Check for stuck candidates from previous runs
|
|
261
|
-
check_stuck_candidates
|
|
262
|
-
|
|
263
|
-
# Set error handling
|
|
264
|
-
set +e # Don't exit on error in the main loop
|
|
265
|
-
|
|
266
|
-
while true; do
|
|
267
|
-
# In parallel mode, let individual algorithm failures happen
|
|
268
|
-
# The generation is finite, so worst case it just completes with many failures
|
|
269
|
-
|
|
270
|
-
# Check if rate limit was hit
|
|
271
|
-
if [[ $rate_limit_hit == true ]]; then
|
|
272
|
-
echo "[DISPATCHER] Rate limit detected. Waiting 60 seconds before retrying..."
|
|
273
|
-
sleep 60
|
|
274
|
-
rate_limit_hit=false
|
|
275
|
-
fi
|
|
276
|
-
|
|
277
|
-
# Count pending work
|
|
278
|
-
pending_count=$(count_pending || echo "0")
|
|
279
|
-
active_workers=${#worker_pids[@]}
|
|
280
|
-
|
|
281
|
-
echo "[DISPATCHER] Status: $pending_count pending, $active_workers active workers"
|
|
282
|
-
|
|
283
|
-
# Debug: Show CSV status if no pending
|
|
284
|
-
if [[ $pending_count -eq 0 ]]; then
|
|
285
|
-
total_rows=$(read_csv_with_lock csv_content && echo "$csv_content" | wc -l | xargs)
|
|
286
|
-
complete_count=$(read_csv_with_lock csv_content && echo "$csv_content" | "$PYTHON_CMD" -c "
|
|
287
|
-
import csv
|
|
288
|
-
import sys
|
|
289
|
-
reader = csv.reader(sys.stdin)
|
|
290
|
-
next(reader) # Skip header
|
|
291
|
-
count = 0
|
|
292
|
-
for row in reader:
|
|
293
|
-
if len(row) >= 5 and row[4] == 'complete':
|
|
294
|
-
count += 1
|
|
295
|
-
print(count)
|
|
296
|
-
")
|
|
297
|
-
echo "[DISPATCHER] CSV has $((total_rows-1)) total candidates, $complete_count complete"
|
|
298
|
-
fi
|
|
299
|
-
|
|
300
|
-
# If no pending work and no active workers, check for auto-ideation
|
|
301
|
-
if [[ $pending_count -eq 0 && $active_workers -eq 0 ]]; then
|
|
302
|
-
echo "[DISPATCHER] No pending candidates found."
|
|
303
|
-
|
|
304
|
-
# Check if auto ideation is enabled
|
|
305
|
-
echo "[DEBUG] AUTO_IDEATE value: '$AUTO_IDEATE'"
|
|
306
|
-
if [[ "$AUTO_IDEATE" == "true" || "$AUTO_IDEATE" == "1" ]]; then
|
|
307
|
-
echo "[DISPATCHER] Auto ideation is enabled. Generating new ideas..."
|
|
308
|
-
|
|
309
|
-
# Check if claude-evolve-ideate exists
|
|
310
|
-
ideate_script="$SCRIPT_DIR/claude-evolve-ideate"
|
|
311
|
-
if [[ ! -f "$ideate_script" ]]; then
|
|
312
|
-
echo "[ERROR] claude-evolve-ideate script not found: $ideate_script" >&2
|
|
313
|
-
echo "[DISPATCHER] Evolution complete - no way to generate more ideas."
|
|
314
|
-
break
|
|
315
|
-
fi
|
|
316
|
-
|
|
317
|
-
# Generate new ideas using the multi-strategy approach
|
|
318
|
-
echo "[DISPATCHER] Calling claude-evolve-ideate to generate new candidates..."
|
|
319
|
-
if ! "$ideate_script"; then
|
|
320
|
-
echo "[ERROR] Failed to generate new ideas" >&2
|
|
321
|
-
echo "[DISPATCHER] Evolution complete - ideation failed."
|
|
322
|
-
break
|
|
323
|
-
fi
|
|
324
|
-
|
|
325
|
-
echo "[DISPATCHER] New ideas generated successfully. Continuing evolution..."
|
|
326
|
-
continue # Go back to start of loop to find the new candidates
|
|
327
|
-
else
|
|
328
|
-
echo "[DISPATCHER] Auto ideation is disabled. Evolution complete."
|
|
329
|
-
echo "[DISPATCHER] Run 'claude-evolve ideate' to generate more candidates."
|
|
330
|
-
echo "[DISPATCHER] Exiting main loop: no work remaining" >&2
|
|
331
|
-
break
|
|
332
|
-
fi
|
|
333
|
-
fi
|
|
334
|
-
|
|
335
|
-
# Start workers if we have pending work and capacity
|
|
336
|
-
while [[ $pending_count -gt 0 && $active_workers -lt $MAX_WORKERS ]]; do
|
|
337
|
-
start_worker
|
|
338
|
-
active_workers=${#worker_pids[@]}
|
|
339
|
-
((pending_count--)) # Optimistically assume this will be picked up
|
|
340
|
-
done
|
|
341
|
-
|
|
342
|
-
# If no active workers and no pending work, we're done
|
|
343
|
-
if [[ $active_workers -eq 0 && $pending_count -eq 0 ]]; then
|
|
344
|
-
echo "[DISPATCHER] No active workers and no pending work. Evolution complete."
|
|
345
|
-
break
|
|
346
|
-
fi
|
|
347
|
-
|
|
348
|
-
# Wait for any worker to finish
|
|
349
|
-
if [[ $active_workers -gt 0 ]]; then
|
|
350
|
-
# Poll for finished workers (macOS compatible)
|
|
351
|
-
sleep 5 # Check every 5 seconds
|
|
352
|
-
|
|
353
|
-
# Check which workers have finished
|
|
354
|
-
for pid in "${worker_pids[@]}"; do
|
|
355
|
-
if ! kill -0 "$pid" 2>/dev/null; then
|
|
356
|
-
# Get exit status
|
|
357
|
-
wait "$pid" 2>/dev/null
|
|
358
|
-
exit_code=$?
|
|
359
|
-
handle_worker_exit "$pid" "$exit_code"
|
|
360
|
-
fi
|
|
361
|
-
done
|
|
362
|
-
|
|
363
|
-
# Safety check - if we have workers but the array is corrupted
|
|
364
|
-
if [[ ${#worker_pids[@]} -eq 0 ]] && jobs -r | grep -q .; then
|
|
365
|
-
echo "[DISPATCHER] Warning: Lost track of workers but jobs still running!" >&2
|
|
366
|
-
echo "[DISPATCHER] Attempting to recover..." >&2
|
|
367
|
-
|
|
368
|
-
# Try to recover PIDs from jobs
|
|
369
|
-
while read -r job_info; do
|
|
370
|
-
if [[ $job_info =~ \[([0-9]+)\][[:space:]]+([0-9]+) ]]; then
|
|
371
|
-
local recovered_pid="${BASH_REMATCH[2]}"
|
|
372
|
-
echo "[DISPATCHER] Recovered worker PID: $recovered_pid" >&2
|
|
373
|
-
worker_pids+=($recovered_pid)
|
|
374
|
-
fi
|
|
375
|
-
done < <(jobs -l)
|
|
376
|
-
fi
|
|
377
|
-
else
|
|
378
|
-
# No active workers but we might be waiting for ideation or have pending work
|
|
379
|
-
sleep 1
|
|
380
|
-
fi
|
|
381
|
-
done
|
|
382
|
-
|
|
383
|
-
echo "[DISPATCHER] Evolution run complete"
|
|
384
|
-
|
|
385
|
-
# Final cleanup check
|
|
386
|
-
if [[ ${#worker_pids[@]} -gt 0 ]]; then
|
|
387
|
-
echo "[DISPATCHER] Warning: ${#worker_pids[@]} workers still active at exit"
|
|
388
|
-
shutdown_workers
|
|
389
|
-
fi
|