claude-evolve 1.7.20 → 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,13 +177,22 @@ 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
179
198
  # Log last few lines of AI output to help debug why it succeeded but didn't change the file
@@ -191,6 +210,7 @@ call_ai_for_ideation() {
191
210
  done
192
211
 
193
212
  # All models tried, none changed the file
213
+ rm -f "$temp_csv_backup"
194
214
  echo "[ERROR] All AI models failed to generate ideas" >&2
195
215
  return 1
196
216
  }
@@ -1108,6 +1128,10 @@ generate_hill_climbing_direct() {
1108
1128
  local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
1109
1129
  cp "$FULL_CSV_PATH" "$temp_csv"
1110
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
+
1111
1135
  # Pre-populate the CSV with stub rows containing the correct IDs and parent IDs
1112
1136
  echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
1113
1137
  # Use first parent as default for stubs (AI will adjust if needed)
@@ -1130,10 +1154,6 @@ generate_hill_climbing_direct() {
1130
1154
  # Get existing Python files for this generation to avoid ID collisions
1131
1155
  local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
1132
1156
 
1133
- # Extract just the IDs from top performers for clarity
1134
- local valid_parent_ids
1135
- valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
1136
-
1137
1157
  # Use relative paths and change to evolution directory so AI can access files
1138
1158
  local temp_csv_basename=$(basename "$temp_csv")
1139
1159
 
@@ -1238,6 +1258,10 @@ generate_structural_mutation_direct() {
1238
1258
  local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
1239
1259
  cp "$FULL_CSV_PATH" "$temp_csv"
1240
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
+
1241
1265
  # Pre-populate the CSV with stub rows
1242
1266
  echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
1243
1267
  local first_parent_id
@@ -1259,10 +1283,6 @@ generate_structural_mutation_direct() {
1259
1283
  # Get existing Python files for this generation to avoid ID collisions
1260
1284
  local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
1261
1285
 
1262
- # Extract just the IDs from top performers for clarity
1263
- local valid_parent_ids
1264
- valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
1265
-
1266
1286
  # Use relative paths and change to evolution directory so AI can access files
1267
1287
  local temp_csv_basename=$(basename "$temp_csv")
1268
1288
 
@@ -1358,6 +1378,10 @@ generate_crossover_direct() {
1358
1378
  local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
1359
1379
  cp "$FULL_CSV_PATH" "$temp_csv"
1360
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
+
1361
1385
  # Pre-populate the CSV with stub rows
1362
1386
  echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
1363
1387
  local first_parent_id
@@ -1379,10 +1403,6 @@ generate_crossover_direct() {
1379
1403
  # Get existing Python files for this generation to avoid ID collisions
1380
1404
  local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
1381
1405
 
1382
- # Extract just the IDs from top performers for clarity
1383
- local valid_parent_ids
1384
- valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
1385
-
1386
1406
  # Use relative paths and change to evolution directory so AI can access files
1387
1407
  local temp_csv_basename=$(basename "$temp_csv")
1388
1408
 
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.20",
3
+ "version": "1.7.21",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",