claude-evolve 1.5.3 → 1.5.6

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.
@@ -513,6 +513,9 @@ print(f'max_desc=\"{desc_escaped}\"')
513
513
  # Calculate total data points for dynamic sizing
514
514
  total_data_points=$(awk 'END {print NR-1}' "$data_file") # Subtract header row
515
515
 
516
+ # Count unique generations
517
+ unique_generations=$(awk '{if(NR>1) print $4}' "$data_file" | sort -nu | wc -l)
518
+
516
519
  # AIDEV-NOTE: Dynamic dot sizing based on data point count
517
520
  # Use significantly larger dots when there are fewer data points for better visibility
518
521
  if [[ $total_data_points -lt 35 ]]; then
@@ -530,16 +533,24 @@ print(f'max_desc=\"{desc_escaped}\"')
530
533
  # Find all generations that have data
531
534
  generations=($(awk '{if(NR>1) print $4}' "$data_file" | sort -n | uniq))
532
535
 
533
- for gen_num in "${generations[@]}"; do
534
- if [[ -n $gen_num ]]; then
535
- color=$(get_gen_color "$gen_num")
536
- if [[ $gen_plots_added -gt 0 ]]; then
537
- 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++))
538
551
  fi
539
- 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\""
540
- ((gen_plots_added++))
541
- fi
542
- done
552
+ done
553
+ fi
543
554
 
544
555
  # Add novel candidates
545
556
  if [[ -s "$novel_file" ]] && [[ $(wc -l < "$novel_file") -gt 1 ]]; then
@@ -586,6 +597,9 @@ print(f'max_desc=\"{desc_escaped}\"')
586
597
  set terminal png size 1200,800
587
598
  set output "$output_file"
588
599
 
600
+ # Define unique generations count
601
+ unique_gens = $unique_generations
602
+
589
603
  # Set up multiplot with proper spacing
590
604
  set multiplot layout 2,1 margins 0.08,0.82,0.15,0.95 spacing 0.1,0.15
591
605
 
@@ -595,7 +609,9 @@ set title "$EVOLUTION_CONTEXT Algorithm Evolution Performance Over Time" font ",
595
609
  unset xlabel
596
610
  set ylabel "Performance Score"
597
611
  set grid y # Only show horizontal grid lines
598
- 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
599
615
 
600
616
  # AIDEV-NOTE: Remove x-axis entirely to avoid tick problems with large datasets
601
617
  unset xtics
@@ -603,6 +619,9 @@ set autoscale
603
619
  set yrange [*:*] # Auto-scale y-axis only
604
620
 
605
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
+
606
625
  plot $plot_cmd
607
626
 
608
627
  #=================== BOTTOM PLOT: Generation Medians ===================
@@ -614,8 +633,13 @@ set boxwidth 0.6
614
633
  unset key
615
634
  set grid y
616
635
 
617
- # Set custom x-axis labels
618
- 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
+ }
619
643
 
620
644
  # Auto-scale for generation plot too
621
645
  set autoscale
@@ -38,6 +38,7 @@ else
38
38
  load_config
39
39
  fi
40
40
 
41
+
41
42
  # Run the Python autostatus script
42
43
  exec "$PYTHON_CMD" -c '
43
44
  import os
@@ -118,9 +119,27 @@ class AutoStatus:
118
119
  "working_dir": os.path.dirname(self.csv_path)
119
120
  }
120
121
 
122
+ # Helpers
123
+ def parse_candidate_id(cid):
124
+ """Return (gen_num, seq_num) for ids like gen123-045; fall back to large numbers."""
125
+ try:
126
+ left, right = cid.split("-", 1)
127
+ gen_num = int(left[3:]) if left.startswith("gen") else 10**9
128
+ seq_num = int(right)
129
+ return gen_num, seq_num
130
+ except Exception:
131
+ return 10**9, 10**9
132
+
133
+ def is_earlier(cid_a, cid_b):
134
+ """True if cid_a is earlier than cid_b by generation, then sequence."""
135
+ ga, sa = parse_candidate_id(cid_a)
136
+ gb, sb = parse_candidate_id(cid_b)
137
+ return (ga, sa) < (gb, sb)
138
+
139
+
121
140
  # Process candidates by generation
122
- all_candidates = []
123
141
  stats_by_gen = {}
142
+ leader = None # Track overall leader with earliest-wins tie behavior
124
143
 
125
144
  for row in rows[1:]: # Skip header
126
145
  if len(row) >= 1 and row[0]: # Must have an ID
@@ -157,20 +176,24 @@ class AutoStatus:
157
176
  description = row[2] if len(row) > 2 else "No description"
158
177
  candidate_info = (candidate_id, description, score)
159
178
  stats_by_gen[gen]["candidates"].append(candidate_info)
160
- all_candidates.append(candidate_info)
179
+
180
+ # Update overall leader: highest raw score; ties -> earliest ID
181
+ if leader is None or score > leader[2] or (score == leader[2] and is_earlier(candidate_id, leader[0])):
182
+ leader = candidate_info
183
+
184
+ # Update generation best: highest raw score; ties -> earliest ID
185
+ if "best" not in stats_by_gen[gen]:
186
+ stats_by_gen[gen]["best"] = candidate_info
187
+ else:
188
+ best_id, _, best_score = stats_by_gen[gen]["best"]
189
+ if score > best_score or (score == best_score and is_earlier(candidate_id, best_id)):
190
+ stats_by_gen[gen]["best"] = candidate_info
161
191
  except ValueError:
162
192
  pass
163
193
 
164
- # Find the overall leader
165
- leader = None
166
- if all_candidates:
167
- leader = max(all_candidates, key=lambda x: x[2])
168
-
169
- # Find best performer in each generation
194
+ # Ensure every generation has a best field
170
195
  for gen in stats_by_gen:
171
- if stats_by_gen[gen]["candidates"]:
172
- stats_by_gen[gen]["best"] = max(stats_by_gen[gen]["candidates"], key=lambda x: x[2])
173
- else:
196
+ if "best" not in stats_by_gen[gen]:
174
197
  stats_by_gen[gen]["best"] = None
175
198
 
176
199
  return {
@@ -344,4 +367,4 @@ class AutoStatus:
344
367
  csv_path = "'"$FULL_CSV_PATH"'"
345
368
  auto_status = AutoStatus(csv_path)
346
369
  auto_status.run()
347
- '
370
+ '
@@ -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