claude-evolve 1.9.10 → 1.11.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.
@@ -1,1806 +1,11 @@
1
- #!/bin/bash
2
-
3
- set -e
4
-
5
- # Load configuration
6
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
- # shellcheck source=../lib/config.sh
8
- source "$SCRIPT_DIR/../lib/config.sh"
9
- # shellcheck source=../lib/csv-lock.sh
10
- source "$SCRIPT_DIR/../lib/csv-lock.sh"
11
- # shellcheck source=../lib/ai-cli.sh
12
- source "$SCRIPT_DIR/../lib/ai-cli.sh"
13
-
14
- # Use CLAUDE_EVOLVE_CONFIG if set, otherwise default
15
- if [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
16
- load_config "$CLAUDE_EVOLVE_CONFIG"
17
- else
18
- # Check if config.yaml exists in current directory
19
- if [[ -f "config.yaml" ]]; then
20
- # Don't export to avoid collision with parallel runs
21
- CONFIG_FILE="$(pwd)/config.yaml"
22
- load_config "$CONFIG_FILE"
23
- else
24
- load_config
25
- fi
26
- fi
27
-
28
- # Setup logging to file
29
- if [[ -n "${FULL_EVOLUTION_DIR:-}" ]]; then
30
- LOG_DIR="$FULL_EVOLUTION_DIR/logs"
31
- mkdir -p "$LOG_DIR"
32
- LOG_FILE="$LOG_DIR/ideate-$$-$(date +%Y%m%d-%H%M%S).log"
33
-
34
- # Log to both terminal and file with timestamps
35
- exec > >(while IFS= read -r line; do echo "$(date '+%Y-%m-%d %H:%M:%S'): $line"; done | tee -a "$LOG_FILE") 2>&1
36
- echo "[IDEATE-$$] Logging to: $LOG_FILE"
37
- fi
38
-
39
- # AIDEV-NOTE: Directory restoration helper to prevent working directory corruption
40
- # Critical for preventing the bug where Ctrl+C during ideation leaves the shell in wrong directory
41
- # Helper to safely change directory with automatic restoration
42
- safe_pushd() {
43
- SAFE_PUSHD_ORIGINAL_PWD=$(pwd)
44
- cd "$1" || return 1
45
- # Set trap to restore directory on any exit, error, or interrupt
46
- trap 'cd "$SAFE_PUSHD_ORIGINAL_PWD" 2>/dev/null || true' EXIT INT TERM ERR
47
- }
48
-
49
- safe_popd() {
50
- if [[ -n "${SAFE_PUSHD_ORIGINAL_PWD:-}" ]]; then
51
- cd "$SAFE_PUSHD_ORIGINAL_PWD" || true
52
- # Clear the trap since we're explicitly returning
53
- trap - EXIT INT TERM ERR
54
- fi
55
- }
56
-
57
- # Helper function to call AI (legacy wrapper)
58
- call_ai_with_limit_check() {
59
- local prompt="$1"
60
-
61
- # Use centralized AI library for ideation (random model selection)
62
- local ai_output
63
- ai_output=$(call_ai_random "$prompt" "ideate")
64
- local ai_exit_code=$?
65
-
66
- if [[ $ai_exit_code -eq 0 ]]; then
67
- echo "[INFO] AI succeeded" >&2
68
- return 0
69
- else
70
- return $ai_exit_code
71
- fi
72
- }
73
-
74
-
75
- # Backward compatibility alias
76
- call_claude_with_limit_check() {
77
- call_ai_with_limit_check "$@"
78
- }
79
-
80
- # Robust AI calling with random model selection (no fallback)
81
- # Returns 0 on success and echoes the successful model name to stdout
82
- call_ai_for_ideation() {
83
- local prompt="$1"
84
- local generation="${2:-01}"
85
- local expected_count="${3:-1}" # Number of ideas expected to be added
86
- local temp_csv_file="${4:-temp-csv-$$.csv}" # Optional temp CSV filename
87
-
88
- # Verify temp CSV exists
89
- if [[ ! -f "$temp_csv_file" ]]; then
90
- echo "[ERROR] Temp CSV file not found at start: $temp_csv_file" >&2
91
- return 1
92
- fi
93
-
94
- # Get the current row count before any modifications (from the pre-populated file with stubs)
95
- local original_csv_count
96
- original_csv_count=$(grep -v '^[[:space:]]*$' "$temp_csv_file" | tail -n +2 | wc -l | tr -d '[:space:]')
97
-
98
- echo "[DEBUG] Pre-populated temp CSV has $original_csv_count rows (includes stub rows with placeholders)" >&2
99
-
100
- # Get models for ideation
101
- local model_list
102
- model_list=$(get_models_for_command "ideate")
103
- local models=()
104
- read -ra models <<< "$model_list"
105
-
106
- if [[ ${#models[@]} -eq 0 ]]; then
107
- echo "[ERROR] No models configured for ideation" >&2
108
- return 1
109
- fi
110
-
111
- # Pick one random model (no fallback)
112
- local num_models=${#models[@]}
113
- local random_index=$((RANDOM % num_models))
114
- local model="${models[$random_index]}"
115
-
116
- echo "[AI] Selected $model for ideate (random from $num_models models)" >&2
117
-
118
- # Call the model directly
119
- local ai_output
120
- ai_output=$(call_ai_model_configured "$model" "$prompt")
121
- local ai_exit_code=$?
122
-
123
- # Check if the file was modified correctly
124
- if [[ ! -f "$temp_csv_file" ]]; then
125
- echo "[ERROR] Temp CSV file not found after $model: $temp_csv_file" >&2
126
- return 1
127
- fi
128
-
129
- local new_csv_count
130
- new_csv_count=$(grep -v '^[[:space:]]*$' "$temp_csv_file" | tail -n +2 | wc -l | tr -d '[:space:]')
131
- local added_count=$((new_csv_count - original_csv_count))
132
-
133
- echo "[DEBUG] After $model: original=$original_csv_count, new=$new_csv_count, added=$added_count" >&2
134
-
135
- # Check if row count is correct (should be same since we're editing stubs, not adding)
136
- if [[ $new_csv_count -ne $original_csv_count ]]; then
137
- if [[ $added_count -lt 0 ]]; then
138
- echo "[ERROR] $model deleted rows ($added_count)" >&2
139
- else
140
- echo "[ERROR] $model added extra rows ($added_count) instead of editing stubs" >&2
141
- fi
142
- return 1
143
- fi
144
-
145
- # Count remaining placeholders - there should be none if AI did its job
146
- local placeholder_count
147
- placeholder_count=$(grep -c "PLACEHOLDER" "$temp_csv_file" 2>/dev/null) || true
148
- placeholder_count=$(echo "$placeholder_count" | tr -d '[:space:]')
149
- placeholder_count=${placeholder_count:-0}
150
-
151
- if [[ $placeholder_count -ne 0 ]]; then
152
- echo "[ERROR] $model left $placeholder_count placeholders unfilled" >&2
153
- return 1
154
- fi
155
-
156
- echo "[INFO] CSV modified by $model: filled $expected_count placeholder rows ✓" >&2
157
-
158
- # Post-process to ensure all description fields are quoted
159
- local fixed_csv_file="${temp_csv_file}.fixed"
160
- if "$PYTHON_CMD" "$SCRIPT_DIR/../lib/csv_fixer.py" "$temp_csv_file" "$fixed_csv_file"; then
161
- mv "$fixed_csv_file" "$temp_csv_file"
162
- echo "[INFO] CSV format validated and fixed if needed" >&2
163
- else
164
- echo "[WARN] CSV format validation failed, using original" >&2
165
- fi
166
-
167
- # Echo the successful model name for caller to capture
168
- echo "$model"
169
- return 0
170
- }
171
-
172
- # Parse arguments
173
- use_strategies=true
174
-
175
- while [[ $# -gt 0 ]]; do
176
- case $1 in
177
- --help)
178
- cat <<EOF
179
- claude-evolve ideate - Generate new algorithm ideas using evolutionary strategies
180
-
181
- USAGE:
182
- claude-evolve ideate [--legacy N]
183
-
184
- OPTIONS:
185
- --legacy N Use legacy mode with N ideas (ignores strategy config)
186
- --help Show this help message
187
-
188
- DESCRIPTION:
189
- Generates algorithm ideas using multi-strategy evolutionary approach:
190
- - Novel exploration: Pure creativity, global search
191
- - Hill climbing: Parameter tuning of top performers
192
- - Structural mutation: Algorithmic changes to top performers
193
- - Crossover hybrid: Combine successful approaches
194
-
195
- Strategy distribution is configured in evolution/config.yaml
196
- EOF
197
- exit 0
198
- ;;
199
- --legacy)
200
- use_strategies=false
201
- shift
202
- if [[ $1 =~ ^[0-9]+$ ]]; then
203
- TOTAL_IDEAS=$1
204
- shift
205
- else
206
- echo "[ERROR] --legacy requires a number" >&2
207
- exit 1
208
- fi
209
- ;;
210
- *)
211
- echo "[ERROR] Unknown option: $1" >&2
212
- exit 1
213
- ;;
214
- esac
215
- done
216
-
217
- # Check workspace using config
218
- if [[ ! -d "$FULL_EVOLUTION_DIR" ]]; then
219
- echo "[ERROR] Evolution workspace not found: $FULL_EVOLUTION_DIR. Run 'claude-evolve setup' first." >&2
220
- exit 1
221
- fi
222
-
223
- # Ensure CSV exists
224
- if [[ ! -f "$FULL_CSV_PATH" ]]; then
225
- echo "id,basedOnId,description,performance,status,idea-LLM,run-LLM" >"$FULL_CSV_PATH"
226
- fi
227
-
228
- # Validate strategy configuration
229
- if [[ $use_strategies == true ]]; then
230
- total_check=$((${NOVEL_EXPLORATION:-0} + ${HILL_CLIMBING:-0} + ${STRUCTURAL_MUTATION:-0} + ${CROSSOVER_HYBRID:-0}))
231
- if [[ $total_check -ne $TOTAL_IDEAS ]]; then
232
- echo "[ERROR] Strategy counts don't sum to total_ideas ($total_check != $TOTAL_IDEAS)" >&2
233
- echo "Check your evolution/config.yaml configuration" >&2
234
- exit 1
235
- fi
236
- fi
237
-
238
- # Get next generation number that doesn't have existing Python files
239
- # AIDEV-NOTE: Returns zero-padded generation number (e.g., "02" not "2")
240
- get_next_generation() {
241
- # Start with generation 1 if no CSV exists
242
- local start_gen=1
243
-
244
- if [[ -f "$FULL_CSV_PATH" ]]; then
245
- # Use Python for proper CSV parsing to find max generation
246
- local max_gen
247
- max_gen=$("$PYTHON_CMD" -c "
248
- import csv
249
- max_gen = 0
250
- with open('$FULL_CSV_PATH', 'r') as f:
251
- reader = csv.reader(f)
252
- next(reader, None) # Skip header
253
- for row in reader:
254
- if row and len(row) > 0:
255
- id_field = row[0].strip()
256
- if id_field.startswith('gen') and '-' in id_field:
257
- try:
258
- gen_part = id_field.split('-')[0] # e.g., 'gen01'
259
- gen_num = int(gen_part[3:]) # Extract number after 'gen'
260
- max_gen = max(max_gen, gen_num)
261
- except (ValueError, IndexError):
262
- pass
263
- print(max_gen)
264
- ")
265
- # Start checking from the next generation after max
266
- start_gen=$((max_gen + 1))
267
- fi
268
-
269
- # Keep incrementing until we find a generation with no Python files
270
- local candidate_gen=$start_gen
271
- while true; do
272
- # Zero-pad to 2 digits for consistency (gen01, gen02, etc.)
273
- local gen_formatted
274
- gen_formatted=$(printf "%02d" "$((10#$candidate_gen))")
275
-
276
- # Check if any Python files exist for this generation (check both padded and unpadded)
277
- local py_files_exist=false
278
- if ls "$FULL_OUTPUT_DIR"/evolution_gen${gen_formatted}-*.py >/dev/null 2>&1; then
279
- py_files_exist=true
280
- elif ls "$FULL_OUTPUT_DIR"/evolution_gen${candidate_gen}-*.py >/dev/null 2>&1; then
281
- # Also check unpadded format for legacy compatibility
282
- py_files_exist=true
283
- fi
284
-
285
- if [[ "$py_files_exist" == "false" ]]; then
286
- # This generation is safe to use - return padded format
287
- echo "$gen_formatted"
288
- return
289
- else
290
- echo "[WARN] Generation $gen_formatted already has Python files, skipping to next generation" >&2
291
- candidate_gen=$((candidate_gen + 1))
292
-
293
- # Safety check to prevent infinite loop
294
- if [[ $candidate_gen -gt 9999 ]]; then
295
- echo "[ERROR] Could not find a safe generation number (checked up to 9999)" >&2
296
- exit 1
297
- fi
298
- fi
299
- done
300
- }
301
-
302
- # This function is no longer used with direct CSV modification approach
303
- # Keeping for backward compatibility but it's not called anywhere
304
- get_next_id_number() {
305
- "$PYTHON_CMD" -c "
306
- import csv
307
- import re
308
- max_id = 0
309
- pattern = re.compile(r'^gen$CURRENT_GENERATION-(\d+)$')
310
- with open('$FULL_CSV_PATH', 'r') as f:
311
- reader = csv.reader(f)
312
- next(reader, None) # Skip header
313
- for row in reader:
314
- if row and len(row) > 0:
315
- match = pattern.match(row[0].strip())
316
- if match:
317
- max_id = max(max_id, int(match.group(1)))
318
- print(max_id + 1)
319
- "
320
- }
321
-
322
- # AIDEV-NOTE: This function had a critical race condition bug that caused wrong rows to be updated
323
- # The bug occurred when parallel processes modified the main CSV between temp CSV creation and append.
324
- # FIX: Now requires original_main_csv_lines parameter (6th arg) to track the exact line count at copy time.
325
- # This ensures we always append the correct new rows from temp CSV, regardless of concurrent modifications.
326
- # Without this fix, the system would update wrong IDs (e.g., claim to add gen81 but update gen80 instead).
327
- #
328
- # Validate that AI directly modified the CSV file
329
- validate_direct_csv_modification() {
330
- local temp_csv="$1"
331
- local expected_count="$2"
332
- local idea_type="$3"
333
- local ai_model="${4:-}" # AI model that generated the ideas
334
- local expected_ids="${5:-}" # Optional: comma or space separated list of expected IDs
335
- local original_main_csv_lines="${6:-}" # CRITICAL: Line count of main CSV when temp CSV was created
336
-
337
- # Check if the file was actually modified
338
- if [[ ! -f "$temp_csv" ]]; then
339
- echo "[ERROR] CSV file was not found after AI modification" >&2
340
- return 1
341
- fi
342
-
343
- # Count data rows in the modified temp CSV
344
- local new_count
345
- new_count=$(grep -v '^[[:space:]]*$' "$temp_csv" | tail -n +2 | wc -l | tr -d '[:space:]')
346
-
347
- # If original line count wasn't provided, fall back to current main CSV count (old behavior)
348
- # This preserves backward compatibility but may have race conditions
349
- if [[ -z "$original_main_csv_lines" ]]; then
350
- echo "[WARN] No original line count provided - using current main CSV count (may cause race conditions)" >&2
351
- original_main_csv_lines=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
352
- fi
353
-
354
- # Calculate how many data rows the temp CSV started with (before stubs were added)
355
- # This should match the original main CSV line count (including header)
356
- local original_data_rows=$((original_main_csv_lines - 1)) # Subtract header
357
-
358
- # Calculate how many rows were actually added to temp CSV
359
- local added_count=$((new_count - original_data_rows))
360
-
361
- # Check if AI overwrote the file instead of appending
362
- if [[ $new_count -lt $original_data_rows ]]; then
363
- echo "[ERROR] AI overwrote the CSV file instead of appending ($new_count < $original_data_rows)" >&2
364
- head -10 "$temp_csv" >&2
365
- return 1
366
- fi
367
-
368
- # Check if no changes were made
369
- if [[ $new_count -eq $original_data_rows ]]; then
370
- echo "[ERROR] CSV file wasn't modified - same number of data rows ($new_count = $original_data_rows)" >&2
371
- head -10 "$temp_csv" >&2
372
- return 1
373
- fi
374
- if [[ $added_count -ne $expected_count ]]; then
375
- echo "[ERROR] Expected to add $expected_count ideas but only added $added_count" >&2
376
- echo "[ERROR] Ideation failed - rejecting partial results to prevent endless loops" >&2
377
- rm -f "$temp_csv"
378
- return 1
379
- fi
380
-
381
- # If expected IDs were provided, validate that the AI used exactly those IDs
382
- if [[ -n "$expected_ids" ]]; then
383
- # Get the IDs that were actually added (last N rows of temp CSV)
384
- local actual_ids
385
- actual_ids=$(tail -n $added_count "$temp_csv" | cut -d',' -f1 | tr -d '"' | tr '\n' ' ' | xargs)
386
-
387
- # Normalize expected_ids (convert commas to spaces, trim)
388
- local expected_ids_normalized
389
- expected_ids_normalized=$(echo "$expected_ids" | tr ',' ' ' | xargs)
390
-
391
- # Compare
392
- if [[ "$actual_ids" != "$expected_ids_normalized" ]]; then
393
- echo "[ERROR] AI used wrong IDs!" >&2
394
- echo "[ERROR] Expected: $expected_ids_normalized" >&2
395
- echo "[ERROR] Actually used: $actual_ids" >&2
396
- echo "[ERROR] Rejecting this attempt - AI must use the exact IDs specified" >&2
397
- rm -f "$temp_csv"
398
- return 1
399
- fi
400
-
401
- echo "[INFO] ✓ AI correctly used the specified IDs: $actual_ids" >&2
402
- fi
403
-
404
- # Use proper locking to safely update the CSV
405
- echo "[INFO] Acquiring CSV lock to apply changes..."
406
-
407
- # Set the lockfile path
408
- CSV_LOCKFILE="$FULL_EVOLUTION_DIR/.evolution.csv.lock"
409
-
410
- if ! acquire_csv_lock; then
411
- echo "[ERROR] Failed to acquire CSV lock for update" >&2
412
- rm -f "$temp_csv"
413
- return 1
414
- fi
415
-
416
- # CRITICAL FIX: Use the original line count (when temp CSV was created) to determine which lines to append
417
- # This prevents race conditions where other processes modify the main CSV between temp CSV creation and append
418
- # Append only the NEW lines from temp CSV (those added after the original content)
419
- echo "[DEBUG] Appending last $added_count rows from temp CSV (from line $((original_main_csv_lines + 1)) onwards)" >&2
420
- tail -n +$((original_main_csv_lines + 1)) "$temp_csv" >> "$FULL_CSV_PATH"
421
-
422
- # Get the IDs that were actually added by reading them from temp CSV (not main CSV)
423
- # This avoids race conditions where other processes add rows to main CSV
424
- local new_ids
425
- new_ids=$(tail -n $added_count "$temp_csv" | grep -v "^id," | cut -d',' -f1 | tr -d '"')
426
- echo "[DEBUG] IDs being added: $new_ids" >&2
427
-
428
- # Clean up temp file
429
- rm -f "$temp_csv"
430
-
431
- # Update idea-LLM field for newly added rows if model is known
432
- if [[ -n "$ai_model" ]]; then
433
- echo "[INFO] Recording that $ai_model generated the ideas" >&2
434
-
435
- # Update each new row with the model that generated it
436
- for id in $new_ids; do
437
- if [[ -n "$id" && "$id" != "id" ]]; then
438
- echo "[DEBUG] Updating field for $id" >&2
439
- "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" field "$id" "idea-LLM" "$ai_model" || echo "[WARN] Failed to update $id" >&2
440
- fi
441
- done
442
- fi
443
-
444
- # Release the lock
445
- release_csv_lock
446
-
447
- echo "[INFO] Successfully added $added_count $idea_type ideas to CSV"
448
- return 0
449
- }
450
-
451
- # DEPRECATED: Old validation function for CSV output approach
452
- validate_and_apply_csv_modification_old() {
453
- local modified_csv="$1"
454
- local temp_csv="$2"
455
- local expected_count="$3"
456
- local idea_type="$4"
457
-
458
- # Check if the response looks like an error message (but not if it's just CSV data containing these words)
459
- if echo "$modified_csv" | head -1 | grep -q "id,basedOnId,description,performance,status"; then
460
- : # This looks like a CSV file, not an error message - continue
461
- elif echo "$modified_csv" | grep -qi "error\|failed\|limit\|exceeded\|sorry\|cannot\|unable"; then
462
- echo "[ERROR] AI failed to modify CSV and returned an error message:" >&2
463
- echo "$modified_csv" | head -200 >&2
464
- return 1
465
- fi
466
-
467
- # Check if response is too short to be a valid CSV
468
- if [[ ${#modified_csv} -lt 50 ]]; then
469
- echo "[ERROR] AI response is too short to be a valid CSV (${#modified_csv} chars):" >&2
470
- echo "$modified_csv" >&2
471
- return 1
472
- fi
473
-
474
- # Extract CSV from AI output (in case there's extra text before it)
475
- local csv_start_line
476
- csv_start_line=$(echo "$modified_csv" | grep -n "id,basedOnId,description,performance,status" | head -1 | cut -d: -f1)
477
-
478
- if [[ -n "$csv_start_line" ]]; then
479
- # Extract CSV starting from the header line
480
- modified_csv=$(echo "$modified_csv" | tail -n +$csv_start_line)
481
- elif ! echo "$modified_csv" | head -1 | grep -q "id,basedOnId,description,performance,status"; then
482
- echo "[ERROR] AI failed to return a valid CSV file. Expected CSV with header, but got:" >&2
483
- echo "$modified_csv" | head -c 500 >&2
484
- echo "" >&2
485
- return 1
486
- fi
487
-
488
- # Write the modified CSV to temp file
489
- echo "$modified_csv" > "$temp_csv"
490
-
491
- # Validate the modified CSV has more entries than original
492
- local original_count
493
- original_count=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
494
- local new_count
495
- new_count=$(wc -l < "$temp_csv" | tr -d '[:space:]')
496
-
497
-
498
- if [[ $new_count -le $original_count ]]; then
499
- echo "[ERROR] Modified CSV doesn't have more entries ($new_count <= $original_count)" >&2
500
- head -10 "$temp_csv" >&2
501
- return 1
502
- fi
503
-
504
- local added_count=$((new_count - original_count))
505
- if [[ $added_count -ne $expected_count ]]; then
506
- echo "[WARN] Expected to add $expected_count ideas but added $added_count" >&2
507
- fi
508
-
509
- # Use proper locking to safely update the CSV
510
- echo "[INFO] Acquiring CSV lock to apply changes..."
511
-
512
- # Set the lockfile path
513
- CSV_LOCKFILE="$FULL_EVOLUTION_DIR/.evolution.csv.lock"
514
-
515
- if ! acquire_csv_lock; then
516
- echo "[ERROR] Failed to acquire CSV lock for update" >&2
517
- rm -f "$temp_csv"
518
- return 1
519
- fi
520
-
521
- # Get just the new entries (skip header and existing entries)
522
- local original_line_count=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
523
-
524
- # Append only the new lines from temp CSV to the main CSV
525
- tail -n +$((original_line_count + 1)) "$temp_csv" >> "$FULL_CSV_PATH"
526
-
527
- # Clean up temp file
528
- rm -f "$temp_csv"
529
-
530
- # Update idea-LLM field for newly added rows if model is known
531
- if [[ -n "$ai_model" ]]; then
532
- echo "[INFO] Recording that $ai_model generated the ideas" >&2
533
- # Get the IDs of the newly added rows (skip header line and strip quotes)
534
- local new_ids
535
- new_ids=$(tail -n $added_count "$FULL_CSV_PATH" | grep -v "^id," | cut -d',' -f1 | tr -d '"')
536
-
537
- # Update each new row with the model that generated it
538
- for id in $new_ids; do
539
- if [[ -n "$id" && "$id" != "id" ]]; then
540
- "$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" field "$id" "idea-LLM" "$ai_model" || echo "[WARN] Failed to update $id" >&2
541
- fi
542
- done
543
- fi
544
-
545
- # Release the lock
546
- release_csv_lock
547
-
548
- echo "[INFO] Successfully added $added_count $idea_type ideas to CSV"
549
- return 0
550
- }
551
-
552
- # DEPRECATED: Old two-step process function - kept for reference
553
- process_ai_ideas_direct_old() {
554
- local count="$1"
555
- local idea_type="$2" # novel, hill-climbing, structural, crossover
556
- local top_performers="${3:-}" # Optional, for non-novel ideas
557
- local ai_output="$4" # The AI's response with ideas
558
-
559
- # Create temporary CSV copy in evolution directory (so AI can access it)
560
- local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
561
- cp "$FULL_CSV_PATH" "$temp_csv"
562
-
563
-
564
- # Build prompt for AI to directly modify the CSV
565
- local csv_prompt="I need you to add exactly $count new $idea_type ideas to this CSV file.
566
-
567
- Here are the $count $idea_type ideas to add:
568
- $ai_output
569
-
570
- Current CSV contents:
571
- $(cat "$temp_csv")
572
-
573
- Instructions:
574
- 1. Add exactly $count new rows to the CSV
575
- 2. Use the next available generation numbers (gen$CURRENT_GENERATION-XXX format)
576
- 3. For each idea, create a row with: id,parent_id,description,,pending
577
- 4. CRITICAL CSV FORMATTING RULES:
578
- - ALWAYS wrap the description field in double quotes
579
- - If the description contains quotes, escape them by doubling them (\" becomes \"\")
580
- - Example: gen01-001,gen00-000,\"Implement adaptive RSI thresholds\",,pending
581
- - BAD: gen01-001,gen00-000,Implement adaptive RSI thresholds,,pending
582
- - NEVER omit quotes - unquoted descriptions cause CSV corruption
583
- 5. For novel ideas: leave parent_id empty
584
- 6. For other idea types: use appropriate parent IDs from these top performers:
585
- $top_performers
586
-
587
- IMPORTANT: Output the complete modified CSV file. Do not add any explanation or other text - just output the CSV."
588
-
589
- echo "[INFO] Having AI directly modify CSV with $count $idea_type ideas..."
590
-
591
- # Get AI to modify the CSV with fallbacks
592
- local modified_csv
593
- local stderr_file="$FULL_EVOLUTION_DIR/stderr-$$.txt"
594
- if ! modified_csv=$(call_ai_for_ideation "$csv_prompt" "$CURRENT_GENERATION" 2>"$stderr_file"); then
595
- echo "[ERROR] AI model failed to modify CSV" >&2
596
- cat "$stderr_file" >&2
597
- rm -f "$temp_csv" "$stderr_file"
598
- return 1
599
- fi
600
- rm -f "$stderr_file"
601
-
602
- # Check if the response looks like an error message
603
- if echo "$modified_csv" | grep -qi "error\|failed\|limit\|exceeded\|sorry\|cannot\|unable"; then
604
- echo "[ERROR] AI failed to modify CSV and returned an error message:" >&2
605
- echo "$modified_csv" | head -200 >&2
606
- rm -f "$temp_csv"
607
- return 1
608
- fi
609
-
610
- # Check if response is too short to be a valid CSV
611
- if [[ ${#modified_csv} -lt 50 ]]; then
612
- echo "[ERROR] AI response is too short to be a valid CSV (${#modified_csv} chars):" >&2
613
- echo "$modified_csv" >&2
614
- rm -f "$temp_csv"
615
- return 1
616
- fi
617
-
618
- # Extract CSV from AI output (in case there's extra text before it)
619
- local csv_start_line
620
- csv_start_line=$(echo "$modified_csv" | grep -n "id,basedOnId,description,performance,status" | head -1 | cut -d: -f1)
621
-
622
- if [[ -n "$csv_start_line" ]]; then
623
- # Extract CSV starting from the header line
624
- modified_csv=$(echo "$modified_csv" | tail -n +$csv_start_line)
625
- elif ! echo "$modified_csv" | head -1 | grep -q "id,basedOnId,description,performance,status"; then
626
- echo "[ERROR] AI failed to return a valid CSV file. Expected CSV with header, but got:" >&2
627
- echo "$modified_csv" | head -c 500 >&2
628
- echo "" >&2
629
- rm -f "$temp_csv"
630
- return 1
631
- fi
632
-
633
- # Write the modified CSV to temp file
634
- echo "$modified_csv" > "$temp_csv"
635
-
636
- # Debug: Show the AI's CSV modification attempt
637
- echo "$modified_csv" | head -c 300 >&2
638
- echo "" >&2
639
- echo "$modified_csv" | tail -c 300 >&2
640
- echo "" >&2
641
-
642
- # Validate the modified CSV has more entries than original
643
- local original_count
644
- original_count=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
645
- local new_count
646
- new_count=$(wc -l < "$temp_csv" | tr -d '[:space:]')
647
-
648
-
649
- if [[ $new_count -le $original_count ]]; then
650
- echo "[ERROR] Modified CSV doesn't have more entries ($new_count <= $original_count)" >&2
651
- cat "$temp_csv" | head -10 >&2
652
- cat "$FULL_CSV_PATH" | head -10 >&2
653
- rm -f "$temp_csv"
654
- return 1
655
- fi
656
-
657
- local added_count=$((new_count - original_count))
658
- if [[ $added_count -ne $count ]]; then
659
- echo "[WARN] Expected to add $count ideas but added $added_count" >&2
660
- fi
661
-
662
- # Use proper locking to safely update the CSV
663
- echo "[INFO] Acquiring CSV lock to apply changes..."
664
-
665
- # Set the lockfile path
666
- CSV_LOCKFILE="$FULL_EVOLUTION_DIR/.evolution.csv.lock"
667
-
668
- if ! acquire_csv_lock; then
669
- echo "[ERROR] Failed to acquire CSV lock for update" >&2
670
- rm -f "$temp_csv"
671
- return 1
672
- fi
673
-
674
- # Get just the new entries (skip header and existing entries)
675
- local original_line_count=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
676
-
677
- # Append only the new lines from temp CSV to the main CSV
678
- tail -n +$((original_line_count + 1)) "$temp_csv" >> "$FULL_CSV_PATH"
679
-
680
- # Clean up temp file
681
- rm -f "$temp_csv"
682
-
683
- # Release the lock
684
- release_csv_lock
685
-
686
- echo "[INFO] Successfully added $added_count $idea_type ideas to CSV"
687
-
688
- return 0
689
- }
690
-
691
- # Get list of existing Python files for a generation
692
- get_existing_py_files_for_generation() {
693
- local generation="$1"
694
- local py_files=""
695
-
696
- # List all Python files for this generation
697
- for py_file in "$FULL_OUTPUT_DIR"/evolution_gen${generation}-*.py; do
698
- if [[ -f "$py_file" ]]; then
699
- local basename=$(basename "$py_file" .py)
700
- local id="${basename#evolution_}"
701
- if [[ -n "$py_files" ]]; then
702
- py_files="$py_files, $id"
703
- else
704
- py_files="$id"
705
- fi
706
- fi
707
- done
708
-
709
- echo "$py_files"
710
- }
711
-
712
- # Add existing Python files warning to prompt
713
- add_existing_files_warning() {
714
- local prompt="$1"
715
- local generation="$2"
716
- local existing_py_files=$(get_existing_py_files_for_generation "$generation")
717
-
718
- if [[ -n "$existing_py_files" ]]; then
719
- prompt+="
720
-
721
- WARNING: The following IDs already have Python files and MUST NOT be reused: $existing_py_files
722
- Skip these IDs when assigning new IDs (e.g., if gen$generation-001 and gen$generation-002 exist as Python files, start with gen$generation-003)"
723
- fi
724
-
725
- echo "$prompt"
726
- }
727
-
728
- # Get next available ID for current generation
729
- get_next_id() {
730
- local generation="$1"
731
- if [[ ! -f "$FULL_CSV_PATH" ]]; then
732
- echo "gen${generation}-001"
733
- return
734
- fi
735
-
736
- # Use Python for proper CSV parsing
737
- local max_id
738
- max_id=$("$PYTHON_CMD" -c "
739
- import csv
740
- import re
741
- max_id = 0
742
- pattern = re.compile(r'^gen${generation}-(\d+)$')
743
- with open('$FULL_CSV_PATH', 'r') as f:
744
- reader = csv.reader(f)
745
- next(reader, None) # Skip header
746
- for row in reader:
747
- if row and len(row) > 0:
748
- id_field = row[0].strip()
749
- match = pattern.match(id_field)
750
- if match:
751
- id_num = int(match.group(1))
752
- max_id = max(max_id, id_num)
753
- print(max_id)
754
- ")
755
-
756
- # Format next ID with generation and 3-digit number
757
- printf "gen%s-%03d" "$generation" $((max_id + 1))
758
- }
759
-
760
- # Get the next N available IDs for current generation as a comma-separated list
761
- get_next_ids() {
762
- local generation="$1"
763
- local count="$2"
764
-
765
- # Get the starting ID number
766
- local start_id
767
- if [[ ! -f "$FULL_CSV_PATH" ]]; then
768
- start_id=1
769
- else
770
- # Use Python for proper CSV parsing
771
- start_id=$("$PYTHON_CMD" -c "
772
- import csv
773
- import re
774
- max_id = 0
775
- pattern = re.compile(r'^gen${generation}-(\d+)$')
776
- with open('$FULL_CSV_PATH', 'r') as f:
777
- reader = csv.reader(f)
778
- next(reader, None) # Skip header
779
- for row in reader:
780
- if row and len(row) > 0:
781
- id_field = row[0].strip()
782
- match = pattern.match(id_field)
783
- if match:
784
- id_num = int(match.group(1))
785
- max_id = max(max_id, id_num)
786
- print(max_id + 1)
787
- ")
788
- fi
789
-
790
- # Generate the list of IDs
791
- local ids=()
792
- for ((i=0; i<count; i++)); do
793
- local id_num=$((start_id + i))
794
- ids+=("$(printf "gen%s-%03d" "$generation" "$id_num")")
795
- done
796
-
797
- # Join with commas
798
- local IFS=','
799
- echo "${ids[*]}"
800
- }
801
-
802
-
803
- # Get top performers for parent selection (absolute + top novel candidates)
804
- get_top_performers() {
805
- local num_requested="$1"
806
- if [[ ! -f "$FULL_CSV_PATH" ]]; then
807
- echo ""
808
- return
809
- fi
810
-
811
- # Use Python to properly parse CSV and find top performers + top novel candidates
812
- "$PYTHON_CMD" -c "
813
- import csv
1
+ #!/usr/bin/env python3
2
+ """Ideation for claude-evolve - generates new algorithm candidates."""
814
3
  import sys
4
+ import os
815
5
 
816
- with open('$FULL_CSV_PATH', 'r') as f:
817
- reader = csv.reader(f)
818
- next(reader) # Skip header
819
-
820
- completed = []
821
- novel = []
822
-
823
- # Collect all completed candidates
824
- for row in reader:
825
- if len(row) >= 5 and row[3] and row[4] == 'complete':
826
- try:
827
- candidate_id = row[0]
828
- parent_id = row[1] if len(row) > 1 else ''
829
- description = row[2] if len(row) > 2 else ''
830
- score = float(row[3])
831
-
832
- completed.append((candidate_id, description, score))
833
-
834
- # Track novel candidates separately
835
- if not parent_id:
836
- novel.append((candidate_id, description, score))
837
-
838
- except ValueError:
839
- pass
840
-
841
- # Sort absolute leaders by score (descending)
842
- completed.sort(key=lambda x: x[2], reverse=True)
843
-
844
- # Sort novel candidates by score (descending)
845
- novel.sort(key=lambda x: x[2], reverse=True)
846
-
847
- # Collect top performers
848
- selected_ids = set()
849
- results = []
850
-
851
- # Add top absolute performers
852
- for i, (candidate_id, description, score) in enumerate(completed[:$num_requested]):
853
- results.append(f'{candidate_id},{description},{score}')
854
- selected_ids.add(candidate_id)
855
-
856
- # Add top novel candidates (if not already selected)
857
- novel_count = 0
858
- for candidate_id, description, score in novel:
859
- if candidate_id not in selected_ids and novel_count < $NUM_REVOLUTION:
860
- results.append(f'{candidate_id},{description},{score}')
861
- selected_ids.add(candidate_id)
862
- novel_count += 1
863
-
864
- # Output all selected candidates
865
- for result in results:
866
- print(result)
867
- "
868
- }
869
-
870
-
871
-
872
- # Generate ideas using AI with multi-strategy approach
873
- ideate_ai_strategies() {
874
- if [[ ! -f "$FULL_BRIEF_PATH" ]]; then
875
- echo "[ERROR] $BRIEF_FILE not found. Run 'claude-evolve setup' first." >&2
876
- exit 1
877
- fi
878
-
879
- # Baseline should already be evaluated by run command
880
-
881
- # Get top performers (now includes top novel candidates)
882
- local top_performers
883
- top_performers=$(get_top_performers "$NUM_ELITES")
884
-
885
- if [[ -z $top_performers ]]; then
886
- echo "[INFO] No completed algorithms found, will use baseline algorithm for hill climbing"
887
- # For hill climbing and mutations, use the baseline algorithm
888
- # Use a special ID that validation script will recognize
889
- top_performers="000,Baseline Algorithm (algorithm.py),0.0"
890
- fi
891
-
892
- echo "[INFO] Generating $TOTAL_IDEAS ideas using multi-strategy approach:"
893
- echo " Novel exploration: $NOVEL_EXPLORATION"
894
- echo " Hill climbing: $HILL_CLIMBING"
895
- echo " Structural mutation: $STRUCTURAL_MUTATION"
896
- echo " Crossover hybrid: $CROSSOVER_HYBRID"
897
-
898
- # Generate each type of idea by having Claude directly edit the CSV
899
- # Track successes - continue even if some strategies fail
900
- local strategies_attempted=0
901
- local strategies_succeeded=0
902
-
903
- # Helper to check if we've reached the target for this generation
904
- check_generation_complete() {
905
- local current_count
906
- current_count=$(grep -c "^gen${CURRENT_GENERATION}-" "$FULL_CSV_PATH" 2>/dev/null) || true
907
- current_count=${current_count:-0}
908
- if [[ $current_count -ge $TOTAL_IDEAS ]]; then
909
- echo "[INFO] Generation $CURRENT_GENERATION reached target ($current_count >= $TOTAL_IDEAS), stopping early" >&2
910
- return 0
911
- fi
912
- return 1
913
- }
914
-
915
- if [[ ${NOVEL_EXPLORATION:-0} -gt 0 ]]; then
916
- ((strategies_attempted++))
917
- if generate_novel_ideas_direct "$NOVEL_EXPLORATION"; then
918
- ((strategies_succeeded++))
919
- check_generation_complete && return 0
920
- else
921
- echo "[WARN] Novel exploration strategy failed, continuing with other strategies" >&2
922
- fi
923
- fi
924
-
925
- if [[ ${HILL_CLIMBING:-0} -gt 0 ]]; then
926
- ((strategies_attempted++))
927
- if generate_hill_climbing_direct "$HILL_CLIMBING" "$top_performers"; then
928
- ((strategies_succeeded++))
929
- check_generation_complete && return 0
930
- else
931
- echo "[WARN] Hill climbing strategy failed, continuing with other strategies" >&2
932
- fi
933
- fi
934
-
935
- if [[ ${STRUCTURAL_MUTATION:-0} -gt 0 ]]; then
936
- ((strategies_attempted++))
937
- if generate_structural_mutation_direct "$STRUCTURAL_MUTATION" "$top_performers"; then
938
- ((strategies_succeeded++))
939
- check_generation_complete && return 0
940
- else
941
- echo "[WARN] Structural mutation strategy failed, continuing with other strategies" >&2
942
- fi
943
- fi
944
-
945
- if [[ ${CROSSOVER_HYBRID:-0} -gt 0 ]]; then
946
- ((strategies_attempted++))
947
- if generate_crossover_direct "$CROSSOVER_HYBRID" "$top_performers"; then
948
- ((strategies_succeeded++))
949
- check_generation_complete && return 0
950
- else
951
- echo "[WARN] Crossover strategy failed, continuing with other strategies" >&2
952
- fi
953
- fi
954
-
955
- echo "[INFO] Strategy results: $strategies_succeeded/$strategies_attempted succeeded" >&2
956
-
957
- # Accept partial results - if ANY strategy succeeded, that's enough
958
- # The workers will process what we have, and next ideation run can add more
959
- # AIDEV-NOTE: Previously required ALL strategies to succeed, which caused
960
- # endless ideation loops because CSV writes weren't rolled back on "rejection"
961
- if [[ ${strategies_succeeded:-0} -gt 0 ]]; then
962
- if [[ $strategies_succeeded -lt $strategies_attempted ]]; then
963
- echo "[INFO] Partial success: $strategies_succeeded/$strategies_attempted strategies completed" >&2
964
- echo "[INFO] Workers will process existing ideas, next ideation can add more" >&2
965
- fi
966
- return 0
967
- else
968
- echo "[ERROR] All ideation strategies failed (0/$strategies_attempted)" >&2
969
- return 1
970
- fi
971
- }
972
-
973
- # Generate novel exploration ideas using direct CSV modification
974
- generate_novel_ideas_direct() {
975
- local count="$1"
976
-
977
- # Get the next available ID BEFORE creating temp CSV
978
- # This ensures each strategy gets unique IDs even in parallel runs
979
- local next_id
980
- next_id=$(get_next_id "$CURRENT_GENERATION")
981
- echo "[INFO] Next available ID for novel ideas: $next_id" >&2
982
-
983
- # Generate the list of IDs this strategy should use
984
- local next_id_num
985
- next_id_num=$(echo "$next_id" | grep -o '[0-9]*$')
986
- # Strip leading zeros to avoid octal interpretation in arithmetic
987
- next_id_num=$((10#$next_id_num))
988
- local required_ids=()
989
- for ((i=0; i<count; i++)); do
990
- required_ids+=("$(printf "gen%s-%03d" "$CURRENT_GENERATION" $((next_id_num + i)))")
991
- done
992
- local required_ids_str="${required_ids[*]}"
993
-
994
- # Create temporary CSV copy in evolution directory (so AI can access it)
995
- local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
996
- cp "$FULL_CSV_PATH" "$temp_csv"
997
-
998
- # CRITICAL: Capture the original line count immediately after copying
999
- # This is needed to correctly append rows later, preventing race conditions
1000
- local original_csv_lines
1001
- original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
1002
- echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
1003
-
1004
- # Pre-populate the CSV with stub rows containing the correct IDs
1005
- # This ensures the AI can't possibly use wrong IDs - it just fills in descriptions
1006
- echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
1007
- for id in "${required_ids[@]}"; do
1008
- echo "$id,,\"[PLACEHOLDER: Replace this with your algorithmic idea]\",,pending" >> "$temp_csv"
1009
- done
1010
-
1011
- echo "[INFO] Generating $count novel exploration ideas with IDs: $required_ids_str"
1012
-
1013
- # Count total lines in temp CSV (including header)
1014
- local total_lines
1015
- total_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
1016
- local read_offset=$((total_lines - 25))
1017
- if [[ $read_offset -lt 1 ]]; then
1018
- read_offset=1
1019
- fi
1020
-
1021
- # Use relative paths and change to evolution directory so AI can access files
1022
- local temp_csv_basename=$(basename "$temp_csv")
1023
-
1024
- # Get existing Python files for this generation to avoid ID collisions
1025
- local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
1026
-
1027
- local prompt="$(get_git_protection_warning)
1028
-
1029
- I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: $temp_csv_basename
1030
-
1031
- THE FILE HAS $total_lines TOTAL LINES. Read from line $read_offset to see the placeholder rows at the end.
1032
-
1033
- Current evolution context:
1034
- - Generation: $CURRENT_GENERATION
1035
- - Algorithm: algorithm.py (base algorithm)
1036
- - Brief: $(head -5 "$FULL_BRIEF_PATH" 2>/dev/null | head -c 500 || echo "No brief file found")
1037
-
1038
- IMPORTANT: DO NOT read algorithm.py or any evolution_*.py files. Focus on creative ideation based on the brief and CSV context only. Reading code files wastes tokens and time.
1039
-
1040
- CROSS-POLLINATION OPPORTUNITY:
1041
- Consider looking at the last 20-30 ideas from other evolution files in sibling directories (../*/evolution.csv).
1042
- These may contain different algorithmic approaches that could inspire novel combinations or variations.
1043
- This is OPTIONAL - only do it if you think it would help generate more diverse ideas.
1044
-
1045
- CRITICAL TASK:
1046
- The CSV file already contains $count stub rows with these IDs: $required_ids_str
1047
- Each stub row has a PLACEHOLDER description like: \"[PLACEHOLDER: Replace this with your algorithmic idea]\"
1048
- Your job is to REPLACE each PLACEHOLDER with a real algorithmic idea description.
1049
-
1050
- ⚠️ CRITICAL FILE READING INSTRUCTIONS ⚠️
1051
- THE CSV FILE IS VERY LARGE (OVER 100,000 TOKENS). YOU WILL RUN OUT OF CONTEXT IF YOU READ IT ALL!
1052
- - DO NOT read the entire file or you will exceed context limits and CRASH
1053
- - Use: Read(file_path='$temp_csv_basename', offset=$read_offset, limit=25)
1054
- - This will read ONLY the last 25 lines where the placeholders are
1055
- - DO NOT READ FROM OFFSET 0 - that will load the entire huge file and fail!
1056
-
1057
- CRITICAL INSTRUCTIONS:
1058
- 1. Read ONLY the last 20-30 lines of the CSV to see the placeholder rows
1059
- 2. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
1060
- 3. DO NOT CHANGE THE IDs - they are already correct ($required_ids_str)
1061
- 4. Use the Edit tool to replace EACH PLACEHOLDER text with a real algorithmic idea
1062
- 5. When editing, preserve the CSV structure: keep the ID and parent_id fields unchanged"
1063
-
1064
- if [[ -n "$existing_py_files" ]]; then
1065
- prompt+="
1066
- 6. IMPORTANT: The following IDs already have Python files: $existing_py_files
1067
- (This is informational only - use the IDs specified above)"
1068
- fi
1069
-
1070
- prompt+="
1071
- 7. CRITICAL CSV FORMATTING RULES:
1072
- - ALWAYS wrap the description field in double quotes
1073
- - If the description contains quotes, escape them by doubling them (\" becomes \"\")
1074
- - Example: gen01-001,,\"Implement adaptive RSI thresholds based on volatility\",,pending
1075
- - BAD: gen01-001,,Implement adaptive RSI thresholds based on volatility,,pending
1076
- - NEVER omit quotes around descriptions - this causes CSV parsing errors
1077
- 9. Each description should be one clear sentence describing a novel algorithmic approach
1078
- 10. Focus on creative, ambitious ideas that haven't been tried yet
1079
- 11. Consider machine learning, new indicators, regime detection, risk management, etc.
1080
-
1081
- IMPORTANT: You must APPEND new rows to the existing CSV file. DO NOT replace the file contents. All existing rows must remain unchanged.
1082
- CRITICAL: You must use your file editing tools (Edit/MultiEdit) to modify the CSV file. DO NOT return CSV text - use your tools to edit the file directly."
1083
-
1084
- # Debug prompt size and context (removed - no longer needed)
1085
-
1086
- # Change to evolution directory so AI can access files (with safe restoration on interrupt)
1087
- safe_pushd "$FULL_EVOLUTION_DIR" || {
1088
- echo "[ERROR] Failed to change to evolution directory" >&2
1089
- rm -f "$temp_csv"
1090
- return 1
1091
- }
1092
-
1093
- # Debug: Show files in evolution directory
1094
-
1095
- # Get AI to directly edit the CSV file
1096
- local ai_response
1097
- if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$count" "$temp_csv_basename"); then
1098
- echo "[ERROR] AI model failed to generate novel ideas" >&2
1099
- safe_popd
1100
- rm -f "$temp_csv"
1101
- return 1
1102
- fi
1103
-
1104
- # Restore working directory
1105
- safe_popd
1106
-
1107
- # Validate that the CSV file was actually modified with correct IDs
1108
- # Pass original_csv_lines to prevent race conditions
1109
- if ! validate_direct_csv_modification "$temp_csv" "$count" "novel" "$ai_response" "$required_ids_str" "$original_csv_lines"; then
1110
- rm -f "$temp_csv"
1111
- return 1
1112
- fi
1113
-
1114
- echo "[INFO] Novel exploration ideas generated successfully"
1115
- return 0
1116
- }
1117
-
1118
- # Generate hill climbing ideas using direct CSV modification
1119
- generate_hill_climbing_direct() {
1120
- local count="$1"
1121
- local top_performers="$2"
1122
-
1123
- # Get the next available ID BEFORE creating temp CSV
1124
- local next_id
1125
- next_id=$(get_next_id "$CURRENT_GENERATION")
1126
- echo "[INFO] Next available ID for hill climbing: $next_id" >&2
1127
-
1128
- # Generate the list of IDs this strategy should use
1129
- local next_id_num
1130
- next_id_num=$(echo "$next_id" | grep -o '[0-9]*$')
1131
- # Strip leading zeros to avoid octal interpretation in arithmetic
1132
- next_id_num=$((10#$next_id_num))
1133
- local required_ids=()
1134
- for ((i=0; i<count; i++)); do
1135
- required_ids+=("$(printf "gen%s-%03d" "$CURRENT_GENERATION" $((next_id_num + i)))")
1136
- done
1137
- local required_ids_str="${required_ids[*]}"
1138
-
1139
- # Create temporary CSV copy in evolution directory (so AI can access it)
1140
- local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
1141
- cp "$FULL_CSV_PATH" "$temp_csv"
1142
-
1143
- # CRITICAL: Capture the original line count immediately after copying
1144
- local original_csv_lines
1145
- original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
1146
- echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
1147
-
1148
- # Extract just the IDs from top performers for clarity (needed before pre-populating)
1149
- local valid_parent_ids
1150
- valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
1151
-
1152
- # Pre-populate the CSV with stub rows containing the correct IDs and parent IDs
1153
- echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
1154
- # Use first parent as default for stubs (AI will adjust if needed)
1155
- local first_parent_id
1156
- first_parent_id=$(echo "$valid_parent_ids" | cut -d',' -f1)
1157
- for id in "${required_ids[@]}"; do
1158
- echo "$id,$first_parent_id,\"[PLACEHOLDER: Replace with parameter tuning idea]\",,pending" >> "$temp_csv"
1159
- done
1160
-
1161
- echo "[INFO] Generating $count hill climbing ideas with IDs: $required_ids_str"
1162
-
1163
- # Count total lines in temp CSV (including header)
1164
- local total_lines
1165
- total_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
1166
- local read_offset=$((total_lines - 25))
1167
- if [[ $read_offset -lt 1 ]]; then
1168
- read_offset=1
1169
- fi
1170
-
1171
- # Get existing Python files for this generation to avoid ID collisions
1172
- local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
1173
-
1174
- # Use relative paths and change to evolution directory so AI can access files
1175
- local temp_csv_basename=$(basename "$temp_csv")
1176
-
1177
- # Build prompt using cat with heredoc to avoid variable expansion issues
1178
- local prompt
1179
- prompt="$(get_git_protection_warning)
1180
-
1181
- $(cat <<EOF
1182
- I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: $temp_csv_basename
1183
-
1184
- THE FILE HAS $total_lines TOTAL LINES. Read from line $read_offset to see the placeholder rows at the end.
1185
-
1186
- IMPORTANT: You MUST use one of these exact parent IDs: $valid_parent_ids
1187
-
1188
- Successful algorithms to tune:
1189
- $top_performers
1190
-
1191
- IMPORTANT: Generate parameter tuning ideas based primarily on the descriptions and scores above.
1192
- EOF
1193
- )"
1194
-
1195
- prompt+="
1196
-
1197
- CROSS-POLLINATION OPPORTUNITY:
1198
- Consider looking at the last 20-30 ideas from other evolution files in sibling directories (../*/evolution.csv).
1199
- These may contain interesting parameter tuning approaches or strategies that could be applied to your top performers.
1200
- This is OPTIONAL - only do it if you think it would help generate more diverse tuning ideas.
1201
-
1202
- ONLY read parent source files (evolution_<PARENT_ID>.py) if:
1203
- - The description is too vague to identify specific parameters
1204
- - You need to verify actual parameter names/values
1205
- - Reading is absolutely necessary for meaningful tuning
1206
-
1207
- If you must read source files:
1208
- - Read in small chunks (offset/limit) to minimize token usage
1209
- - Focus only on finding parameter definitions at the top of the file
1210
- - Do NOT read the entire implementation
1211
-
1212
- Most of the time, you can infer parameters from descriptions like 'RSI with threshold 30' or 'MA period 20'.
1213
-
1214
- CRITICAL TASK:
1215
- The CSV file already contains $count stub rows with these IDs: $required_ids_str
1216
- Each stub row has a PLACEHOLDER description like: \"[PLACEHOLDER: Replace with parameter tuning idea]\"
1217
- Your job is to REPLACE each PLACEHOLDER with a real parameter tuning idea.
1218
-
1219
- ⚠️ CRITICAL FILE READING INSTRUCTIONS ⚠️
1220
- THE CSV FILE IS VERY LARGE (OVER 100,000 TOKENS). YOU WILL RUN OUT OF CONTEXT IF YOU READ IT ALL!
1221
- - DO NOT read the entire file or you will exceed context limits and CRASH
1222
- - Use: Read(file_path='$temp_csv_basename', offset=$read_offset, limit=25)
1223
- - This will read ONLY the last 25 lines where the placeholders are
1224
- - DO NOT READ FROM OFFSET 0 - that will load the entire huge file and fail!
1225
-
1226
- CRITICAL INSTRUCTIONS:
1227
- 1. Read ONLY the last 25 lines using the offset specified above
1228
- 2. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
1229
- 3. DO NOT CHANGE THE IDs - they are already correct ($required_ids_str)
1230
- 4. Use the Edit tool to replace EACH PLACEHOLDER text with a parameter tuning idea
1231
- 5. When editing, preserve the CSV structure: keep the ID field unchanged
1232
- 6. You may change the parent_id field if needed to reference a different top performer
1233
- 7. Each description should focus on adjusting specific parameters - include current and new values
1234
- Example: \"Lower rsi_entry from 21 to 18\" or \"Increase MA period from 20 to 50\"
1235
- 8. CRITICAL: When editing, preserve the CSV formatting with proper quoting"
1236
-
1237
- # Change to evolution directory so AI can access files (with safe restoration on interrupt)
1238
- safe_pushd "$FULL_EVOLUTION_DIR" || {
1239
- echo "[ERROR] Failed to change to evolution directory" >&2
1240
- rm -f "$temp_csv"
1241
- return 1
1242
- }
1243
-
1244
- # Get AI to directly edit the CSV file
1245
- local ai_response
1246
- if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$count" "$temp_csv_basename"); then
1247
- echo "[ERROR] AI model failed to generate hill climbing ideas" >&2
1248
- safe_popd
1249
- rm -f "$temp_csv"
1250
- return 1
1251
- fi
1252
-
1253
- # Restore working directory
1254
- safe_popd
1255
-
1256
- # Validate that the CSV file was actually modified with correct IDs
1257
- # Pass original_csv_lines to prevent race conditions
1258
- if ! validate_direct_csv_modification "$temp_csv" "$count" "hill-climbing" "$ai_response" "$required_ids_str" "$original_csv_lines"; then
1259
- rm -f "$temp_csv"
1260
- return 1
1261
- fi
1262
-
1263
- echo "[INFO] Hill climbing ideas generated successfully"
1264
- return 0
1265
- }
1266
-
1267
- # Generate structural mutation ideas using direct CSV modification
1268
- generate_structural_mutation_direct() {
1269
- local count="$1"
1270
- local top_performers="$2"
1271
-
1272
- # Get the next available ID BEFORE creating temp CSV
1273
- local next_id
1274
- next_id=$(get_next_id "$CURRENT_GENERATION")
1275
- echo "[INFO] Next available ID for structural mutation: $next_id" >&2
1276
-
1277
- # Generate the list of IDs this strategy should use
1278
- local next_id_num
1279
- next_id_num=$(echo "$next_id" | grep -o '[0-9]*$')
1280
- # Strip leading zeros to avoid octal interpretation in arithmetic
1281
- next_id_num=$((10#$next_id_num))
1282
- local required_ids=()
1283
- for ((i=0; i<count; i++)); do
1284
- required_ids+=("$(printf "gen%s-%03d" "$CURRENT_GENERATION" $((next_id_num + i)))")
1285
- done
1286
- local required_ids_str="${required_ids[*]}"
1287
-
1288
- # Create temporary CSV copy in evolution directory (so AI can access it)
1289
- local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
1290
- cp "$FULL_CSV_PATH" "$temp_csv"
1291
-
1292
- # CRITICAL: Capture the original line count immediately after copying
1293
- local original_csv_lines
1294
- original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
1295
- echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
1296
-
1297
- # Extract just the IDs from top performers for clarity (needed before pre-populating)
1298
- local valid_parent_ids
1299
- valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
1300
-
1301
- # Pre-populate the CSV with stub rows
1302
- echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
1303
- local first_parent_id
1304
- first_parent_id=$(echo "$valid_parent_ids" | cut -d',' -f1)
1305
- for id in "${required_ids[@]}"; do
1306
- echo "$id,$first_parent_id,\"[PLACEHOLDER: Replace with structural modification idea]\",,pending" >> "$temp_csv"
1307
- done
1308
-
1309
- echo "[INFO] Generating $count structural mutation ideas with IDs: $required_ids_str"
1310
-
1311
- # Count total lines in temp CSV (including header)
1312
- local total_lines
1313
- total_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
1314
- local read_offset=$((total_lines - 25))
1315
- if [[ $read_offset -lt 1 ]]; then
1316
- read_offset=1
1317
- fi
1318
-
1319
- # Get existing Python files for this generation to avoid ID collisions
1320
- local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
1321
-
1322
- # Use relative paths and change to evolution directory so AI can access files
1323
- local temp_csv_basename=$(basename "$temp_csv")
1324
-
1325
- # Build prompt using cat with heredoc to avoid variable expansion issues
1326
- local prompt
1327
- prompt="$(get_git_protection_warning)
1328
-
1329
- $(cat <<EOF
1330
- I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: $temp_csv_basename
1331
-
1332
- THE FILE HAS $total_lines TOTAL LINES. Read from line $read_offset to see the placeholder rows at the end.
1333
-
1334
- IMPORTANT: You MUST use one of these exact parent IDs: $valid_parent_ids
1335
-
1336
- Successful algorithms to modify structurally:
1337
- $top_performers
1338
-
1339
- IMPORTANT: DO NOT read evolution_*.py files. Generate structural ideas based ONLY on:
1340
- - The algorithm descriptions above
1341
- - The performance scores
1342
- - Your knowledge of common algorithmic structures and patterns
1343
- Reading code files wastes tokens and time. Focus on high-level architectural ideas based on the descriptions.
1344
- EOF
1345
- )"
1346
-
1347
- prompt+="
1348
-
1349
- CROSS-POLLINATION OPPORTUNITY:
1350
- Consider looking at the last 20-30 ideas from other evolution files in sibling directories (../*/evolution.csv).
1351
- These may contain different structural approaches or architectural patterns that could be adapted to your top performers.
1352
- This is OPTIONAL - only do it if you think it would help generate more creative structural modifications.
1353
-
1354
- CRITICAL TASK:
1355
- The CSV file already contains $count stub rows with these IDs: $required_ids_str
1356
- Each stub row has a PLACEHOLDER description like: \"[PLACEHOLDER: Replace with structural modification idea]\"
1357
- Your job is to REPLACE each PLACEHOLDER with a real structural modification idea.
1358
-
1359
- ⚠️ CRITICAL FILE READING INSTRUCTIONS ⚠️
1360
- THE CSV FILE IS VERY LARGE (OVER 100,000 TOKENS). YOU WILL RUN OUT OF CONTEXT IF YOU READ IT ALL!
1361
- - DO NOT read the entire file or you will exceed context limits and CRASH
1362
- - Use: Read(file_path='$temp_csv_basename', offset=$read_offset, limit=25)
1363
- - This will read ONLY the last 25 lines where the placeholders are
1364
- - DO NOT READ FROM OFFSET 0 - that will load the entire huge file and fail!
1365
-
1366
- CRITICAL INSTRUCTIONS:
1367
- 1. Read ONLY the last 25 lines using the offset specified above
1368
- 2. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
1369
- 3. DO NOT CHANGE THE IDs - they are already correct ($required_ids_str)
1370
- 4. Use the Edit tool to replace EACH PLACEHOLDER text with a structural modification idea
1371
- 5. When editing, preserve the CSV structure: keep the ID field unchanged
1372
- 6. You may change the parent_id field if needed to reference a different top performer
1373
- 7. Each description should focus on architectural/structural changes
1374
- 8. CRITICAL: When editing, preserve the CSV formatting with proper quoting"
1375
-
1376
- # Change to evolution directory so AI can access files (with safe restoration on interrupt)
1377
- safe_pushd "$FULL_EVOLUTION_DIR" || {
1378
- echo "[ERROR] Failed to change to evolution directory" >&2
1379
- rm -f "$temp_csv"
1380
- return 1
1381
- }
1382
-
1383
- # Get AI to directly edit the CSV file
1384
- local ai_response
1385
- if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$count" "$temp_csv_basename"); then
1386
- echo "[ERROR] AI model failed to generate structural mutation ideas" >&2
1387
- safe_popd
1388
- rm -f "$temp_csv"
1389
- return 1
1390
- fi
1391
-
1392
- # Restore working directory
1393
- safe_popd
1394
-
1395
- # Validate that the CSV file was actually modified with correct IDs
1396
- # Pass original_csv_lines to prevent race conditions
1397
- if ! validate_direct_csv_modification "$temp_csv" "$count" "structural" "$ai_response" "$required_ids_str" "$original_csv_lines"; then
1398
- rm -f "$temp_csv"
1399
- return 1
1400
- fi
1401
-
1402
- echo "[INFO] Structural mutation ideas generated successfully"
1403
- return 0
1404
- }
1405
-
1406
- # Generate crossover hybrid ideas using direct CSV modification
1407
- generate_crossover_direct() {
1408
- local count="$1"
1409
- local top_performers="$2"
1410
-
1411
- # Get the next available ID BEFORE creating temp CSV
1412
- local next_id
1413
- next_id=$(get_next_id "$CURRENT_GENERATION")
1414
- echo "[INFO] Next available ID for crossover: $next_id" >&2
1415
-
1416
- # Generate the list of IDs this strategy should use
1417
- local next_id_num
1418
- next_id_num=$(echo "$next_id" | grep -o '[0-9]*$')
1419
- # Strip leading zeros to avoid octal interpretation in arithmetic
1420
- next_id_num=$((10#$next_id_num))
1421
- local required_ids=()
1422
- for ((i=0; i<count; i++)); do
1423
- required_ids+=("$(printf "gen%s-%03d" "$CURRENT_GENERATION" $((next_id_num + i)))")
1424
- done
1425
- local required_ids_str="${required_ids[*]}"
1426
-
1427
- # Create temporary CSV copy in evolution directory (so AI can access it)
1428
- local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
1429
- cp "$FULL_CSV_PATH" "$temp_csv"
1430
-
1431
- # CRITICAL: Capture the original line count immediately after copying
1432
- local original_csv_lines
1433
- original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
1434
- echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
1435
-
1436
- # Extract just the IDs from top performers for clarity (needed before pre-populating)
1437
- local valid_parent_ids
1438
- valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
1439
-
1440
- # Pre-populate the CSV with stub rows
1441
- echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
1442
- local first_parent_id
1443
- first_parent_id=$(echo "$valid_parent_ids" | cut -d',' -f1)
1444
- for id in "${required_ids[@]}"; do
1445
- echo "$id,$first_parent_id,\"[PLACEHOLDER: Replace with crossover hybrid idea]\",,pending" >> "$temp_csv"
1446
- done
1447
-
1448
- echo "[INFO] Generating $count crossover hybrid ideas with IDs: $required_ids_str"
1449
-
1450
- # Count total lines in temp CSV (including header)
1451
- local total_lines
1452
- total_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
1453
- local read_offset=$((total_lines - 25))
1454
- if [[ $read_offset -lt 1 ]]; then
1455
- read_offset=1
1456
- fi
1457
-
1458
- # Get existing Python files for this generation to avoid ID collisions
1459
- local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
1460
-
1461
- # Use relative paths and change to evolution directory so AI can access files
1462
- local temp_csv_basename=$(basename "$temp_csv")
1463
-
1464
- # Build prompt using cat with heredoc to avoid variable expansion issues
1465
- local prompt
1466
- prompt="$(get_git_protection_warning)
1467
-
1468
- $(cat <<EOF
1469
- I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: $temp_csv_basename
1470
-
1471
- THE FILE HAS $total_lines TOTAL LINES. Read from line $read_offset to see the placeholder rows at the end.
1472
-
1473
- IMPORTANT: You MUST use ONLY these exact parent IDs: $valid_parent_ids
1474
-
1475
- Top performers to combine (reference at least 2 in each idea):
1476
- $top_performers
1477
-
1478
- IMPORTANT: DO NOT read evolution_*.py files. Generate crossover ideas based ONLY on:
1479
- - The algorithm descriptions above (which describe their key features)
1480
- - The performance scores
1481
- - Your knowledge of how different algorithmic approaches can be combined
1482
- Reading code files wastes tokens and time. Focus on combining the described features creatively.
1483
- EOF
1484
- )"
1485
-
1486
- prompt+="
1487
-
1488
- CROSS-POLLINATION OPPORTUNITY:
1489
- Consider looking at the last 20-30 ideas from other evolution files in sibling directories (../*/evolution.csv).
1490
- These parallel evolutionary strains may have developed different successful features that could be crossed with your top performers.
1491
- This is OPTIONAL - only do it if you think it would help generate more innovative hybrid ideas.
1492
-
1493
- CRITICAL TASK:
1494
- The CSV file already contains $count stub rows with these IDs: $required_ids_str
1495
- Each stub row has a PLACEHOLDER description like: \"[PLACEHOLDER: Replace with crossover hybrid idea]\"
1496
- Your job is to REPLACE each PLACEHOLDER with a real crossover/hybrid idea that combines 2+ algorithms.
1497
-
1498
- ⚠️ CRITICAL FILE READING INSTRUCTIONS ⚠️
1499
- THE CSV FILE IS VERY LARGE (OVER 100,000 TOKENS). YOU WILL RUN OUT OF CONTEXT IF YOU READ IT ALL!
1500
- - DO NOT read the entire file or you will exceed context limits and CRASH
1501
- - Use: Read(file_path='$temp_csv_basename', offset=$read_offset, limit=25)
1502
- - This will read ONLY the last 25 lines where the placeholders are
1503
- - DO NOT READ FROM OFFSET 0 - that will load the entire huge file and fail!
1504
-
1505
- CRITICAL INSTRUCTIONS:
1506
- 1. Read ONLY the last 25 lines using the offset specified above
1507
- 2. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
1508
- 3. DO NOT CHANGE THE IDs - they are already correct ($required_ids_str)
1509
- 4. Use the Edit tool to replace EACH PLACEHOLDER text with a crossover idea
1510
- 5. When editing, preserve the CSV structure: keep the ID field unchanged
1511
- 6. You may change the parent_id field if needed (choose the primary parent)
1512
- 7. Each description should combine actual elements from 2+ top performers
1513
- 8. CRITICAL: When editing, preserve the CSV formatting with proper quoting"
1514
-
1515
- # Change to evolution directory so AI can access files (with safe restoration on interrupt)
1516
- safe_pushd "$FULL_EVOLUTION_DIR" || {
1517
- echo "[ERROR] Failed to change to evolution directory" >&2
1518
- rm -f "$temp_csv"
1519
- return 1
1520
- }
1521
-
1522
- # Get AI to directly edit the CSV file
1523
- local ai_response
1524
- if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$count" "$temp_csv_basename"); then
1525
- echo "[ERROR] AI model failed to generate crossover hybrid ideas" >&2
1526
- safe_popd
1527
- rm -f "$temp_csv"
1528
- return 1
1529
- fi
1530
-
1531
- # Restore working directory
1532
- safe_popd
1533
-
1534
- # Validate that the CSV file was actually modified with correct IDs
1535
- # Pass original_csv_lines to prevent race conditions
1536
- if ! validate_direct_csv_modification "$temp_csv" "$count" "crossover" "$ai_response" "$required_ids_str" "$original_csv_lines"; then
1537
- rm -f "$temp_csv"
1538
- return 1
1539
- fi
1540
-
1541
- echo "[INFO] Crossover hybrid ideas generated successfully"
1542
- return 0
1543
- }
1544
-
1545
- # Legacy AI generation mode (for backward compatibility)
1546
- ideate_ai_legacy() {
1547
- if [[ ! -f "$FULL_BRIEF_PATH" ]]; then
1548
- echo "[ERROR] $BRIEF_FILE not found. Run 'claude-evolve setup' first." >&2
1549
- exit 1
1550
- fi
1551
-
1552
- # Create temporary CSV copy in evolution directory (so AI can access it)
1553
- local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
1554
- cp "$FULL_CSV_PATH" "$temp_csv"
1555
-
1556
- # CRITICAL: Capture the original line count immediately after copying
1557
- local original_csv_lines
1558
- original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
1559
- echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
1560
-
1561
- echo "[INFO] Generating $TOTAL_IDEAS ideas (legacy mode)..."
1562
-
1563
- # Get top performers for context
1564
- local top_performers=""
1565
- if [[ -f "$FULL_CSV_PATH" ]]; then
1566
- # Simple top performers extraction (lines with non-empty performance)
1567
- top_performers=$(awk -F, 'NR > 1 && $4 != "" { print $1 ": " $3 " (score: " $4 ")" }' "$FULL_CSV_PATH" | head -5)
1568
- fi
1569
-
1570
- # Build prompt for direct CSV modification
1571
- # Use relative paths and change to evolution directory so AI can access files
1572
- local temp_csv_basename=$(basename "$temp_csv")
1573
-
1574
- # Build initial prompt safely
1575
- local prompt
1576
- prompt="$(get_git_protection_warning)
1577
-
1578
- $(cat <<EOF
1579
- I need you to use your file editing capabilities to add exactly $TOTAL_IDEAS algorithmic ideas to the CSV file: $temp_csv_basename
1580
-
1581
- Current evolution context:
1582
- - Generation: $CURRENT_GENERATION
1583
- - Algorithm: algorithm.py (base algorithm)
1584
- - Brief: $(head -10 "$FULL_BRIEF_PATH" 2>/dev/null | head -c 1000 || echo "No brief file found")
1585
-
1586
- IMPORTANT: DO NOT read algorithm.py or any evolution_*.py files - that uses too many tokens and is unnecessary for ideation. Just generate creative ideas based on the brief and top performers listed above. Focus your creativity on the problem space, not the implementation details.
1587
- EOF
1588
- )"
1589
-
1590
- if [[ -n $top_performers ]]; then
1591
- prompt+="
1592
-
1593
- Top Performing Algorithms So Far:
1594
- $top_performers"
1595
- fi
1596
-
1597
- prompt+="
1598
-
1599
- CRITICAL INSTRUCTIONS:
1600
- 1. Use the Read tool to examine the current CSV file
1601
- IMPORTANT: If the CSV file is large (>200 lines), read it in chunks using the offset and limit parameters to avoid context overload
1602
- Example: Read(file_path='temp-csv-123.csv', offset=0, limit=100) then Read(offset=100, limit=100), etc.
1603
- 2. DO NOT DELETE OR REPLACE ANY EXISTING ROWS - YOU MUST PRESERVE ALL EXISTING DATA
1604
- 3. Find the highest ID number for generation $CURRENT_GENERATION (e.g., if gen$CURRENT_GENERATION-003 exists, next should be gen$CURRENT_GENERATION-004)
1605
- 4. If no gen$CURRENT_GENERATION entries exist yet, start with gen$CURRENT_GENERATION-001
1606
- 5. Use the Edit or MultiEdit tool to APPEND exactly $TOTAL_IDEAS new rows AT THE END of the CSV file
1607
- 6. For each idea, create a row with: id,parent_id,description,,pending
1608
- 7. CRITICAL CSV FORMATTING RULES:
1609
- - ALWAYS wrap the description field in double quotes
1610
- - If the description contains quotes, escape them by doubling them (\" becomes \"\")
1611
- - Example: gen01-001,gen00-000,\"Implement adaptive RSI thresholds based on volatility\",,pending
1612
- - BAD: gen01-001,gen00-000,Implement adaptive RSI thresholds based on volatility,,pending
1613
- - NEVER omit quotes around descriptions - this causes CSV parsing errors that corrupt the data
1614
- 8. Mix both parameter tuning and structural changes
1615
- 9. If building on existing algorithms, use their ID as parent_id, otherwise leave parent_id empty
1616
-
1617
- ⚠️ AVOID ONLY: Kelly floor/cap adjustments that assume leverage > 1.0 (these get clamped and have no effect)
1618
-
1619
- ✅ EXPLORE ALL CREATIVE POSSIBILITIES INCLUDING:
1620
- - Machine Learning: Neural networks, ensemble methods, reinforcement learning (use train() method)
1621
- - Advanced Indicators: Custom combinations, multi-timeframe signals, cross-asset indicators
1622
- - Market Regime Detection: VIX patterns, correlation analysis, volatility clustering
1623
- - Risk Management: Dynamic stops, portfolio heat, correlation-based position sizing
1624
- - Alternative Strategies: New sub-strategies, momentum variants, mean reversion innovations
1625
- - Multi-Asset Signals: Sector rotation, bond yields, commodity signals
1626
- - Time-Based Patterns: Intraday effects, calendar anomalies, volatility timing
1627
- - Parameter Optimization: Entry thresholds, indicator periods, strategy weights
1628
-
1629
- IMPORTANT: You must APPEND new rows to the existing CSV file. DO NOT replace the file contents. All existing rows must remain unchanged.
1630
- CRITICAL: You must use your file editing tools (Edit/MultiEdit) to modify the CSV file. DO NOT return CSV text - use your tools to edit the file directly."
1631
-
1632
- # Change to evolution directory so AI can access files (with safe restoration on interrupt)
1633
- safe_pushd "$FULL_EVOLUTION_DIR" || {
1634
- echo "[ERROR] Failed to change to evolution directory" >&2
1635
- rm -f "$temp_csv"
1636
- return 1
1637
- }
1638
-
1639
- # Get AI to directly edit the CSV file
1640
- local ai_response
1641
- if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$TOTAL_IDEAS" "$temp_csv_basename"); then
1642
- echo "[ERROR] AI model failed to generate ideas" >&2
1643
- safe_popd
1644
- rm -f "$temp_csv"
1645
- return 1
1646
- fi
1647
-
1648
- # Restore working directory
1649
- safe_popd
1650
-
1651
- # Validate that the CSV file was actually modified
1652
- # Pass original_csv_lines to prevent race conditions
1653
- if ! validate_direct_csv_modification "$temp_csv" "$TOTAL_IDEAS" "mixed" "$ai_response" "" "$original_csv_lines"; then
1654
- rm -f "$temp_csv"
1655
- return 1
1656
- fi
1657
-
1658
- echo "[INFO] Legacy ideas generated"
1659
- return 0
1660
- }
1661
-
1662
- # AIDEV-NOTE: Determine which generation to use for ideation
1663
- # Check if the HIGHEST existing generation needs more ideas before creating a new one
1664
- get_highest_generation() {
1665
- if [[ ! -f "$FULL_CSV_PATH" ]]; then
1666
- echo "0"
1667
- return
1668
- fi
1669
- "$PYTHON_CMD" -c "
1670
- import csv
1671
- max_gen = 0
1672
- with open('$FULL_CSV_PATH', 'r') as f:
1673
- reader = csv.reader(f)
1674
- next(reader, None) # Skip header
1675
- for row in reader:
1676
- if row and len(row) > 0:
1677
- id_field = row[0].strip()
1678
- if id_field.startswith('gen') and '-' in id_field:
1679
- try:
1680
- gen_part = id_field.split('-')[0]
1681
- gen_num = int(gen_part[3:])
1682
- max_gen = max(max_gen, gen_num)
1683
- except (ValueError, IndexError):
1684
- pass
1685
- print(max_gen)
1686
- "
1687
- }
1688
-
1689
- count_generation_ideas() {
1690
- local gen="$1"
1691
- local count
1692
- count=$(grep -c "^gen${gen}-" "$FULL_CSV_PATH" 2>/dev/null) || true
1693
- echo "${count:-0}"
1694
- }
1695
-
1696
- count_generation_pending() {
1697
- local gen="$1"
1698
- local count
1699
- count=$(grep -c "^gen${gen}-.*,pending" "$FULL_CSV_PATH" 2>/dev/null) || true
1700
- echo "${count:-0}"
1701
- }
1702
-
1703
- # Find the right generation to use
1704
- # AIDEV-NOTE: Generation numbers must be zero-padded to 2 digits (gen01, gen02, etc.)
1705
- # to maintain consistency with existing CSV data format
1706
- highest_gen=$(get_highest_generation)
1707
- if [[ ${highest_gen:-0} -eq 0 ]]; then
1708
- # No generations yet, start with 01
1709
- CURRENT_GENERATION="01"
1710
- echo "[INFO] No existing generations, starting with generation 01"
1711
- else
1712
- # Check if highest generation needs more ideas
1713
- # Use unpadded for grep patterns (matches both gen1- and gen01-)
1714
- existing_ideas=$(count_generation_ideas "$highest_gen")
1715
- pending_ideas=$(count_generation_pending "$highest_gen")
1716
- # Also check padded format
1717
- padded_gen=$(printf "%02d" "$((10#$highest_gen))")
1718
- existing_ideas_padded=$(count_generation_ideas "$padded_gen")
1719
- pending_ideas_padded=$(count_generation_pending "$padded_gen")
1720
- # Use whichever found more (handles both gen1 and gen01 formats)
1721
- if [[ ${existing_ideas_padded:-0} -gt ${existing_ideas:-0} ]]; then
1722
- existing_ideas=$existing_ideas_padded
1723
- pending_ideas=$pending_ideas_padded
1724
- fi
1725
-
1726
- echo "[INFO] Highest generation: $highest_gen with $existing_ideas total ideas ($pending_ideas pending)"
1727
-
1728
- if [[ $existing_ideas -ge $TOTAL_IDEAS ]]; then
1729
- # Highest generation is full, create a new one
1730
- CURRENT_GENERATION=$(get_next_generation)
1731
- # Ensure it's zero-padded
1732
- CURRENT_GENERATION=$(printf "%02d" "$((10#$CURRENT_GENERATION))")
1733
- echo "[INFO] Generation $highest_gen is full ($existing_ideas >= $TOTAL_IDEAS), creating generation $CURRENT_GENERATION"
1734
- elif [[ $pending_ideas -ge $TOTAL_IDEAS ]]; then
1735
- # Already have enough pending, skip ideation
1736
- echo "[INFO] Generation $padded_gen already has $pending_ideas pending ideas (target: $TOTAL_IDEAS)"
1737
- echo "[INFO] Skipping ideation - workers will process existing ideas"
1738
- exit 0
1739
- else
1740
- # Continue filling the current highest generation - use padded format
1741
- CURRENT_GENERATION=$(printf "%02d" "$((10#$highest_gen))")
1742
- echo "[INFO] Continuing generation $CURRENT_GENERATION (need $((TOTAL_IDEAS - existing_ideas)) more ideas)"
1743
- fi
1744
- fi
1745
-
1746
- echo "[INFO] Starting ideation for generation $CURRENT_GENERATION"
1747
-
1748
- # Main execution with retry logic and exponential backoff
1749
- retry_count=0
1750
- wait_seconds=300 # Start with 5 minutes
1751
- max_wait_seconds=300 # Cap at 5 minutes
1752
-
1753
- while true; do
1754
- # Re-check pending count in case another process added ideas
1755
- pending_count=$(grep -c "^gen${CURRENT_GENERATION}-.*,pending" "$FULL_CSV_PATH" 2>/dev/null) || true
1756
- pending_count=${pending_count:-0}
1757
- total_count=$(grep -c "^gen${CURRENT_GENERATION}-" "$FULL_CSV_PATH" 2>/dev/null) || true
1758
- total_count=${total_count:-0}
1759
-
1760
- if [[ $total_count -ge $TOTAL_IDEAS ]]; then
1761
- echo "[INFO] Generation $CURRENT_GENERATION now has $total_count ideas (target: $TOTAL_IDEAS)"
1762
- echo "[INFO] Ideation complete for this generation"
1763
- exit 0
1764
- elif [[ ${pending_count:-0} -gt 0 ]]; then
1765
- echo "[INFO] Found $pending_count pending, $total_count total for generation $CURRENT_GENERATION (target: $TOTAL_IDEAS)"
1766
- fi
1767
-
1768
- if [[ $use_strategies == true ]]; then
1769
- echo "[INFO] Multi-strategy AI generation mode"
1770
- if ideate_ai_strategies; then
1771
- echo "[INFO] Ideation complete! Check $EVOLUTION_CSV for new ideas."
1772
- exit 0
1773
- fi
1774
- else
1775
- echo "[INFO] Legacy AI generation mode"
1776
- if ideate_ai_legacy; then
1777
- echo "[INFO] Ideation complete! Check $EVOLUTION_CSV for new ideas."
1778
- exit 0
1779
- fi
1780
- fi
1781
-
1782
- # If we reach here, ideation failed
1783
- ((retry_count++))
1784
-
1785
- echo "[WARN] All ideation attempts failed (retry #$retry_count)" >&2
1786
- echo "[INFO] This could be temporary API rate limits or service issues" >&2
1787
- echo "[INFO] Waiting $wait_seconds seconds ($(($wait_seconds / 60)) minutes) before retrying..." >&2
1788
-
1789
- # Sleep with countdown
1790
- remaining=$wait_seconds
1791
- while [[ $remaining -gt 0 ]]; do
1792
- if [[ $((remaining % 60)) -eq 0 ]]; then
1793
- echo "[INFO] Retrying in $((remaining / 60)) minutes..." >&2
1794
- fi
1795
- sleep 60
1796
- remaining=$((remaining - 60))
1797
- done
6
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'lib'))
1798
7
 
1799
- echo "[INFO] Retrying ideation (attempt #$((retry_count + 1)))..." >&2
8
+ from evolve_ideate import main
1800
9
 
1801
- # Exponential backoff: double the wait time, up to max
1802
- wait_seconds=$((wait_seconds * 2))
1803
- if [[ $wait_seconds -gt $max_wait_seconds ]]; then
1804
- wait_seconds=$max_wait_seconds
1805
- fi
1806
- done
10
+ if __name__ == '__main__':
11
+ sys.exit(main())