claude-evolve 1.3.26 → 1.3.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -203,6 +203,46 @@ your-project/
203
203
  └── (your main project files)
204
204
  ```
205
205
 
206
+ ## Evaluator Output Format
207
+
208
+ Your evaluator must output a performance score to stdout. Three formats are supported:
209
+
210
+ ### 1. Plain Number (Simplest)
211
+ Just output a single floating-point number:
212
+ ```
213
+ 1.077506371224117
214
+ ```
215
+
216
+ ### 2. JSON with "score" field
217
+ ```json
218
+ {"score": 0.95}
219
+ ```
220
+
221
+ ### 3. JSON with "performance" field
222
+ ```json
223
+ {"performance": 1.234}
224
+ ```
225
+
226
+ ### 4. JSON with additional metrics (Advanced)
227
+ You can include additional metrics that will be automatically added as new columns to the CSV:
228
+ ```json
229
+ {
230
+ "score": 0.95,
231
+ "sharpe_ratio": 1.23,
232
+ "max_drawdown": -0.15,
233
+ "total_return": 0.42,
234
+ "win_rate": 0.65
235
+ }
236
+ ```
237
+
238
+ **Important notes:**
239
+ - Higher scores indicate better performance
240
+ - A score of 0 indicates complete failure
241
+ - Non-zero exit codes indicate evaluation errors
242
+ - Any additional output (warnings, logs) should go to stderr, not stdout
243
+ - Additional JSON fields will be automatically added as new CSV columns
244
+ - New columns are added after the standard columns (id, basedOnId, description, performance, status)
245
+
206
246
  ## Environment Variables for Evaluators
207
247
 
208
248
  When your evaluator.py runs, it has access to the `EXPERIMENT_ID` environment variable containing the current experiment's ID (e.g., `gen07-001`). This allows evaluators to:
@@ -221,7 +261,10 @@ experiment_id = os.environ.get('EXPERIMENT_ID', 'unknown')
221
261
 
222
262
  # Use it for logging or file naming
223
263
  output_file = f"results_{experiment_id}.json"
224
- print(f"Evaluating experiment: {experiment_id}")
264
+ print(f"Evaluating experiment: {experiment_id}", file=sys.stderr) # Use stderr for logs!
265
+
266
+ # Output just the score
267
+ print(score) # Simple number to stdout
225
268
  ```
226
269
 
227
270
  ## Configuration
@@ -78,7 +78,6 @@ call_claude_with_limit_check() {
78
78
 
79
79
  # Parse arguments
80
80
  use_strategies=true
81
- no_ai=false
82
81
 
83
82
  while [[ $# -gt 0 ]]; do
84
83
  case $1 in
@@ -87,11 +86,10 @@ while [[ $# -gt 0 ]]; do
87
86
  claude-evolve ideate - Generate new algorithm ideas using evolutionary strategies
88
87
 
89
88
  USAGE:
90
- claude-evolve ideate [--legacy N] [--no-ai]
89
+ claude-evolve ideate [--legacy N]
91
90
 
92
91
  OPTIONS:
93
92
  --legacy N Use legacy mode with N ideas (ignores strategy config)
94
- --no-ai Use manual entry mode instead of AI generation
95
93
  --help Show this help message
96
94
 
97
95
  DESCRIPTION:
@@ -116,10 +114,6 @@ EOF
116
114
  exit 1
117
115
  fi
118
116
  ;;
119
- --no-ai)
120
- no_ai=true
121
- shift
122
- ;;
123
117
  *)
124
118
  echo "[ERROR] Unknown option: $1" >&2
125
119
  exit 1
@@ -195,21 +189,6 @@ get_next_id() {
195
189
  printf "gen%s-%03d" "$generation" $((max_id + 1))
196
190
  }
197
191
 
198
- # Add idea to CSV manually (fallback for manual mode)
199
- add_idea_manual() {
200
- local description="$1"
201
- local based_on_id="$2"
202
- local generation="$3"
203
- local id
204
- id=$(get_next_id "$generation")
205
-
206
- # Escape quotes in description
207
- local escaped_desc="${description//\"/\"\"}"
208
-
209
- # Append to CSV
210
- echo "${id},${based_on_id},\"${escaped_desc}\",," >>"$FULL_CSV_PATH"
211
- echo "[INFO] Added idea: $description"
212
- }
213
192
 
214
193
  # Get top performers for parent selection
215
194
  get_top_performers() {
@@ -246,47 +225,12 @@ with open('$FULL_CSV_PATH', 'r') as f:
246
225
  "
247
226
  }
248
227
 
249
- # Manual entry mode
250
- ideate_manual() {
251
- local ideas_added=0
252
-
253
- for ((i = 1; i <= TOTAL_IDEAS; i++)); do
254
- if [[ $TOTAL_IDEAS -eq 1 ]]; then
255
- read -r -p "Enter algorithm idea (or empty to skip): " description
256
- else
257
- read -r -p "Enter algorithm idea $i/$TOTAL_IDEAS (or empty to skip): " description
258
- fi
259
-
260
- if [[ -z $description ]]; then
261
- echo "[INFO] Empty description, skipping idea"
262
- continue
263
- fi
264
-
265
- add_idea_manual "$description" "" "$CURRENT_GENERATION"
266
- ((ideas_added++))
267
-
268
- if [[ $i -lt $TOTAL_IDEAS ]]; then
269
- read -r -p "Add another idea? (y/N) " continue_adding
270
- if [[ $continue_adding != "y" && $continue_adding != "Y" ]]; then
271
- break
272
- fi
273
- fi
274
- done
275
-
276
- echo "[INFO] Added $ideas_added idea(s) to $EVOLUTION_CSV"
277
- }
278
228
 
279
229
  # Generate ideas using AI with multi-strategy approach
280
230
  ideate_ai_strategies() {
281
- # Check for AI CLI (codex or claude)
282
- if ! command -v codex >/dev/null 2>&1 && ! command -v claude >/dev/null 2>&1; then
283
- echo "[WARN] No AI CLI found (codex or claude). Falling back to manual entry."
284
- return 1
285
- fi
286
-
287
231
  if [[ ! -f "$FULL_BRIEF_PATH" ]]; then
288
- echo "[WARN] $BRIEF_FILE not found. Falling back to manual entry."
289
- return 1
232
+ echo "[ERROR] $BRIEF_FILE not found. Run 'claude-evolve setup' first." >&2
233
+ exit 1
290
234
  fi
291
235
 
292
236
  # Get top performers
@@ -531,6 +475,16 @@ Requirements for new CSV rows:
531
475
  - Each description should be one clear sentence combining elements from different algorithms
532
476
  - Be specific about what elements to merge
533
477
  - All new rows should have empty performance and status fields
478
+
479
+ CRITICAL CSV FORMAT RULES:
480
+ - DO NOT modify the CSV header row
481
+ - DO NOT change the column order
482
+ - DO NOT add extra columns or fields
483
+ - DO NOT modify existing rows - only append new ones
484
+ - DO NOT add extra blank lines or formatting
485
+ - Maintain exact CSV format: id,basedOnId,description,performance,status
486
+ - Leave performance and status fields completely empty (just commas)
487
+ - Use proper CSV quoting only when descriptions contain commas
534
488
  - CRITICAL: You must read the relevant algorithm files to:
535
489
  * Identify the specific improvements that made each algorithm successful
536
490
  * Understand which components are compatible for merging
@@ -553,15 +507,9 @@ Add exactly $count hybrid combination rows to the CSV file now."
553
507
 
554
508
  # Legacy AI generation mode (for backward compatibility)
555
509
  ideate_ai_legacy() {
556
- # Check for AI CLI (codex or claude)
557
- if ! command -v codex >/dev/null 2>&1 && ! command -v claude >/dev/null 2>&1; then
558
- echo "[WARN] No AI CLI found (codex or claude). Falling back to manual entry."
559
- return 1
560
- fi
561
-
562
510
  if [[ ! -f "$FULL_BRIEF_PATH" ]]; then
563
- echo "[WARN] $BRIEF_FILE not found. Falling back to manual entry."
564
- return 1
511
+ echo "[ERROR] $BRIEF_FILE not found. Run 'claude-evolve setup' first." >&2
512
+ exit 1
565
513
  fi
566
514
 
567
515
  # Get top performers (pure shell)
@@ -606,6 +554,16 @@ Requirements for new CSV rows:
606
554
  - Each description should be one clear sentence describing an algorithmic approach
607
555
  - All new rows should have empty performance and status fields
608
556
 
557
+ CRITICAL CSV FORMAT RULES:
558
+ - DO NOT modify the CSV header row
559
+ - DO NOT change the column order
560
+ - DO NOT add extra columns or fields
561
+ - DO NOT modify existing rows - only append new ones
562
+ - DO NOT add extra blank lines or formatting
563
+ - Maintain exact CSV format: id,basedOnId,description,performance,status
564
+ - Leave performance and status fields completely empty (just commas)
565
+ - Use proper CSV quoting only when descriptions contain commas
566
+
609
567
  Add exactly $TOTAL_IDEAS algorithm variation rows to the CSV file now."
610
568
 
611
569
  echo "[INFO] Generating $TOTAL_IDEAS ideas (legacy mode)..."
@@ -621,19 +579,12 @@ CURRENT_GENERATION=$(get_next_generation)
621
579
  echo "[INFO] Starting ideation for generation $CURRENT_GENERATION"
622
580
 
623
581
  # Main execution
624
- if [[ $no_ai == true ]]; then
625
- echo "[INFO] Manual entry mode"
626
- ideate_manual
627
- elif [[ $use_strategies == true ]]; then
582
+ if [[ $use_strategies == true ]]; then
628
583
  echo "[INFO] Multi-strategy AI generation mode"
629
- if ! ideate_ai_strategies; then
630
- echo "[INFO] Falling back to manual entry"
631
- ideate_manual
632
- fi
584
+ ideate_ai_strategies
585
+ echo "[INFO] Ideation complete! Check $EVOLUTION_CSV for new ideas."
633
586
  else
634
587
  echo "[INFO] Legacy AI generation mode"
635
- if ! ideate_ai_legacy; then
636
- echo "[INFO] Falling back to manual entry"
637
- ideate_manual
638
- fi
588
+ ideate_ai_legacy
589
+ echo "[INFO] Ideation complete! Check $EVOLUTION_CSV for new ideas."
639
590
  fi
@@ -239,7 +239,49 @@ fi
239
239
 
240
240
  # Process results
241
241
  if [[ $eval_exit_code -eq 0 ]]; then
242
- # Extract score
242
+ # First, check if output is just a plain number
243
+ if [[ $eval_output =~ ^[[:space:]]*-?[0-9]+\.?[0-9]*[[:space:]]*$ ]]; then
244
+ score=$(echo "$eval_output" | tr -d ' ')
245
+ if (( $(echo "$score == 0" | bc -l) )); then
246
+ update_csv_row_with_lock "$candidate_id" "status" "failed"
247
+ update_csv_row_with_lock "$candidate_id" "performance" "$score"
248
+ echo "[WORKER-$$] ✗ Evaluation failed with score 0"
249
+ exit 1
250
+ else
251
+ update_csv_row_with_lock "$candidate_id" "performance" "$score"
252
+ update_csv_row_with_lock "$candidate_id" "status" "complete"
253
+ echo "[WORKER-$$] ✓ Evaluation complete, score: $score"
254
+ exit 0
255
+ fi
256
+ fi
257
+
258
+ # Try to parse as JSON and extract all fields
259
+ if echo "$eval_output" | jq . >/dev/null 2>&1; then
260
+ # Valid JSON - use CSV helper to update with all fields
261
+ if ! acquire_csv_lock; then
262
+ echo "[ERROR] Failed to acquire CSV lock" >&2
263
+ exit 1
264
+ fi
265
+
266
+ score=$("$PYTHON_CMD" "$SCRIPT_DIR/../lib/csv_helper.py" update_with_json "$FULL_CSV_PATH" "$candidate_id" "$eval_output")
267
+ release_csv_lock
268
+
269
+ if [[ -n $score ]] && [[ $score != "0" ]]; then
270
+ echo "[WORKER-$$] ✓ Evaluation complete, score: $score"
271
+ # Extract and display additional fields if present
272
+ if additional_fields=$(echo "$eval_output" | jq -r 'to_entries | map(select(.key != "score" and .key != "performance")) | map("\(.key): \(.value)") | join(", ")' 2>/dev/null); then
273
+ if [[ -n $additional_fields ]]; then
274
+ echo "[WORKER-$$] Additional metrics: $additional_fields"
275
+ fi
276
+ fi
277
+ exit 0
278
+ else
279
+ echo "[WORKER-$$] ✗ Evaluation failed with score 0"
280
+ exit 1
281
+ fi
282
+ fi
283
+
284
+ # Fallback: Try simple grep for score/performance fields
243
285
  if score=$(echo "$eval_output" | grep -o '"score"[[:space:]]*:[[:space:]]*[0-9.]*' | cut -d: -f2 | tr -d ' '); then
244
286
  if [[ -n $score ]]; then
245
287
  if (( $(echo "$score == 0" | bc -l) )); then
@@ -274,6 +316,7 @@ if [[ $eval_exit_code -eq 0 ]]; then
274
316
  fi
275
317
 
276
318
  echo "[ERROR] No score found in evaluator output" >&2
319
+ echo "[ERROR] Expected: plain number (e.g., 1.23) or JSON with 'score' or 'performance' field" >&2
277
320
  update_csv_row_with_lock "$candidate_id" "status" "failed"
278
321
  exit 1
279
322
  else
package/lib/csv_helper.py CHANGED
@@ -1,120 +1,159 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- CSV helper for claude-evolve to properly handle CSV parsing with quoted fields.
3
+ CSV helper for dynamic column management in claude-evolve.
4
+ Handles adding new columns and updating rows with arbitrary fields.
4
5
  """
6
+
5
7
  import csv
6
- import sys
7
8
  import json
9
+ import sys
10
+ import os
11
+ from typing import Dict, List, Any
8
12
 
9
- def find_pending_row(csv_path):
10
- """Find the first pending row in the CSV."""
11
- with open(csv_path, 'r') as f:
12
- reader = csv.reader(f)
13
- next(reader) # Skip header
14
- for row_num, row in enumerate(reader, start=2):
15
- # If row has fewer than 5 fields, it's pending
16
- if len(row) < 5:
17
- return row_num
18
-
19
- # Ensure row has at least 5 fields for status check
20
- while len(row) < 5:
21
- row.append('')
22
-
23
- status = row[4].strip()
24
- # Check if status is pending or empty
25
- if status == 'pending' or status == '':
26
- return row_num
27
- return None
28
-
29
- def get_row_data(csv_path, row_num):
30
- """Get data from a specific row."""
31
- with open(csv_path, 'r') as f:
32
- reader = csv.reader(f)
33
- for i, row in enumerate(reader, start=1):
34
- if i == row_num:
35
- # Ensure row has at least 5 fields
36
- while len(row) < 5:
37
- row.append('')
38
- return {
39
- 'id': row[0],
40
- 'basedOnId': row[1],
41
- 'description': row[2],
42
- 'performance': row[3],
43
- 'status': row[4]
44
- }
45
- return None
46
13
 
47
- def update_row(csv_path, row_num, performance, status):
48
- """Update a specific row in the CSV."""
49
- rows = []
50
- with open(csv_path, 'r') as f:
14
+ def read_csv(filepath: str) -> tuple[list[str], list[list[str]]]:
15
+ """Read CSV and return headers and rows."""
16
+ with open(filepath, 'r') as f:
51
17
  reader = csv.reader(f)
18
+ headers = next(reader, [])
52
19
  rows = list(reader)
20
+ return headers, rows
21
+
22
+
23
+ def write_csv(filepath: str, headers: list[str], rows: list[list[str]]):
24
+ """Write CSV with headers and rows."""
25
+ with open(filepath, 'w', newline='') as f:
26
+ writer = csv.writer(f)
27
+ writer.writerow(headers)
28
+ writer.writerows(rows)
29
+
30
+
31
+ def ensure_columns(headers: list[str], rows: list[list[str]], new_fields: dict) -> tuple[list[str], list[list[str]]]:
32
+ """Add new columns if they don't exist and ensure all rows have correct length."""
33
+ # Find which fields need to be added as new columns
34
+ existing_columns = set(headers)
35
+ new_columns = []
36
+
37
+ for field in new_fields:
38
+ if field not in existing_columns and field not in ['id', 'basedOnId', 'description', 'performance', 'status']:
39
+ new_columns.append(field)
53
40
 
54
- # Update the specific row
55
- if row_num <= len(rows):
56
- row = rows[row_num - 1]
57
- # Ensure row has at least 5 fields
58
- while len(row) < 5:
41
+ # Add new columns to headers (after status column)
42
+ if new_columns:
43
+ headers = headers + new_columns
44
+
45
+ # Ensure all rows have the correct number of columns
46
+ for row in rows:
47
+ while len(row) < len(headers):
59
48
  row.append('')
60
- row[3] = performance # performance field
61
- row[4] = status # status field
62
49
 
63
- # Write back
64
- with open(csv_path, 'w', newline='') as f:
65
- writer = csv.writer(f)
66
- writer.writerows(rows)
50
+ return headers, rows
67
51
 
68
- if __name__ == '__main__':
52
+
53
+ def update_row_with_fields(headers: list[str], rows: list[list[str]], target_id: str, fields: dict):
54
+ """Update a specific row with multiple fields."""
55
+ # Find column indices
56
+ col_indices = {header: i for i, header in enumerate(headers)}
57
+
58
+ # Find and update the target row
59
+ for row in rows:
60
+ if row[0] == target_id:
61
+ for field, value in fields.items():
62
+ if field in col_indices:
63
+ row[col_indices[field]] = str(value)
64
+ break
65
+
66
+
67
+ def main():
68
+ """Main entry point for CSV operations."""
69
69
  if len(sys.argv) < 3:
70
- print("Usage: csv_helper.py <command> <csv_path> [args...]", file=sys.stderr)
70
+ print("Usage: csv_helper.py <operation> <args...>", file=sys.stderr)
71
71
  sys.exit(1)
72
72
 
73
- command = sys.argv[1]
74
- csv_path = sys.argv[2]
73
+ operation = sys.argv[1]
75
74
 
76
- try:
77
- if command == 'find_pending':
78
- row_num = find_pending_row(csv_path)
79
- if row_num:
80
- print(row_num)
81
- sys.exit(0)
82
- else:
83
- sys.exit(1)
84
-
85
- elif command == 'get_row':
86
- if len(sys.argv) < 4:
87
- print("Usage: csv_helper.py get_row <csv_path> <row_num>", file=sys.stderr)
88
- sys.exit(1)
89
- row_num = int(sys.argv[3])
90
- data = get_row_data(csv_path, row_num)
91
- if data:
92
- # Output as shell variable assignments
93
- for key, value in data.items():
94
- # Escape special characters for shell
95
- value = value.replace('\\', '\\\\')
96
- value = value.replace('"', '\\"')
97
- value = value.replace('$', '\\$')
98
- value = value.replace('`', '\\`')
99
- print(f'{key}="{value}"')
100
- sys.exit(0)
101
- else:
102
- sys.exit(1)
75
+ if operation == "update_with_json":
76
+ # Args: csv_file, target_id, json_output
77
+ if len(sys.argv) != 5:
78
+ print("Usage: csv_helper.py update_with_json <csv_file> <target_id> <json_output>", file=sys.stderr)
79
+ sys.exit(1)
103
80
 
104
- elif command == 'update_row':
105
- if len(sys.argv) < 6:
106
- print("Usage: csv_helper.py update_row <csv_path> <row_num> <performance> <status>", file=sys.stderr)
107
- sys.exit(1)
108
- row_num = int(sys.argv[3])
109
- performance = sys.argv[4]
110
- status = sys.argv[5]
111
- update_row(csv_path, row_num, performance, status)
112
- sys.exit(0)
81
+ csv_file = sys.argv[2]
82
+ target_id = sys.argv[3]
83
+ json_output = sys.argv[4]
113
84
 
114
- else:
115
- print(f"Unknown command: {command}", file=sys.stderr)
85
+ try:
86
+ # Parse JSON output
87
+ data = json.loads(json_output)
88
+
89
+ # Extract performance/score
90
+ performance = data.get('performance') or data.get('score', 0)
91
+
92
+ # Build fields to update
93
+ fields = {'performance': performance, 'status': 'complete' if performance > 0 else 'failed'}
94
+
95
+ # Add all other fields from the JSON
96
+ for key, value in data.items():
97
+ if key not in ['performance', 'score', 'status']:
98
+ fields[key] = value
99
+
100
+ # Read CSV
101
+ headers, rows = read_csv(csv_file)
102
+
103
+ # Ensure columns exist for all fields
104
+ headers, rows = ensure_columns(headers, rows, fields)
105
+
106
+ # Update the row
107
+ update_row_with_fields(headers, rows, target_id, fields)
108
+
109
+ # Write back
110
+ write_csv(csv_file + '.tmp', headers, rows)
111
+ os.rename(csv_file + '.tmp', csv_file)
112
+
113
+ # Return the performance score
114
+ print(performance)
115
+
116
+ except json.JSONDecodeError:
117
+ print("0") # Invalid JSON means failed
118
+ sys.exit(1)
119
+ except Exception as e:
120
+ print(f"Error: {e}", file=sys.stderr)
121
+ print("0")
116
122
  sys.exit(1)
123
+
124
+ elif operation == "update_field":
125
+ # Args: csv_file, target_id, field, value
126
+ if len(sys.argv) != 6:
127
+ print("Usage: csv_helper.py update_field <csv_file> <target_id> <field> <value>", file=sys.stderr)
128
+ sys.exit(1)
129
+
130
+ csv_file = sys.argv[2]
131
+ target_id = sys.argv[3]
132
+ field = sys.argv[4]
133
+ value = sys.argv[5]
134
+
135
+ try:
136
+ # Read CSV
137
+ headers, rows = read_csv(csv_file)
138
+
139
+ # Ensure column exists
140
+ headers, rows = ensure_columns(headers, rows, {field: value})
141
+
142
+ # Update the row
143
+ update_row_with_fields(headers, rows, target_id, {field: value})
144
+
145
+ # Write back
146
+ write_csv(csv_file + '.tmp', headers, rows)
147
+ os.rename(csv_file + '.tmp', csv_file)
117
148
 
118
- except Exception as e:
119
- print(f"Error: {e}", file=sys.stderr)
120
- sys.exit(1)
149
+ except Exception as e:
150
+ print(f"Error: {e}", file=sys.stderr)
151
+ sys.exit(1)
152
+
153
+ else:
154
+ print(f"Unknown operation: {operation}", file=sys.stderr)
155
+ sys.exit(1)
156
+
157
+
158
+ if __name__ == "__main__":
159
+ main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-evolve",
3
- "version": "1.3.26",
3
+ "version": "1.3.28",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",
@@ -40,11 +40,10 @@ def evaluate_performance(algorithm_module):
40
40
  end_time = time.time()
41
41
  execution_time = end_time - start_time
42
42
 
43
- return {
44
- "execution_time": execution_time,
45
- "score": 1.0 / execution_time if execution_time > 0 else 0,
46
- "status": "success"
47
- }
43
+ # Calculate a performance score (higher is better)
44
+ score = 1.0 / execution_time if execution_time > 0 else 0
45
+
46
+ return score # Simple: just return the number
48
47
 
49
48
 
50
49
  def main():
@@ -60,15 +59,18 @@ def main():
60
59
 
61
60
  try:
62
61
  algorithm_module = load_algorithm(algorithm_file)
63
- metrics = evaluate_performance(algorithm_module)
64
- print(json.dumps(metrics))
62
+ score = evaluate_performance(algorithm_module)
63
+
64
+ # Option 1: Just print the number (simplest)
65
+ print(score)
66
+
67
+ # Option 2: Print as JSON (if you need more structure)
68
+ # print(json.dumps({"score": score}))
69
+
65
70
  sys.exit(0)
66
71
  except Exception as e:
67
- error_result = {
68
- "error": str(e),
69
- "status": "failed"
70
- }
71
- print(json.dumps(error_result))
72
+ # Log errors to stderr, not stdout
73
+ print(f"Error: {e}", file=sys.stderr)
72
74
  sys.exit(1)
73
75
 
74
76