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.
@@ -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 - this is ALL that matters
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
- if [[ $new_csv_count -gt $original_csv_count ]]; then
154
- echo "[INFO] CSV was modified by $model ($new_csv_count vs $original_csv_count rows) - validating format..." >&2
155
-
156
- # Post-process to ensure all description fields are quoted
157
- local fixed_csv_file="${temp_csv_file}.fixed"
158
-
159
- # Use the CSV fixer script
160
- if "$PYTHON_CMD" "$SCRIPT_DIR/../lib/csv_fixer.py" "$temp_csv_file" "$fixed_csv_file"; then
161
- mv "$fixed_csv_file" "$temp_csv_file"
162
- echo "[INFO] CSV format validated and fixed if needed" >&2
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] CSV format validation failed, using original" >&2
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 "[WARN] Expected to add $expected_count ideas but added $added_count" >&2
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
- # Success if at least one strategy worked
864
- if [[ $strategies_succeeded -gt 0 ]]; then
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] All ideation strategies failed" >&2
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
  }
@@ -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 previous generation has at least one completed item
587
- if ! check_previous_generation_has_completed "$FULL_CSV_PATH"; then
588
- echo "[DISPATCHER] Evolution complete - previous generation has no completed items."
589
- echo "[DISPATCHER] This prevents endless ideation loops when API limits are hit."
590
- echo "[DISPATCHER] Wait for current generation to complete, then run 'claude-evolve ideate' manually."
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-evolve",
3
- "version": "1.7.8",
3
+ "version": "1.7.12",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",