claude-evolve 1.5.2 → 1.5.4

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 CHANGED
@@ -30,14 +30,29 @@ Evolution runs indefinitely until you stop it. Perfect for overnight optimizatio
30
30
 
31
31
  ## Commands
32
32
 
33
+ ### Core Commands
33
34
  ```bash
34
35
  claude-evolve # Interactive menu
35
36
  claude-evolve setup # Initialize workspace
36
37
  claude-evolve ideate # Generate new algorithm ideas
37
38
  claude-evolve run # Start evolution loop (runs forever)
38
- claude-evolve analyze # View results and progress
39
+ claude-evolve analyze # View results and progress with charts
39
40
  claude-evolve status # Quick progress overview
41
+ claude-evolve autostatus # Live auto-updating status display
42
+ ```
43
+
44
+ ### Management Commands
45
+ ```bash
40
46
  claude-evolve edit # Manage candidate statuses
47
+ claude-evolve config # View/edit configuration
48
+ claude-evolve cleanup # Clean up old lock files
49
+ ```
50
+
51
+ ### Maintenance Commands
52
+ ```bash
53
+ claude-evolve clean-invalid # Remove invalid candidates
54
+ claude-evolve cleanup-duplicates # Remove duplicate entries
55
+ claude-evolve csv-fix # Fix CSV formatting issues
41
56
  ```
42
57
 
43
58
  ## Working with Multiple Projects
@@ -246,7 +246,8 @@ with open('$csv_file', 'r') as f:
246
246
  "
247
247
 
248
248
  # Process generation stats
249
- for gen in $(cut -d' ' -f1 "$gen_stats_file" | sort -u || echo ""); do
249
+ # Sort generations numerically
250
+ for gen in $(cut -d' ' -f1 "$gen_stats_file" | sort -u | awk '{print substr($0,4), $0}' | sort -n | cut -d' ' -f2 || echo ""); do
250
251
  [[ -z "$gen" ]] && continue
251
252
  total_in_gen=$(grep -c "^$gen " "$gen_stats_file" 2>/dev/null || echo "0")
252
253
  completed_in_gen=$(grep -c "^$gen completed" "$gen_stats_file" 2>/dev/null || echo "0")
@@ -461,7 +462,8 @@ print(f'max_desc=\"{desc_escaped}\"')
461
462
  # Create generation averages file and track max generation
462
463
  gen_index=1
463
464
  max_gen_num=0
464
- for gen in $(cut -d' ' -f1 "$gen_data_temp" | sort -u); do
465
+ # Sort generations numerically for graph
466
+ for gen in $(cut -d' ' -f1 "$gen_data_temp" | sort -u | awk '{print substr($0,4), $0}' | sort -n | cut -d' ' -f2); do
465
467
  if grep -q "^$gen " "$gen_data_temp"; then
466
468
  # Calculate median for this generation
467
469
  # AIDEV-NOTE: Changed from mean to median to be more robust to outliers
@@ -511,6 +513,9 @@ print(f'max_desc=\"{desc_escaped}\"')
511
513
  # Calculate total data points for dynamic sizing
512
514
  total_data_points=$(awk 'END {print NR-1}' "$data_file") # Subtract header row
513
515
 
516
+ # Count unique generations
517
+ unique_generations=$(awk '{if(NR>1) print $4}' "$data_file" | sort -nu | wc -l)
518
+
514
519
  # AIDEV-NOTE: Dynamic dot sizing based on data point count
515
520
  # Use significantly larger dots when there are fewer data points for better visibility
516
521
  if [[ $total_data_points -lt 35 ]]; then
@@ -528,16 +533,24 @@ print(f'max_desc=\"{desc_escaped}\"')
528
533
  # Find all generations that have data
529
534
  generations=($(awk '{if(NR>1) print $4}' "$data_file" | sort -n | uniq))
530
535
 
531
- for gen_num in "${generations[@]}"; do
532
- if [[ -n $gen_num ]]; then
533
- color=$(get_gen_color "$gen_num")
534
- if [[ $gen_plots_added -gt 0 ]]; then
535
- plot_cmd="$plot_cmd, \\"$'\n'
536
+ # If too many generations (>10), use a simplified plot without individual generation legends
537
+ if [[ $unique_generations -gt 10 ]]; then
538
+ # Single plot with color gradient based on generation number
539
+ plot_cmd="\"$data_file\" using 1:3:(\$4) with points palette pointsize $regular_dot_size notitle"
540
+ gen_plots_added=1
541
+ else
542
+ # Original plotting with individual generation legends
543
+ for gen_num in "${generations[@]}"; do
544
+ if [[ -n $gen_num ]]; then
545
+ color=$(get_gen_color "$gen_num")
546
+ if [[ $gen_plots_added -gt 0 ]]; then
547
+ plot_cmd="$plot_cmd, \\"$'\n'
548
+ fi
549
+ plot_cmd="${plot_cmd} \"$data_file\" using (\$4==$gen_num?\$1:1/0):3 with points linecolor rgb \"$color\" pointsize $regular_dot_size title \"Gen $gen_num\""
550
+ ((gen_plots_added++))
536
551
  fi
537
- plot_cmd="${plot_cmd} \"$data_file\" using (\$4==$gen_num?\$1:1/0):3 with points linecolor rgb \"$color\" pointsize $regular_dot_size title \"Gen $gen_num\""
538
- ((gen_plots_added++))
539
- fi
540
- done
552
+ done
553
+ fi
541
554
 
542
555
  # Add novel candidates
543
556
  if [[ -s "$novel_file" ]] && [[ $(wc -l < "$novel_file") -gt 1 ]]; then
@@ -568,7 +581,8 @@ print(f'max_desc=\"{desc_escaped}\"')
568
581
  # Build x-axis labels for generation chart (include all generations from data)
569
582
  xtics_labels=""
570
583
  label_index=1
571
- for gen in $(cut -d' ' -f1 "$gen_data_temp" | sort -u); do
584
+ # Sort generations numerically for graph
585
+ for gen in $(cut -d' ' -f1 "$gen_data_temp" | sort -u | awk '{print substr($0,4), $0}' | sort -n | cut -d' ' -f2); do
572
586
  if [[ -n $gen ]]; then
573
587
  gen_display=$(echo "$gen" | sed 's/gen0*//')
574
588
  if [[ -n $xtics_labels ]]; then
@@ -583,6 +597,9 @@ print(f'max_desc=\"{desc_escaped}\"')
583
597
  set terminal png size 1200,800
584
598
  set output "$output_file"
585
599
 
600
+ # Define unique generations count
601
+ unique_gens = $unique_generations
602
+
586
603
  # Set up multiplot with proper spacing
587
604
  set multiplot layout 2,1 margins 0.08,0.82,0.15,0.95 spacing 0.1,0.15
588
605
 
@@ -592,7 +609,9 @@ set title "$EVOLUTION_CONTEXT Algorithm Evolution Performance Over Time" font ",
592
609
  unset xlabel
593
610
  set ylabel "Performance Score"
594
611
  set grid y # Only show horizontal grid lines
595
- set key outside right
612
+
613
+ # Show legend only if 10 or fewer generations
614
+ if (unique_gens <= 10) set key outside right; else unset key
596
615
 
597
616
  # AIDEV-NOTE: Remove x-axis entirely to avoid tick problems with large datasets
598
617
  unset xtics
@@ -600,6 +619,9 @@ set autoscale
600
619
  set yrange [*:*] # Auto-scale y-axis only
601
620
 
602
621
  # Define colors for generations
622
+ # Use palette for many generations
623
+ if (unique_gens > 10) set palette model RGB defined (0 "#1f77b4", 1 "#ff7f0e", 2 "#2ca02c", 3 "#d62728", 4 "#9467bd", 5 "#8c564b", 6 "#e377c2")
624
+
603
625
  plot $plot_cmd
604
626
 
605
627
  #=================== BOTTOM PLOT: Generation Medians ===================
@@ -611,8 +633,13 @@ set boxwidth 0.6
611
633
  unset key
612
634
  set grid y
613
635
 
614
- # Set custom x-axis labels
615
- set xtics ($xtics_labels)
636
+ # Set custom x-axis labels (but hide if too many generations)
637
+ if (unique_gens > 10) {
638
+ set xtics auto
639
+ set xtics rotate by -45
640
+ } else {
641
+ set xtics ($xtics_labels)
642
+ }
616
643
 
617
644
  # Auto-scale for generation plot too
618
645
  set autoscale
@@ -246,8 +246,8 @@ class AutoStatus:
246
246
  print("-" * min(self.display.cols, len(header_fmt)))
247
247
  row += 1
248
248
 
249
- # Sort generations
250
- sorted_gens = sorted(generations.keys())
249
+ # Sort generations numerically by extracting the number after 'gen'
250
+ sorted_gens = sorted(generations.keys(), key=lambda g: int(g[3:]) if g.startswith("gen") and g[3:].isdigit() else 0)
251
251
 
252
252
  # Calculate how many generations we can show
253
253
  available_rows = self.display.rows - row - 1 # Leave room at bottom
@@ -20,7 +20,10 @@ show_help() {
20
20
  claude-evolve edit - Manage evolution candidate statuses by generation or status
21
21
 
22
22
  USAGE:
23
- claude-evolve edit <selector> <action>
23
+ claude-evolve edit [--recent-generations=N] <selector> <action>
24
+
25
+ OPTIONS:
26
+ --recent-generations=N Limit operations to the most recent N generations only
24
27
 
25
28
  SELECTORS:
26
29
  gen01, gen02, etc. Target specific generation
@@ -41,29 +44,56 @@ ACTIONS:
41
44
  delete Delete candidates from CSV and remove .py files (asks confirmation)
42
45
 
43
46
  EXAMPLES:
44
- claude-evolve edit gen03 failed # Mark all gen03 as failed
45
- claude-evolve edit failed pending # Reset all failed candidates to pending
46
- claude-evolve edit failed failed-retry1 # Convert failed to retry status (bug fixing)
47
- claude-evolve edit complete failed # Mark all complete as failed for re-run
48
- claude-evolve edit all pending # Mark everything as pending for re-run
49
- claude-evolve edit gen02 reboot # Full reset of gen02 (delete files + clear data)
50
- claude-evolve edit gen02 delete # Delete gen02 from CSV and remove .py files
47
+ claude-evolve edit gen03 failed # Mark all gen03 as failed
48
+ claude-evolve edit failed pending # Reset all failed candidates to pending
49
+ claude-evolve edit --recent-generations=15 failed pending # Reset only recent 15 gen failures
50
+ claude-evolve edit --recent-generations=5 complete failed # Re-run recent 5 gen completions
51
+ claude-evolve edit failed failed-retry1 # Convert failed to retry status (bug fixing)
52
+ claude-evolve edit complete failed # Mark all complete as failed for re-run
53
+ claude-evolve edit all pending # Mark everything as pending for re-run
54
+ claude-evolve edit gen02 reboot # Full reset of gen02 (delete files + clear data)
55
+ claude-evolve edit gen02 delete # Delete gen02 from CSV and remove .py files
51
56
 
52
57
  DESCRIPTION:
53
58
  This command helps manage evolution runs when you need to re-evaluate candidates.
54
59
  Use status selectors (failed, complete, etc.) to bulk-change candidates by status.
55
60
  Use 'reboot' for complete reset including file deletion.
61
+ Use --recent-generations to limit operations to recent work only, useful for large systems.
56
62
  EOF
57
63
  }
58
64
 
59
65
  # Parse arguments
60
- if [[ $# -ne 2 ]]; then
66
+ recent_generations=""
67
+ args=()
68
+
69
+ while [[ $# -gt 0 ]]; do
70
+ case "$1" in
71
+ --recent-generations=*)
72
+ recent_generations="${1#*=}"
73
+ if [[ ! "$recent_generations" =~ ^[1-9][0-9]*$ ]]; then
74
+ echo "[ERROR] --recent-generations must be a positive integer" >&2
75
+ exit 1
76
+ fi
77
+ shift
78
+ ;;
79
+ --help|-h)
80
+ show_help
81
+ exit 0
82
+ ;;
83
+ *)
84
+ args+=("$1")
85
+ shift
86
+ ;;
87
+ esac
88
+ done
89
+
90
+ if [[ ${#args[@]} -ne 2 ]]; then
61
91
  show_help
62
92
  exit 1
63
93
  fi
64
94
 
65
- SELECTOR="$1"
66
- ACTION="$2"
95
+ SELECTOR="${args[0]}"
96
+ ACTION="${args[1]}"
67
97
 
68
98
  # Validate configuration
69
99
  if ! validate_config; then
@@ -99,7 +129,11 @@ update_candidates_status() {
99
129
  local new_status="$2"
100
130
  local clear_scores="$3"
101
131
 
102
- echo "[INFO] Updating candidates matching '$selector' to status: $new_status"
132
+ local filter_msg=""
133
+ if [[ -n "$recent_generations" ]]; then
134
+ filter_msg=" (limited to recent $recent_generations generations)"
135
+ fi
136
+ echo "[INFO] Updating candidates matching '$selector' to status: $new_status${filter_msg}"
103
137
 
104
138
  # Use Python to safely edit the CSV
105
139
  "$PYTHON_CMD" -c "
@@ -112,6 +146,7 @@ csv_file = '$FULL_CSV_PATH'
112
146
  selector = '$selector'
113
147
  new_status = '$new_status'
114
148
  clear_scores = '$clear_scores' == 'true'
149
+ recent_generations = '$recent_generations'
115
150
 
116
151
 
117
152
  try:
@@ -127,6 +162,31 @@ try:
127
162
  header = rows[0]
128
163
  updated_count = 0
129
164
 
165
+ # If recent_generations is specified, determine which generations to include
166
+ recent_gen_set = set()
167
+ if recent_generations and recent_generations.isdigit():
168
+ n_recent = int(recent_generations)
169
+
170
+ # Find all generation numbers from candidate IDs
171
+ all_generations = set()
172
+ for i in range(1, len(rows)):
173
+ row = rows[i]
174
+ if len(row) < 1:
175
+ continue
176
+ candidate_id = row[0]
177
+
178
+ # Extract generation number from candidate_id (e.g., gen01-001 -> 1)
179
+ match = re.match(r'^gen(\d+)-', candidate_id)
180
+ if match:
181
+ gen_num = int(match.group(1))
182
+ all_generations.add(gen_num)
183
+
184
+ # Get the most recent N generations
185
+ if all_generations:
186
+ sorted_generations = sorted(all_generations, reverse=True)
187
+ recent_gen_set = set(sorted_generations[:n_recent])
188
+ print(f'[INFO] Filtering to recent generations: {sorted(recent_gen_set)}', file=sys.stderr)
189
+
130
190
  # Update matching rows
131
191
  for i in range(1, len(rows)):
132
192
  row = rows[i]
@@ -152,6 +212,18 @@ try:
152
212
  else:
153
213
  matches = current_status == selector
154
214
 
215
+ # Apply recent generations filter if specified
216
+ if matches and recent_gen_set:
217
+ # Extract generation number from candidate_id
218
+ gen_match = re.match(r'^gen(\d+)-', candidate_id)
219
+ if gen_match:
220
+ candidate_gen = int(gen_match.group(1))
221
+ if candidate_gen not in recent_gen_set:
222
+ matches = False # Filter out this candidate
223
+ else:
224
+ # Non-generation candidates (like baseline) - exclude when filtering by recent generations
225
+ matches = False
226
+
155
227
  if matches:
156
228
  if clear_scores:
157
229
  # Clear everything after description (keep id, basedOnId, description)
@@ -192,6 +264,12 @@ delete_evolution_files() {
192
264
  return
193
265
  fi
194
266
 
267
+ local filter_msg=""
268
+ if [[ -n "$recent_generations" ]]; then
269
+ filter_msg=" (limited to recent $recent_generations generations)"
270
+ fi
271
+ echo "[INFO] Deleting evolution files for '$selector'${filter_msg}..."
272
+
195
273
  local deleted_count=0
196
274
 
197
275
  if [[ "$selector" == "all" ]]; then
@@ -225,15 +303,46 @@ import re
225
303
 
226
304
  csv_file = '$FULL_CSV_PATH'
227
305
  selector = '$selector'
306
+ recent_generations = '$recent_generations'
228
307
 
229
308
 
230
309
  try:
231
310
  with open(csv_file, 'r') as f:
232
311
  reader = csv.reader(f)
233
- next(reader) # Skip header
312
+ rows = list(reader)
313
+
314
+ if not rows:
315
+ print('')
316
+ sys.exit(0)
317
+
318
+ # Skip header if present
319
+ start_idx = 1 if rows and rows[0] and rows[0][0].lower() == 'id' else 0
320
+
321
+ # Determine recent generations if filtering is requested
322
+ recent_gen_set = set()
323
+ if recent_generations and recent_generations.isdigit():
324
+ n_recent = int(recent_generations)
325
+
326
+ # Find all generation numbers from candidate IDs
327
+ all_generations = set()
328
+ for row in rows[start_idx:]:
329
+ if len(row) < 1:
330
+ continue
331
+ candidate_id = row[0]
332
+
333
+ # Extract generation number from candidate_id (e.g., gen01-001 -> 1)
334
+ match = re.match(r'^gen(\d+)-', candidate_id)
335
+ if match:
336
+ gen_num = int(match.group(1))
337
+ all_generations.add(gen_num)
338
+
339
+ # Get the most recent N generations
340
+ if all_generations:
341
+ sorted_generations = sorted(all_generations, reverse=True)
342
+ recent_gen_set = set(sorted_generations[:n_recent])
234
343
 
235
344
  candidates = []
236
- for row in reader:
345
+ for row in rows[start_idx:]:
237
346
  if len(row) < 1:
238
347
  continue
239
348
 
@@ -249,6 +358,18 @@ try:
249
358
  else:
250
359
  matches = current_status == selector
251
360
 
361
+ # Apply recent generations filter if specified
362
+ if matches and recent_gen_set:
363
+ # Extract generation number from candidate_id
364
+ gen_match = re.match(r'^gen(\d+)-', candidate_id)
365
+ if gen_match:
366
+ candidate_gen = int(gen_match.group(1))
367
+ if candidate_gen not in recent_gen_set:
368
+ matches = False # Filter out this candidate
369
+ else:
370
+ # Non-generation candidates (like baseline) - exclude when filtering by recent generations
371
+ matches = False
372
+
252
373
  if matches:
253
374
  candidates.append(candidate_id)
254
375
 
@@ -284,7 +405,11 @@ except Exception as e:
284
405
  delete_candidates_from_csv() {
285
406
  local selector="$1"
286
407
 
287
- echo "[INFO] Deleting candidates matching '$selector' from CSV..."
408
+ local filter_msg=""
409
+ if [[ -n "$recent_generations" ]]; then
410
+ filter_msg=" (limited to recent $recent_generations generations)"
411
+ fi
412
+ echo "[INFO] Deleting candidates matching '$selector' from CSV${filter_msg}..."
288
413
 
289
414
  "$PYTHON_CMD" -c "
290
415
  import sys
@@ -293,6 +418,7 @@ from lib.evolution_csv import EvolutionCSV
293
418
  import re
294
419
 
295
420
  selector = '$selector'
421
+ recent_generations = '$recent_generations'
296
422
  deleted_count = 0
297
423
 
298
424
  with EvolutionCSV('$FULL_CSV_PATH') as csv:
@@ -306,6 +432,29 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
306
432
  has_header = rows and rows[0] and rows[0][0].lower() == 'id'
307
433
  start_idx = 1 if has_header else 0
308
434
 
435
+ # Determine recent generations if filtering is requested
436
+ recent_gen_set = set()
437
+ if recent_generations and recent_generations.isdigit():
438
+ n_recent = int(recent_generations)
439
+
440
+ # Find all generation numbers from candidate IDs
441
+ all_generations = set()
442
+ for row in rows[start_idx:]:
443
+ if not row or not row[0].strip():
444
+ continue
445
+ candidate_id = row[0].strip()
446
+
447
+ # Extract generation number from candidate_id (e.g., gen01-001 -> 1)
448
+ match = re.match(r'^gen(\d+)-', candidate_id)
449
+ if match:
450
+ gen_num = int(match.group(1))
451
+ all_generations.add(gen_num)
452
+
453
+ # Get the most recent N generations
454
+ if all_generations:
455
+ sorted_generations = sorted(all_generations, reverse=True)
456
+ recent_gen_set = set(sorted_generations[:n_recent])
457
+
309
458
  for row in rows[start_idx:]:
310
459
  if not row or not row[0].strip():
311
460
  continue
@@ -327,6 +476,18 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
327
476
  else:
328
477
  matches = current_status == selector
329
478
 
479
+ # Apply recent generations filter if specified
480
+ if matches and recent_gen_set:
481
+ # Extract generation number from candidate_id
482
+ gen_match = re.match(r'^gen(\d+)-', candidate_id)
483
+ if gen_match:
484
+ candidate_gen = int(gen_match.group(1))
485
+ if candidate_gen not in recent_gen_set:
486
+ matches = False # Filter out this candidate
487
+ else:
488
+ # Non-generation candidates (like baseline) - exclude when filtering by recent generations
489
+ matches = False
490
+
330
491
  if matches:
331
492
  candidates_to_delete.append(candidate_id)
332
493
 
@@ -341,7 +502,11 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
341
502
  }
342
503
 
343
504
  # Main execution
344
- echo "[INFO] Processing '$SELECTOR' with action: $ACTION"
505
+ info_msg="Processing '$SELECTOR' with action: $ACTION"
506
+ if [[ -n "$recent_generations" ]]; then
507
+ info_msg="$info_msg (limited to recent $recent_generations generations)"
508
+ fi
509
+ echo "[INFO] $info_msg"
345
510
 
346
511
  case "$ACTION" in
347
512
  failed)
@@ -387,4 +552,4 @@ echo "[INFO] Edit operation complete"
387
552
 
388
553
  # Call status command to show current state
389
554
  echo ""
390
- "$SCRIPT_DIR/claude-evolve-status" --brief
555
+ "$SCRIPT_DIR/claude-evolve-status" --brief