claude-evolve 1.3.27 → 1.3.29

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,250 @@
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
51
+
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
+
67
66
 
68
- if __name__ == '__main__':
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)
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)
84
80
 
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)
81
+ csv_file = sys.argv[2]
82
+ target_id = sys.argv[3]
83
+ json_output = sys.argv[4]
103
84
 
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)
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")
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]
113
134
 
114
- else:
115
- print(f"Unknown command: {command}", file=sys.stderr)
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)
148
+
149
+ except Exception as e:
150
+ print(f"Error: {e}", file=sys.stderr)
151
+ sys.exit(1)
152
+
153
+ elif operation == "find_pending":
154
+ # Args: csv_file
155
+ if len(sys.argv) != 3:
156
+ print("Usage: csv_helper.py find_pending <csv_file>", file=sys.stderr)
157
+ sys.exit(1)
158
+
159
+ csv_file = sys.argv[2]
160
+
161
+ try:
162
+ headers, rows = read_csv(csv_file)
163
+
164
+ # Find first row with empty status or status == "pending"
165
+ for i, row in enumerate(rows, start=2): # Start at 2 (1-indexed, skip header)
166
+ if len(row) < 5 or row[4] == '' or row[4] == 'pending':
167
+ print(i)
168
+ sys.exit(0)
169
+
170
+ # No pending found
171
+ sys.exit(1)
172
+
173
+ except Exception as e:
174
+ print(f"Error: {e}", file=sys.stderr)
175
+ sys.exit(1)
176
+
177
+ elif operation == "get_row":
178
+ # Args: csv_file, row_num
179
+ if len(sys.argv) != 4:
180
+ print("Usage: csv_helper.py get_row <csv_file> <row_num>", file=sys.stderr)
181
+ sys.exit(1)
182
+
183
+ csv_file = sys.argv[2]
184
+ row_num = int(sys.argv[3])
185
+
186
+ try:
187
+ headers, rows = read_csv(csv_file)
188
+
189
+ # Get the specific row (row_num is 1-indexed, includes header)
190
+ if row_num < 2 or row_num > len(rows) + 1:
191
+ print(f"Row {row_num} out of range", file=sys.stderr)
192
+ sys.exit(1)
193
+
194
+ row = rows[row_num - 2] # Convert to 0-indexed, skip header
195
+
196
+ # Output shell variable assignments
197
+ print(f'id="{row[0] if len(row) > 0 else ""}"')
198
+ print(f'based_on_id="{row[1] if len(row) > 1 else ""}"')
199
+ print(f'description="{row[2] if len(row) > 2 else ""}"')
200
+ print(f'performance="{row[3] if len(row) > 3 else ""}"')
201
+ print(f'status="{row[4] if len(row) > 4 else ""}"')
202
+
203
+ except Exception as e:
204
+ print(f"Error: {e}", file=sys.stderr)
205
+ sys.exit(1)
206
+
207
+ elif operation == "update_row":
208
+ # Args: csv_file, row_num, performance, status
209
+ if len(sys.argv) != 6:
210
+ print("Usage: csv_helper.py update_row <csv_file> <row_num> <performance> <status>", file=sys.stderr)
116
211
  sys.exit(1)
212
+
213
+ csv_file = sys.argv[2]
214
+ row_num = int(sys.argv[3])
215
+ performance = sys.argv[4]
216
+ status = sys.argv[5]
217
+
218
+ try:
219
+ headers, rows = read_csv(csv_file)
220
+
221
+ # Update the specific row
222
+ if row_num < 2 or row_num > len(rows) + 1:
223
+ print(f"Row {row_num} out of range", file=sys.stderr)
224
+ sys.exit(1)
117
225
 
118
- except Exception as e:
119
- print(f"Error: {e}", file=sys.stderr)
120
- sys.exit(1)
226
+ row_idx = row_num - 2 # Convert to 0-indexed, skip header
227
+
228
+ # Ensure row has enough columns
229
+ while len(rows[row_idx]) < 5:
230
+ rows[row_idx].append('')
231
+
232
+ # Update performance and status
233
+ rows[row_idx][3] = performance
234
+ rows[row_idx][4] = status
235
+
236
+ # Write back
237
+ write_csv(csv_file + '.tmp', headers, rows)
238
+ os.rename(csv_file + '.tmp', csv_file)
239
+
240
+ except Exception as e:
241
+ print(f"Error: {e}", file=sys.stderr)
242
+ sys.exit(1)
243
+
244
+ else:
245
+ print(f"Unknown operation: {operation}", file=sys.stderr)
246
+ sys.exit(1)
247
+
248
+
249
+ if __name__ == "__main__":
250
+ 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.29",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",