claude-evolve 1.7.8 → 1.7.12
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/bin/claude-evolve-ideate +35 -23
- package/bin/claude-evolve-run +115 -9
- package/package.json +1 -1
package/bin/claude-evolve-ideate
CHANGED
|
@@ -145,28 +145,35 @@ call_ai_for_ideation() {
|
|
|
145
145
|
ai_output=$(call_ai_model_configured "$model" "$prompt")
|
|
146
146
|
local ai_exit_code=$?
|
|
147
147
|
|
|
148
|
-
# Check if the file was modified
|
|
148
|
+
# Check if the file was modified AND has correct count
|
|
149
149
|
if [[ -f "$temp_csv_file" ]]; then
|
|
150
150
|
local new_csv_count
|
|
151
151
|
new_csv_count=$(grep -v '^[[:space:]]*$' "$temp_csv_file" | tail -n +2 | wc -l)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
152
|
+
local added_count=$((new_csv_count - original_csv_count))
|
|
153
|
+
|
|
154
|
+
if [[ $added_count -gt 0 ]]; then
|
|
155
|
+
# Validate count BEFORE accepting
|
|
156
|
+
if [[ $added_count -eq $expected_count ]]; then
|
|
157
|
+
echo "[INFO] CSV modified by $model: added $added_count/$expected_count rows ✓" >&2
|
|
158
|
+
|
|
159
|
+
# Post-process to ensure all description fields are quoted
|
|
160
|
+
local fixed_csv_file="${temp_csv_file}.fixed"
|
|
161
|
+
|
|
162
|
+
# Use the CSV fixer script
|
|
163
|
+
if "$PYTHON_CMD" "$SCRIPT_DIR/../lib/csv_fixer.py" "$temp_csv_file" "$fixed_csv_file"; then
|
|
164
|
+
mv "$fixed_csv_file" "$temp_csv_file"
|
|
165
|
+
echo "[INFO] CSV format validated and fixed if needed" >&2
|
|
166
|
+
else
|
|
167
|
+
echo "[WARN] CSV format validation failed, using original" >&2
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Echo the successful model name for caller to capture
|
|
171
|
+
echo "$model"
|
|
172
|
+
return 0
|
|
163
173
|
else
|
|
164
|
-
echo "[WARN]
|
|
174
|
+
echo "[WARN] $model added wrong count: $added_count (expected $expected_count) - trying next model" >&2
|
|
175
|
+
# Continue to next model - maybe another will get it right
|
|
165
176
|
fi
|
|
166
|
-
|
|
167
|
-
# Echo the successful model name for caller to capture
|
|
168
|
-
echo "$model"
|
|
169
|
-
return 0
|
|
170
177
|
else
|
|
171
178
|
echo "[INFO] CSV unchanged after $model (exit code: $ai_exit_code)" >&2
|
|
172
179
|
# Continue to next model
|
|
@@ -366,9 +373,12 @@ validate_direct_csv_modification() {
|
|
|
366
373
|
|
|
367
374
|
local added_count=$((new_count - current_original_count))
|
|
368
375
|
if [[ $added_count -ne $expected_count ]]; then
|
|
369
|
-
echo "[
|
|
376
|
+
echo "[ERROR] Expected to add $expected_count ideas but only added $added_count" >&2
|
|
377
|
+
echo "[ERROR] Ideation failed - rejecting partial results to prevent endless loops" >&2
|
|
378
|
+
rm -f "$temp_csv"
|
|
379
|
+
return 1
|
|
370
380
|
fi
|
|
371
|
-
|
|
381
|
+
|
|
372
382
|
# Use proper locking to safely update the CSV
|
|
373
383
|
echo "[INFO] Acquiring CSV lock to apply changes..."
|
|
374
384
|
|
|
@@ -859,12 +869,14 @@ ideate_ai_strategies() {
|
|
|
859
869
|
fi
|
|
860
870
|
|
|
861
871
|
echo "[INFO] Strategy results: $strategies_succeeded/$strategies_attempted succeeded" >&2
|
|
862
|
-
|
|
863
|
-
#
|
|
864
|
-
|
|
872
|
+
|
|
873
|
+
# REQUIRE ALL strategies to succeed - no partial results
|
|
874
|
+
# Accepting partial results leads to endless loops with 1 idea per generation
|
|
875
|
+
if [[ $strategies_succeeded -eq $strategies_attempted ]]; then
|
|
865
876
|
return 0
|
|
866
877
|
else
|
|
867
|
-
echo "[ERROR]
|
|
878
|
+
echo "[ERROR] Not all ideation strategies succeeded ($strategies_succeeded/$strategies_attempted)" >&2
|
|
879
|
+
echo "[ERROR] Rejecting partial results - will retry with exponential backoff" >&2
|
|
868
880
|
return 1
|
|
869
881
|
fi
|
|
870
882
|
}
|
package/bin/claude-evolve-run
CHANGED
|
@@ -553,17 +553,70 @@ except Exception as e:
|
|
|
553
553
|
return $?
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
+
# Track loop iterations for periodic cleanup
|
|
557
|
+
loop_iteration=0
|
|
558
|
+
last_stuck_check=0
|
|
559
|
+
|
|
556
560
|
# Main dispatch loop
|
|
557
561
|
while true; do
|
|
558
562
|
# Clean up finished workers
|
|
559
563
|
cleanup_workers
|
|
560
|
-
|
|
564
|
+
|
|
561
565
|
# Check if API limit was reached
|
|
562
566
|
if [[ "$api_limit_reached" == "true" ]]; then
|
|
563
567
|
echo "[DISPATCHER] Stopping evolution run due to API usage limits" >&2
|
|
564
568
|
break
|
|
565
569
|
fi
|
|
566
|
-
|
|
570
|
+
|
|
571
|
+
# Periodic cleanup of stuck candidates (every 5 iterations, ~25 seconds)
|
|
572
|
+
((loop_iteration++))
|
|
573
|
+
if [[ $((loop_iteration - last_stuck_check)) -ge 5 ]]; then
|
|
574
|
+
last_stuck_check=$loop_iteration
|
|
575
|
+
|
|
576
|
+
# Reset stuck 'running' candidates (no active worker) and unknown statuses
|
|
577
|
+
if [[ -f "$FULL_CSV_PATH" && ${#worker_pids[@]} -eq 0 ]]; then
|
|
578
|
+
echo "[DISPATCHER] Checking for stuck candidates (no active workers)..."
|
|
579
|
+
"$PYTHON_CMD" -c "
|
|
580
|
+
import csv
|
|
581
|
+
import sys
|
|
582
|
+
from pathlib import Path
|
|
583
|
+
|
|
584
|
+
csv_file = '$FULL_CSV_PATH'
|
|
585
|
+
rows = []
|
|
586
|
+
|
|
587
|
+
with open(csv_file, 'r') as f:
|
|
588
|
+
rows = list(csv.reader(f))
|
|
589
|
+
|
|
590
|
+
reset_count = 0
|
|
591
|
+
has_header = rows and rows[0] and rows[0][0].lower() == 'id'
|
|
592
|
+
start_idx = 1 if has_header else 0
|
|
593
|
+
|
|
594
|
+
for i in range(start_idx, len(rows)):
|
|
595
|
+
if len(rows[i]) > 4:
|
|
596
|
+
status = rows[i][4].strip() if rows[i][4] else ''
|
|
597
|
+
candidate_id = rows[i][0] if rows[i] else ''
|
|
598
|
+
|
|
599
|
+
# Reset 'running' when no workers are active
|
|
600
|
+
if status == 'running':
|
|
601
|
+
print(f'[INFO] Resetting stuck running candidate: {candidate_id}', file=sys.stderr)
|
|
602
|
+
rows[i][4] = 'pending'
|
|
603
|
+
reset_count += 1
|
|
604
|
+
# Reset unknown statuses like 'ready'
|
|
605
|
+
elif status not in ['', 'pending', 'complete', 'failed', 'failed-ai-retry',
|
|
606
|
+
'failed-retry1', 'failed-retry2', 'failed-retry3', 'skipped']:
|
|
607
|
+
print(f'[WARN] Resetting unknown status \"{status}\" to pending: {candidate_id}', file=sys.stderr)
|
|
608
|
+
rows[i][4] = 'pending'
|
|
609
|
+
reset_count += 1
|
|
610
|
+
|
|
611
|
+
if reset_count > 0:
|
|
612
|
+
with open(csv_file + '.tmp', 'w', newline='') as f:
|
|
613
|
+
csv.writer(f).writerows(rows)
|
|
614
|
+
Path(csv_file + '.tmp').rename(csv_file)
|
|
615
|
+
print(f'[INFO] Reset {reset_count} stuck/unknown candidates to pending', file=sys.stderr)
|
|
616
|
+
" || true
|
|
617
|
+
fi
|
|
618
|
+
fi
|
|
619
|
+
|
|
567
620
|
# Get current status
|
|
568
621
|
csv_stats=$(get_csv_stats "$FULL_CSV_PATH")
|
|
569
622
|
read -r total_rows complete_count pending_count <<< "$csv_stats"
|
|
@@ -578,18 +631,71 @@ while true; do
|
|
|
578
631
|
# If no pending work and no active workers, check for auto-ideation
|
|
579
632
|
if [[ $pending_count -eq 0 && $active_workers -eq 0 ]]; then
|
|
580
633
|
echo "[DISPATCHER] No pending candidates found."
|
|
581
|
-
|
|
634
|
+
|
|
635
|
+
# Before blocking, do final check for stuck work (immediate, not periodic)
|
|
636
|
+
echo "[DISPATCHER] Performing final verification for stuck candidates..."
|
|
637
|
+
stuck_work_count=$("$PYTHON_CMD" -c "
|
|
638
|
+
import csv
|
|
639
|
+
csv_file = '$FULL_CSV_PATH'
|
|
640
|
+
stuck = 0
|
|
641
|
+
with open(csv_file, 'r') as f:
|
|
642
|
+
reader = csv.reader(f)
|
|
643
|
+
next(reader, None) # Skip header
|
|
644
|
+
for row in reader:
|
|
645
|
+
if len(row) > 4:
|
|
646
|
+
status = row[4].strip() if row[4] else ''
|
|
647
|
+
# Count running, unknown, and retry statuses
|
|
648
|
+
if status in ['running'] or \
|
|
649
|
+
(status and status not in ['', 'pending', 'complete', 'failed', 'skipped',
|
|
650
|
+
'failed-ai-retry', 'failed-retry1', 'failed-retry2', 'failed-retry3']):
|
|
651
|
+
stuck += 1
|
|
652
|
+
print(stuck)
|
|
653
|
+
" 2>/dev/null || echo "0")
|
|
654
|
+
|
|
655
|
+
if [[ $stuck_work_count -gt 0 ]]; then
|
|
656
|
+
echo "[DISPATCHER] Found $stuck_work_count stuck candidates - running cleanup cycle..."
|
|
657
|
+
# Force immediate cleanup
|
|
658
|
+
loop_iteration=$((last_stuck_check + 5))
|
|
659
|
+
continue # Go back to top of loop to trigger cleanup
|
|
660
|
+
fi
|
|
661
|
+
|
|
582
662
|
# Check if auto ideation is enabled
|
|
583
663
|
if [[ "$AUTO_IDEATE" == "true" || "$AUTO_IDEATE" == "1" ]]; then
|
|
584
664
|
echo "[DISPATCHER] Auto ideation is enabled. Checking prerequisites..."
|
|
585
|
-
|
|
586
|
-
# Check if
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
665
|
+
|
|
666
|
+
# Check if we have enough completed work to generate meaningful ideas
|
|
667
|
+
# Note: With bottom-up processing, highest gen might be processed first and fail
|
|
668
|
+
# So we check overall completion rate, not just the highest generation
|
|
669
|
+
total_completed=$("$PYTHON_CMD" -c "
|
|
670
|
+
import csv
|
|
671
|
+
completed = 0
|
|
672
|
+
with open('$FULL_CSV_PATH', 'r') as f:
|
|
673
|
+
reader = csv.reader(f)
|
|
674
|
+
next(reader, None)
|
|
675
|
+
for row in reader:
|
|
676
|
+
if len(row) > 4 and row[4].strip() == 'complete':
|
|
677
|
+
completed += 1
|
|
678
|
+
print(completed)
|
|
679
|
+
" 2>/dev/null || echo "0")
|
|
680
|
+
|
|
681
|
+
if [[ $total_completed -lt 3 ]]; then
|
|
682
|
+
echo "[DISPATCHER] Evolution blocked - insufficient completed candidates ($total_completed < 3)."
|
|
683
|
+
echo "[DISPATCHER] This prevents ideation when there's no successful work to learn from."
|
|
684
|
+
echo "[DISPATCHER] Possible causes:"
|
|
685
|
+
echo "[DISPATCHER] - Early generations failing (common with bottom-up processing)"
|
|
686
|
+
echo "[DISPATCHER] - Algorithm bugs preventing any completions"
|
|
687
|
+
echo "[DISPATCHER] - API rate limits or infrastructure issues"
|
|
688
|
+
echo "[DISPATCHER] Recommendation: Fix evaluation issues and run 'claude-evolve run' again"
|
|
591
689
|
break
|
|
592
690
|
fi
|
|
691
|
+
|
|
692
|
+
# Also check highest generation (catches endless loop case)
|
|
693
|
+
if ! check_previous_generation_has_completed "$FULL_CSV_PATH"; then
|
|
694
|
+
echo "[DISPATCHER] WARNING: Highest generation has no completed items."
|
|
695
|
+
echo "[DISPATCHER] This can happen with bottom-up processing if early gens fail."
|
|
696
|
+
echo "[DISPATCHER] Continuing since we have $total_completed total completions..."
|
|
697
|
+
# Don't break - allow ideation if we have enough overall completions
|
|
698
|
+
fi
|
|
593
699
|
|
|
594
700
|
echo "[DISPATCHER] Prerequisites met. Generating new ideas..."
|
|
595
701
|
|