claude-evolve 1.3.44 → 1.4.1

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.
@@ -251,10 +251,9 @@ cleanup_workers() {
251
251
  worker_pids=("${new_pids[@]}")
252
252
  }
253
253
 
254
- # Function to count pending candidates
254
+ # Function to count pending candidates - UNIFIED LOGIC
255
255
  count_pending_candidates() {
256
- "$PYTHON_CMD" "$SCRIPT_DIR/../lib/csv_helper.py" find_pending "$FULL_CSV_PATH" >/dev/null 2>&1
257
- echo $? # 0 if found, 1 if not found
256
+ "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" count
258
257
  }
259
258
 
260
259
  # Function to get CSV stats
@@ -271,26 +270,8 @@ get_csv_stats() {
271
270
  total_rows=$(wc -l < "$csv_path" | tr -d '[:space:]')
272
271
  complete_count=$(grep ',complete' "$csv_path" 2>/dev/null | wc -l | tr -d '[:space:]')
273
272
 
274
- # Count pending using same logic as find_next_pending_with_lock
275
- # This includes rows with <5 fields AND rows with empty/pending status
276
- pending_count=$("$PYTHON_CMD" -c "
277
- import csv
278
- import sys
279
-
280
- pending_count = 0
281
- with open('$csv_path', 'r') as f:
282
- reader = csv.reader(f)
283
- rows = list(reader)
284
-
285
- for i in range(1, len(rows)):
286
- # Same logic as find_next_pending_with_lock
287
- if len(rows[i]) < 5:
288
- pending_count += 1
289
- elif len(rows[i]) >= 5 and (rows[i][4] == 'pending' or rows[i][4] == ''):
290
- pending_count += 1
291
-
292
- print(pending_count)
293
- ")
273
+ # Count pending using UNIFIED CSV logic
274
+ pending_count=$("$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$csv_path" count)
294
275
 
295
276
  echo "$total_rows $complete_count $pending_count"
296
277
  }
@@ -298,9 +279,45 @@ print(pending_count)
298
279
  echo "[DISPATCHER] Starting unified evolution engine"
299
280
  echo "[DISPATCHER] Configuration: max_workers=$MAX_WORKERS, timeout=${timeout_seconds:-none}"
300
281
 
301
- # Validate CSV and clean up stuck statuses
282
+ # Validate CSV and clean up stuck statuses and duplicates
302
283
  if [[ -f "$FULL_CSV_PATH" ]]; then
303
284
  echo "[DISPATCHER] Validating CSV and cleaning up..."
285
+
286
+ # First check for and clean up duplicates
287
+ echo "[DISPATCHER] Checking for duplicate entries..."
288
+ duplicate_check_output=$("$PYTHON_CMD" "$SCRIPT_DIR/claude-evolve-cleanup-duplicates" "$FULL_CSV_PATH" 2>&1)
289
+ if echo "$duplicate_check_output" | grep -q "Found.*duplicate"; then
290
+ echo "[DISPATCHER] WARNING: Duplicate entries detected in CSV!"
291
+ echo "$duplicate_check_output"
292
+ echo "[DISPATCHER] Automatically cleaning up duplicates..."
293
+ if "$PYTHON_CMD" "$SCRIPT_DIR/claude-evolve-cleanup-duplicates" "$FULL_CSV_PATH" --fix; then
294
+ echo "[DISPATCHER] Duplicates cleaned up successfully"
295
+ else
296
+ echo "[ERROR] Failed to clean up duplicates" >&2
297
+ exit 1
298
+ fi
299
+ else
300
+ echo "[DISPATCHER] No duplicates found"
301
+ fi
302
+
303
+ # Check for and clean up invalid entries
304
+ echo "[DISPATCHER] Checking for invalid entries..."
305
+ invalid_check_output=$("$PYTHON_CMD" "$SCRIPT_DIR/claude-evolve-clean-invalid" "$FULL_CSV_PATH" --dry-run 2>&1)
306
+ if echo "$invalid_check_output" | grep -q "Found.*invalid"; then
307
+ echo "[DISPATCHER] WARNING: Invalid entries detected in CSV!"
308
+ echo "$invalid_check_output"
309
+ echo "[DISPATCHER] Automatically cleaning up invalid entries..."
310
+ if "$PYTHON_CMD" "$SCRIPT_DIR/claude-evolve-clean-invalid" "$FULL_CSV_PATH"; then
311
+ echo "[DISPATCHER] Invalid entries cleaned up successfully"
312
+ else
313
+ echo "[ERROR] Failed to clean up invalid entries" >&2
314
+ exit 1
315
+ fi
316
+ else
317
+ echo "[DISPATCHER] No invalid entries found"
318
+ fi
319
+
320
+ # Then validate and clean stuck statuses
304
321
  if ! "$PYTHON_CMD" -c "
305
322
  import csv
306
323
  import sys
@@ -340,12 +357,13 @@ try:
340
357
  os.rename(csv_file + '.tmp', csv_file)
341
358
  print(f'[INFO] Reset {changed} stuck running candidates to pending')
342
359
 
343
- # Count pending candidates
344
- pending = 0
345
- for i in range(1, len(rows)):
346
- # Row with < 5 fields or empty/pending status in field 5
347
- if len(rows[i]) < 5 or (len(rows[i]) >= 5 and rows[i][4] in ['', 'pending']):
348
- pending += 1
360
+ # Count pending candidates using UNIFIED logic
361
+ import sys
362
+ sys.path.append('$SCRIPT_DIR/../lib')
363
+ from evolution_csv import EvolutionCSV
364
+
365
+ with EvolutionCSV(csv_file) as csv_ops:
366
+ pending = csv_ops.count_pending_candidates()
349
367
 
350
368
  print(f'[INFO] CSV loaded: {len(rows)-1} total candidates, {pending} pending')
351
369
 
@@ -388,6 +406,37 @@ else
388
406
  echo "[DISPATCHER] No cleanup issues detected - proceeding with run"
389
407
  fi
390
408
 
409
+ # Ensure baseline algorithm performance is recorded
410
+ ensure_baseline_entry() {
411
+ # Check if baseline already exists
412
+ if "$PYTHON_CMD" -c "
413
+ import csv
414
+ with open('$FULL_CSV_PATH', 'r') as f:
415
+ reader = csv.reader(f)
416
+ next(reader, None) # Skip header
417
+ for row in reader:
418
+ if len(row) >= 2:
419
+ candidate_id = row[0]
420
+ parent_id = row[1] if len(row) > 1 else ''
421
+ # Check for baseline entry (empty parent and baseline-like ID)
422
+ if not parent_id and ('baseline' in candidate_id.lower() or candidate_id.startswith('000') or candidate_id == '0'):
423
+ print('found')
424
+ exit(0)
425
+ exit(1)
426
+ "; then
427
+ echo "[DISPATCHER] Baseline performance already recorded"
428
+ else
429
+ echo "[DISPATCHER] No baseline found, adding baseline-000 for evaluation..."
430
+
431
+ # Add baseline entry as pending
432
+ echo "baseline-000,,Original algorithm.py performance,,pending" >> "$FULL_CSV_PATH"
433
+ echo "[DISPATCHER] Added baseline-000 to evaluation queue"
434
+ fi
435
+ }
436
+
437
+ # Check for baseline before starting main loop
438
+ ensure_baseline_entry
439
+
391
440
  # With retry mechanism, we don't need consecutive failure tracking
392
441
  # Failures are handled gracefully through the retry system
393
442
 
@@ -111,6 +111,7 @@ csv_file = '$FULL_CSV_PATH'
111
111
  show_brief = '$SHOW_BRIEF' == 'true'
112
112
  show_winner_only = '$SHOW_WINNER_ONLY' == 'true'
113
113
  evolution_context = '$EVOLUTION_CONTEXT'
114
+ num_novel_to_show = int('${NUM_REVOLUTION:-2}')
114
115
 
115
116
  def normalize_status(status):
116
117
  '''Convert retry statuses to base status for counting.'''
@@ -183,6 +184,7 @@ try:
183
184
  if all_candidates:
184
185
  winner = max(all_candidates, key=lambda x: x[2])
185
186
 
187
+
186
188
  # Show winner only
187
189
  if show_winner_only:
188
190
  if winner:
@@ -218,6 +220,27 @@ try:
218
220
  print('🏆 CURRENT LEADER: None (no completed candidates)')
219
221
  print()
220
222
 
223
+ # Show top novel candidates
224
+ novel_candidates = []
225
+ for row in rows[1:]:
226
+ if len(row) >= 5 and row[3] and row[4] == 'complete' and not row[1]:
227
+ try:
228
+ candidate_id = row[0]
229
+ description = row[2] if len(row) > 2 else ''
230
+ score = float(row[3])
231
+ novel_candidates.append((candidate_id, description, score))
232
+ except ValueError:
233
+ pass
234
+
235
+ if novel_candidates:
236
+ novel_candidates.sort(key=lambda x: x[2], reverse=True)
237
+ print(f'🌟 TOP NOVEL CANDIDATES:')
238
+ # Use the num_novel_to_show variable set at the top
239
+ for i, (candidate_id, description, score) in enumerate(novel_candidates[:num_novel_to_show]):
240
+ print(f' {i+1}. {candidate_id} (score: {score:.4f})')
241
+ print(f' {description}')
242
+ print()
243
+
221
244
  # Show per-generation breakdown (unless brief mode)
222
245
  if not show_brief and stats_by_gen:
223
246
  print('📈 BY GENERATION:')
@@ -232,7 +255,12 @@ try:
232
255
  status_str = f'{data[\"pending\"]}p {data[\"complete\"]}c {data[\"failed\"]}f {data[\"running\"]}r'
233
256
 
234
257
  if gen_best:
235
- print(f' {gen}: {total} total ({status_str}) - best: {gen_best[0]} ({gen_best[2]:.4f})')
258
+ # Check if this generation's best is the overall winner
259
+ if winner and gen_best[0] == winner[0]:
260
+ # Highlight in green if it's the overall winner
261
+ print(f' {gen}: {total} total ({status_str}) - best: \033[32m{gen_best[0]} ({gen_best[2]:.4f})\033[0m')
262
+ else:
263
+ print(f' {gen}: {total} total ({status_str}) - best: {gen_best[0]} ({gen_best[2]:.4f})')
236
264
  else:
237
265
  print(f' {gen}: {total} total ({status_str}) - best: none')
238
266