claude-evolve 1.8.6 → 1.8.8
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-run +16 -1
- package/bin/claude-evolve-worker +6 -0
- package/lib/evolution_csv.py +95 -23
- package/package.json +1 -1
package/bin/claude-evolve-run
CHANGED
|
@@ -295,8 +295,23 @@ get_csv_stats() {
|
|
|
295
295
|
echo "[DISPATCHER] Starting unified evolution engine"
|
|
296
296
|
echo "[DISPATCHER] Configuration: max_workers=$MAX_WORKERS, timeout=${timeout_seconds:-none}"
|
|
297
297
|
|
|
298
|
-
# Clean up any stuck 'running' statuses at startup
|
|
298
|
+
# Clean up any stuck 'running' statuses and duplicates at startup
|
|
299
299
|
if [[ -f "$FULL_CSV_PATH" ]]; then
|
|
300
|
+
echo "[DISPATCHER] Checking for duplicate candidates..."
|
|
301
|
+
"$PYTHON_CMD" -c "
|
|
302
|
+
import sys
|
|
303
|
+
sys.path.insert(0, '$SCRIPT_DIR/..')
|
|
304
|
+
from lib.evolution_csv import EvolutionCSV
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
308
|
+
dup_count = csv.remove_duplicate_candidates()
|
|
309
|
+
if dup_count == 0:
|
|
310
|
+
print('[DISPATCHER] No duplicate candidates found', file=sys.stderr)
|
|
311
|
+
except Exception as e:
|
|
312
|
+
print(f'[ERROR] Failed to remove duplicates: {e}', file=sys.stderr)
|
|
313
|
+
" 2>&1 || true
|
|
314
|
+
|
|
300
315
|
echo "[DISPATCHER] Resetting any stuck 'running' candidates to 'pending'..."
|
|
301
316
|
if "$SCRIPT_DIR/claude-evolve-edit" running pending >/dev/null 2>&1; then
|
|
302
317
|
echo "[DISPATCHER] Successfully reset stuck candidates"
|
package/bin/claude-evolve-worker
CHANGED
|
@@ -600,11 +600,17 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
|
600
600
|
if rows:
|
|
601
601
|
start_idx = 1 if rows and rows[0] and rows[0][0].lower() == 'id' else 0
|
|
602
602
|
status_count = {}
|
|
603
|
+
running_candidates = []
|
|
603
604
|
for row in rows[start_idx:]:
|
|
604
605
|
if len(row) > 4:
|
|
605
606
|
status = row[4].strip() or 'pending'
|
|
606
607
|
status_count[status] = status_count.get(status, 0) + 1
|
|
608
|
+
if status == 'running':
|
|
609
|
+
candidate_id = row[0].strip().strip('\"') if len(row) > 0 else '?'
|
|
610
|
+
running_candidates.append(candidate_id)
|
|
607
611
|
print(f'Status counts: {status_count}', file=sys.stderr)
|
|
612
|
+
if running_candidates:
|
|
613
|
+
print(f'Running candidates: {running_candidates}', file=sys.stderr)
|
|
608
614
|
" 2>&1 || true
|
|
609
615
|
|
|
610
616
|
# Try to claim a pending candidate
|
package/lib/evolution_csv.py
CHANGED
|
@@ -205,27 +205,33 @@ class EvolutionCSV:
|
|
|
205
205
|
rows = self._read_csv()
|
|
206
206
|
if not rows:
|
|
207
207
|
return False
|
|
208
|
-
|
|
208
|
+
|
|
209
209
|
updated = False
|
|
210
|
-
|
|
210
|
+
update_count = 0
|
|
211
|
+
|
|
211
212
|
# Skip header row if it exists
|
|
212
213
|
start_idx = 1 if rows and rows[0] and rows[0][0].lower() == 'id' else 0
|
|
213
|
-
|
|
214
|
+
|
|
215
|
+
# Update ALL matching rows (in case of duplicates)
|
|
214
216
|
for i in range(start_idx, len(rows)):
|
|
215
217
|
row = rows[i]
|
|
216
|
-
|
|
218
|
+
|
|
217
219
|
if self.is_valid_candidate_row(row) and row[0].strip().strip('"') == candidate_id.strip().strip('"'):
|
|
218
220
|
# Ensure row has at least 5 columns
|
|
219
221
|
while len(row) < 5:
|
|
220
222
|
row.append('')
|
|
221
|
-
|
|
223
|
+
|
|
222
224
|
row[4] = new_status
|
|
223
225
|
updated = True
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
update_count += 1
|
|
227
|
+
# Don't break - continue to update ALL instances (handles duplicates)
|
|
228
|
+
|
|
229
|
+
if update_count > 1:
|
|
230
|
+
print(f'[WARN] Updated {update_count} duplicate entries for candidate {candidate_id} to status {new_status}', file=sys.stderr)
|
|
231
|
+
|
|
226
232
|
if updated:
|
|
227
233
|
self._write_csv(rows)
|
|
228
|
-
|
|
234
|
+
|
|
229
235
|
return updated
|
|
230
236
|
|
|
231
237
|
def update_candidate_performance(self, candidate_id: str, performance: str) -> bool:
|
|
@@ -233,27 +239,33 @@ class EvolutionCSV:
|
|
|
233
239
|
rows = self._read_csv()
|
|
234
240
|
if not rows:
|
|
235
241
|
return False
|
|
236
|
-
|
|
242
|
+
|
|
237
243
|
updated = False
|
|
238
|
-
|
|
244
|
+
update_count = 0
|
|
245
|
+
|
|
239
246
|
# Skip header row if it exists
|
|
240
247
|
start_idx = 1 if rows and rows[0] and rows[0][0].lower() == 'id' else 0
|
|
241
|
-
|
|
248
|
+
|
|
249
|
+
# Update ALL matching rows (in case of duplicates)
|
|
242
250
|
for i in range(start_idx, len(rows)):
|
|
243
251
|
row = rows[i]
|
|
244
|
-
|
|
252
|
+
|
|
245
253
|
if self.is_valid_candidate_row(row) and row[0].strip().strip('"') == candidate_id.strip().strip('"'):
|
|
246
254
|
# Ensure row has at least 4 columns
|
|
247
255
|
while len(row) < 4:
|
|
248
256
|
row.append('')
|
|
249
|
-
|
|
257
|
+
|
|
250
258
|
row[3] = performance # Performance is column 4 (index 3)
|
|
251
259
|
updated = True
|
|
252
|
-
|
|
253
|
-
|
|
260
|
+
update_count += 1
|
|
261
|
+
# Don't break - continue to update ALL instances (handles duplicates)
|
|
262
|
+
|
|
263
|
+
if update_count > 1:
|
|
264
|
+
print(f'[WARN] Updated {update_count} duplicate entries for candidate {candidate_id} performance', file=sys.stderr)
|
|
265
|
+
|
|
254
266
|
if updated:
|
|
255
267
|
self._write_csv(rows)
|
|
256
|
-
|
|
268
|
+
|
|
257
269
|
return updated
|
|
258
270
|
|
|
259
271
|
def update_candidate_field(self, candidate_id: str, field_name: str, value: str) -> bool:
|
|
@@ -304,8 +316,10 @@ class EvolutionCSV:
|
|
|
304
316
|
|
|
305
317
|
# Update the candidate's field
|
|
306
318
|
updated = False
|
|
319
|
+
update_count = 0
|
|
307
320
|
start_idx = 1 if has_header else 0
|
|
308
|
-
|
|
321
|
+
|
|
322
|
+
# Update ALL matching rows (in case of duplicates)
|
|
309
323
|
for i in range(start_idx, len(rows)):
|
|
310
324
|
row = rows[i]
|
|
311
325
|
# Strip quotes from both stored ID and search ID for comparison
|
|
@@ -315,14 +329,18 @@ class EvolutionCSV:
|
|
|
315
329
|
# Ensure row has enough columns
|
|
316
330
|
while len(row) <= field_index:
|
|
317
331
|
row.append('')
|
|
318
|
-
|
|
332
|
+
|
|
319
333
|
row[field_index] = value
|
|
320
334
|
updated = True
|
|
321
|
-
|
|
322
|
-
|
|
335
|
+
update_count += 1
|
|
336
|
+
# Don't break - continue to update ALL instances (handles duplicates)
|
|
337
|
+
|
|
338
|
+
if update_count > 1:
|
|
339
|
+
print(f'[WARN] Updated {update_count} duplicate entries for candidate {candidate_id} field {field_name}', file=sys.stderr)
|
|
340
|
+
|
|
323
341
|
if updated:
|
|
324
342
|
self._write_csv(rows)
|
|
325
|
-
|
|
343
|
+
|
|
326
344
|
return updated
|
|
327
345
|
|
|
328
346
|
def get_candidate_info(self, candidate_id: str) -> Optional[Dict[str, str]]:
|
|
@@ -434,14 +452,25 @@ class EvolutionCSV:
|
|
|
434
452
|
'failed-retry1', 'failed-retry2', 'failed-retry3', 'skipped',
|
|
435
453
|
'failed-parent-missing', 'running'}
|
|
436
454
|
|
|
455
|
+
# Track seen IDs to detect duplicates
|
|
456
|
+
seen_ids = {}
|
|
457
|
+
|
|
437
458
|
for i in range(start_idx, len(rows)):
|
|
438
459
|
if len(rows[i]) > 4:
|
|
439
460
|
status = rows[i][4].strip() if rows[i][4] else ''
|
|
440
|
-
candidate_id = rows[i][0] if rows[i] else ''
|
|
461
|
+
candidate_id = rows[i][0].strip().strip('"') if rows[i] else ''
|
|
462
|
+
|
|
463
|
+
# Check for duplicate IDs
|
|
464
|
+
if candidate_id in seen_ids:
|
|
465
|
+
print(f'[WARN] Duplicate candidate ID found: {candidate_id} at rows {seen_ids[candidate_id]} and {i}', file=sys.stderr)
|
|
466
|
+
print(f'[WARN] Row {seen_ids[candidate_id]}: status={rows[seen_ids[candidate_id]][4] if len(rows[seen_ids[candidate_id]]) > 4 else "?"}', file=sys.stderr)
|
|
467
|
+
print(f'[WARN] Row {i}: status={status}', file=sys.stderr)
|
|
468
|
+
else:
|
|
469
|
+
seen_ids[candidate_id] = i
|
|
441
470
|
|
|
442
471
|
# Reset 'running' when no workers are active
|
|
443
472
|
if status == 'running':
|
|
444
|
-
print(f'[INFO] Resetting stuck running candidate: {candidate_id}', file=sys.stderr)
|
|
473
|
+
print(f'[INFO] Resetting stuck running candidate: {candidate_id} (row {i})', file=sys.stderr)
|
|
445
474
|
rows[i][4] = 'pending'
|
|
446
475
|
reset_count += 1
|
|
447
476
|
# Reset unknown statuses
|
|
@@ -482,6 +511,49 @@ class EvolutionCSV:
|
|
|
482
511
|
|
|
483
512
|
return stuck
|
|
484
513
|
|
|
514
|
+
def remove_duplicate_candidates(self) -> int:
|
|
515
|
+
"""
|
|
516
|
+
Remove duplicate candidate entries, keeping only the first occurrence.
|
|
517
|
+
Returns the number of duplicates removed.
|
|
518
|
+
"""
|
|
519
|
+
rows = self._read_csv()
|
|
520
|
+
if not rows:
|
|
521
|
+
return 0
|
|
522
|
+
|
|
523
|
+
has_header = rows and rows[0] and rows[0][0].lower() == 'id'
|
|
524
|
+
start_idx = 1 if has_header else 0
|
|
525
|
+
|
|
526
|
+
# Track seen IDs and their row indices
|
|
527
|
+
seen_ids = {}
|
|
528
|
+
rows_to_remove = []
|
|
529
|
+
|
|
530
|
+
for i in range(start_idx, len(rows)):
|
|
531
|
+
if len(rows[i]) > 0:
|
|
532
|
+
candidate_id = rows[i][0].strip().strip('"')
|
|
533
|
+
|
|
534
|
+
if candidate_id in seen_ids:
|
|
535
|
+
# Duplicate found
|
|
536
|
+
first_row = seen_ids[candidate_id]
|
|
537
|
+
status_first = rows[first_row][4] if len(rows[first_row]) > 4 else ''
|
|
538
|
+
status_dup = rows[i][4] if len(rows[i]) > 4 else ''
|
|
539
|
+
|
|
540
|
+
print(f'[WARN] Removing duplicate candidate {candidate_id}:', file=sys.stderr)
|
|
541
|
+
print(f'[WARN] Keeping row {first_row}: status={status_first}', file=sys.stderr)
|
|
542
|
+
print(f'[WARN] Removing row {i}: status={status_dup}', file=sys.stderr)
|
|
543
|
+
rows_to_remove.append(i)
|
|
544
|
+
else:
|
|
545
|
+
seen_ids[candidate_id] = i
|
|
546
|
+
|
|
547
|
+
if rows_to_remove:
|
|
548
|
+
# Remove rows in reverse order to maintain indices
|
|
549
|
+
for idx in sorted(rows_to_remove, reverse=True):
|
|
550
|
+
del rows[idx]
|
|
551
|
+
|
|
552
|
+
self._write_csv(rows)
|
|
553
|
+
print(f'[INFO] Removed {len(rows_to_remove)} duplicate candidate(s)', file=sys.stderr)
|
|
554
|
+
|
|
555
|
+
return len(rows_to_remove)
|
|
556
|
+
|
|
485
557
|
def has_pending_work(self) -> bool:
|
|
486
558
|
"""Check if there are any pending candidates. Used by dispatcher."""
|
|
487
559
|
return self.count_pending_candidates() > 0
|