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.
@@ -95,66 +95,76 @@ call_ai_for_ideation() {
95
95
  gen_num=1
96
96
  fi
97
97
 
98
- # Get the current row count before any modifications
99
- local original_csv_count
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
- original_csv_count=$(grep -v '^[[:space:]]*$' "$temp_csv_file" | tail -n +2 | wc -l)
102
+ cp "$temp_csv_file" "$temp_csv_backup"
102
103
  else
103
- original_csv_count=0
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 ensures each model starts with the original data
137
- if [[ -f "$FULL_CSV_PATH" ]]; then
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 AND has correct count
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
- if [[ $added_count -gt 0 ]]; then
155
- # Validate count BEFORE accepting
156
- if [[ $added_count -eq $expected_count ]]; then
157
- echo "[INFO] CSV modified by $model: added $added_count/$expected_count rows ✓" >&2
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 added wrong count: $added_count (expected $expected_count) - trying next model" >&2
175
- # Continue to next model - maybe another will get it right
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
 
@@ -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 (line number artifacts)
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 filters out rows with invalid candidate IDs.
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
- # Check if candidate ID is valid
71
- if not is_valid_candidate_id(candidate_id):
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-evolve",
3
- "version": "1.7.18",
3
+ "version": "1.7.21",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",