claude-evolve 1.7.18 → 1.7.21
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/bin/claude-evolve-ideate +63 -37
- package/bin/claude-evolve-worker +8 -0
- package/lib/csv_fixer.py +44 -5
- package/package.json +1 -1
package/bin/claude-evolve-ideate
CHANGED
|
@@ -95,66 +95,76 @@ call_ai_for_ideation() {
|
|
|
95
95
|
gen_num=1
|
|
96
96
|
fi
|
|
97
97
|
|
|
98
|
-
#
|
|
99
|
-
|
|
98
|
+
# Make a backup of the pre-populated temp CSV (which includes stub rows from caller)
|
|
99
|
+
# This preserves the stub rows that the caller added
|
|
100
|
+
local temp_csv_backup="${temp_csv_file}.backup"
|
|
100
101
|
if [[ -f "$temp_csv_file" ]]; then
|
|
101
|
-
|
|
102
|
+
cp "$temp_csv_file" "$temp_csv_backup"
|
|
102
103
|
else
|
|
103
|
-
|
|
104
|
+
echo "[ERROR] Temp CSV file not found at start: $temp_csv_file" >&2
|
|
105
|
+
return 1
|
|
104
106
|
fi
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
|
|
108
|
+
# Get the current row count before any modifications (from the pre-populated file with stubs)
|
|
109
|
+
local original_csv_count
|
|
110
|
+
original_csv_count=$(grep -v '^[[:space:]]*$' "$temp_csv_file" | tail -n +2 | wc -l)
|
|
111
|
+
|
|
112
|
+
echo "[DEBUG] Pre-populated temp CSV has $original_csv_count rows (includes stub rows with placeholders)" >&2
|
|
113
|
+
|
|
107
114
|
# Get models for ideation
|
|
108
115
|
local model_list
|
|
109
116
|
model_list=$(get_models_for_command "ideate")
|
|
110
117
|
local models=()
|
|
111
118
|
read -ra models <<< "$model_list"
|
|
112
|
-
|
|
119
|
+
|
|
113
120
|
if [[ ${#models[@]} -eq 0 ]]; then
|
|
114
121
|
echo "[ERROR] No models configured for ideation" >&2
|
|
122
|
+
rm -f "$temp_csv_backup"
|
|
115
123
|
return 1
|
|
116
124
|
fi
|
|
117
|
-
|
|
125
|
+
|
|
118
126
|
# Calculate starting index for round-robin
|
|
119
127
|
local num_models=${#models[@]}
|
|
120
128
|
local start_index=$((gen_num % num_models))
|
|
121
|
-
|
|
129
|
+
|
|
122
130
|
# Create ordered list based on round-robin
|
|
123
131
|
local ordered_models=()
|
|
124
132
|
for ((i=0; i<num_models; i++)); do
|
|
125
133
|
local idx=$(((start_index + i) % num_models))
|
|
126
134
|
ordered_models+=("${models[$idx]}")
|
|
127
135
|
done
|
|
128
|
-
|
|
136
|
+
|
|
129
137
|
echo "[AI] Model order for ideate (round-robin): ${ordered_models[*]}" >&2
|
|
130
|
-
|
|
138
|
+
|
|
131
139
|
# Try each model until CSV changes
|
|
132
140
|
for model in "${ordered_models[@]}"; do
|
|
133
141
|
echo "[AI] Attempting ideate with $model" >&2
|
|
134
142
|
|
|
135
|
-
# Restore temp CSV before each attempt (in case previous model corrupted it)
|
|
136
|
-
# This
|
|
137
|
-
|
|
138
|
-
cp "$FULL_CSV_PATH" "$temp_csv_file"
|
|
139
|
-
# Recapture original count in case it changed
|
|
140
|
-
original_csv_count=$(grep -v '^[[:space:]]*$' "$temp_csv_file" | tail -n +2 | wc -l)
|
|
141
|
-
fi
|
|
143
|
+
# Restore temp CSV from backup before each attempt (in case previous model corrupted it)
|
|
144
|
+
# This preserves the stub rows that the caller pre-populated
|
|
145
|
+
cp "$temp_csv_backup" "$temp_csv_file"
|
|
142
146
|
|
|
143
147
|
# Call the model directly
|
|
144
148
|
local ai_output
|
|
145
149
|
ai_output=$(call_ai_model_configured "$model" "$prompt")
|
|
146
150
|
local ai_exit_code=$?
|
|
147
151
|
|
|
148
|
-
# Check if the file was modified
|
|
152
|
+
# Check if the file was modified correctly
|
|
149
153
|
if [[ -f "$temp_csv_file" ]]; then
|
|
150
154
|
local new_csv_count
|
|
151
155
|
new_csv_count=$(grep -v '^[[:space:]]*$' "$temp_csv_file" | tail -n +2 | wc -l)
|
|
152
156
|
local added_count=$((new_csv_count - original_csv_count))
|
|
153
157
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
echo "[DEBUG] After $model: original=$original_csv_count, new=$new_csv_count, added=$added_count" >&2
|
|
159
|
+
|
|
160
|
+
# Check if row count is correct (should be same since we're editing stubs, not adding)
|
|
161
|
+
if [[ $new_csv_count -eq $original_csv_count ]]; then
|
|
162
|
+
# Count remaining placeholders - there should be none if AI did its job
|
|
163
|
+
local placeholder_count
|
|
164
|
+
placeholder_count=$(grep -c "PLACEHOLDER" "$temp_csv_file" 2>/dev/null || echo "0")
|
|
165
|
+
|
|
166
|
+
if [[ $placeholder_count -eq 0 ]]; then
|
|
167
|
+
echo "[INFO] CSV modified by $model: filled $expected_count placeholder rows ✓" >&2
|
|
158
168
|
|
|
159
169
|
# Post-process to ensure all description fields are quoted
|
|
160
170
|
local fixed_csv_file="${temp_csv_file}.fixed"
|
|
@@ -167,15 +177,30 @@ call_ai_for_ideation() {
|
|
|
167
177
|
echo "[WARN] CSV format validation failed, using original" >&2
|
|
168
178
|
fi
|
|
169
179
|
|
|
180
|
+
# Clean up backup file
|
|
181
|
+
rm -f "$temp_csv_backup"
|
|
182
|
+
|
|
170
183
|
# Echo the successful model name for caller to capture
|
|
171
184
|
echo "$model"
|
|
172
185
|
return 0
|
|
173
186
|
else
|
|
174
|
-
echo "[WARN] $model
|
|
175
|
-
# Continue to next model
|
|
187
|
+
echo "[WARN] $model left $placeholder_count placeholders unfilled - trying next model" >&2
|
|
188
|
+
# Continue to next model
|
|
176
189
|
fi
|
|
190
|
+
elif [[ $added_count -lt 0 ]]; then
|
|
191
|
+
echo "[WARN] $model deleted rows ($added_count) - trying next model" >&2
|
|
192
|
+
# Continue to next model
|
|
193
|
+
elif [[ $added_count -gt 0 ]]; then
|
|
194
|
+
echo "[WARN] $model added extra rows ($added_count) instead of editing stubs - trying next model" >&2
|
|
195
|
+
# Continue to next model
|
|
177
196
|
else
|
|
178
197
|
echo "[INFO] CSV unchanged after $model (exit code: $ai_exit_code)" >&2
|
|
198
|
+
# Log last few lines of AI output to help debug why it succeeded but didn't change the file
|
|
199
|
+
if [[ -n "$ai_output" ]]; then
|
|
200
|
+
echo "[AI] Last 10 lines of $model output:" >&2
|
|
201
|
+
echo "$ai_output" | tail -n 10 >&2
|
|
202
|
+
echo "[AI] ---" >&2
|
|
203
|
+
fi
|
|
179
204
|
# Continue to next model
|
|
180
205
|
fi
|
|
181
206
|
else
|
|
@@ -185,6 +210,7 @@ call_ai_for_ideation() {
|
|
|
185
210
|
done
|
|
186
211
|
|
|
187
212
|
# All models tried, none changed the file
|
|
213
|
+
rm -f "$temp_csv_backup"
|
|
188
214
|
echo "[ERROR] All AI models failed to generate ideas" >&2
|
|
189
215
|
return 1
|
|
190
216
|
}
|
|
@@ -1102,6 +1128,10 @@ generate_hill_climbing_direct() {
|
|
|
1102
1128
|
local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
|
|
1103
1129
|
cp "$FULL_CSV_PATH" "$temp_csv"
|
|
1104
1130
|
|
|
1131
|
+
# Extract just the IDs from top performers for clarity (needed before pre-populating)
|
|
1132
|
+
local valid_parent_ids
|
|
1133
|
+
valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
|
|
1134
|
+
|
|
1105
1135
|
# Pre-populate the CSV with stub rows containing the correct IDs and parent IDs
|
|
1106
1136
|
echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
|
|
1107
1137
|
# Use first parent as default for stubs (AI will adjust if needed)
|
|
@@ -1124,10 +1154,6 @@ generate_hill_climbing_direct() {
|
|
|
1124
1154
|
# Get existing Python files for this generation to avoid ID collisions
|
|
1125
1155
|
local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
|
|
1126
1156
|
|
|
1127
|
-
# Extract just the IDs from top performers for clarity
|
|
1128
|
-
local valid_parent_ids
|
|
1129
|
-
valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
|
|
1130
|
-
|
|
1131
1157
|
# Use relative paths and change to evolution directory so AI can access files
|
|
1132
1158
|
local temp_csv_basename=$(basename "$temp_csv")
|
|
1133
1159
|
|
|
@@ -1232,6 +1258,10 @@ generate_structural_mutation_direct() {
|
|
|
1232
1258
|
local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
|
|
1233
1259
|
cp "$FULL_CSV_PATH" "$temp_csv"
|
|
1234
1260
|
|
|
1261
|
+
# Extract just the IDs from top performers for clarity (needed before pre-populating)
|
|
1262
|
+
local valid_parent_ids
|
|
1263
|
+
valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
|
|
1264
|
+
|
|
1235
1265
|
# Pre-populate the CSV with stub rows
|
|
1236
1266
|
echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
|
|
1237
1267
|
local first_parent_id
|
|
@@ -1253,10 +1283,6 @@ generate_structural_mutation_direct() {
|
|
|
1253
1283
|
# Get existing Python files for this generation to avoid ID collisions
|
|
1254
1284
|
local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
|
|
1255
1285
|
|
|
1256
|
-
# Extract just the IDs from top performers for clarity
|
|
1257
|
-
local valid_parent_ids
|
|
1258
|
-
valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
|
|
1259
|
-
|
|
1260
1286
|
# Use relative paths and change to evolution directory so AI can access files
|
|
1261
1287
|
local temp_csv_basename=$(basename "$temp_csv")
|
|
1262
1288
|
|
|
@@ -1352,6 +1378,10 @@ generate_crossover_direct() {
|
|
|
1352
1378
|
local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
|
|
1353
1379
|
cp "$FULL_CSV_PATH" "$temp_csv"
|
|
1354
1380
|
|
|
1381
|
+
# Extract just the IDs from top performers for clarity (needed before pre-populating)
|
|
1382
|
+
local valid_parent_ids
|
|
1383
|
+
valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
|
|
1384
|
+
|
|
1355
1385
|
# Pre-populate the CSV with stub rows
|
|
1356
1386
|
echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
|
|
1357
1387
|
local first_parent_id
|
|
@@ -1373,10 +1403,6 @@ generate_crossover_direct() {
|
|
|
1373
1403
|
# Get existing Python files for this generation to avoid ID collisions
|
|
1374
1404
|
local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
|
|
1375
1405
|
|
|
1376
|
-
# Extract just the IDs from top performers for clarity
|
|
1377
|
-
local valid_parent_ids
|
|
1378
|
-
valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
|
|
1379
|
-
|
|
1380
1406
|
# Use relative paths and change to evolution directory so AI can access files
|
|
1381
1407
|
local temp_csv_basename=$(basename "$temp_csv")
|
|
1382
1408
|
|
package/bin/claude-evolve-worker
CHANGED
|
@@ -255,6 +255,14 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
|
255
255
|
|
|
256
256
|
if [[ "$current_status" == "complete" ]]; then
|
|
257
257
|
echo "[WORKER-$$] Already evaluated - skipping"
|
|
258
|
+
# Reset status back to complete since get_next_pending_candidate() set it to running
|
|
259
|
+
"$PYTHON_CMD" -c "
|
|
260
|
+
import sys
|
|
261
|
+
sys.path.insert(0, '$SCRIPT_DIR/..')
|
|
262
|
+
from lib.evolution_csv import EvolutionCSV
|
|
263
|
+
with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
264
|
+
csv.update_candidate_status('$candidate_id', 'complete')
|
|
265
|
+
" 2>/dev/null || true
|
|
258
266
|
return 0
|
|
259
267
|
fi
|
|
260
268
|
|
package/lib/csv_fixer.py
CHANGED
|
@@ -9,6 +9,31 @@ import csv
|
|
|
9
9
|
import sys
|
|
10
10
|
import re
|
|
11
11
|
|
|
12
|
+
def clean_candidate_id(candidate_id):
|
|
13
|
+
"""
|
|
14
|
+
Clean and normalize a candidate ID.
|
|
15
|
+
Returns (cleaned_id, was_modified)
|
|
16
|
+
"""
|
|
17
|
+
if not candidate_id or candidate_id == "id":
|
|
18
|
+
return candidate_id, False
|
|
19
|
+
|
|
20
|
+
original = candidate_id
|
|
21
|
+
cleaned = candidate_id
|
|
22
|
+
|
|
23
|
+
# Strip leading/trailing whitespace
|
|
24
|
+
cleaned = cleaned.strip()
|
|
25
|
+
|
|
26
|
+
# Remove any internal spaces (e.g., "gen01 -001" -> "gen01-001")
|
|
27
|
+
cleaned = re.sub(r'\s+', '', cleaned)
|
|
28
|
+
|
|
29
|
+
# Remove pipe characters and anything before them (line number artifacts)
|
|
30
|
+
if '|' in cleaned:
|
|
31
|
+
# Extract the part after the last pipe
|
|
32
|
+
parts = cleaned.split('|')
|
|
33
|
+
cleaned = parts[-1].strip()
|
|
34
|
+
|
|
35
|
+
return cleaned, (cleaned != original)
|
|
36
|
+
|
|
12
37
|
def is_valid_candidate_id(candidate_id):
|
|
13
38
|
"""
|
|
14
39
|
Check if a candidate ID is valid.
|
|
@@ -25,7 +50,7 @@ def is_valid_candidate_id(candidate_id):
|
|
|
25
50
|
if not candidate_id or candidate_id == "id":
|
|
26
51
|
return True # Header row
|
|
27
52
|
|
|
28
|
-
# Reject IDs containing pipe characters
|
|
53
|
+
# Reject IDs still containing pipe characters after cleaning
|
|
29
54
|
if '|' in candidate_id:
|
|
30
55
|
return False
|
|
31
56
|
|
|
@@ -46,13 +71,14 @@ def fix_csv_format(input_file, output_file):
|
|
|
46
71
|
"""
|
|
47
72
|
Read a CSV file and ensure all fields are properly quoted.
|
|
48
73
|
The csv module handles quoting automatically based on content.
|
|
49
|
-
Also
|
|
74
|
+
Also cleans and validates candidate IDs, filtering out invalid rows.
|
|
50
75
|
"""
|
|
51
76
|
with open(input_file, 'r') as infile:
|
|
52
77
|
reader = csv.reader(infile)
|
|
53
78
|
rows = list(reader)
|
|
54
79
|
|
|
55
80
|
rejected_count = 0
|
|
81
|
+
cleaned_count = 0
|
|
56
82
|
filtered_rows = []
|
|
57
83
|
|
|
58
84
|
for i, row in enumerate(rows):
|
|
@@ -67,14 +93,27 @@ def fix_csv_format(input_file, output_file):
|
|
|
67
93
|
|
|
68
94
|
candidate_id = row[0] if len(row) > 0 else ""
|
|
69
95
|
|
|
70
|
-
#
|
|
71
|
-
|
|
96
|
+
# Clean the candidate ID
|
|
97
|
+
cleaned_id, was_modified = clean_candidate_id(candidate_id)
|
|
98
|
+
|
|
99
|
+
if was_modified:
|
|
100
|
+
cleaned_count += 1
|
|
101
|
+
print(f"[INFO] Cleaned ID: '{candidate_id}' -> '{cleaned_id}'", file=sys.stderr)
|
|
102
|
+
row[0] = cleaned_id
|
|
103
|
+
|
|
104
|
+
# Check if candidate ID is valid after cleaning
|
|
105
|
+
if not is_valid_candidate_id(cleaned_id):
|
|
72
106
|
rejected_count += 1
|
|
73
|
-
print(f"[WARN] Rejecting corrupted record with invalid ID: {candidate_id}", file=sys.stderr)
|
|
107
|
+
print(f"[WARN] Rejecting corrupted record with invalid ID: {candidate_id} (cleaned: {cleaned_id})", file=sys.stderr)
|
|
74
108
|
continue
|
|
75
109
|
|
|
110
|
+
# Trim whitespace from all other fields
|
|
111
|
+
row = [field.strip() if isinstance(field, str) else field for field in row]
|
|
112
|
+
|
|
76
113
|
filtered_rows.append(row)
|
|
77
114
|
|
|
115
|
+
if cleaned_count > 0:
|
|
116
|
+
print(f"[INFO] Cleaned {cleaned_count} IDs (removed spaces, pipes, etc.)", file=sys.stderr)
|
|
78
117
|
if rejected_count > 0:
|
|
79
118
|
print(f"[INFO] Filtered out {rejected_count} corrupted records", file=sys.stderr)
|
|
80
119
|
|