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.
- package/bin/claude-evolve-analyze +29 -13
- package/bin/claude-evolve-clean-invalid +117 -0
- package/bin/claude-evolve-cleanup-duplicates +131 -0
- package/bin/claude-evolve-ideate +433 -310
- package/bin/claude-evolve-run +79 -30
- package/bin/claude-evolve-status +23 -0
- package/bin/claude-evolve-worker +24 -24
- package/lib/__pycache__/evolution_csv.cpython-311.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-313.pyc +0 -0
- package/lib/config.sh +3 -0
- package/lib/csv_helper_robust.py +121 -0
- package/lib/evolution_csv.py +349 -0
- package/package.json +1 -1
|
@@ -194,14 +194,9 @@ else
|
|
|
194
194
|
fi
|
|
195
195
|
|
|
196
196
|
echo
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
echo "Performance: $top_score"
|
|
201
|
-
echo "Description: $top_desc"
|
|
202
|
-
else
|
|
203
|
-
echo "No completed candidates yet"
|
|
204
|
-
fi
|
|
197
|
+
|
|
198
|
+
# Call status command to show winners and revolutionary improvers
|
|
199
|
+
"$SCRIPT_DIR/claude-evolve-status" --brief
|
|
205
200
|
|
|
206
201
|
# Generation analysis
|
|
207
202
|
echo
|
|
@@ -321,10 +316,12 @@ if command -v gnuplot >/dev/null 2>&1 && [[ $valid_performance_count -gt 0 ]]; t
|
|
|
321
316
|
# Create data files for gnuplot
|
|
322
317
|
data_file="/tmp/evolution_data_$$.dat"
|
|
323
318
|
winner_file="/tmp/evolution_winner_$$.dat"
|
|
319
|
+
novel_file="/tmp/evolution_novel_$$.dat"
|
|
324
320
|
gen_avg_file="/tmp/evolution_gen_avg_$$.dat"
|
|
325
321
|
|
|
326
322
|
echo "# Row ID Performance Generation" >"$data_file"
|
|
327
323
|
echo "# Generation MedianPerformance Color" >"$gen_avg_file"
|
|
324
|
+
echo "# Row ID Performance Generation" >"$novel_file"
|
|
328
325
|
|
|
329
326
|
# Get color by generation number (rotates through 7 colors)
|
|
330
327
|
get_gen_color() {
|
|
@@ -350,14 +347,15 @@ if command -v gnuplot >/dev/null 2>&1 && [[ $valid_performance_count -gt 0 ]]; t
|
|
|
350
347
|
max_row=0
|
|
351
348
|
max_id=""
|
|
352
349
|
|
|
353
|
-
# Use Python to generate chart data
|
|
350
|
+
# Use Python to generate chart data and identify revolutionary improvers
|
|
354
351
|
"$PYTHON_CMD" -c "
|
|
355
352
|
import csv
|
|
356
353
|
import re
|
|
357
354
|
|
|
358
355
|
with open('$csv_file', 'r') as f:
|
|
359
356
|
reader = csv.reader(f)
|
|
360
|
-
|
|
357
|
+
rows = list(reader)
|
|
358
|
+
header = rows[0]
|
|
361
359
|
|
|
362
360
|
completed_order = 0 # Track order of completion
|
|
363
361
|
|
|
@@ -367,15 +365,19 @@ with open('$csv_file', 'r') as f:
|
|
|
367
365
|
with open('$gen_data_temp', 'w') as gen_f:
|
|
368
366
|
pass # Clear file
|
|
369
367
|
|
|
368
|
+
with open('$novel_file', 'w') as novel_f:
|
|
369
|
+
novel_f.write('# Order ID Performance Generation\\n')
|
|
370
|
+
|
|
370
371
|
max_perf = 0
|
|
371
372
|
max_id = ''
|
|
372
373
|
max_order = 0
|
|
373
374
|
|
|
374
|
-
|
|
375
|
+
|
|
376
|
+
for row in rows[1:]:
|
|
375
377
|
if len(row) < 5:
|
|
376
378
|
continue
|
|
377
379
|
|
|
378
|
-
id,
|
|
380
|
+
id, parent_id, desc, perf, status = row[0], row[1] if len(row) > 1 else '', row[2] if len(row) > 2 else '', row[3], row[4]
|
|
379
381
|
|
|
380
382
|
# Extract generation from ID
|
|
381
383
|
gen = 'gen01' # default
|
|
@@ -396,6 +398,11 @@ with open('$csv_file', 'r') as f:
|
|
|
396
398
|
with open('$data_file', 'a') as f:
|
|
397
399
|
f.write(f'{completed_order} \"{id}\" {perf} {gen_num}\\n')
|
|
398
400
|
|
|
401
|
+
# Write to novel file if this is a novel candidate
|
|
402
|
+
if not parent_id:
|
|
403
|
+
with open('$novel_file', 'a') as f:
|
|
404
|
+
f.write(f'{completed_order} \"{id}\" {perf} {gen_num}\\n')
|
|
405
|
+
|
|
399
406
|
# Write to gen temp file
|
|
400
407
|
with open('$gen_data_temp', 'a') as f:
|
|
401
408
|
f.write(f'{gen} {perf}\\n')
|
|
@@ -532,6 +539,15 @@ print(f'max_desc=\"{desc_escaped}\"')
|
|
|
532
539
|
fi
|
|
533
540
|
done
|
|
534
541
|
|
|
542
|
+
# Add novel candidates
|
|
543
|
+
if [[ -s "$novel_file" ]] && [[ $(wc -l < "$novel_file") -gt 1 ]]; then
|
|
544
|
+
if [[ $gen_plots_added -gt 0 ]]; then
|
|
545
|
+
plot_cmd="$plot_cmd, \\"$'\n'
|
|
546
|
+
fi
|
|
547
|
+
plot_cmd="${plot_cmd} \"$novel_file\" using 1:3 with points pointtype 8 pointsize $winner_dot_size linecolor rgb \"#ff1493\" title \"Novel Candidates\""
|
|
548
|
+
((gen_plots_added++))
|
|
549
|
+
fi
|
|
550
|
+
|
|
535
551
|
# Add winner point
|
|
536
552
|
if [[ -n $max_id && -s "$winner_file" ]]; then
|
|
537
553
|
if [[ $gen_plots_added -gt 0 ]]; then
|
|
@@ -618,7 +634,7 @@ EOF
|
|
|
618
634
|
exit 0
|
|
619
635
|
fi
|
|
620
636
|
|
|
621
|
-
rm -f "$data_file" "$winner_file" "$gen_avg_file" "$gen_data_temp"
|
|
637
|
+
rm -f "$data_file" "$winner_file" "$novel_file" "$gen_avg_file" "$gen_data_temp"
|
|
622
638
|
echo "Chart saved to: $output_file"
|
|
623
639
|
|
|
624
640
|
# Always try to open chart (not just when --open is used)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Cleanup script to remove invalid entries from evolution CSV files.
|
|
4
|
+
This removes entries with IDs that contain shell constructs or other invalid content.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import csv
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import argparse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def is_invalid_id(candidate_id):
|
|
15
|
+
"""Check if a candidate ID contains invalid content."""
|
|
16
|
+
if not candidate_id:
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
# Check for shell constructs
|
|
20
|
+
invalid_patterns = [
|
|
21
|
+
r'EOF.*<.*null',
|
|
22
|
+
r'<<.*EOF',
|
|
23
|
+
r'<.*dev.*null',
|
|
24
|
+
r'^\s*#', # Comments
|
|
25
|
+
r'^\s*$', # Empty
|
|
26
|
+
r'[<>|&;`$]', # Shell special characters
|
|
27
|
+
r'^\s*\[', # Bash test constructs
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
for pattern in invalid_patterns:
|
|
31
|
+
if re.search(pattern, candidate_id, re.IGNORECASE):
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
# Check for excessively long IDs (likely errors)
|
|
35
|
+
if len(candidate_id) > 50:
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def clean_csv(csv_file, dry_run=False):
|
|
42
|
+
"""Remove invalid entries from CSV file."""
|
|
43
|
+
if not os.path.exists(csv_file):
|
|
44
|
+
print(f"Error: CSV file {csv_file} not found")
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
invalid_entries = []
|
|
48
|
+
valid_rows = []
|
|
49
|
+
|
|
50
|
+
with open(csv_file, 'r') as f:
|
|
51
|
+
reader = csv.reader(f)
|
|
52
|
+
header = next(reader)
|
|
53
|
+
valid_rows.append(header)
|
|
54
|
+
|
|
55
|
+
for row_num, row in enumerate(reader, start=2):
|
|
56
|
+
if not row or len(row) == 0:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
candidate_id = row[0].strip() if row[0] else ''
|
|
60
|
+
|
|
61
|
+
if is_invalid_id(candidate_id):
|
|
62
|
+
invalid_entries.append({
|
|
63
|
+
'row_num': row_num,
|
|
64
|
+
'id': candidate_id,
|
|
65
|
+
'row': row
|
|
66
|
+
})
|
|
67
|
+
else:
|
|
68
|
+
valid_rows.append(row)
|
|
69
|
+
|
|
70
|
+
if not invalid_entries:
|
|
71
|
+
print("No invalid entries found.")
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
print(f"Found {len(invalid_entries)} invalid entries:")
|
|
75
|
+
for entry in invalid_entries:
|
|
76
|
+
print(f" Row {entry['row_num']}: ID='{entry['id']}'")
|
|
77
|
+
if len(entry['row']) > 4:
|
|
78
|
+
print(f" Status: {entry['row'][4]}")
|
|
79
|
+
|
|
80
|
+
if dry_run:
|
|
81
|
+
print("\nDry run - no changes made.")
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
# Write cleaned CSV
|
|
85
|
+
backup_file = csv_file + '.backup'
|
|
86
|
+
print(f"\nBacking up to: {backup_file}")
|
|
87
|
+
os.rename(csv_file, backup_file)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
with open(csv_file, 'w', newline='') as f:
|
|
91
|
+
writer = csv.writer(f)
|
|
92
|
+
writer.writerows(valid_rows)
|
|
93
|
+
|
|
94
|
+
print(f"Cleaned CSV written to: {csv_file}")
|
|
95
|
+
print(f"Removed {len(invalid_entries)} invalid entries")
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Error writing cleaned CSV: {e}")
|
|
100
|
+
print("Restoring backup...")
|
|
101
|
+
os.rename(backup_file, csv_file)
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def main():
|
|
106
|
+
parser = argparse.ArgumentParser(description='Remove invalid entries from evolution CSV')
|
|
107
|
+
parser.add_argument('csv_file', help='Path to evolution CSV file')
|
|
108
|
+
parser.add_argument('--dry-run', action='store_true', help='Show what would be removed without making changes')
|
|
109
|
+
|
|
110
|
+
args = parser.parse_args()
|
|
111
|
+
|
|
112
|
+
success = clean_csv(args.csv_file, args.dry_run)
|
|
113
|
+
sys.exit(0 if success else 1)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == '__main__':
|
|
117
|
+
main()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Cleanup script to remove duplicate entries from evolution CSV files.
|
|
4
|
+
This fixes the issue where improper CSV parsing caused duplicate IDs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import csv
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
import argparse
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def find_duplicates(csv_file):
|
|
15
|
+
"""Find duplicate IDs and return information about them."""
|
|
16
|
+
if not os.path.exists(csv_file):
|
|
17
|
+
print(f"Error: CSV file {csv_file} not found")
|
|
18
|
+
return {}
|
|
19
|
+
|
|
20
|
+
id_entries = defaultdict(list)
|
|
21
|
+
|
|
22
|
+
with open(csv_file, 'r') as f:
|
|
23
|
+
reader = csv.reader(f)
|
|
24
|
+
header = next(reader)
|
|
25
|
+
|
|
26
|
+
for row_num, row in enumerate(reader, start=2):
|
|
27
|
+
if row and len(row) > 0:
|
|
28
|
+
candidate_id = row[0].strip()
|
|
29
|
+
if candidate_id:
|
|
30
|
+
id_entries[candidate_id].append({
|
|
31
|
+
'row_num': row_num,
|
|
32
|
+
'row': row,
|
|
33
|
+
'basedOnId': row[1].strip() if len(row) > 1 else '',
|
|
34
|
+
'description': row[2].strip() if len(row) > 2 else '',
|
|
35
|
+
'performance': row[3].strip() if len(row) > 3 else '',
|
|
36
|
+
'status': row[4].strip() if len(row) > 4 else ''
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
# Find duplicates
|
|
40
|
+
duplicates = {id_val: entries for id_val, entries in id_entries.items() if len(entries) > 1}
|
|
41
|
+
|
|
42
|
+
return duplicates, header
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def choose_best_entry(entries):
|
|
46
|
+
"""Choose the best entry to keep from duplicates."""
|
|
47
|
+
# Priority order:
|
|
48
|
+
# 1. Completed entries with performance score
|
|
49
|
+
# 2. Entries with empty basedOnId (original entries)
|
|
50
|
+
# 3. Most complete entry
|
|
51
|
+
|
|
52
|
+
completed_entries = [e for e in entries if e['status'] == 'complete' and e['performance']]
|
|
53
|
+
|
|
54
|
+
if completed_entries:
|
|
55
|
+
# Keep the one with the highest performance
|
|
56
|
+
return max(completed_entries, key=lambda x: float(x['performance']) if x['performance'] else 0)
|
|
57
|
+
|
|
58
|
+
# If no completed entries, prefer ones with empty basedOnId (original entries)
|
|
59
|
+
original_entries = [e for e in entries if not e['basedOnId']]
|
|
60
|
+
if original_entries:
|
|
61
|
+
return original_entries[0]
|
|
62
|
+
|
|
63
|
+
# Otherwise, keep the most complete entry
|
|
64
|
+
return max(entries, key=lambda x: len([f for f in x['row'] if f.strip()]))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def cleanup_csv(csv_file, dry_run=True):
|
|
68
|
+
"""Clean up duplicate entries in CSV file."""
|
|
69
|
+
duplicates, header = find_duplicates(csv_file)
|
|
70
|
+
|
|
71
|
+
if not duplicates:
|
|
72
|
+
print(f"No duplicates found in {csv_file}")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
print(f"Found {len(duplicates)} duplicate IDs in {csv_file}:")
|
|
76
|
+
|
|
77
|
+
# Read all rows
|
|
78
|
+
with open(csv_file, 'r') as f:
|
|
79
|
+
reader = csv.reader(f)
|
|
80
|
+
all_rows = list(reader)
|
|
81
|
+
|
|
82
|
+
rows_to_remove = set()
|
|
83
|
+
|
|
84
|
+
for candidate_id, entries in duplicates.items():
|
|
85
|
+
print(f"\n {candidate_id}: {len(entries)} entries")
|
|
86
|
+
|
|
87
|
+
best_entry = choose_best_entry(entries)
|
|
88
|
+
|
|
89
|
+
for entry in entries:
|
|
90
|
+
if entry == best_entry:
|
|
91
|
+
print(f" Row {entry['row_num']}: KEEP - {entry['status']} {entry['performance']}")
|
|
92
|
+
else:
|
|
93
|
+
print(f" Row {entry['row_num']}: REMOVE - {entry['status']} {entry['performance']}")
|
|
94
|
+
rows_to_remove.add(entry['row_num'] - 1) # Convert to 0-based index
|
|
95
|
+
|
|
96
|
+
if dry_run:
|
|
97
|
+
print(f"\nDry run mode: Would remove {len(rows_to_remove)} duplicate rows")
|
|
98
|
+
print("Run with --fix to actually remove duplicates")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
# Remove duplicate rows
|
|
102
|
+
cleaned_rows = []
|
|
103
|
+
for i, row in enumerate(all_rows):
|
|
104
|
+
if i not in rows_to_remove:
|
|
105
|
+
cleaned_rows.append(row)
|
|
106
|
+
|
|
107
|
+
# Write cleaned CSV
|
|
108
|
+
backup_file = f"{csv_file}.backup.{os.getpid()}"
|
|
109
|
+
os.rename(csv_file, backup_file)
|
|
110
|
+
print(f"\nCreated backup: {backup_file}")
|
|
111
|
+
|
|
112
|
+
with open(csv_file, 'w', newline='') as f:
|
|
113
|
+
writer = csv.writer(f)
|
|
114
|
+
writer.writerows(cleaned_rows)
|
|
115
|
+
|
|
116
|
+
print(f"Removed {len(rows_to_remove)} duplicate rows from {csv_file}")
|
|
117
|
+
print(f"Cleaned CSV has {len(cleaned_rows)} rows (including header)")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def main():
|
|
121
|
+
parser = argparse.ArgumentParser(description='Clean up duplicate entries in evolution CSV files')
|
|
122
|
+
parser.add_argument('csv_file', help='Path to CSV file to clean')
|
|
123
|
+
parser.add_argument('--fix', action='store_true', help='Actually fix the file (default is dry run)')
|
|
124
|
+
|
|
125
|
+
args = parser.parse_args()
|
|
126
|
+
|
|
127
|
+
cleanup_csv(args.csv_file, dry_run=not args.fix)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == '__main__':
|
|
131
|
+
main()
|