claude-evolve 1.4.12 → 1.4.13
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-autostatus +117 -110
- package/bin/claude-evolve-edit +82 -6
- package/bin/claude-evolve-ideate +535 -218
- package/bin/claude-evolve-ideate.debug +907 -0
- package/bin/claude-evolve-run +49 -7
- package/bin/claude-evolve-worker +121 -21
- package/lib/__pycache__/evolution_csv.cpython-311.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-313.pyc +0 -0
- package/lib/evolution_csv.py +36 -2
- package/lib/validate_parent_ids.py +232 -0
- package/package.json +1 -1
|
@@ -105,54 +105,79 @@ class AutoStatus:
|
|
|
105
105
|
|
|
106
106
|
def get_status_data(self):
|
|
107
107
|
"""Get current status data from CSV."""
|
|
108
|
-
# Read CSV data directly
|
|
108
|
+
# Read CSV data directly - using list reader to handle position-based access
|
|
109
109
|
with open(self.csv_path, "r") as f:
|
|
110
|
-
reader = csv.
|
|
110
|
+
reader = csv.reader(f)
|
|
111
111
|
rows = list(reader)
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
113
|
+
if len(rows) <= 1:
|
|
114
|
+
return {
|
|
115
|
+
"leader": None,
|
|
116
|
+
"generations": {},
|
|
117
|
+
"csv_path": self.csv_path,
|
|
118
|
+
"working_dir": os.path.dirname(self.csv_path)
|
|
119
|
+
}
|
|
121
120
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
status = row.get("status", "unknown")
|
|
126
|
-
if status in status_counts:
|
|
127
|
-
status_counts[status] += 1
|
|
128
|
-
|
|
129
|
-
# Collect performance for completed
|
|
130
|
-
if status == "complete" and "performance" in row and row["performance"]:
|
|
131
|
-
try:
|
|
132
|
-
perf_values.append(float(row["performance"]))
|
|
133
|
-
except ValueError:
|
|
134
|
-
pass
|
|
121
|
+
# Process candidates by generation
|
|
122
|
+
all_candidates = []
|
|
123
|
+
stats_by_gen = {}
|
|
135
124
|
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
125
|
+
for row in rows[1:]: # Skip header
|
|
126
|
+
if len(row) >= 1 and row[0]: # Must have an ID
|
|
127
|
+
candidate_id = row[0]
|
|
128
|
+
|
|
129
|
+
# Extract generation (e.g., "gen03" from "gen03-001")
|
|
130
|
+
if "-" in candidate_id:
|
|
131
|
+
gen = candidate_id.split("-")[0]
|
|
132
|
+
|
|
133
|
+
# Get status and performance
|
|
134
|
+
status = row[4] if len(row) > 4 and row[4] else "pending"
|
|
135
|
+
performance = row[3] if len(row) > 3 and row[3] else ""
|
|
136
|
+
|
|
137
|
+
# Normalize failed-retry* to failed
|
|
138
|
+
if status.startswith("failed"):
|
|
139
|
+
status = "failed"
|
|
140
|
+
|
|
141
|
+
# Track by generation
|
|
142
|
+
if gen not in stats_by_gen:
|
|
143
|
+
stats_by_gen[gen] = {
|
|
144
|
+
"pending": 0, "complete": 0, "failed": 0, "running": 0,
|
|
145
|
+
"candidates": []
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if status in stats_by_gen[gen]:
|
|
149
|
+
stats_by_gen[gen][status] += 1
|
|
150
|
+
else:
|
|
151
|
+
stats_by_gen[gen]["pending"] += 1
|
|
152
|
+
|
|
153
|
+
# Collect candidate info
|
|
154
|
+
if status == "complete" and performance:
|
|
155
|
+
try:
|
|
156
|
+
score = float(performance)
|
|
157
|
+
description = row[2] if len(row) > 2 else "No description"
|
|
158
|
+
candidate_info = (candidate_id, description, score)
|
|
159
|
+
stats_by_gen[gen]["candidates"].append(candidate_info)
|
|
160
|
+
all_candidates.append(candidate_info)
|
|
161
|
+
except ValueError:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
# Find the overall leader
|
|
165
|
+
leader = None
|
|
166
|
+
if all_candidates:
|
|
167
|
+
leader = max(all_candidates, key=lambda x: x[2])
|
|
146
168
|
|
|
147
|
-
#
|
|
148
|
-
|
|
149
|
-
|
|
169
|
+
# Find best performer in each generation
|
|
170
|
+
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:
|
|
174
|
+
stats_by_gen[gen]["best"] = None
|
|
150
175
|
|
|
151
176
|
return {
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
177
|
+
"leader": leader,
|
|
178
|
+
"generations": stats_by_gen,
|
|
179
|
+
"csv_path": self.csv_path,
|
|
180
|
+
"working_dir": os.path.dirname(self.csv_path)
|
|
156
181
|
}
|
|
157
182
|
|
|
158
183
|
def format_duration(self, seconds):
|
|
@@ -186,97 +211,79 @@ class AutoStatus:
|
|
|
186
211
|
print(f"\033[1;36m{header.center(self.display.cols)}\033[0m")
|
|
187
212
|
row += 1
|
|
188
213
|
|
|
189
|
-
# Timestamp
|
|
214
|
+
# Timestamp and working dir
|
|
190
215
|
self.display.move_cursor(row, 1)
|
|
191
216
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
192
|
-
|
|
217
|
+
working_dir = os.path.basename(data["working_dir"])
|
|
218
|
+
print(f"Last updated: {timestamp} | Working dir: {working_dir} | Press '\''q'\'' to quit")
|
|
193
219
|
row += 2
|
|
194
220
|
|
|
195
|
-
#
|
|
196
|
-
self.display.move_cursor(row, 1)
|
|
197
|
-
print(f"CSV: {data['\''csv_path'\'']}")
|
|
198
|
-
row += 2
|
|
199
|
-
|
|
200
|
-
# Status summary
|
|
201
|
-
self.display.move_cursor(row, 1)
|
|
202
|
-
print("\033[1mStatus Summary:\033[0m")
|
|
203
|
-
row += 1
|
|
204
|
-
|
|
205
|
-
counts = data["counts"]
|
|
206
|
-
status_line = (f" Total: {counts['\''total'\'']} | "
|
|
207
|
-
f"\033[33mPending: {counts['\''pending'\'']}\033[0m | "
|
|
208
|
-
f"\033[36mRunning: {counts['\''running'\'']}\033[0m | "
|
|
209
|
-
f"\033[32mComplete: {counts['\''complete'\'']}\033[0m | "
|
|
210
|
-
f"\033[31mFailed: {counts['\''failed'\'']}\033[0m")
|
|
211
|
-
|
|
221
|
+
# Leader
|
|
212
222
|
self.display.move_cursor(row, 1)
|
|
213
|
-
|
|
223
|
+
if data["leader"]:
|
|
224
|
+
leader_id, leader_desc, leader_score = data["leader"]
|
|
225
|
+
# Truncate description for leader
|
|
226
|
+
max_desc_len = self.display.cols - 30
|
|
227
|
+
if len(leader_desc) > max_desc_len:
|
|
228
|
+
leader_desc = leader_desc[:max_desc_len-3] + "..."
|
|
229
|
+
print(f"\033[1;32mLeader:\033[0m {leader_id} | {leader_score:.4f} | {leader_desc}")
|
|
230
|
+
else:
|
|
231
|
+
print("\033[1;32mLeader:\033[0m None (no completed candidates)")
|
|
214
232
|
row += 2
|
|
215
233
|
|
|
216
|
-
#
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
print("\033[1mPerformance Stats:\033[0m")
|
|
220
|
-
row += 1
|
|
221
|
-
|
|
222
|
-
perf = data["performance"]
|
|
223
|
-
self.display.move_cursor(row, 1)
|
|
224
|
-
print(f" Min: {perf['\''min'\'']:.4f} | Max: {perf['\''max'\'']:.4f} | "
|
|
225
|
-
f"Mean: {perf['\''mean'\'']:.4f} | Count: {perf['\''count'\'']}")
|
|
226
|
-
row += 2
|
|
227
|
-
|
|
228
|
-
# Recent candidates
|
|
229
|
-
if data["recent"]:
|
|
230
|
-
self.display.move_cursor(row, 1)
|
|
231
|
-
print("\033[1mRecent Candidates:\033[0m")
|
|
232
|
-
row += 1
|
|
233
|
-
|
|
234
|
+
# Generation table
|
|
235
|
+
generations = data["generations"]
|
|
236
|
+
if generations:
|
|
234
237
|
# Table header
|
|
235
238
|
self.display.move_cursor(row, 1)
|
|
236
|
-
header_fmt =
|
|
237
|
-
|
|
239
|
+
header_fmt = "{:<10} | {:^20} | {:>10} | {:>8} | {}".format(
|
|
240
|
+
"Generation", "Stats (p/c/f/r)", "Top ID", "Score", "Description"
|
|
241
|
+
)
|
|
242
|
+
print("\033[1m" + header_fmt[:self.display.cols] + "\033[0m")
|
|
238
243
|
row += 1
|
|
239
244
|
|
|
240
245
|
self.display.move_cursor(row, 1)
|
|
241
246
|
print("-" * min(self.display.cols, len(header_fmt)))
|
|
242
247
|
row += 1
|
|
243
248
|
|
|
244
|
-
#
|
|
245
|
-
|
|
246
|
-
|
|
249
|
+
# Sort generations
|
|
250
|
+
sorted_gens = sorted(generations.keys())
|
|
251
|
+
|
|
252
|
+
# Calculate how many generations we can show
|
|
253
|
+
available_rows = self.display.rows - row - 1 # Leave room at bottom
|
|
254
|
+
start_idx = max(0, len(sorted_gens) - available_rows)
|
|
255
|
+
|
|
256
|
+
# Show generations (most recent at bottom)
|
|
257
|
+
for gen in sorted_gens[start_idx:]:
|
|
258
|
+
if row >= self.display.rows - 1:
|
|
247
259
|
break
|
|
248
260
|
|
|
249
|
-
|
|
261
|
+
gen_data = generations[gen]
|
|
262
|
+
stats_str = f"{gen_data['\''pending'\'']}/{gen_data['\''complete'\'']}/{gen_data['\''failed'\'']}/{gen_data['\''running'\'']}"
|
|
250
263
|
|
|
251
|
-
|
|
252
|
-
status = candidate.get("status", "unknown")
|
|
253
|
-
if status == "complete":
|
|
254
|
-
color = "\033[32m" # Green
|
|
255
|
-
elif status == "running":
|
|
256
|
-
color = "\033[36m" # Cyan
|
|
257
|
-
elif status == "failed":
|
|
258
|
-
color = "\033[31m" # Red
|
|
259
|
-
elif status == "pending":
|
|
260
|
-
color = "\033[33m" # Yellow
|
|
261
|
-
else:
|
|
262
|
-
color = "\033[0m" # Default
|
|
264
|
+
self.display.move_cursor(row, 1)
|
|
263
265
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
if gen_data["best"]:
|
|
267
|
+
best_id, best_desc, best_score = gen_data["best"]
|
|
268
|
+
# Truncate description
|
|
269
|
+
max_desc_len = self.display.cols - 55
|
|
270
|
+
if len(best_desc) > max_desc_len:
|
|
271
|
+
best_desc = best_desc[:max_desc_len-3] + "..."
|
|
272
|
+
|
|
273
|
+
# Highlight if this is the overall leader
|
|
274
|
+
if data["leader"] and best_id == data["leader"][0]:
|
|
275
|
+
line = "{:<10} | {:^20} | \033[32m{:>10}\033[0m | {:>8.4f} | {}".format(
|
|
276
|
+
gen, stats_str, best_id, best_score, best_desc
|
|
277
|
+
)
|
|
278
|
+
else:
|
|
279
|
+
line = "{:<10} | {:^20} | {:>10} | {:>8.4f} | {}".format(
|
|
280
|
+
gen, stats_str, best_id, best_score, best_desc
|
|
281
|
+
)
|
|
270
282
|
else:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
desc = candidate.get("description", "")
|
|
275
|
-
max_desc_len = self.display.cols - 35 # Account for other columns
|
|
276
|
-
if len(desc) > max_desc_len:
|
|
277
|
-
desc = desc[:max_desc_len-3] + "..."
|
|
283
|
+
line = "{:<10} | {:^20} | {:>10} | {:>8} | {}".format(
|
|
284
|
+
gen, stats_str, "-", "-", "No completed candidates"
|
|
285
|
+
)
|
|
278
286
|
|
|
279
|
-
line = f"{candidate['\''id'\'']:>8} | {color}{status:^10}\033[0m | {perf:>11} | {desc}"
|
|
280
287
|
print(line[:self.display.cols])
|
|
281
288
|
row += 1
|
|
282
289
|
|
package/bin/claude-evolve-edit
CHANGED
|
@@ -33,11 +33,12 @@ SELECTORS:
|
|
|
33
33
|
ACTIONS:
|
|
34
34
|
failed Mark candidates as failed (keeps scores)
|
|
35
35
|
complete Mark candidates as complete (keeps scores)
|
|
36
|
-
pending Mark candidates as pending (
|
|
36
|
+
pending Mark candidates as pending (clears all data)
|
|
37
37
|
failed-retry1 Mark candidates for retry attempt 1 (bug fixing)
|
|
38
38
|
failed-retry2 Mark candidates for retry attempt 2 (bug fixing)
|
|
39
39
|
failed-retry3 Mark candidates for retry attempt 3 (bug fixing)
|
|
40
40
|
reboot Reset completely (delete .py files, clear scores, set pending)
|
|
41
|
+
delete Delete candidates from CSV and remove .py files (asks confirmation)
|
|
41
42
|
|
|
42
43
|
EXAMPLES:
|
|
43
44
|
claude-evolve edit gen03 failed # Mark all gen03 as failed
|
|
@@ -46,6 +47,7 @@ EXAMPLES:
|
|
|
46
47
|
claude-evolve edit complete failed # Mark all complete as failed for re-run
|
|
47
48
|
claude-evolve edit all pending # Mark everything as pending for re-run
|
|
48
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
|
|
49
51
|
|
|
50
52
|
DESCRIPTION:
|
|
51
53
|
This command helps manage evolution runs when you need to re-evaluate candidates.
|
|
@@ -77,9 +79,9 @@ fi
|
|
|
77
79
|
|
|
78
80
|
# Validate action
|
|
79
81
|
case "$ACTION" in
|
|
80
|
-
failed|complete|pending|failed-retry1|failed-retry2|failed-retry3|reboot) ;;
|
|
82
|
+
failed|complete|pending|failed-retry1|failed-retry2|failed-retry3|reboot|delete) ;;
|
|
81
83
|
*)
|
|
82
|
-
echo "[ERROR] Action must be one of: failed, complete, pending, failed-retry1, failed-retry2, failed-retry3, reboot" >&2
|
|
84
|
+
echo "[ERROR] Action must be one of: failed, complete, pending, failed-retry1, failed-retry2, failed-retry3, reboot, delete" >&2
|
|
83
85
|
exit 1
|
|
84
86
|
;;
|
|
85
87
|
esac
|
|
@@ -152,9 +154,10 @@ try:
|
|
|
152
154
|
|
|
153
155
|
if matches:
|
|
154
156
|
if clear_scores:
|
|
155
|
-
#
|
|
157
|
+
# Clear everything after description (keep id, basedOnId, description)
|
|
156
158
|
if len(row) >= 3:
|
|
157
|
-
|
|
159
|
+
# Keep first 3 columns, then add empty performance and the new status
|
|
160
|
+
rows[i] = [row[0], row[1], row[2], '', new_status]
|
|
158
161
|
updated_count += 1
|
|
159
162
|
else:
|
|
160
163
|
# Just update status (preserve other fields)
|
|
@@ -277,6 +280,66 @@ except Exception as e:
|
|
|
277
280
|
echo "[INFO] Deleted $deleted_count evolution files"
|
|
278
281
|
}
|
|
279
282
|
|
|
283
|
+
# Function to delete candidates from CSV
|
|
284
|
+
delete_candidates_from_csv() {
|
|
285
|
+
local selector="$1"
|
|
286
|
+
|
|
287
|
+
echo "[INFO] Deleting candidates matching '$selector' from CSV..."
|
|
288
|
+
|
|
289
|
+
"$PYTHON_CMD" -c "
|
|
290
|
+
import sys
|
|
291
|
+
sys.path.insert(0, '$SCRIPT_DIR/..')
|
|
292
|
+
from lib.evolution_csv import EvolutionCSV
|
|
293
|
+
import re
|
|
294
|
+
|
|
295
|
+
selector = '$selector'
|
|
296
|
+
deleted_count = 0
|
|
297
|
+
|
|
298
|
+
with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
299
|
+
# Read CSV directly to get all candidates
|
|
300
|
+
import csv as csv_module
|
|
301
|
+
candidates_to_delete = []
|
|
302
|
+
|
|
303
|
+
with open('$FULL_CSV_PATH', 'r') as f:
|
|
304
|
+
reader = csv_module.reader(f)
|
|
305
|
+
rows = list(reader)
|
|
306
|
+
has_header = rows and rows[0] and rows[0][0].lower() == 'id'
|
|
307
|
+
start_idx = 1 if has_header else 0
|
|
308
|
+
|
|
309
|
+
for row in rows[start_idx:]:
|
|
310
|
+
if not row or not row[0].strip():
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
candidate_id = row[0].strip()
|
|
314
|
+
current_status = row[4].strip() if len(row) > 4 else ''
|
|
315
|
+
|
|
316
|
+
matches = False
|
|
317
|
+
if selector == 'all':
|
|
318
|
+
matches = True
|
|
319
|
+
elif selector.startswith('gen') and re.match(r'^gen\\d+$', selector):
|
|
320
|
+
# Generation selector (e.g., gen01, gen02)
|
|
321
|
+
gen_pattern = f'^{selector}-'
|
|
322
|
+
matches = re.match(gen_pattern, candidate_id) is not None
|
|
323
|
+
elif selector == 'pending':
|
|
324
|
+
matches = current_status == '' or current_status == 'pending'
|
|
325
|
+
elif selector == 'failed':
|
|
326
|
+
matches = current_status.startswith('failed')
|
|
327
|
+
else:
|
|
328
|
+
matches = current_status == selector
|
|
329
|
+
|
|
330
|
+
if matches:
|
|
331
|
+
candidates_to_delete.append(candidate_id)
|
|
332
|
+
|
|
333
|
+
# Delete candidates
|
|
334
|
+
for candidate_id in candidates_to_delete:
|
|
335
|
+
csv.delete_candidate(candidate_id)
|
|
336
|
+
deleted_count += 1
|
|
337
|
+
print(f'[INFO] Deleted from CSV: {candidate_id}')
|
|
338
|
+
|
|
339
|
+
print(f'[INFO] Deleted {deleted_count} candidates from CSV')
|
|
340
|
+
"
|
|
341
|
+
}
|
|
342
|
+
|
|
280
343
|
# Main execution
|
|
281
344
|
echo "[INFO] Processing '$SELECTOR' with action: $ACTION"
|
|
282
345
|
|
|
@@ -288,7 +351,7 @@ case "$ACTION" in
|
|
|
288
351
|
update_candidates_status "$SELECTOR" "complete" "false"
|
|
289
352
|
;;
|
|
290
353
|
pending)
|
|
291
|
-
update_candidates_status "$SELECTOR" "" "
|
|
354
|
+
update_candidates_status "$SELECTOR" "pending" "true" # Clear all data and set to pending
|
|
292
355
|
;;
|
|
293
356
|
failed-retry1)
|
|
294
357
|
update_candidates_status "$SELECTOR" "failed-retry1" "false"
|
|
@@ -305,6 +368,19 @@ case "$ACTION" in
|
|
|
305
368
|
update_candidates_status "$SELECTOR" "" "true" # Clear scores and set pending
|
|
306
369
|
echo "[INFO] Reboot complete: files deleted, scores cleared, status set to pending"
|
|
307
370
|
;;
|
|
371
|
+
delete)
|
|
372
|
+
# Ask for confirmation
|
|
373
|
+
read -p "[WARNING] This will permanently delete candidates matching '$SELECTOR' from CSV and remove their .py files. Are you sure? (yes/no): " confirmation
|
|
374
|
+
if [[ "$confirmation" != "yes" ]]; then
|
|
375
|
+
echo "[INFO] Delete operation cancelled"
|
|
376
|
+
exit 0
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
echo "[INFO] Performing delete of '$SELECTOR'..."
|
|
380
|
+
delete_evolution_files "$SELECTOR"
|
|
381
|
+
delete_candidates_from_csv "$SELECTOR"
|
|
382
|
+
echo "[INFO] Delete complete: candidates removed from CSV and files deleted"
|
|
383
|
+
;;
|
|
308
384
|
esac
|
|
309
385
|
|
|
310
386
|
echo "[INFO] Edit operation complete"
|