claude-evolve 1.3.27 → 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
@@ -223,11 +223,25 @@ Just output a single floating-point number:
223
223
  {"performance": 1.234}
224
224
  ```
225
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
+
226
238
  **Important notes:**
227
239
  - Higher scores indicate better performance
228
240
  - A score of 0 indicates complete failure
229
241
  - Non-zero exit codes indicate evaluation errors
230
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)
231
245
 
232
246
  ## Environment Variables for Evaluators
233
247
 
@@ -255,7 +255,33 @@ if [[ $eval_exit_code -eq 0 ]]; then
255
255
  fi
256
256
  fi
257
257
 
258
- # Try JSON "score" field
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
259
285
  if score=$(echo "$eval_output" | grep -o '"score"[[:space:]]*:[[:space:]]*[0-9.]*' | cut -d: -f2 | tr -d ' '); then
260
286
  if [[ -n $score ]]; then
261
287
  if (( $(echo "$score == 0" | bc -l) )); then
@@ -272,7 +298,7 @@ if [[ $eval_exit_code -eq 0 ]]; then
272
298
  fi
273
299
  fi
274
300
 
275
- # Try JSON "performance" field
301
+ # Try "performance" field
276
302
  if score=$(echo "$eval_output" | grep -o '"performance"[[:space:]]*:[[:space:]]*[0-9.]*' | cut -d: -f2 | tr -d ' '); then
277
303
  if [[ -n $score ]]; then
278
304
  if (( $(echo "$score == 0" | bc -l) )); then
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.27",
3
+ "version": "1.3.28",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",