claude-evolve 1.3.11 → 1.3.13
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 +5 -1
- package/bin/claude-evolve-analyze +190 -91
- package/bin/claude-evolve-run-parallel +53 -10
- package/bin/claude-evolve-worker +27 -17
- package/lib/config.sh +28 -1
- package/lib/csv-lock.sh +54 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -172,7 +172,11 @@ Evolution experiments can fail for various reasons. The system tracks these fail
|
|
|
172
172
|
### Required
|
|
173
173
|
- Node.js >= 14.0.0
|
|
174
174
|
- Python 3.x (for algorithm execution)
|
|
175
|
-
-
|
|
175
|
+
- Automatically detected on all platforms
|
|
176
|
+
- Windows: Uses `python` if it's Python 3
|
|
177
|
+
- macOS/Linux: Prefers `python3`
|
|
178
|
+
- Can override in config.yaml: `python_cmd: "C:\\Python39\\python.exe"`
|
|
179
|
+
- Bash shell (Git Bash on Windows, native on macOS/Linux)
|
|
176
180
|
- [Claude CLI](https://docs.anthropic.com/en/docs/claude-code) (`claude` command)
|
|
177
181
|
|
|
178
182
|
### Optional (but recommended)
|
|
@@ -92,35 +92,69 @@ top_score=""
|
|
|
92
92
|
top_id=""
|
|
93
93
|
top_desc=""
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
95
|
+
# Use Python to parse CSV and generate stats
|
|
96
|
+
eval "$("$PYTHON_CMD" -c "
|
|
97
|
+
import csv
|
|
98
|
+
|
|
99
|
+
# Initialize counters
|
|
100
|
+
total = 0
|
|
101
|
+
completed = 0
|
|
102
|
+
running = 0
|
|
103
|
+
failed = 0
|
|
104
|
+
pending = 0
|
|
105
|
+
total_performance = 0
|
|
106
|
+
count_with_performance = 0
|
|
107
|
+
top_score = None
|
|
108
|
+
top_id = ''
|
|
109
|
+
top_desc = ''
|
|
110
|
+
|
|
111
|
+
with open('$csv_file', 'r') as f:
|
|
112
|
+
reader = csv.reader(f)
|
|
113
|
+
next(reader) # Skip header
|
|
114
|
+
|
|
115
|
+
for row in reader:
|
|
116
|
+
if len(row) < 5:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
id, _, desc, perf, status = row[:5]
|
|
120
|
+
total += 1
|
|
121
|
+
|
|
122
|
+
if status in ['complete', 'completed']:
|
|
123
|
+
completed += 1
|
|
124
|
+
if perf and perf != '':
|
|
125
|
+
try:
|
|
126
|
+
perf_val = float(perf)
|
|
127
|
+
if perf_val > 0: # Skip zeros (they're errors)
|
|
128
|
+
total_performance += perf_val
|
|
129
|
+
count_with_performance += 1
|
|
130
|
+
|
|
131
|
+
if top_score is None or perf_val > top_score:
|
|
132
|
+
top_score = perf_val
|
|
133
|
+
top_id = id
|
|
134
|
+
top_desc = desc
|
|
135
|
+
except ValueError:
|
|
136
|
+
pass
|
|
137
|
+
elif status == 'running':
|
|
138
|
+
running += 1
|
|
139
|
+
elif status in ['failed', 'timeout', 'interrupted']:
|
|
140
|
+
failed += 1
|
|
141
|
+
else:
|
|
142
|
+
pending += 1
|
|
143
|
+
|
|
144
|
+
# Output shell variable assignments
|
|
145
|
+
print(f'total={total}')
|
|
146
|
+
print(f'completed={completed}')
|
|
147
|
+
print(f'running={running}')
|
|
148
|
+
print(f'failed={failed}')
|
|
149
|
+
print(f'pending={pending}')
|
|
150
|
+
print(f'total_performance={total_performance}')
|
|
151
|
+
print(f'count_with_performance={count_with_performance}')
|
|
152
|
+
print(f'top_score={top_score if top_score is not None else \"\"}')
|
|
153
|
+
print(f'top_id=\"{top_id}\"')
|
|
154
|
+
# Escape special characters in description
|
|
155
|
+
desc_escaped = top_desc.replace('\\\\', '\\\\\\\\').replace('\"', '\\\\\"').replace('\$', '\\\\\$').replace('\`', '\\\\\`')
|
|
156
|
+
print(f'top_desc=\"{desc_escaped}\"')
|
|
157
|
+
")"
|
|
124
158
|
|
|
125
159
|
# Display summary
|
|
126
160
|
echo "Total Candidates: $total"
|
|
@@ -154,30 +188,44 @@ echo "=== Generation Analysis ==="
|
|
|
154
188
|
gen_stats_file="/tmp/evolution_gen_stats_$$.tmp"
|
|
155
189
|
>"$gen_stats_file"
|
|
156
190
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
191
|
+
# Use Python to write generation stats
|
|
192
|
+
"$PYTHON_CMD" -c "
|
|
193
|
+
import csv
|
|
194
|
+
import re
|
|
195
|
+
|
|
196
|
+
with open('$csv_file', 'r') as f:
|
|
197
|
+
reader = csv.reader(f)
|
|
198
|
+
next(reader) # Skip header
|
|
199
|
+
|
|
200
|
+
with open('$gen_stats_file', 'w') as out:
|
|
201
|
+
for row in reader:
|
|
202
|
+
if len(row) < 5:
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
id, _, desc, perf, status = row[:5]
|
|
206
|
+
|
|
207
|
+
# Extract generation from ID
|
|
208
|
+
gen = 'gen01' # default for old numeric IDs
|
|
209
|
+
match = re.match(r'^(gen[0-9]+)-', id)
|
|
210
|
+
if match:
|
|
211
|
+
gen = match.group(1)
|
|
212
|
+
elif re.match(r'^[0-9]+$', id):
|
|
213
|
+
gen = 'gen00' # Mark old numeric IDs as gen00
|
|
214
|
+
|
|
215
|
+
# Write generation data
|
|
216
|
+
out.write(gen + ' ')
|
|
217
|
+
if status in ['complete', 'completed'] and perf and perf != '':
|
|
218
|
+
try:
|
|
219
|
+
perf_val = float(perf)
|
|
220
|
+
if perf_val > 0:
|
|
221
|
+
out.write(f'completed {perf}\\n')
|
|
222
|
+
else:
|
|
223
|
+
out.write('error\\n')
|
|
224
|
+
except ValueError:
|
|
225
|
+
out.write('error\\n')
|
|
226
|
+
else:
|
|
227
|
+
out.write('incomplete\\n')
|
|
228
|
+
"
|
|
181
229
|
|
|
182
230
|
# Process generation stats
|
|
183
231
|
for gen in $(cut -d' ' -f1 "$gen_stats_file" | sort -u || echo ""); do
|
|
@@ -206,14 +254,30 @@ rm -f "$gen_stats_file"
|
|
|
206
254
|
|
|
207
255
|
# Count valid performance entries for chart (excluding zeros)
|
|
208
256
|
valid_performance_count=0
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
257
|
+
# Count valid performance entries using Python
|
|
258
|
+
valid_performance_count=$("$PYTHON_CMD" -c "
|
|
259
|
+
import csv
|
|
260
|
+
|
|
261
|
+
count = 0
|
|
262
|
+
with open('$csv_file', 'r') as f:
|
|
263
|
+
reader = csv.reader(f)
|
|
264
|
+
next(reader) # Skip header
|
|
265
|
+
|
|
266
|
+
for row in reader:
|
|
267
|
+
if len(row) < 5:
|
|
268
|
+
continue
|
|
269
|
+
status = row[4]
|
|
270
|
+
perf = row[3]
|
|
271
|
+
|
|
272
|
+
if status in ['complete', 'completed'] and perf and perf != '':
|
|
273
|
+
try:
|
|
274
|
+
if float(perf) > 0:
|
|
275
|
+
count += 1
|
|
276
|
+
except ValueError:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
print(count)
|
|
280
|
+
")
|
|
217
281
|
|
|
218
282
|
# Simple chart generation using gnuplot if available
|
|
219
283
|
if command -v gnuplot >/dev/null 2>&1 && [[ $valid_performance_count -gt 0 ]]; then
|
|
@@ -250,40 +314,75 @@ if command -v gnuplot >/dev/null 2>&1 && [[ $valid_performance_count -gt 0 ]]; t
|
|
|
250
314
|
max_row=0
|
|
251
315
|
max_id=""
|
|
252
316
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
317
|
+
# Use Python to generate chart data
|
|
318
|
+
"$PYTHON_CMD" -c "
|
|
319
|
+
import csv
|
|
320
|
+
import re
|
|
321
|
+
|
|
322
|
+
row_num = 0
|
|
323
|
+
max_perf = 0
|
|
324
|
+
max_row = 0
|
|
325
|
+
max_id = ''
|
|
326
|
+
|
|
327
|
+
with open('$csv_file', 'r') as f:
|
|
328
|
+
reader = csv.reader(f)
|
|
329
|
+
next(reader) # Skip header
|
|
256
330
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if [[ $id =~ ^(gen[0-9]+)- ]]; then
|
|
260
|
-
gen="${BASH_REMATCH[1]}"
|
|
261
|
-
fi
|
|
331
|
+
data_lines = []
|
|
332
|
+
gen_temp_lines = []
|
|
262
333
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if [[ $id =~ ^gen([0-9]+)- ]]; then
|
|
270
|
-
gen_num=$((10#${BASH_REMATCH[1]}))
|
|
271
|
-
fi
|
|
272
|
-
|
|
273
|
-
echo "$row_num \"$id\" $perf $gen_num" >>"$data_file"
|
|
334
|
+
for row in reader:
|
|
335
|
+
if len(row) < 5:
|
|
336
|
+
continue
|
|
337
|
+
|
|
338
|
+
row_num += 1
|
|
339
|
+
id, _, desc, perf, status = row[:5]
|
|
274
340
|
|
|
275
|
-
#
|
|
276
|
-
|
|
341
|
+
# Extract generation from ID
|
|
342
|
+
gen = 'gen01' # default
|
|
343
|
+
match = re.match(r'^(gen[0-9]+)-', id)
|
|
344
|
+
if match:
|
|
345
|
+
gen = match.group(1)
|
|
277
346
|
|
|
278
|
-
#
|
|
279
|
-
if
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
347
|
+
# Only include completed algorithms with non-zero performance
|
|
348
|
+
if perf and perf != '' and status in ['complete', 'completed']:
|
|
349
|
+
try:
|
|
350
|
+
perf_val = float(perf)
|
|
351
|
+
if perf_val > 0:
|
|
352
|
+
# Assign generation number for coloring (1-based)
|
|
353
|
+
gen_num = 1
|
|
354
|
+
match = re.match(r'^gen([0-9]+)-', id)
|
|
355
|
+
if match:
|
|
356
|
+
gen_num = int(match.group(1))
|
|
357
|
+
|
|
358
|
+
data_lines.append(f'{row_num} \"{id}\" {perf} {gen_num}')
|
|
359
|
+
gen_temp_lines.append(f'{gen} {perf}')
|
|
360
|
+
|
|
361
|
+
# Track the winner
|
|
362
|
+
if perf_val > max_perf:
|
|
363
|
+
max_perf = perf_val
|
|
364
|
+
max_row = row_num
|
|
365
|
+
max_id = id
|
|
366
|
+
except ValueError:
|
|
367
|
+
pass
|
|
368
|
+
|
|
369
|
+
# Write data file
|
|
370
|
+
with open('$data_file', 'a') as f:
|
|
371
|
+
for line in data_lines:
|
|
372
|
+
f.write(line + '\\n')
|
|
373
|
+
|
|
374
|
+
# Write gen temp file
|
|
375
|
+
with open('$gen_data_temp', 'a') as f:
|
|
376
|
+
for line in gen_temp_lines:
|
|
377
|
+
f.write(line + '\\n')
|
|
378
|
+
|
|
379
|
+
# Output max values for shell
|
|
380
|
+
print(f'max_perf={max_perf}')
|
|
381
|
+
print(f'max_row={max_row}')
|
|
382
|
+
print(f'max_id=\"{max_id}\"')
|
|
383
|
+
" | while read -r line; do
|
|
384
|
+
eval "$line"
|
|
385
|
+
done
|
|
287
386
|
|
|
288
387
|
# Create generation averages file and track max generation
|
|
289
388
|
gen_index=1
|
|
@@ -63,7 +63,18 @@ count_pending() {
|
|
|
63
63
|
return
|
|
64
64
|
fi
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
# Use Python for proper CSV parsing with quoted fields
|
|
67
|
+
echo "$csv_content" | "$PYTHON_CMD" -c "
|
|
68
|
+
import csv
|
|
69
|
+
import sys
|
|
70
|
+
reader = csv.reader(sys.stdin)
|
|
71
|
+
next(reader) # Skip header
|
|
72
|
+
count = 0
|
|
73
|
+
for row in reader:
|
|
74
|
+
if len(row) >= 5 and (row[4] == 'pending' or row[4] == ''):
|
|
75
|
+
count += 1
|
|
76
|
+
print(count)
|
|
77
|
+
"
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
# Start a worker
|
|
@@ -198,20 +209,42 @@ trap 'echo "[DISPATCHER] Exiting with code $?" >&2' EXIT
|
|
|
198
209
|
# Check for stuck "running" candidates from previous runs
|
|
199
210
|
check_stuck_candidates() {
|
|
200
211
|
if read_csv_with_lock csv_content; then
|
|
201
|
-
local stuck_count=$(echo "$csv_content" |
|
|
212
|
+
local stuck_count=$(echo "$csv_content" | "$PYTHON_CMD" -c "
|
|
213
|
+
import csv
|
|
214
|
+
import sys
|
|
215
|
+
reader = csv.reader(sys.stdin)
|
|
216
|
+
next(reader) # Skip header
|
|
217
|
+
count = 0
|
|
218
|
+
for row in reader:
|
|
219
|
+
if len(row) >= 5 and row[4] == 'running':
|
|
220
|
+
count += 1
|
|
221
|
+
print(count)
|
|
222
|
+
")
|
|
202
223
|
if [[ $stuck_count -gt 0 ]]; then
|
|
203
224
|
echo "[DISPATCHER] Found $stuck_count candidates stuck in 'running' status"
|
|
204
225
|
echo "[DISPATCHER] Resetting them to 'pending' for retry..."
|
|
205
226
|
|
|
206
227
|
# Reset stuck candidates
|
|
207
228
|
if acquire_csv_lock; then
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
229
|
+
"$PYTHON_CMD" -c "
|
|
230
|
+
import csv
|
|
231
|
+
import sys
|
|
232
|
+
|
|
233
|
+
# Read CSV
|
|
234
|
+
with open('$FULL_CSV_PATH', 'r') as f:
|
|
235
|
+
reader = csv.reader(f)
|
|
236
|
+
rows = list(reader)
|
|
237
|
+
|
|
238
|
+
# Reset running to pending
|
|
239
|
+
for i in range(1, len(rows)):
|
|
240
|
+
if len(rows[i]) >= 5 and rows[i][4] == 'running':
|
|
241
|
+
rows[i][4] = 'pending'
|
|
242
|
+
|
|
243
|
+
# Write back
|
|
244
|
+
with open('${FULL_CSV_PATH}.tmp', 'w', newline='') as f:
|
|
245
|
+
writer = csv.writer(f)
|
|
246
|
+
writer.writerows(rows)
|
|
247
|
+
" && mv -f "${FULL_CSV_PATH}.tmp" "$FULL_CSV_PATH"
|
|
215
248
|
release_csv_lock
|
|
216
249
|
fi
|
|
217
250
|
fi
|
|
@@ -247,7 +280,17 @@ while true; do
|
|
|
247
280
|
# Debug: Show CSV status if no pending
|
|
248
281
|
if [[ $pending_count -eq 0 ]]; then
|
|
249
282
|
total_rows=$(read_csv_with_lock csv_content && echo "$csv_content" | wc -l | xargs)
|
|
250
|
-
complete_count=$(read_csv_with_lock csv_content && echo "$csv_content" |
|
|
283
|
+
complete_count=$(read_csv_with_lock csv_content && echo "$csv_content" | "$PYTHON_CMD" -c "
|
|
284
|
+
import csv
|
|
285
|
+
import sys
|
|
286
|
+
reader = csv.reader(sys.stdin)
|
|
287
|
+
next(reader) # Skip header
|
|
288
|
+
count = 0
|
|
289
|
+
for row in reader:
|
|
290
|
+
if len(row) >= 5 and row[4] == 'complete':
|
|
291
|
+
count += 1
|
|
292
|
+
print(count)
|
|
293
|
+
")
|
|
251
294
|
echo "[DISPATCHER] CSV has $((total_rows-1)) total candidates, $complete_count complete"
|
|
252
295
|
fi
|
|
253
296
|
|
package/bin/claude-evolve-worker
CHANGED
|
@@ -74,29 +74,39 @@ if ! read_csv_with_lock csv_content; then
|
|
|
74
74
|
exit 1
|
|
75
75
|
fi
|
|
76
76
|
|
|
77
|
-
# Extract candidate data
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
# Extract candidate data using Python
|
|
78
|
+
eval "$("$PYTHON_CMD" -c "
|
|
79
|
+
import csv
|
|
80
|
+
import sys
|
|
81
|
+
import io
|
|
82
|
+
|
|
83
|
+
csv_content = '''$csv_content'''
|
|
84
|
+
reader = csv.reader(io.StringIO(csv_content))
|
|
85
|
+
next(reader) # Skip header
|
|
86
|
+
|
|
87
|
+
found = False
|
|
88
|
+
for row in reader:
|
|
89
|
+
if len(row) >= 5 and row[0] == '$candidate_id':
|
|
90
|
+
# Escape special characters for shell
|
|
91
|
+
desc = row[2].replace('\\\\', '\\\\\\\\').replace('\"', '\\\\\"').replace('\$', '\\\\\$').replace('\`', '\\\\\`')
|
|
92
|
+
print(f'id=\"{row[0]}\"')
|
|
93
|
+
print(f'based_on_id=\"{row[1]}\"')
|
|
94
|
+
print(f'description=\"{desc}\"')
|
|
95
|
+
print(f'performance=\"{row[3]}\"')
|
|
96
|
+
print(f'status=\"{row[4]}\"')
|
|
97
|
+
print('found=true')
|
|
98
|
+
found = True
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
if not found:
|
|
102
|
+
print('found=false')
|
|
103
|
+
")"
|
|
90
104
|
|
|
91
105
|
if [[ $found == false ]]; then
|
|
92
106
|
echo "[ERROR] Candidate ID not found: $candidate_id" >&2
|
|
93
107
|
exit 1
|
|
94
108
|
fi
|
|
95
109
|
|
|
96
|
-
# Clean up description
|
|
97
|
-
description=${description#\"}
|
|
98
|
-
description=${description%\"}
|
|
99
|
-
|
|
100
110
|
echo "[WORKER-$$] Description: $description"
|
|
101
111
|
echo "[WORKER-$$] Based on ID: $based_on_id"
|
|
102
112
|
|
package/lib/config.sh
CHANGED
|
@@ -9,7 +9,25 @@ DEFAULT_BRIEF_FILE="BRIEF.md"
|
|
|
9
9
|
DEFAULT_EVOLUTION_CSV="evolution.csv"
|
|
10
10
|
DEFAULT_OUTPUT_DIR=""
|
|
11
11
|
DEFAULT_PARENT_SELECTION="best"
|
|
12
|
-
|
|
12
|
+
# Detect Python command based on platform
|
|
13
|
+
detect_python_cmd() {
|
|
14
|
+
# Try python3 first (macOS, Linux)
|
|
15
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
16
|
+
echo "python3"
|
|
17
|
+
# Try python (Windows, some Linux)
|
|
18
|
+
elif command -v python >/dev/null 2>&1; then
|
|
19
|
+
# Verify it's Python 3
|
|
20
|
+
if python -c "import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)" 2>/dev/null; then
|
|
21
|
+
echo "python"
|
|
22
|
+
else
|
|
23
|
+
echo "python3" # Fallback
|
|
24
|
+
fi
|
|
25
|
+
else
|
|
26
|
+
echo "python3" # Default fallback
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
DEFAULT_PYTHON_CMD="$(detect_python_cmd)"
|
|
13
31
|
|
|
14
32
|
# Default ideation strategy values
|
|
15
33
|
DEFAULT_TOTAL_IDEAS=15
|
|
@@ -195,7 +213,16 @@ validate_config() {
|
|
|
195
213
|
|
|
196
214
|
if ! command -v "$PYTHON_CMD" >/dev/null 2>&1; then
|
|
197
215
|
echo "[ERROR] Python command not found: $PYTHON_CMD" >&2
|
|
216
|
+
echo "[ERROR] Please install Python 3.x or set python_cmd in config.yaml" >&2
|
|
217
|
+
echo "[ERROR] Examples: python_cmd: \"python\" or python_cmd: \"C:\\Python39\\python.exe\"" >&2
|
|
198
218
|
((errors++))
|
|
219
|
+
else
|
|
220
|
+
# Verify Python version is 3.x
|
|
221
|
+
if ! "$PYTHON_CMD" -c "import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)" 2>/dev/null; then
|
|
222
|
+
echo "[ERROR] Python 3.x required, but $PYTHON_CMD appears to be Python 2" >&2
|
|
223
|
+
echo "[ERROR] Please set python_cmd in config.yaml to point to Python 3" >&2
|
|
224
|
+
((errors++))
|
|
225
|
+
fi
|
|
199
226
|
fi
|
|
200
227
|
|
|
201
228
|
return $errors
|
package/lib/csv-lock.sh
CHANGED
|
@@ -94,19 +94,19 @@ update_csv_row_with_lock() {
|
|
|
94
94
|
local target_id="$1"
|
|
95
95
|
local field="$2"
|
|
96
96
|
local value="$3"
|
|
97
|
-
local csv_file="${EVOLUTION_DIR:-evolution}/evolution.csv"
|
|
97
|
+
local csv_file="${FULL_CSV_PATH:-${EVOLUTION_DIR:-evolution}/evolution.csv}"
|
|
98
98
|
|
|
99
99
|
if ! acquire_csv_lock; then
|
|
100
100
|
return 1
|
|
101
101
|
fi
|
|
102
102
|
|
|
103
|
-
# Determine field position
|
|
103
|
+
# Determine field position (0-based for Python)
|
|
104
104
|
local field_pos
|
|
105
105
|
case "$field" in
|
|
106
|
-
"status") field_pos=
|
|
107
|
-
"performance") field_pos=
|
|
108
|
-
"description") field_pos=
|
|
109
|
-
"basedOnId") field_pos=
|
|
106
|
+
"status") field_pos=4 ;;
|
|
107
|
+
"performance") field_pos=3 ;;
|
|
108
|
+
"description") field_pos=2 ;;
|
|
109
|
+
"basedOnId") field_pos=1 ;;
|
|
110
110
|
*)
|
|
111
111
|
echo "ERROR: Unknown field: $field" >&2
|
|
112
112
|
release_csv_lock
|
|
@@ -114,11 +114,27 @@ update_csv_row_with_lock() {
|
|
|
114
114
|
;;
|
|
115
115
|
esac
|
|
116
116
|
|
|
117
|
-
# Update CSV using
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
# Update CSV using Python
|
|
118
|
+
"$PYTHON_CMD" -c "
|
|
119
|
+
import csv
|
|
120
|
+
import sys
|
|
121
|
+
|
|
122
|
+
# Read CSV
|
|
123
|
+
with open('$csv_file', 'r') as f:
|
|
124
|
+
reader = csv.reader(f)
|
|
125
|
+
rows = list(reader)
|
|
126
|
+
|
|
127
|
+
# Update the specific field
|
|
128
|
+
for i in range(1, len(rows)):
|
|
129
|
+
if rows[i][0] == '$target_id':
|
|
130
|
+
rows[i][$field_pos] = '$value'
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
# Write back
|
|
134
|
+
with open('${csv_file}.tmp', 'w', newline='') as f:
|
|
135
|
+
writer = csv.writer(f)
|
|
136
|
+
writer.writerows(rows)
|
|
137
|
+
" && mv -f "${csv_file}.tmp" "$csv_file"
|
|
122
138
|
|
|
123
139
|
release_csv_lock
|
|
124
140
|
return 0
|
|
@@ -127,27 +143,40 @@ update_csv_row_with_lock() {
|
|
|
127
143
|
# Find next pending candidate with lock
|
|
128
144
|
# Usage: next_pending=$(find_next_pending_with_lock)
|
|
129
145
|
find_next_pending_with_lock() {
|
|
130
|
-
local csv_file="${EVOLUTION_DIR:-evolution}/evolution.csv"
|
|
146
|
+
local csv_file="${FULL_CSV_PATH:-${EVOLUTION_DIR:-evolution}/evolution.csv}"
|
|
131
147
|
|
|
132
148
|
if ! acquire_csv_lock; then
|
|
133
149
|
return 1
|
|
134
150
|
fi
|
|
135
151
|
|
|
136
|
-
# Find oldest pending candidate and update to running
|
|
137
|
-
local candidate=$(
|
|
138
|
-
|
|
139
|
-
|
|
152
|
+
# Find oldest pending candidate and update to running using Python
|
|
153
|
+
local candidate=$("$PYTHON_CMD" -c "
|
|
154
|
+
import csv
|
|
155
|
+
import sys
|
|
156
|
+
|
|
157
|
+
# Read CSV
|
|
158
|
+
with open('$csv_file', 'r') as f:
|
|
159
|
+
reader = csv.reader(f)
|
|
160
|
+
rows = list(reader)
|
|
161
|
+
|
|
162
|
+
# Find first pending candidate
|
|
163
|
+
candidate_id = None
|
|
164
|
+
for i in range(1, len(rows)):
|
|
165
|
+
if len(rows[i]) >= 5 and (rows[i][4] == 'pending' or rows[i][4] == ''):
|
|
166
|
+
candidate_id = rows[i][0]
|
|
167
|
+
rows[i][4] = 'running' # Update status
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
# Write back if we found a candidate
|
|
171
|
+
if candidate_id:
|
|
172
|
+
with open('${csv_file}.tmp', 'w', newline='') as f:
|
|
173
|
+
writer = csv.writer(f)
|
|
174
|
+
writer.writerows(rows)
|
|
175
|
+
print(candidate_id)
|
|
176
|
+
")
|
|
140
177
|
|
|
141
178
|
if [ -n "$candidate" ]; then
|
|
142
|
-
|
|
143
|
-
awk -F',' -v OFS=',' -v id="$candidate" '
|
|
144
|
-
NR==1 || $1 != id { print }
|
|
145
|
-
$1 == id {
|
|
146
|
-
# Preserve existing fields but set status to running
|
|
147
|
-
if ($5 == "" || $5 == "pending") $5 = "running"
|
|
148
|
-
print
|
|
149
|
-
}
|
|
150
|
-
' "$csv_file" > "${csv_file}.tmp" && mv -f "${csv_file}.tmp" "$csv_file"
|
|
179
|
+
mv -f "${csv_file}.tmp" "$csv_file"
|
|
151
180
|
fi
|
|
152
181
|
|
|
153
182
|
release_csv_lock
|