claude-evolve 1.4.12 → 1.5.0
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-autostatus +117 -110
- package/bin/claude-evolve-edit +82 -6
- package/bin/claude-evolve-ideate +604 -209
- package/bin/claude-evolve-ideate.debug +907 -0
- package/bin/claude-evolve-run +49 -7
- package/bin/claude-evolve-worker +249 -25
- package/lib/__pycache__/evolution_csv.cpython-311.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-313.pyc +0 -0
- package/lib/evolution_csv.py +36 -2
- package/lib/validate_parent_ids.py +232 -0
- package/package.json +1 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Validate parent IDs in AI-generated ideas for claude-evolve ideation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import csv
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import re
|
|
10
|
+
from typing import List, Set, Dict, Tuple, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_valid_parent_ids(csv_path: str) -> Set[str]:
|
|
14
|
+
"""Extract all valid candidate IDs from the CSV that can be used as parents."""
|
|
15
|
+
valid_ids = set()
|
|
16
|
+
valid_ids.add("") # Empty string is valid for novel ideas
|
|
17
|
+
valid_ids.add("000") # Special ID for baseline algorithm
|
|
18
|
+
valid_ids.add("0") # Alternative baseline ID
|
|
19
|
+
valid_ids.add("gen00-000") # Another baseline format
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
with open(csv_path, 'r') as f:
|
|
23
|
+
reader = csv.reader(f)
|
|
24
|
+
next(reader, None) # Skip header
|
|
25
|
+
for row in reader:
|
|
26
|
+
if row and len(row) > 0:
|
|
27
|
+
candidate_id = row[0].strip()
|
|
28
|
+
if candidate_id:
|
|
29
|
+
valid_ids.add(candidate_id)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"[ERROR] Failed to read CSV: {e}", file=sys.stderr)
|
|
32
|
+
|
|
33
|
+
return valid_ids
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_and_fix_parent_id(parent_id: str, valid_ids: Set[str], idea_type: str,
|
|
37
|
+
top_performers: Optional[List[Tuple[str, str, float]]] = None) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Validate a parent ID and fix it if invalid.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
parent_id: The parent ID to validate
|
|
43
|
+
valid_ids: Set of valid parent IDs
|
|
44
|
+
idea_type: Type of idea (novel, hill-climbing, structural, crossover)
|
|
45
|
+
top_performers: List of (id, description, score) tuples for non-novel ideas
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A valid parent ID (may be fixed)
|
|
49
|
+
"""
|
|
50
|
+
# Novel ideas should have empty parent
|
|
51
|
+
if idea_type == "novel":
|
|
52
|
+
return ""
|
|
53
|
+
|
|
54
|
+
# Check if parent ID is valid
|
|
55
|
+
if parent_id in valid_ids:
|
|
56
|
+
return parent_id
|
|
57
|
+
|
|
58
|
+
# For non-novel ideas, we need a valid parent
|
|
59
|
+
if top_performers and len(top_performers) > 0:
|
|
60
|
+
# Return the first top performer's ID
|
|
61
|
+
return top_performers[0][0]
|
|
62
|
+
|
|
63
|
+
# If no top performers, return empty (will be caught as error later)
|
|
64
|
+
return ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_ai_line(line: str, idea_type: str) -> Tuple[str, str]:
|
|
68
|
+
"""
|
|
69
|
+
Parse a line from AI output to extract parent ID and description.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Tuple of (parent_id, description)
|
|
73
|
+
"""
|
|
74
|
+
line = line.strip()
|
|
75
|
+
parent_id = ""
|
|
76
|
+
description = line
|
|
77
|
+
|
|
78
|
+
if idea_type != "novel":
|
|
79
|
+
# Look for "From X:" pattern
|
|
80
|
+
match = re.match(r'^From\s+([^:]+):\s*(.+)$', line, re.IGNORECASE)
|
|
81
|
+
if match:
|
|
82
|
+
parent_id = match.group(1).strip()
|
|
83
|
+
description = match.group(2).strip()
|
|
84
|
+
|
|
85
|
+
return parent_id, description
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def validate_ai_output(ai_output: str, count: int, idea_type: str, csv_path: str,
|
|
89
|
+
top_performers_str: str = "") -> List[Dict[str, str]]:
|
|
90
|
+
"""
|
|
91
|
+
Validate AI output and return validated ideas.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
ai_output: Raw AI output
|
|
95
|
+
count: Expected number of ideas
|
|
96
|
+
idea_type: Type of idea (novel, hill-climbing, structural, crossover)
|
|
97
|
+
csv_path: Path to CSV file
|
|
98
|
+
top_performers_str: String containing top performers (format: "id,description,score\n...")
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of validated ideas with 'parent_id' and 'description' keys
|
|
102
|
+
"""
|
|
103
|
+
# Get valid parent IDs
|
|
104
|
+
valid_ids = get_valid_parent_ids(csv_path)
|
|
105
|
+
|
|
106
|
+
# Parse top performers
|
|
107
|
+
top_performers = []
|
|
108
|
+
if top_performers_str:
|
|
109
|
+
for line in top_performers_str.strip().split('\n'):
|
|
110
|
+
if line:
|
|
111
|
+
parts = line.split(',', 2)
|
|
112
|
+
if len(parts) >= 3:
|
|
113
|
+
try:
|
|
114
|
+
top_performers.append((parts[0], parts[1], float(parts[2])))
|
|
115
|
+
except ValueError:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
# Process AI output
|
|
119
|
+
validated_ideas = []
|
|
120
|
+
lines = ai_output.strip().split('\n')
|
|
121
|
+
|
|
122
|
+
print(f"[DEBUG] Processing {len(lines)} lines from AI output for {idea_type} ideas", file=sys.stderr)
|
|
123
|
+
|
|
124
|
+
for line in lines:
|
|
125
|
+
# Skip empty lines and metadata
|
|
126
|
+
if not line or line.strip() == '' or line.startswith('#') or line.startswith('[') or line.startswith('=='):
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
# Skip debug/info messages from AI tools
|
|
130
|
+
if line.strip().startswith('[INFO]') or line.strip().startswith('[WARN]') or line.strip().startswith('[ERROR]') or line.strip().startswith('[DEBUG]'):
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
# Clean the line
|
|
134
|
+
line = line.strip()
|
|
135
|
+
line = re.sub(r'^[0-9]+\.?\s*', '', line) # Remove numbering
|
|
136
|
+
line = re.sub(r'^-\s*', '', line) # Remove bullet points
|
|
137
|
+
|
|
138
|
+
# Parse parent ID and description
|
|
139
|
+
parent_id, description = parse_ai_line(line, idea_type)
|
|
140
|
+
|
|
141
|
+
# Validate parent ID
|
|
142
|
+
if parent_id and parent_id not in valid_ids:
|
|
143
|
+
print(f"[WARN] Invalid parent ID '{parent_id}' for {idea_type} idea - fixing...", file=sys.stderr)
|
|
144
|
+
print(f"[INFO] Valid parent IDs are: {', '.join(sorted([id for id in valid_ids if id]))[:200]}...", file=sys.stderr)
|
|
145
|
+
parent_id = validate_and_fix_parent_id(parent_id, valid_ids, idea_type, top_performers)
|
|
146
|
+
print(f"[INFO] Fixed parent ID to: '{parent_id}'", file=sys.stderr)
|
|
147
|
+
|
|
148
|
+
# For non-novel ideas, ensure we have a parent
|
|
149
|
+
if idea_type != "novel" and not parent_id:
|
|
150
|
+
if top_performers:
|
|
151
|
+
parent_id = top_performers[0][0]
|
|
152
|
+
print(f"[INFO] Assigned parent ID '{parent_id}' to idea without parent", file=sys.stderr)
|
|
153
|
+
else:
|
|
154
|
+
print(f"[ERROR] Non-novel idea without parent and no top performers available", file=sys.stderr)
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
# Skip if description is too short or contains shell artifacts
|
|
158
|
+
if len(description) < 20:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
if any(word in description for word in ['EOF', '/dev/null', '<<<', '>>>', '#!/bin/bash']):
|
|
162
|
+
print(f"[WARN] Skipping description with shell artifacts: {description[:50]}...", file=sys.stderr)
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
validated_ideas.append({
|
|
166
|
+
'parent_id': parent_id,
|
|
167
|
+
'description': description
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
if len(validated_ideas) >= count:
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
return validated_ideas
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def main():
|
|
177
|
+
"""Main entry point for validation script."""
|
|
178
|
+
if len(sys.argv) < 5:
|
|
179
|
+
print("Usage: validate_parent_ids.py <ai_output_file> <count> <idea_type> <csv_path> [top_performers_file]", file=sys.stderr)
|
|
180
|
+
sys.exit(1)
|
|
181
|
+
|
|
182
|
+
ai_output_file = sys.argv[1]
|
|
183
|
+
count = int(sys.argv[2])
|
|
184
|
+
idea_type = sys.argv[3]
|
|
185
|
+
csv_path = sys.argv[4]
|
|
186
|
+
top_performers_file = sys.argv[5] if len(sys.argv) > 5 else None
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
# Read AI output
|
|
190
|
+
with open(ai_output_file, 'r') as f:
|
|
191
|
+
ai_output = f.read()
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print(f"[ERROR] Failed to read AI output file {ai_output_file}: {e}", file=sys.stderr)
|
|
194
|
+
sys.exit(1)
|
|
195
|
+
|
|
196
|
+
# Read top performers if provided
|
|
197
|
+
top_performers_str = ""
|
|
198
|
+
if top_performers_file and top_performers_file != "none":
|
|
199
|
+
try:
|
|
200
|
+
with open(top_performers_file, 'r') as f:
|
|
201
|
+
top_performers_str = f.read()
|
|
202
|
+
except Exception as e:
|
|
203
|
+
print(f"[WARN] Failed to read top performers file {top_performers_file}: {e}", file=sys.stderr)
|
|
204
|
+
|
|
205
|
+
# Check if AI output is empty or looks like an error
|
|
206
|
+
if not ai_output.strip():
|
|
207
|
+
print(f"[ERROR] AI output is empty", file=sys.stderr)
|
|
208
|
+
sys.exit(1)
|
|
209
|
+
|
|
210
|
+
if len(ai_output) < 50:
|
|
211
|
+
print(f"[WARN] AI output is suspiciously short: {ai_output}", file=sys.stderr)
|
|
212
|
+
|
|
213
|
+
# Validate
|
|
214
|
+
validated_ideas = validate_ai_output(ai_output, count, idea_type, csv_path, top_performers_str)
|
|
215
|
+
|
|
216
|
+
# Output validated ideas as JSON
|
|
217
|
+
print(json.dumps(validated_ideas))
|
|
218
|
+
|
|
219
|
+
# Return error ONLY if no valid ideas at all
|
|
220
|
+
if len(validated_ideas) == 0:
|
|
221
|
+
print(f"[ERROR] No valid ideas found in AI output. First 500 chars:", file=sys.stderr)
|
|
222
|
+
print(ai_output[:500], file=sys.stderr)
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
elif len(validated_ideas) < count:
|
|
225
|
+
print(f"[WARN] Only validated {len(validated_ideas)} out of {count} requested {idea_type} ideas", file=sys.stderr)
|
|
226
|
+
print(f"[INFO] AI appears to have generated fewer ideas than requested.", file=sys.stderr)
|
|
227
|
+
print(f"[INFO] Proceeding with {len(validated_ideas)} valid ideas.", file=sys.stderr)
|
|
228
|
+
# Don't exit with error - we have some valid ideas!
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
if __name__ == "__main__":
|
|
232
|
+
main()
|