claude-evolve 1.3.39 → 1.3.40

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
@@ -55,6 +55,7 @@ claude-evolve setup # Initialize evolution workspace
55
55
  claude-evolve ideate # Generate new algorithm ideas
56
56
  claude-evolve run # Execute evolution candidates
57
57
  claude-evolve analyze # Analyze evolution results
58
+ claude-evolve edit # Manage candidate statuses by generation
58
59
  claude-evolve config # Manage configuration settings
59
60
  ```
60
61
 
@@ -90,6 +91,18 @@ Analyzes evolution progress and generates insights:
90
91
  - Best-performing algorithm variants
91
92
  - Suggestions for future evolution directions
92
93
 
94
+ #### claude-evolve-edit
95
+ Manages candidate statuses by generation for re-evaluation workflows:
96
+ - Mark generations as failed, complete, or pending
97
+ - Reset entire generations (delete files and clear scores)
98
+ - Essential for re-running evaluations when algorithms or evaluators change
99
+
100
+ ```bash
101
+ claude-evolve edit gen03 failed # Mark all gen03 as failed
102
+ claude-evolve edit all pending # Mark everything as pending for re-run
103
+ claude-evolve edit gen02 reboot # Full reset of gen02 (delete files + clear data)
104
+ ```
105
+
93
106
  #### claude-evolve-config
94
107
  Manages configuration settings:
95
108
  - View current configuration
@@ -203,6 +216,30 @@ your-project/
203
216
  └── (your main project files)
204
217
  ```
205
218
 
219
+ ## Evolution CSV Format
220
+
221
+ The evolution.csv file tracks all candidates and their results. The core columns are:
222
+
223
+ **Required columns (positions 1-5):**
224
+ 1. **id** - Unique identifier for each candidate (e.g., gen01-001, gen02-015)
225
+ 2. **basedOnId** - Parent algorithm this was derived from (empty for novel ideas)
226
+ 3. **description** - What changes this variant implements
227
+ 4. **performance** - Score from evaluator (column 4) - this drives evolution selection
228
+ 5. **status** - Current state: empty/"pending", "running", "complete", "failed", "timeout"
229
+
230
+ **Additional columns:**
231
+ - Any other metrics your evaluator outputs (fitness, sharpe, total_return, yearly_return, max_drawdown, volatility, etc.)
232
+ - Error messages for failed runs
233
+ - Execution time
234
+
235
+ **Key behaviors:**
236
+ - The system uses column 4 (performance) for evolution selection, regardless of its name
237
+ - Column 5 (status) determines what needs to be run
238
+ - Empty or "pending" status means the candidate is ready to run
239
+ - You can reset a candidate to pending by deleting all fields after the description (columns 4+)
240
+ - Additional columns are automatically added when your evaluator returns JSON with extra fields
241
+ - Rows with fewer than 5 fields are treated as pending candidates
242
+
206
243
  ## Evaluator Output Format
207
244
 
208
245
  Your evaluator must output a performance score to stdout. The system looks for either `performance` or `score` fields. Four formats are supported:
@@ -275,8 +312,9 @@ print(score) # Simple number to stdout
275
312
  Edit `evolution/config.yaml` to customize:
276
313
 
277
314
  ```yaml
278
- # Working directory for evolution files
279
- evolution_dir: "evolution"
315
+ # NOTE: The evolution directory is automatically inferred from this config file's location.
316
+ # For example, if this file is at /path/to/my-experiment/config.yaml,
317
+ # then the evolution directory will be /path/to/my-experiment/
280
318
 
281
319
  # Algorithm and evaluator file paths
282
320
  algorithm_file: "algorithm.py"
@@ -77,7 +77,30 @@ if [[ ! -f $csv_file ]]; then
77
77
  exit 1
78
78
  fi
79
79
 
80
- echo "=== Evolution Analysis Summary ==="
80
+ # Determine what we're evolving based on paths
81
+ EVOLUTION_CONTEXT=""
82
+ if [[ -n "$EVOLUTION_DIR" ]]; then
83
+ # Get the evolution directory name (e.g., "evolution-atr" -> "ATR")
84
+ EVOLUTION_NAME=$(basename "$EVOLUTION_DIR")
85
+ EVOLUTION_CONTEXT="${EVOLUTION_NAME#evolution-}"
86
+ EVOLUTION_CONTEXT=$(echo "$EVOLUTION_CONTEXT" | tr '[:lower:]' '[:upper:]')
87
+ fi
88
+
89
+ # If we can't determine from evolution dir, try from algorithm path
90
+ if [[ -z "$EVOLUTION_CONTEXT" && -n "$ALGORITHM_FILE" ]]; then
91
+ # Get algorithm file name
92
+ if [[ -f "$FULL_ALGORITHM_PATH" ]]; then
93
+ ALGO_NAME=$(basename "$FULL_ALGORITHM_PATH" .py)
94
+ EVOLUTION_CONTEXT="$ALGO_NAME"
95
+ fi
96
+ fi
97
+
98
+ # Default if we still can't determine
99
+ if [[ -z "$EVOLUTION_CONTEXT" ]]; then
100
+ EVOLUTION_CONTEXT="Algorithm"
101
+ fi
102
+
103
+ echo "=== Evolution Analysis Summary - $EVOLUTION_CONTEXT ==="
81
104
  echo
82
105
 
83
106
  # Count totals (pure shell)
@@ -402,6 +425,7 @@ with open('$csv_file', 'r') as f:
402
425
 
403
426
  max_perf = 0
404
427
  max_id = ''
428
+ max_desc = ''
405
429
  max_order = 0
406
430
  completed_order = 0
407
431
 
@@ -415,12 +439,16 @@ with open('$csv_file', 'r') as f:
415
439
  max_perf = perf_val
416
440
  max_order = completed_order
417
441
  max_id = row[0]
442
+ max_desc = row[2] if len(row) > 2 else ''
418
443
  except ValueError:
419
444
  pass
420
445
 
421
446
  print(f'max_perf={max_perf}')
422
447
  print(f'max_row={max_order}')
423
448
  print(f'max_id=\"{max_id}\"')
449
+ # Escape special characters in description for shell
450
+ desc_escaped = max_desc.replace('\\\\', '\\\\\\\\').replace('\"', '\\\\\"').replace('\$', '\\\\\$').replace('\`', '\\\\\`')
451
+ print(f'max_desc=\"{desc_escaped}\"')
424
452
  ")"
425
453
 
426
454
  # Create generation averages file and track max generation
@@ -544,7 +572,7 @@ set multiplot layout 2,1 margins 0.08,0.82,0.15,0.95 spacing 0.1,0.15
544
572
 
545
573
  #=================== TOP PLOT: Performance Over Time ===================
546
574
  # AIDEV-NOTE: Removed x-axis to eliminate tick overlap and formatting issues
547
- set title "Algorithm Evolution Performance Over Time" font ",14"
575
+ set title "$EVOLUTION_CONTEXT Algorithm Evolution Performance Over Time" font ",14"
548
576
  unset xlabel
549
577
  set ylabel "Performance Score"
550
578
  set grid y # Only show horizontal grid lines
@@ -578,10 +606,11 @@ plot "$gen_avg_file" using 1:3 with boxes linecolor rgb "#4CAF50" notitle
578
606
 
579
607
  unset multiplot
580
608
 
581
- # Add winner label at bottom
582
- set terminal png size 1200,830
609
+ # Add winner label and description at bottom
610
+ set terminal png size 1200,850
583
611
  set output "$output_file"
584
- set label "Best Overall: $max_id (Score: $max_perf)" at screen 0.5, 0.05 center font ",12"
612
+ set label "Best Overall: $max_id (Score: $max_perf)" at screen 0.5, 0.07 center font ",12"
613
+ set label "$max_desc" at screen 0.5, 0.04 center font ",10" textcolor rgb "#666666"
585
614
  replot
586
615
  EOF
587
616
  else
@@ -0,0 +1,297 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ # Load configuration
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ # shellcheck source=../lib/config.sh
8
+ source "$SCRIPT_DIR/../lib/config.sh"
9
+
10
+ # Use CLAUDE_EVOLVE_CONFIG if set, otherwise default
11
+ if [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
12
+ load_config "$CLAUDE_EVOLVE_CONFIG"
13
+ else
14
+ load_config
15
+ fi
16
+
17
+ # Function to show help
18
+ show_help() {
19
+ cat <<EOF
20
+ claude-evolve cleanup - Clean up unchanged algorithms and their descendants
21
+
22
+ USAGE:
23
+ claude-evolve cleanup [OPTIONS]
24
+
25
+ OPTIONS:
26
+ --dry-run Show what would be done without making changes
27
+ --force Actually perform the cleanup (required for real changes)
28
+ --help Show this help message
29
+
30
+ DESCRIPTION:
31
+ This tool finds algorithm files that are identical to their parent and:
32
+ 1. Deletes the unchanged .py files
33
+ 2. Resets those candidates to pending status in CSV
34
+ 3. Finds and cleans up any descendants that inherited from the bad copies
35
+
36
+ Use --dry-run first to see what would be affected.
37
+
38
+ EXAMPLES:
39
+ claude-evolve cleanup --dry-run # Preview changes
40
+ claude-evolve cleanup --force # Actually clean up
41
+ EOF
42
+ }
43
+
44
+ # Parse arguments
45
+ DRY_RUN=true
46
+ FORCE=false
47
+
48
+ while [[ $# -gt 0 ]]; do
49
+ case $1 in
50
+ --dry-run)
51
+ DRY_RUN=true
52
+ shift
53
+ ;;
54
+ --force)
55
+ FORCE=true
56
+ DRY_RUN=false
57
+ shift
58
+ ;;
59
+ --help)
60
+ show_help
61
+ exit 0
62
+ ;;
63
+ *)
64
+ echo "[ERROR] Unknown option: $1" >&2
65
+ exit 1
66
+ ;;
67
+ esac
68
+ done
69
+
70
+ if [[ $FORCE == false ]]; then
71
+ DRY_RUN=true
72
+ fi
73
+
74
+ # Validate configuration
75
+ if ! validate_config; then
76
+ echo "[ERROR] Configuration validation failed" >&2
77
+ exit 1
78
+ fi
79
+
80
+ # Check if CSV exists
81
+ if [[ ! -f "$FULL_CSV_PATH" ]]; then
82
+ echo "[ERROR] Evolution CSV not found: $FULL_CSV_PATH" >&2
83
+ exit 1
84
+ fi
85
+
86
+ echo "🧹 Claude-Evolve Duplicate Cleanup Tool"
87
+ echo "========================================"
88
+ echo "Evolution directory: $FULL_EVOLUTION_DIR"
89
+ echo "CSV file: $FULL_CSV_PATH"
90
+ echo "Mode: $(if [[ $DRY_RUN == true ]]; then echo "DRY RUN (preview only)"; else echo "FORCE (will make changes)"; fi)"
91
+ echo ""
92
+
93
+ # Use Python to analyze and clean up duplicates
94
+ "$PYTHON_CMD" -c "
95
+ import csv
96
+ import os
97
+ import sys
98
+ import shutil
99
+ from pathlib import Path
100
+
101
+ csv_file = '$FULL_CSV_PATH'
102
+ evolution_dir = '$FULL_EVOLUTION_DIR'
103
+ dry_run = '$DRY_RUN' == 'true'
104
+ algorithm_file = '$FULL_ALGORITHM_PATH'
105
+
106
+ def files_identical(file1, file2):
107
+ \"\"\"Check if two files have identical content.\"\"\"
108
+ if not os.path.exists(file1) or not os.path.exists(file2):
109
+ return False
110
+
111
+ try:
112
+ with open(file1, 'rb') as f1, open(file2, 'rb') as f2:
113
+ return f1.read() == f2.read()
114
+ except Exception:
115
+ return False
116
+
117
+ def get_algorithm_file_path(candidate_id, base_algorithm):
118
+ \"\"\"Get the file path for a candidate's algorithm.\"\"\"
119
+ # Handle both old and new format IDs
120
+ if candidate_id.isdigit():
121
+ filename = f'evolution_id{candidate_id}.py'
122
+ else:
123
+ filename = f'evolution_{candidate_id}.py'
124
+
125
+ return os.path.join(evolution_dir, filename)
126
+
127
+ def get_parent_file_path(based_on_id, base_algorithm):
128
+ \"\"\"Get the file path for a parent algorithm.\"\"\"
129
+ if not based_on_id or based_on_id == '0' or based_on_id == '\"\"':
130
+ return base_algorithm
131
+
132
+ # Handle both old and new format IDs
133
+ if based_on_id.isdigit():
134
+ filename = f'evolution_id{based_on_id}.py'
135
+ else:
136
+ filename = f'evolution_{based_on_id}.py'
137
+
138
+ return os.path.join(evolution_dir, filename)
139
+
140
+ try:
141
+ # Read CSV
142
+ with open(csv_file, 'r') as f:
143
+ reader = csv.reader(f)
144
+ rows = list(reader)
145
+
146
+ if len(rows) <= 1:
147
+ print('No candidates found in CSV')
148
+ sys.exit(0)
149
+
150
+ header = rows[0]
151
+ candidates = {}
152
+
153
+ # Build candidate map
154
+ for i, row in enumerate(rows[1:], 1):
155
+ if len(row) >= 3:
156
+ candidate_id = row[0]
157
+ based_on_id = row[1] if len(row) > 1 else ''
158
+ description = row[2] if len(row) > 2 else ''
159
+ performance = row[3] if len(row) > 3 else ''
160
+ status = row[4] if len(row) > 4 else ''
161
+
162
+ candidates[candidate_id] = {
163
+ 'row_index': i,
164
+ 'based_on_id': based_on_id,
165
+ 'description': description,
166
+ 'performance': performance,
167
+ 'status': status,
168
+ 'file_path': get_algorithm_file_path(candidate_id, algorithm_file)
169
+ }
170
+
171
+ print(f'Found {len(candidates)} candidates to analyze')
172
+ print('')
173
+
174
+ # Find unchanged candidates
175
+ unchanged_candidates = []
176
+
177
+ for candidate_id, info in candidates.items():
178
+ if not info['based_on_id'] or info['based_on_id'] == '0' or info['based_on_id'] == '\"\"':
179
+ # Skip root candidates (no parent)
180
+ continue
181
+
182
+ parent_file = get_parent_file_path(info['based_on_id'], algorithm_file)
183
+ candidate_file = info['file_path']
184
+
185
+ if os.path.exists(candidate_file) and files_identical(candidate_file, parent_file):
186
+ unchanged_candidates.append(candidate_id)
187
+ print(f'📋 UNCHANGED: {candidate_id} is identical to parent {info[\"based_on_id\"]}')
188
+ print(f' File: {os.path.basename(candidate_file)}')
189
+ print(f' Description: {info[\"description\"]}')
190
+ print(f' Status: {info[\"status\"]}')
191
+ print('')
192
+
193
+ if not unchanged_candidates:
194
+ print('✅ No unchanged candidates found - all algorithms appear to be properly mutated!')
195
+ sys.exit(0)
196
+
197
+ print(f'Found {len(unchanged_candidates)} unchanged candidates')
198
+ print('')
199
+
200
+ # Find descendants of unchanged candidates
201
+ def find_descendants(bad_parent_id, all_candidates, found=None):
202
+ if found is None:
203
+ found = set()
204
+
205
+ for cand_id, info in all_candidates.items():
206
+ if info['based_on_id'] == bad_parent_id and cand_id not in found:
207
+ found.add(cand_id)
208
+ # Recursively find descendants of this candidate
209
+ find_descendants(cand_id, all_candidates, found)
210
+
211
+ return found
212
+
213
+ all_affected = set(unchanged_candidates)
214
+
215
+ # Find all descendants
216
+ for unchanged_id in unchanged_candidates:
217
+ descendants = find_descendants(unchanged_id, candidates)
218
+ all_affected.update(descendants)
219
+
220
+ if descendants:
221
+ print(f'🔗 DESCENDANTS of {unchanged_id}: {sorted(descendants)}')
222
+
223
+ print('')
224
+ print(f'📊 SUMMARY:')
225
+ print(f' • {len(unchanged_candidates)} unchanged candidates')
226
+ print(f' • {len(all_affected) - len(unchanged_candidates)} descendants affected')
227
+ print(f' • {len(all_affected)} total candidates to clean up')
228
+ print('')
229
+
230
+ if dry_run:
231
+ print('🔍 DRY RUN - Showing what would be done:')
232
+ print('')
233
+
234
+ for candidate_id in sorted(all_affected):
235
+ info = candidates[candidate_id]
236
+ action = 'DELETE FILE & RESET' if candidate_id in unchanged_candidates else 'RESET (descendant)'
237
+ print(f' {action}: {candidate_id}')
238
+ print(f' File: {os.path.basename(info[\"file_path\"])}')
239
+ print(f' Description: {info[\"description\"]}')
240
+ print('')
241
+
242
+ print('To actually perform cleanup, run with --force')
243
+ else:
244
+ print('🧹 PERFORMING CLEANUP:')
245
+ print('')
246
+
247
+ # Delete files and update CSV
248
+ files_deleted = 0
249
+ rows_updated = 0
250
+
251
+ for candidate_id in sorted(all_affected):
252
+ info = candidates[candidate_id]
253
+
254
+ # Delete file if it exists (for unchanged candidates)
255
+ if candidate_id in unchanged_candidates and os.path.exists(info['file_path']):
256
+ try:
257
+ os.remove(info['file_path'])
258
+ files_deleted += 1
259
+ print(f' ✅ DELETED: {os.path.basename(info[\"file_path\"])}')
260
+ except Exception as e:
261
+ print(f' ❌ FAILED to delete {os.path.basename(info[\"file_path\"])}: {e}')
262
+
263
+ # Reset CSV row (clear performance and status, keep description)
264
+ row_idx = info['row_index']
265
+ if len(rows[row_idx]) >= 5:
266
+ # Clear performance (column 3) and status (column 4), but keep first 3 columns
267
+ rows[row_idx] = rows[row_idx][:3] + ['', ''] + rows[row_idx][5:]
268
+ rows_updated += 1
269
+ print(f' ✅ RESET CSV: {candidate_id} -> pending')
270
+
271
+ # Write updated CSV
272
+ try:
273
+ with open(csv_file + '.tmp', 'w', newline='') as f:
274
+ writer = csv.writer(f)
275
+ writer.writerows(rows)
276
+
277
+ # Atomic replace
278
+ os.rename(csv_file + '.tmp', csv_file)
279
+ print('')
280
+ print(f'✅ CLEANUP COMPLETE:')
281
+ print(f' • {files_deleted} files deleted')
282
+ print(f' • {rows_updated} CSV rows reset to pending')
283
+ print(f' • CSV updated successfully')
284
+
285
+ except Exception as e:
286
+ print(f'❌ FAILED to update CSV: {e}')
287
+ sys.exit(1)
288
+
289
+ except Exception as e:
290
+ print(f'Error: {e}')
291
+ sys.exit(1)
292
+ "
293
+
294
+ echo ""
295
+ if [[ $DRY_RUN == true ]]; then
296
+ echo "💡 TIP: Run with --force to actually perform the cleanup"
297
+ fi