claude-evolve 1.5.3 → 1.5.6

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.
@@ -7,6 +7,17 @@ source "$SCRIPT_DIR/../lib/config.sh"
7
7
  source "$SCRIPT_DIR/../lib/csv-lock.sh"
8
8
  source "$SCRIPT_DIR/../lib/ai-cli.sh"
9
9
 
10
+ # Setup logging to file
11
+ if [[ -n "${FULL_EVOLUTION_DIR:-}" ]]; then
12
+ LOG_DIR="$FULL_EVOLUTION_DIR/logs"
13
+ mkdir -p "$LOG_DIR"
14
+ LOG_FILE="$LOG_DIR/worker-$$-$(date +%Y%m%d-%H%M%S).log"
15
+
16
+ # Log to both terminal and file with timestamps
17
+ exec > >(while IFS= read -r line; do echo "$(date '+%Y-%m-%d %H:%M:%S'): $line"; done | tee -a "$LOG_FILE") 2>&1
18
+ echo "[WORKER-$$] Logging to: $LOG_FILE"
19
+ fi
20
+
10
21
  # Track current candidate for cleanup
11
22
  CURRENT_CANDIDATE_ID=""
12
23
  TERMINATION_SIGNAL=""
@@ -14,34 +25,11 @@ TERMINATION_SIGNAL=""
14
25
  # Cleanup function to handle termination
15
26
  cleanup_on_exit() {
16
27
  if [[ -n "$CURRENT_CANDIDATE_ID" ]]; then
17
- # Only mark as failed if it was a timeout (SIGTERM from timeout command)
18
- # For user interruption (Ctrl-C) or kill, leave it for retry
19
- if [[ "$TERMINATION_SIGNAL" == "TERM" ]]; then
20
- echo "[WORKER-$$] Timeout detected, marking $CURRENT_CANDIDATE_ID as failed" >&2
21
- "$PYTHON_CMD" -c "
22
- import sys
23
- sys.path.insert(0, '$SCRIPT_DIR/..')
24
- from lib.evolution_csv import EvolutionCSV
25
- try:
26
- with EvolutionCSV('$FULL_CSV_PATH') as csv:
27
- csv.update_candidate_status('$CURRENT_CANDIDATE_ID', 'failed')
28
- except:
29
- pass # Best effort cleanup
30
- " 2>/dev/null || true
31
- else
32
- echo "[WORKER-$$] Interrupted, leaving $CURRENT_CANDIDATE_ID for retry" >&2
33
- # Optionally reset to pending instead of leaving as running
34
- "$PYTHON_CMD" -c "
35
- import sys
36
- sys.path.insert(0, '$SCRIPT_DIR/..')
37
- from lib.evolution_csv import EvolutionCSV
38
- try:
39
- with EvolutionCSV('$FULL_CSV_PATH') as csv:
40
- csv.update_candidate_status('$CURRENT_CANDIDATE_ID', 'pending')
41
- except:
42
- pass # Best effort cleanup
43
- " 2>/dev/null || true
44
- fi
28
+ echo "[WORKER-$$] Worker terminated while processing $CURRENT_CANDIDATE_ID" >&2
29
+ # If we're interrupted while processing, leave it as "running"
30
+ # This prevents other workers from picking it up in the same session
31
+ # A human can manually reset to pending if needed
32
+ echo "[WORKER-$$] Leaving $CURRENT_CANDIDATE_ID in current state" >&2
45
33
  fi
46
34
  }
47
35
 
@@ -85,6 +73,17 @@ call_ai_for_evolution() {
85
73
  local prompt="$1"
86
74
  local candidate_id="$2"
87
75
 
76
+ # Get target file path from worker context
77
+ local target_file="$FULL_OUTPUT_DIR/evolution_${candidate_id}.py"
78
+
79
+ # Capture file state before AI call
80
+ local file_hash_before=""
81
+ local file_mtime_before=""
82
+ if [[ -f "$target_file" ]]; then
83
+ file_hash_before=$(shasum -a 256 "$target_file" 2>/dev/null | cut -d' ' -f1)
84
+ file_mtime_before=$(stat -f %m "$target_file" 2>/dev/null || stat -c %Y "$target_file" 2>/dev/null)
85
+ fi
86
+
88
87
  # Extract generation and ID numbers for round-robin calculation
89
88
  local gen_num=0
90
89
  local id_num=0
@@ -108,14 +107,32 @@ call_ai_for_evolution() {
108
107
  exit 3
109
108
  fi
110
109
 
111
- if [[ $ai_exit_code -eq 0 ]]; then
112
- echo "[WORKER-$$] AI succeeded" >&2
110
+ # Check if the target file was actually modified
111
+ local file_was_modified=false
112
+ if [[ -f "$target_file" ]]; then
113
+ local file_hash_after
114
+ local file_mtime_after
115
+ file_hash_after=$(shasum -a 256 "$target_file" 2>/dev/null | cut -d' ' -f1)
116
+ file_mtime_after=$(stat -f %m "$target_file" 2>/dev/null || stat -c %Y "$target_file" 2>/dev/null)
117
+
118
+ if [[ "$file_hash_before" != "$file_hash_after" ]] || [[ "$file_mtime_before" != "$file_mtime_after" ]]; then
119
+ file_was_modified=true
120
+ fi
121
+ fi
122
+
123
+ # Success if file was modified OR exit code is 0 (for cases where file validation isn't applicable)
124
+ if [[ "$file_was_modified" == "true" ]] || [[ $ai_exit_code -eq 0 ]]; then
125
+ if [[ "$file_was_modified" == "true" ]]; then
126
+ echo "[WORKER-$$] AI successfully modified $target_file (exit code: $ai_exit_code)" >&2
127
+ else
128
+ echo "[WORKER-$$] AI succeeded with exit code 0" >&2
129
+ fi
113
130
  # Output the result for the worker to use
114
131
  echo "$ai_output"
115
132
  return 0
116
133
  fi
117
134
 
118
- echo "[WORKER-$$] All AI models failed" >&2
135
+ echo "[WORKER-$$] AI failed: exit code $ai_exit_code, no file changes detected" >&2
119
136
  return 1
120
137
  }
121
138
 
@@ -135,6 +152,12 @@ process_candidate() {
135
152
  echo "[WORKER-$$] Description: $description"
136
153
  echo "[WORKER-$$] Based on ID: $parent_id"
137
154
 
155
+ # Treat "baseline-000" parent ID as empty/baseline
156
+ if [[ "$parent_id" == "baseline-000" ]]; then
157
+ parent_id=""
158
+ echo "[WORKER-$$] Parent ID 'baseline-000' treated as baseline (empty parent)"
159
+ fi
160
+
138
161
  # Determine source algorithm
139
162
  local source_file
140
163
  if [[ -z "$parent_id" ]]; then
@@ -213,16 +236,38 @@ CRITICAL: Do NOT use any git commands (git add, git commit, git reset, etc.). On
213
236
 
214
237
  # Try AI models with round-robin based on candidate ID
215
238
  if ! call_ai_for_evolution "$evolution_prompt" "$candidate_id"; then
216
- echo "[WORKER-$$] ERROR: All AI models failed to generate code" >&2
239
+ echo "[WORKER-$$] ERROR: All AI models failed to generate code - leaving as pending for retry" >&2
217
240
  cd "$original_pwd"
218
241
  rm -f "$target_file" # Clean up on failure
219
- return 1
242
+ # Return with special code to indicate AI failure (should remain pending)
243
+ return 77
220
244
  fi
221
245
 
222
246
  # Restore working directory
223
247
  cd "$original_pwd"
224
248
 
225
249
  echo "[WORKER-$$] Evolution applied successfully"
250
+
251
+ # Record which AI model generated the code (regardless of evaluation outcome)
252
+ if [[ -n "${SUCCESSFUL_RUN_MODEL:-}" ]]; then
253
+ echo "[WORKER-$$] Recording that $SUCCESSFUL_RUN_MODEL generated the code" >&2
254
+ "$PYTHON_CMD" -c "
255
+ import sys
256
+ sys.path.insert(0, '$SCRIPT_DIR/..')
257
+ from lib.evolution_csv import EvolutionCSV
258
+ with EvolutionCSV('$FULL_CSV_PATH') as csv:
259
+ csv.update_candidate_field('$candidate_id', 'run-LLM', '$SUCCESSFUL_RUN_MODEL')
260
+ " || echo "[WORKER-$$] Warning: Failed to record run-LLM field" >&2
261
+ fi
262
+
263
+ # Check if the generated Python file has syntax errors
264
+ echo "[WORKER-$$] Checking Python syntax..." >&2
265
+ if ! "$PYTHON_CMD" -m py_compile "$target_file" 2>&1; then
266
+ echo "[WORKER-$$] ERROR: Generated Python file has syntax errors!" >&2
267
+ echo "[WORKER-$$] File: $target_file" >&2
268
+ # This is still an evaluation failure, not an AI failure
269
+ return 1
270
+ fi
226
271
  fi
227
272
  fi
228
273
 
@@ -239,11 +284,22 @@ CRITICAL: Do NOT use any git commands (git add, git commit, git reset, etc.). On
239
284
  eval_arg=""
240
285
  fi
241
286
  local eval_cmd=("$PYTHON_CMD" "$FULL_EVALUATOR_PATH" "$eval_arg")
287
+
288
+ # Add memory limiting if configured
289
+ if [[ -n "$MEMORY_LIMIT_MB" ]] && [[ "$MEMORY_LIMIT_MB" -gt 0 ]]; then
290
+ eval_cmd=("$PYTHON_CMD" "$SCRIPT_DIR/../lib/memory_limit_wrapper.py" "$MEMORY_LIMIT_MB" "${eval_cmd[@]}")
291
+ fi
292
+
293
+ # Add timeout if configured
242
294
  [[ -n "$timeout_seconds" ]] && eval_cmd=(timeout "$timeout_seconds" "${eval_cmd[@]}")
243
295
 
244
296
  # Run evaluation with tee to both display and capture output
245
297
  # Use stdbuf to disable buffering for real-time output
246
- if stdbuf -o0 -e0 "${eval_cmd[@]}" 2>&1 | tee "$eval_output_file" >&2; then
298
+ # IMPORTANT: Use PIPESTATUS to get the exit code of the evaluation command, not tee
299
+ stdbuf -o0 -e0 "${eval_cmd[@]}" 2>&1 | tee "$eval_output_file" >&2
300
+ local eval_exit_code=${PIPESTATUS[0]} # Get exit code of first command in pipe
301
+
302
+ if [[ $eval_exit_code -eq 0 ]]; then
247
303
  local eval_end=$(date +%s)
248
304
  local eval_duration=$((eval_end - eval_start))
249
305
 
@@ -353,14 +409,19 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
353
409
  echo "[WORKER-$$] Output: $eval_output" >&2
354
410
  # rm -f "$eval_output_file" # Keep for debugging
355
411
  echo "[WORKER-$$] Evaluation output saved to: $eval_output_file" >&2
412
+ # Clear CURRENT_CANDIDATE_ID before returning
413
+ CURRENT_CANDIDATE_ID=""
356
414
  return 1
357
415
  fi
358
416
 
359
417
  # Clean up temp file (comment out to keep for debugging)
360
418
  # rm -f "$eval_output_file"
361
419
  echo "[WORKER-$$] Evaluation output saved to: $eval_output_file" >&2
420
+
421
+ # Clear CURRENT_CANDIDATE_ID on successful completion
422
+ CURRENT_CANDIDATE_ID=""
362
423
  else
363
- local exit_code=$?
424
+ local exit_code=$eval_exit_code
364
425
  # Read any output that was captured before failure
365
426
  eval_output=$(<"$eval_output_file")
366
427
  # rm -f "$eval_output_file" # Keep for debugging
@@ -370,22 +431,56 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
370
431
  echo "[WORKER-$$] Output: $eval_output" >&2
371
432
 
372
433
  # Mark as failed in CSV
373
- "$PYTHON_CMD" -c "
434
+ echo "[WORKER-$$] Marking $candidate_id as failed in CSV" >&2
435
+ if ! "$PYTHON_CMD" -c "
374
436
  import sys
375
437
  sys.path.insert(0, '$SCRIPT_DIR/..')
376
438
  from lib.evolution_csv import EvolutionCSV
377
- with EvolutionCSV('$FULL_CSV_PATH') as csv:
378
- csv.update_candidate_status('$candidate_id', 'failed')
379
- "
439
+ try:
440
+ with EvolutionCSV('$FULL_CSV_PATH') as csv:
441
+ success = csv.update_candidate_status('$candidate_id', 'failed')
442
+ if not success:
443
+ print(f'ERROR: Failed to update status for {candidate_id}', file=sys.stderr)
444
+ sys.exit(1)
445
+ except Exception as e:
446
+ print(f'ERROR: Exception updating status: {e}', file=sys.stderr)
447
+ sys.exit(1)
448
+ " 2>&1; then
449
+ echo "[WORKER-$$] ERROR: Failed to update CSV status to failed" >&2
450
+ else
451
+ echo "[WORKER-$$] Successfully marked $candidate_id as failed" >&2
452
+ fi
380
453
 
454
+ # Clear CURRENT_CANDIDATE_ID before returning to prevent cleanup handler from resetting it
455
+ CURRENT_CANDIDATE_ID=""
381
456
  return $exit_code
382
457
  fi
383
458
  }
384
459
 
460
+ # Don't reset running candidates on startup - they might be legitimately being processed by another worker
461
+
385
462
  # Main worker loop
386
463
  echo "[WORKER-$$] Worker started"
387
464
 
388
465
  while true; do
466
+ # Debug: Show current status of all candidates
467
+ echo "[WORKER-$$] Current candidate statuses:" >&2
468
+ "$PYTHON_CMD" -c "
469
+ import sys
470
+ sys.path.insert(0, '$SCRIPT_DIR/..')
471
+ from lib.evolution_csv import EvolutionCSV
472
+ with EvolutionCSV('$FULL_CSV_PATH') as csv:
473
+ rows = csv._read_csv()
474
+ if rows:
475
+ start_idx = 1 if rows and rows[0] and rows[0][0].lower() == 'id' else 0
476
+ status_count = {}
477
+ for row in rows[start_idx:]:
478
+ if len(row) > 4:
479
+ status = row[4].strip() or 'pending'
480
+ status_count[status] = status_count.get(status, 0) + 1
481
+ print(f'Status counts: {status_count}', file=sys.stderr)
482
+ " 2>&1 || true
483
+
389
484
  # Try to claim a pending candidate
390
485
  candidate_info=$("$PYTHON_CMD" -c "
391
486
  import sys
@@ -412,12 +507,26 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
412
507
  # Set current candidate for cleanup
413
508
  CURRENT_CANDIDATE_ID="$candidate_id"
414
509
 
415
- # Process the candidate
416
- if process_candidate "$candidate_id" "$parent_id" "$description"; then
510
+ # Process the candidate and capture exit code
511
+ process_candidate "$candidate_id" "$parent_id" "$description"
512
+ process_exit_code=$?
513
+
514
+ if [[ $process_exit_code -eq 0 ]]; then
417
515
  echo "[WORKER-$$] Successfully processed $candidate_id"
516
+ elif [[ $process_exit_code -eq 77 ]]; then
517
+ # Special exit code 77 means AI failed to generate code
518
+ echo "[WORKER-$$] AI generation failed for $candidate_id - marking as failed-ai-retry"
519
+ # Mark with special status that indicates AI generation failed (not evaluation)
520
+ "$PYTHON_CMD" -c "
521
+ import sys
522
+ sys.path.insert(0, '$SCRIPT_DIR/..')
523
+ from lib.evolution_csv import EvolutionCSV
524
+ with EvolutionCSV('$FULL_CSV_PATH') as csv:
525
+ csv.update_candidate_status('$candidate_id', 'failed-ai-retry')
526
+ " 2>/dev/null || true
418
527
  else
419
528
  echo "[WORKER-$$] Failed to process $candidate_id"
420
- # Ensure status is set to failed (might already be done in process_candidate)
529
+ # Other failures (evaluation errors, etc) mark as failed
421
530
  "$PYTHON_CMD" -c "
422
531
  import sys
423
532
  sys.path.insert(0, '$SCRIPT_DIR/..')
package/lib/ai-cli.sh CHANGED
@@ -12,31 +12,50 @@ call_ai_model_configured() {
12
12
  local model_name="$1"
13
13
  local prompt="$2"
14
14
 
15
+ # Record start time
16
+ local start_time=$(date +%s)
17
+
15
18
  # Build command directly based on model
16
19
  case "$model_name" in
17
20
  opus|sonnet)
18
21
  local ai_output
19
- ai_output=$(timeout 300 claude --dangerously-skip-permissions --model "$model_name" -p "$prompt" 2>&1)
22
+ ai_output=$(timeout 180 claude --dangerously-skip-permissions --model "$model_name" -p "$prompt" 2>&1)
23
+ local ai_exit_code=$?
24
+ ;;
25
+ gpt5high)
26
+ local ai_output
27
+ ai_output=$(timeout 420 codex exec --profile gpt5high --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
20
28
  local ai_exit_code=$?
21
29
  ;;
22
- o3)
30
+ o3high)
23
31
  local ai_output
24
- ai_output=$(timeout 300 codex exec -m o3 --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
32
+ ai_output=$(timeout 500 codex exec --profile o3high --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
25
33
  local ai_exit_code=$?
26
34
  ;;
27
35
  codex)
28
36
  local ai_output
29
- ai_output=$(timeout 300 codex exec --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
37
+ ai_output=$(timeout 420 codex exec --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
30
38
  local ai_exit_code=$?
31
39
  ;;
32
40
  gemini)
33
41
  # Debug: Show exact command
34
- echo "[DEBUG] Running: timeout 300 gemini -y -p <prompt>" >&2
42
+ echo "[DEBUG] Running: timeout 1200 gemini -y -p <prompt>" >&2
35
43
  echo "[DEBUG] Working directory: $(pwd)" >&2
36
44
  echo "[DEBUG] Files in current dir:" >&2
37
45
  ls -la temp-csv-*.csv 2>&1 | head -5 >&2
38
46
  local ai_output
39
- ai_output=$(timeout 300 gemini -y -p "$prompt" 2>&1)
47
+ # Gemini needs longer timeout as it streams output while working (20 minutes)
48
+ ai_output=$(timeout 1200 gemini -y -p "$prompt" 2>&1)
49
+ local ai_exit_code=$?
50
+ ;;
51
+ cursor-sonnet)
52
+ local ai_output
53
+ ai_output=$(timeout 180 cursor-agent sonnet -p "$prompt" 2>&1)
54
+ local ai_exit_code=$?
55
+ ;;
56
+ cursor-opus)
57
+ local ai_output
58
+ ai_output=$(timeout 300 cursor-agent opus -p "$prompt" 2>&1)
40
59
  local ai_exit_code=$?
41
60
  ;;
42
61
  *)
@@ -48,8 +67,12 @@ call_ai_model_configured() {
48
67
  # Debug: log model and prompt size
49
68
  echo "[DEBUG] Calling $model_name with prompt of ${#prompt} characters" >&2
50
69
 
51
- # Always log basic info
52
- echo "[AI] $model_name exit code: $ai_exit_code, output length: ${#ai_output} chars" >&2
70
+ # Calculate duration
71
+ local end_time=$(date +%s)
72
+ local duration=$((end_time - start_time))
73
+
74
+ # Always log basic info with timing
75
+ echo "[AI] $model_name exit code: $ai_exit_code, output length: ${#ai_output} chars, duration: ${duration}s" >&2
53
76
 
54
77
  # Show detailed output if verbose or if there was an error
55
78
  if [[ "${VERBOSE_AI_OUTPUT:-false}" == "true" ]] || [[ $ai_exit_code -ne 0 ]]; then
@@ -100,7 +123,7 @@ clean_ai_output() {
100
123
  local model_name="$2"
101
124
 
102
125
  # Handle codex-specific output format
103
- if [[ "$model_name" == "codex" || "$model_name" == "o3" ]]; then
126
+ if [[ "$model_name" == "codex" || "$model_name" == "o3high" || "$model_name" == "gpt5high" ]]; then
104
127
  # Clean codex output - extract content between "codex" marker and "tokens used"
105
128
  if echo "$output" | grep -q "^\[.*\] codex$"; then
106
129
  # Extract content between "codex" line and "tokens used" line
@@ -191,11 +214,19 @@ call_ai_with_round_robin() {
191
214
  ai_output=$(call_ai_model_configured "$model" "$prompt")
192
215
  local ai_exit_code=$?
193
216
 
194
- # Just check exit code - no interpretation
195
- if [[ $ai_exit_code -eq 0 ]]; then
196
- # Clean output if needed
197
- ai_output=$(clean_ai_output "$ai_output" "$model")
198
- echo "[AI] $model returned exit code 0" >&2
217
+ # Clean output if needed
218
+ ai_output=$(clean_ai_output "$ai_output" "$model")
219
+
220
+ # Success if exit code is 0, or if it's just a timeout (124)
221
+ # Timeout doesn't mean the AI failed - it may have completed the task
222
+ if [[ $ai_exit_code -eq 0 ]] || [[ $ai_exit_code -eq 124 ]]; then
223
+ if [[ $ai_exit_code -eq 124 ]]; then
224
+ echo "[AI] $model timed out but continuing (exit code: 124)" >&2
225
+ else
226
+ echo "[AI] $model returned exit code 0" >&2
227
+ fi
228
+ # Export the successful model for tracking (used by worker)
229
+ export SUCCESSFUL_RUN_MODEL="$model"
199
230
  # Debug: log what AI returned on success
200
231
  if [[ "${DEBUG_AI_SUCCESS:-}" == "true" ]]; then
201
232
  echo "[AI] $model success output preview:" >&2
package/lib/config.sh CHANGED
@@ -49,17 +49,13 @@ DEFAULT_AUTO_IDEATE=true
49
49
  # Default retry value
50
50
  DEFAULT_MAX_RETRIES=3
51
51
 
52
- # Default LLM CLI configuration (using eval for compatibility)
53
- declare -a DEFAULT_LLM_CLI_KEYS
54
- declare -a DEFAULT_LLM_CLI_VALUES
55
- DEFAULT_LLM_CLI_KEYS=(o3 codex gemini opus sonnet)
56
- DEFAULT_LLM_CLI_VALUES[0]='codex exec -m o3 --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
57
- DEFAULT_LLM_CLI_VALUES[1]='codex exec --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
58
- DEFAULT_LLM_CLI_VALUES[2]='gemini -y -p "{{PROMPT}}"'
59
- DEFAULT_LLM_CLI_VALUES[3]='claude --dangerously-skip-permissions --model opus -p "{{PROMPT}}"'
60
- DEFAULT_LLM_CLI_VALUES[4]='claude --dangerously-skip-permissions --model sonnet -p "{{PROMPT}}"'
61
- DEFAULT_LLM_RUN="sonnet"
62
- DEFAULT_LLM_IDEATE="gemini o3 opus"
52
+ # Default memory limit (in MB, 0 means no limit)
53
+ # Set to reasonable limit for ML workloads - about half of available system RAM
54
+ DEFAULT_MEMORY_LIMIT_MB=12288
55
+
56
+ # Default LLM CLI configuration - use simple variables instead of arrays
57
+ DEFAULT_LLM_RUN="sonnet cursor-sonnet"
58
+ DEFAULT_LLM_IDEATE="gemini opus gpt5high o3high cursor-opus"
63
59
 
64
60
  # Load configuration from config file
65
61
  load_config() {
@@ -96,14 +92,20 @@ load_config() {
96
92
  # Set retry default
97
93
  MAX_RETRIES="$DEFAULT_MAX_RETRIES"
98
94
 
95
+ # Set memory limit default
96
+ MEMORY_LIMIT_MB="$DEFAULT_MEMORY_LIMIT_MB"
97
+
99
98
  # Set LLM CLI defaults (compatibility for older bash)
100
99
  # Initialize associative array for LLM commands
101
100
  # Use simpler approach for compatibility
102
- LLM_CLI_o3='codex exec -m o3 --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
101
+ LLM_CLI_gpt5high='codex exec --profile gpt5high --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
102
+ LLM_CLI_o3high='codex exec --profile o3high --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
103
103
  LLM_CLI_codex='codex exec --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
104
104
  LLM_CLI_gemini='gemini -y -p "{{PROMPT}}"'
105
105
  LLM_CLI_opus='claude --dangerously-skip-permissions --model opus -p "{{PROMPT}}"'
106
106
  LLM_CLI_sonnet='claude --dangerously-skip-permissions --model sonnet -p "{{PROMPT}}"'
107
+ LLM_CLI_cursor_sonnet='cursor-agent sonnet -p "{{PROMPT}}"'
108
+ LLM_CLI_cursor_opus='cursor-agent opus -p "{{PROMPT}}"'
107
109
  LLM_RUN="$DEFAULT_LLM_RUN"
108
110
  LLM_IDEATE="$DEFAULT_LLM_IDEATE"
109
111
 
@@ -202,12 +204,14 @@ load_config() {
202
204
  # Model definition - key is model name, value is command template
203
205
  # Remove single quotes from value if present
204
206
  value=$(echo "$value" | sed "s/^'//;s/'$//")
207
+ # Convert dashes to underscores for bash variable names
208
+ var_key=$(echo "$key" | sed 's/-/_/g')
205
209
  # Debug config loading
206
210
  if [[ "${DEBUG_CONFIG:-}" == "true" ]]; then
207
- echo "[CONFIG DEBUG] Setting LLM_CLI_${key} = '$value'" >&2
211
+ echo "[CONFIG DEBUG] Setting LLM_CLI_${var_key} = '$value'" >&2
208
212
  fi
209
213
  # Use dynamic variable name for compatibility
210
- eval "LLM_CLI_${key}=\"$value\""
214
+ eval "LLM_CLI_${var_key}=\"$value\""
211
215
  fi
212
216
  else
213
217
  # Handle top-level keys
@@ -221,6 +225,7 @@ load_config() {
221
225
  python_cmd) PYTHON_CMD="$value" ;;
222
226
  auto_ideate) AUTO_IDEATE="$value" ;;
223
227
  max_retries) MAX_RETRIES="$value" ;;
228
+ memory_limit_mb) MEMORY_LIMIT_MB="$value" ;;
224
229
  evolution_dir)
225
230
  echo "[WARN] evolution_dir in config is ignored - automatically inferred from config file location" >&2
226
231
  ;;
@@ -316,14 +321,18 @@ show_config() {
316
321
  echo " Lock timeout: $LOCK_TIMEOUT"
317
322
  echo " Auto ideate: $AUTO_IDEATE"
318
323
  echo " Max retries: $MAX_RETRIES"
324
+ echo " Memory limit: ${MEMORY_LIMIT_MB}MB"
319
325
  echo " LLM configuration:"
320
326
  # Show LLM configurations using dynamic variable names
321
- for model in o3 codex gemini opus sonnet; do
327
+ for model in gpt5high o3high codex gemini opus sonnet cursor_sonnet cursor_opus; do
322
328
  var_name="LLM_CLI_${model}"
323
- if [[ -n "${!var_name}" ]]; then
324
- echo " $model: ${!var_name}"
329
+ var_value=$(eval echo "\$$var_name")
330
+ if [[ -n "$var_value" ]]; then
331
+ # Convert underscore back to dash for display
332
+ display_name=$(echo "$model" | sed 's/_/-/g')
333
+ echo " $display_name: $var_value"
325
334
  fi
326
335
  done
327
336
  echo " LLM for run: $LLM_RUN"
328
337
  echo " LLM for ideate: $LLM_IDEATE"
329
- }
338
+ }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CSV format fixer for claude-evolve
4
+ Ensures proper quoting of CSV fields, especially descriptions
5
+ """
6
+
7
+ import csv
8
+ import sys
9
+
10
+ def fix_csv_format(input_file, output_file):
11
+ """
12
+ Read a CSV file and ensure all fields are properly quoted.
13
+ The csv module handles quoting automatically based on content.
14
+ """
15
+ with open(input_file, 'r') as infile:
16
+ reader = csv.reader(infile)
17
+ rows = list(reader)
18
+
19
+ with open(output_file, 'w', newline='') as outfile:
20
+ writer = csv.writer(outfile, quoting=csv.QUOTE_NONNUMERIC)
21
+
22
+ # Write all rows - csv.writer handles quoting automatically
23
+ for row in rows:
24
+ writer.writerow(row)
25
+
26
+ if __name__ == "__main__":
27
+ if len(sys.argv) != 3:
28
+ print("Usage: csv_fixer.py <input_file> <output_file>", file=sys.stderr)
29
+ sys.exit(1)
30
+
31
+ try:
32
+ fix_csv_format(sys.argv[1], sys.argv[2])
33
+ except Exception as e:
34
+ print(f"Error fixing CSV: {e}", file=sys.stderr)
35
+ sys.exit(1)
@@ -199,7 +199,7 @@ class EvolutionCSV:
199
199
  for i in range(start_idx, len(rows)):
200
200
  row = rows[i]
201
201
 
202
- if self.is_valid_candidate_row(row) and row[0].strip() == candidate_id:
202
+ if self.is_valid_candidate_row(row) and row[0].strip().strip('"') == candidate_id.strip().strip('"'):
203
203
  # Ensure row has at least 5 columns
204
204
  while len(row) < 5:
205
205
  row.append('')
@@ -227,7 +227,7 @@ class EvolutionCSV:
227
227
  for i in range(start_idx, len(rows)):
228
228
  row = rows[i]
229
229
 
230
- if self.is_valid_candidate_row(row) and row[0].strip() == candidate_id:
230
+ if self.is_valid_candidate_row(row) and row[0].strip().strip('"') == candidate_id.strip().strip('"'):
231
231
  # Ensure row has at least 4 columns
232
232
  while len(row) < 4:
233
233
  row.append('')
@@ -263,10 +263,14 @@ class EvolutionCSV:
263
263
  field_index = i
264
264
  break
265
265
 
266
- # If field doesn't exist, add it to header
266
+ # If field doesn't exist, add it to header and extend all rows
267
267
  if field_index is None:
268
268
  field_index = len(header_row)
269
269
  header_row.append(field_name)
270
+ # Extend all data rows with empty values for the new column
271
+ for i in range(1, len(rows)):
272
+ while len(rows[i]) <= field_index:
273
+ rows[i].append('')
270
274
  else:
271
275
  # No header - we'll use predefined positions for known fields
272
276
  field_map = {
@@ -274,7 +278,9 @@ class EvolutionCSV:
274
278
  'basedonid': 1,
275
279
  'description': 2,
276
280
  'performance': 3,
277
- 'status': 4
281
+ 'status': 4,
282
+ 'idea-llm': 5,
283
+ 'run-llm': 6
278
284
  }
279
285
  field_index = field_map.get(field_name.lower())
280
286
  if field_index is None:
@@ -287,7 +293,10 @@ class EvolutionCSV:
287
293
 
288
294
  for i in range(start_idx, len(rows)):
289
295
  row = rows[i]
290
- if self.is_valid_candidate_row(row) and row[0].strip() == candidate_id:
296
+ # Strip quotes from both stored ID and search ID for comparison
297
+ stored_id = row[0].strip().strip('"') if len(row) > 0 else ''
298
+ search_id = candidate_id.strip().strip('"')
299
+ if self.is_valid_candidate_row(row) and stored_id == search_id:
291
300
  # Ensure row has enough columns
292
301
  while len(row) <= field_index:
293
302
  row.append('')
@@ -311,7 +320,7 @@ class EvolutionCSV:
311
320
  start_idx = 1 if rows and rows[0] and rows[0][0].lower() == 'id' else 0
312
321
 
313
322
  for row in rows[start_idx:]:
314
- if self.is_valid_candidate_row(row) and row[0].strip() == candidate_id:
323
+ if self.is_valid_candidate_row(row) and row[0].strip().strip('"') == candidate_id.strip().strip('"'):
315
324
  return {
316
325
  'id': row[0].strip() if len(row) > 0 else '',
317
326
  'basedOnId': row[1].strip() if len(row) > 1 else '',
@@ -344,7 +353,7 @@ class EvolutionCSV:
344
353
 
345
354
  for i in range(start_idx, len(rows)):
346
355
  row = rows[i]
347
- if self.is_valid_candidate_row(row) and row[0].strip() == candidate_id:
356
+ if self.is_valid_candidate_row(row) and row[0].strip().strip('"') == candidate_id.strip().strip('"'):
348
357
  deleted = True
349
358
  # Skip this row (delete it)
350
359
  continue
@@ -371,6 +380,7 @@ def main():
371
380
  print(" update <id> <status> - Update candidate status")
372
381
  print(" perf <id> <performance> - Update candidate performance")
373
382
  print(" info <id> - Get candidate info")
383
+ print(" field <id> <field> <val>- Update specific field")
374
384
  print(" check - Check if has pending work")
375
385
  sys.exit(1)
376
386
 
@@ -430,6 +440,17 @@ def main():
430
440
  has_work = csv_ops.has_pending_work()
431
441
  print("yes" if has_work else "no")
432
442
 
443
+ elif command == 'field' and len(sys.argv) >= 5:
444
+ candidate_id = sys.argv[3]
445
+ field_name = sys.argv[4]
446
+ value = sys.argv[5] if len(sys.argv) >= 6 else ''
447
+ success = csv_ops.update_candidate_field(candidate_id, field_name, value)
448
+ if success:
449
+ print(f"Updated {candidate_id} field {field_name} to {value}")
450
+ else:
451
+ print(f"Failed to update {candidate_id} field {field_name}")
452
+ sys.exit(1)
453
+
433
454
  else:
434
455
  print(f"Unknown command: {command}")
435
456
  sys.exit(1)