claude-evolve 1.3.43 → 1.4.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.
@@ -251,10 +251,9 @@ cleanup_workers() {
251
251
  worker_pids=("${new_pids[@]}")
252
252
  }
253
253
 
254
- # Function to count pending candidates
254
+ # Function to count pending candidates - UNIFIED LOGIC
255
255
  count_pending_candidates() {
256
- "$PYTHON_CMD" "$SCRIPT_DIR/../lib/csv_helper.py" find_pending "$FULL_CSV_PATH" >/dev/null 2>&1
257
- echo $? # 0 if found, 1 if not found
256
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" count
258
257
  }
259
258
 
260
259
  # Function to get CSV stats
@@ -271,26 +270,8 @@ get_csv_stats() {
271
270
  total_rows=$(wc -l < "$csv_path" | tr -d '[:space:]')
272
271
  complete_count=$(grep ',complete' "$csv_path" 2>/dev/null | wc -l | tr -d '[:space:]')
273
272
 
274
- # Count pending using same logic as find_next_pending_with_lock
275
- # This includes rows with <5 fields AND rows with empty/pending status
276
- pending_count=$("$PYTHON_CMD" -c "
277
- import csv
278
- import sys
279
-
280
- pending_count = 0
281
- with open('$csv_path', 'r') as f:
282
- reader = csv.reader(f)
283
- rows = list(reader)
284
-
285
- for i in range(1, len(rows)):
286
- # Same logic as find_next_pending_with_lock
287
- if len(rows[i]) < 5:
288
- pending_count += 1
289
- elif len(rows[i]) >= 5 and (rows[i][4] == 'pending' or rows[i][4] == ''):
290
- pending_count += 1
291
-
292
- print(pending_count)
293
- ")
273
+ # Count pending using UNIFIED CSV logic
274
+ pending_count=$("$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$csv_path" count)
294
275
 
295
276
  echo "$total_rows $complete_count $pending_count"
296
277
  }
@@ -298,9 +279,45 @@ print(pending_count)
298
279
  echo "[DISPATCHER] Starting unified evolution engine"
299
280
  echo "[DISPATCHER] Configuration: max_workers=$MAX_WORKERS, timeout=${timeout_seconds:-none}"
300
281
 
301
- # Validate CSV and clean up stuck statuses
282
+ # Validate CSV and clean up stuck statuses and duplicates
302
283
  if [[ -f "$FULL_CSV_PATH" ]]; then
303
284
  echo "[DISPATCHER] Validating CSV and cleaning up..."
285
+
286
+ # First check for and clean up duplicates
287
+ echo "[DISPATCHER] Checking for duplicate entries..."
288
+ duplicate_check_output=$("$PYTHON_CMD" "$SCRIPT_DIR/claude-evolve-cleanup-duplicates" "$FULL_CSV_PATH" 2>&1)
289
+ if echo "$duplicate_check_output" | grep -q "Found.*duplicate"; then
290
+ echo "[DISPATCHER] WARNING: Duplicate entries detected in CSV!"
291
+ echo "$duplicate_check_output"
292
+ echo "[DISPATCHER] Automatically cleaning up duplicates..."
293
+ if "$PYTHON_CMD" "$SCRIPT_DIR/claude-evolve-cleanup-duplicates" "$FULL_CSV_PATH" --fix; then
294
+ echo "[DISPATCHER] Duplicates cleaned up successfully"
295
+ else
296
+ echo "[ERROR] Failed to clean up duplicates" >&2
297
+ exit 1
298
+ fi
299
+ else
300
+ echo "[DISPATCHER] No duplicates found"
301
+ fi
302
+
303
+ # Check for and clean up invalid entries
304
+ echo "[DISPATCHER] Checking for invalid entries..."
305
+ invalid_check_output=$("$PYTHON_CMD" "$SCRIPT_DIR/claude-evolve-clean-invalid" "$FULL_CSV_PATH" --dry-run 2>&1)
306
+ if echo "$invalid_check_output" | grep -q "Found.*invalid"; then
307
+ echo "[DISPATCHER] WARNING: Invalid entries detected in CSV!"
308
+ echo "$invalid_check_output"
309
+ echo "[DISPATCHER] Automatically cleaning up invalid entries..."
310
+ if "$PYTHON_CMD" "$SCRIPT_DIR/claude-evolve-clean-invalid" "$FULL_CSV_PATH"; then
311
+ echo "[DISPATCHER] Invalid entries cleaned up successfully"
312
+ else
313
+ echo "[ERROR] Failed to clean up invalid entries" >&2
314
+ exit 1
315
+ fi
316
+ else
317
+ echo "[DISPATCHER] No invalid entries found"
318
+ fi
319
+
320
+ # Then validate and clean stuck statuses
304
321
  if ! "$PYTHON_CMD" -c "
305
322
  import csv
306
323
  import sys
@@ -340,12 +357,13 @@ try:
340
357
  os.rename(csv_file + '.tmp', csv_file)
341
358
  print(f'[INFO] Reset {changed} stuck running candidates to pending')
342
359
 
343
- # Count pending candidates
344
- pending = 0
345
- for i in range(1, len(rows)):
346
- # Row with < 5 fields or empty/pending status in field 5
347
- if len(rows[i]) < 5 or (len(rows[i]) >= 5 and rows[i][4] in ['', 'pending']):
348
- pending += 1
360
+ # Count pending candidates using UNIFIED logic
361
+ import sys
362
+ sys.path.append('$SCRIPT_DIR/../lib')
363
+ from evolution_csv import EvolutionCSV
364
+
365
+ with EvolutionCSV(csv_file) as csv_ops:
366
+ pending = csv_ops.count_pending_candidates()
349
367
 
350
368
  print(f'[INFO] CSV loaded: {len(rows)-1} total candidates, {pending} pending')
351
369
 
@@ -388,6 +406,37 @@ else
388
406
  echo "[DISPATCHER] No cleanup issues detected - proceeding with run"
389
407
  fi
390
408
 
409
+ # Ensure baseline algorithm performance is recorded
410
+ ensure_baseline_entry() {
411
+ # Check if baseline already exists
412
+ if "$PYTHON_CMD" -c "
413
+ import csv
414
+ with open('$FULL_CSV_PATH', 'r') as f:
415
+ reader = csv.reader(f)
416
+ next(reader, None) # Skip header
417
+ for row in reader:
418
+ if len(row) >= 2:
419
+ candidate_id = row[0]
420
+ parent_id = row[1] if len(row) > 1 else ''
421
+ # Check for baseline entry (empty parent and baseline-like ID)
422
+ if not parent_id and ('baseline' in candidate_id.lower() or candidate_id.startswith('000') or candidate_id == '0'):
423
+ print('found')
424
+ exit(0)
425
+ exit(1)
426
+ "; then
427
+ echo "[DISPATCHER] Baseline performance already recorded"
428
+ else
429
+ echo "[DISPATCHER] No baseline found, adding baseline-000 for evaluation..."
430
+
431
+ # Add baseline entry as pending
432
+ echo "baseline-000,,Original algorithm.py performance,,pending" >> "$FULL_CSV_PATH"
433
+ echo "[DISPATCHER] Added baseline-000 to evaluation queue"
434
+ fi
435
+ }
436
+
437
+ # Check for baseline before starting main loop
438
+ ensure_baseline_entry
439
+
391
440
  # With retry mechanism, we don't need consecutive failure tracking
392
441
  # Failures are handled gracefully through the retry system
393
442
 
@@ -111,6 +111,7 @@ csv_file = '$FULL_CSV_PATH'
111
111
  show_brief = '$SHOW_BRIEF' == 'true'
112
112
  show_winner_only = '$SHOW_WINNER_ONLY' == 'true'
113
113
  evolution_context = '$EVOLUTION_CONTEXT'
114
+ num_novel_to_show = int('${NUM_REVOLUTION:-2}')
114
115
 
115
116
  def normalize_status(status):
116
117
  '''Convert retry statuses to base status for counting.'''
@@ -183,6 +184,7 @@ try:
183
184
  if all_candidates:
184
185
  winner = max(all_candidates, key=lambda x: x[2])
185
186
 
187
+
186
188
  # Show winner only
187
189
  if show_winner_only:
188
190
  if winner:
@@ -218,6 +220,27 @@ try:
218
220
  print('🏆 CURRENT LEADER: None (no completed candidates)')
219
221
  print()
220
222
 
223
+ # Show top novel candidates
224
+ novel_candidates = []
225
+ for row in rows[1:]:
226
+ if len(row) >= 5 and row[3] and row[4] == 'complete' and not row[1]:
227
+ try:
228
+ candidate_id = row[0]
229
+ description = row[2] if len(row) > 2 else ''
230
+ score = float(row[3])
231
+ novel_candidates.append((candidate_id, description, score))
232
+ except ValueError:
233
+ pass
234
+
235
+ if novel_candidates:
236
+ novel_candidates.sort(key=lambda x: x[2], reverse=True)
237
+ print(f'🌟 TOP NOVEL CANDIDATES:')
238
+ # Use the num_novel_to_show variable set at the top
239
+ for i, (candidate_id, description, score) in enumerate(novel_candidates[:num_novel_to_show]):
240
+ print(f' {i+1}. {candidate_id} (score: {score:.4f})')
241
+ print(f' {description}')
242
+ print()
243
+
221
244
  # Show per-generation breakdown (unless brief mode)
222
245
  if not show_brief and stats_by_gen:
223
246
  print('📈 BY GENERATION:')
@@ -31,26 +31,26 @@ handle_failure() {
31
31
 
32
32
  if [[ $new_retry_num -le $MAX_RETRIES ]]; then
33
33
  local new_status="failed-retry${new_retry_num}"
34
- update_csv_row_with_lock "$candidate_id" "status" "$new_status"
35
- update_csv_row_with_lock "$candidate_id" "performance" "$performance"
34
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "$new_status"
35
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" perf "$candidate_id" "$performance"
36
36
  echo "[WORKER-$$] ✗ Retry $retry_num failed, marked as $new_status"
37
37
  exit 1
38
38
  else
39
- update_csv_row_with_lock "$candidate_id" "status" "failed"
40
- update_csv_row_with_lock "$candidate_id" "performance" "$performance"
39
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "failed"
40
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" perf "$candidate_id" "$performance"
41
41
  echo "[WORKER-$$] ✗ Max retries ($MAX_RETRIES) exceeded, marking as permanently failed"
42
42
  exit 1
43
43
  fi
44
44
  elif [[ $current_status == "failed" ]]; then
45
45
  # Initial failure, convert to retry1 if retries are enabled
46
46
  if [[ $MAX_RETRIES -gt 0 ]]; then
47
- update_csv_row_with_lock "$candidate_id" "status" "failed-retry1"
48
- update_csv_row_with_lock "$candidate_id" "performance" "$performance"
47
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "failed-retry1"
48
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" perf "$candidate_id" "$performance"
49
49
  echo "[WORKER-$$] ✗ Initial failure, marked as failed-retry1 for retry"
50
50
  exit 1
51
51
  else
52
- update_csv_row_with_lock "$candidate_id" "status" "failed"
53
- update_csv_row_with_lock "$candidate_id" "performance" "$performance"
52
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "failed"
53
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" perf "$candidate_id" "$performance"
54
54
  echo "[WORKER-$$] ✗ Failed (retries disabled)"
55
55
  # Use exit code 1 - systemic issue since retries are disabled
56
56
  exit 1
@@ -58,13 +58,13 @@ handle_failure() {
58
58
  else
59
59
  # Not a failure scenario, convert to retry1 if retries enabled
60
60
  if [[ $MAX_RETRIES -gt 0 ]]; then
61
- update_csv_row_with_lock "$candidate_id" "status" "failed-retry1"
62
- update_csv_row_with_lock "$candidate_id" "performance" "$performance"
61
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "failed-retry1"
62
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" perf "$candidate_id" "$performance"
63
63
  echo "[WORKER-$$] ✗ Evaluation failed, marked as failed-retry1 for retry"
64
64
  exit 1
65
65
  else
66
- update_csv_row_with_lock "$candidate_id" "status" "failed"
67
- update_csv_row_with_lock "$candidate_id" "performance" "$performance"
66
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "failed"
67
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" perf "$candidate_id" "$performance"
68
68
  echo "[WORKER-$$] ✗ Evaluation failed (retries disabled)"
69
69
  exit 1
70
70
  fi
@@ -110,9 +110,9 @@ while [[ $# -gt 0 ]]; do
110
110
  esac
111
111
  done
112
112
 
113
- # If no ID provided, find next pending
113
+ # If no ID provided, find next pending using UNIFIED LOGIC
114
114
  if [[ -z $candidate_id ]]; then
115
- candidate_result=$(find_next_pending_with_lock)
115
+ candidate_result=$("$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" next)
116
116
  if [[ -z $candidate_result ]]; then
117
117
  echo "[DEBUG] No pending candidates found" >&2
118
118
  exit 0
@@ -128,8 +128,8 @@ if [[ -z $candidate_id ]]; then
128
128
  original_candidate_status=""
129
129
  fi
130
130
  else
131
- # Mark specified candidate as running
132
- update_csv_row_with_lock "$candidate_id" "status" "running"
131
+ # Mark specified candidate as running using UNIFIED LOGIC
132
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "running"
133
133
  original_candidate_status="" # Unknown for manually specified candidates
134
134
  fi
135
135
 
@@ -334,7 +334,7 @@ The file currently contains the parent algorithm. Modify it according to the des
334
334
  echo "[WORKER-$$] Cleaned up temp file due to rate limit" >&2
335
335
  fi
336
336
  # Reset to pending so it can be retried later
337
- update_csv_row_with_lock "$candidate_id" "status" "pending"
337
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "pending"
338
338
  exit 2 # Special exit code for rate limit
339
339
  fi
340
340
 
@@ -391,7 +391,7 @@ if [[ -n $timeout_seconds ]]; then
391
391
  eval_exit_code=$?
392
392
  if [[ $eval_exit_code -eq 124 ]]; then
393
393
  echo "[ERROR] Evaluation timed out" >&2
394
- update_csv_row_with_lock "$candidate_id" "status" "timeout"
394
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "timeout"
395
395
  exit 1
396
396
  fi
397
397
  fi
@@ -421,8 +421,8 @@ if [[ $eval_exit_code -eq 0 ]]; then
421
421
  handle_failure "$candidate_id" "$retry_status" "$score"
422
422
  exit 1
423
423
  else
424
- update_csv_row_with_lock "$candidate_id" "performance" "$score"
425
- update_csv_row_with_lock "$candidate_id" "status" "complete"
424
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" perf "$candidate_id" "$score"
425
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "complete"
426
426
  echo "[WORKER-$$] ✓ Evaluation complete, score: $score"
427
427
  exit 0
428
428
  fi
@@ -461,8 +461,8 @@ if [[ $eval_exit_code -eq 0 ]]; then
461
461
  handle_failure "$candidate_id" "$retry_status" "$score"
462
462
  exit 1
463
463
  else
464
- update_csv_row_with_lock "$candidate_id" "performance" "$score"
465
- update_csv_row_with_lock "$candidate_id" "status" "complete"
464
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" perf "$candidate_id" "$score"
465
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "complete"
466
466
  echo "[WORKER-$$] ✓ Evaluation complete, score: $score"
467
467
  exit 0
468
468
  fi
@@ -476,8 +476,8 @@ if [[ $eval_exit_code -eq 0 ]]; then
476
476
  handle_failure "$candidate_id" "$retry_status" "$score"
477
477
  exit 1
478
478
  else
479
- update_csv_row_with_lock "$candidate_id" "performance" "$score"
480
- update_csv_row_with_lock "$candidate_id" "status" "complete"
479
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" perf "$candidate_id" "$score"
480
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" update "$candidate_id" "complete"
481
481
  echo "[WORKER-$$] ✓ Evaluation complete, score: $score"
482
482
  exit 0
483
483
  fi
package/lib/config.sh CHANGED
@@ -36,6 +36,7 @@ DEFAULT_HILL_CLIMBING=5
36
36
  DEFAULT_STRUCTURAL_MUTATION=3
37
37
  DEFAULT_CROSSOVER_HYBRID=4
38
38
  DEFAULT_NUM_ELITES=3
39
+ DEFAULT_NUM_REVOLUTION=2 # Number of top novel candidates to include
39
40
 
40
41
  # Default parallel execution values
41
42
  DEFAULT_PARALLEL_ENABLED=false
@@ -70,6 +71,7 @@ load_config() {
70
71
  STRUCTURAL_MUTATION="$DEFAULT_STRUCTURAL_MUTATION"
71
72
  CROSSOVER_HYBRID="$DEFAULT_CROSSOVER_HYBRID"
72
73
  NUM_ELITES="$DEFAULT_NUM_ELITES"
74
+ NUM_REVOLUTION="$DEFAULT_NUM_REVOLUTION"
73
75
 
74
76
  # Set parallel execution defaults
75
77
  PARALLEL_ENABLED="$DEFAULT_PARALLEL_ENABLED"
@@ -138,6 +140,7 @@ load_config() {
138
140
  structural_mutation) STRUCTURAL_MUTATION="$value" ;;
139
141
  crossover_hybrid) CROSSOVER_HYBRID="$value" ;;
140
142
  num_elites) NUM_ELITES="$value" ;;
143
+ num_revolution) NUM_REVOLUTION="$value" ;;
141
144
  esac
142
145
  elif [[ $in_parallel_section == true ]]; then
143
146
  # Handle indented keys in parallel section
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env python3
2
+ """Robust CSV helper for evolution system that handles edge cases properly."""
3
+
4
+ import csv
5
+ import sys
6
+
7
+
8
+ def is_valid_candidate_row(row):
9
+ """Check if a row represents a valid candidate (not empty, has ID)."""
10
+ if not row:
11
+ return False
12
+ if len(row) == 0:
13
+ return False
14
+ # First column should have a non-empty ID
15
+ if not row[0] or row[0].strip() == '':
16
+ return False
17
+ return True
18
+
19
+
20
+ def is_pending_candidate(row):
21
+ """Check if a candidate row is pending (needs processing)."""
22
+ if not is_valid_candidate_row(row):
23
+ return False
24
+
25
+ # Must have at least 5 columns to check status
26
+ if len(row) < 5:
27
+ return True # Incomplete row is pending
28
+
29
+ # Check status field (5th column, index 4)
30
+ status = row[4].strip().lower() if row[4] else ''
31
+
32
+ # Blank, missing, "pending", or "running" all mean pending
33
+ if not status or status in ['pending', 'running']:
34
+ return True
35
+
36
+ # Check for retry statuses
37
+ if status.startswith('failed-retry'):
38
+ return True
39
+
40
+ return False
41
+
42
+
43
+ def get_pending_candidates(csv_file):
44
+ """Get list of pending candidate IDs from CSV."""
45
+ pending = []
46
+
47
+ try:
48
+ with open(csv_file, 'r') as f:
49
+ reader = csv.reader(f)
50
+ # Skip header
51
+ next(reader, None)
52
+
53
+ for row in reader:
54
+ if is_pending_candidate(row):
55
+ candidate_id = row[0].strip()
56
+ status = row[4].strip() if len(row) > 4 else ''
57
+ pending.append((candidate_id, status))
58
+
59
+ except Exception as e:
60
+ print(f"Error reading CSV: {e}", file=sys.stderr)
61
+ return []
62
+
63
+ return pending
64
+
65
+
66
+ def update_candidate_status(csv_file, candidate_id, new_status):
67
+ """Update the status of a specific candidate."""
68
+ rows = []
69
+ updated = False
70
+
71
+ try:
72
+ # Read all rows
73
+ with open(csv_file, 'r') as f:
74
+ reader = csv.reader(f)
75
+ rows = list(reader)
76
+
77
+ # Update the specific candidate
78
+ for i, row in enumerate(rows):
79
+ if is_valid_candidate_row(row) and row[0].strip() == candidate_id:
80
+ # Ensure row has at least 5 columns
81
+ while len(row) < 5:
82
+ row.append('')
83
+ row[4] = new_status
84
+ updated = True
85
+ break
86
+
87
+ # Write back if updated
88
+ if updated:
89
+ with open(csv_file, 'w', newline='') as f:
90
+ writer = csv.writer(f)
91
+ writer.writerows(rows)
92
+
93
+ return updated
94
+
95
+ except Exception as e:
96
+ print(f"Error updating CSV: {e}", file=sys.stderr)
97
+ return False
98
+
99
+
100
+ if __name__ == '__main__':
101
+ # Test functionality
102
+ if len(sys.argv) < 2:
103
+ print("Usage: csv_helper_robust.py <csv_file> [command]")
104
+ sys.exit(1)
105
+
106
+ csv_file = sys.argv[1]
107
+ command = sys.argv[2] if len(sys.argv) > 2 else 'list'
108
+
109
+ if command == 'list':
110
+ pending = get_pending_candidates(csv_file)
111
+ for candidate_id, status in pending:
112
+ print(f"{candidate_id}|{status}")
113
+
114
+ elif command == 'update' and len(sys.argv) >= 5:
115
+ candidate_id = sys.argv[3]
116
+ new_status = sys.argv[4]
117
+ if update_candidate_status(csv_file, candidate_id, new_status):
118
+ print(f"Updated {candidate_id} to {new_status}")
119
+ else:
120
+ print(f"Failed to update {candidate_id}")
121
+ sys.exit(1)