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 +14 -0
- package/bin/claude-evolve-worker +28 -2
- package/lib/csv_helper.py +138 -99
- package/package.json +1 -1
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
|
|
package/bin/claude-evolve-worker
CHANGED
|
@@ -255,7 +255,33 @@ if [[ $eval_exit_code -eq 0 ]]; then
|
|
|
255
255
|
fi
|
|
256
256
|
fi
|
|
257
257
|
|
|
258
|
-
# Try JSON
|
|
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
|
|
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
|
|
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
|
|
48
|
-
"""
|
|
49
|
-
|
|
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
|
-
#
|
|
55
|
-
if
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 <
|
|
70
|
+
print("Usage: csv_helper.py <operation> <args...>", file=sys.stderr)
|
|
71
71
|
sys.exit(1)
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
csv_path = sys.argv[2]
|
|
73
|
+
operation = sys.argv[1]
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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()
|