claude-evolve 1.8.49 → 1.9.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-ideate +7 -26
- package/bin/claude-evolve-ideate-py +15 -0
- package/bin/claude-evolve-run-py +15 -0
- package/bin/claude-evolve-worker-py +15 -0
- package/lib/__pycache__/ai_cli.cpython-314.pyc +0 -0
- package/lib/__pycache__/embedding.cpython-314.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-314.pyc +0 -0
- package/lib/__pycache__/evolve_ideate.cpython-314.pyc +0 -0
- package/lib/__pycache__/evolve_run.cpython-314.pyc +0 -0
- package/lib/__pycache__/evolve_worker.cpython-314.pyc +0 -0
- package/lib/ai-cli.sh +2 -2
- package/lib/ai_cli.py +196 -0
- package/lib/embedding.py +200 -0
- package/lib/evolution_csv.py +325 -0
- package/lib/evolve_ideate.py +509 -0
- package/lib/evolve_run.py +402 -0
- package/lib/evolve_worker.py +518 -0
- package/package.json +10 -10
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Ideation module for claude-evolve.
|
|
4
|
+
Generates new algorithm ideas using various strategies.
|
|
5
|
+
|
|
6
|
+
AIDEV-NOTE: This is the Python port of bin/claude-evolve-ideate.
|
|
7
|
+
Includes novelty filtering to prevent near-duplicate ideas.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import shutil
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import List, Optional, Dict, Tuple
|
|
20
|
+
|
|
21
|
+
# Add lib to path
|
|
22
|
+
SCRIPT_DIR = Path(__file__).parent
|
|
23
|
+
sys.path.insert(0, str(SCRIPT_DIR.parent))
|
|
24
|
+
|
|
25
|
+
from lib.evolution_csv import EvolutionCSV
|
|
26
|
+
from lib.ai_cli import call_ai, get_git_protection_warning, AIError
|
|
27
|
+
from lib.embedding import check_novelty as check_embedding_novelty, get_embedding, set_cache_file, save_cache
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class IdeationConfig:
|
|
32
|
+
"""Configuration for ideation."""
|
|
33
|
+
csv_path: str
|
|
34
|
+
evolution_dir: str
|
|
35
|
+
brief_path: str
|
|
36
|
+
algorithm_path: str
|
|
37
|
+
|
|
38
|
+
# Strategy counts
|
|
39
|
+
total_ideas: int = 15
|
|
40
|
+
novel_exploration: int = 3
|
|
41
|
+
hill_climbing: int = 5
|
|
42
|
+
structural_mutation: int = 3
|
|
43
|
+
crossover_hybrid: int = 4
|
|
44
|
+
num_elites: int = 3
|
|
45
|
+
|
|
46
|
+
# Novelty filtering
|
|
47
|
+
novelty_enabled: bool = True
|
|
48
|
+
novelty_threshold: float = 0.92
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class Idea:
|
|
53
|
+
"""A generated idea."""
|
|
54
|
+
id: str
|
|
55
|
+
based_on_id: str
|
|
56
|
+
description: str
|
|
57
|
+
strategy: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class IdeationContext:
|
|
62
|
+
"""Context for ideation strategies."""
|
|
63
|
+
generation: int
|
|
64
|
+
top_performers: List[Dict]
|
|
65
|
+
brief_content: str
|
|
66
|
+
existing_descriptions: List[str]
|
|
67
|
+
config: IdeationConfig
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class IdeationStrategy(ABC):
|
|
71
|
+
"""Base class for ideation strategies."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, config: IdeationConfig, csv: EvolutionCSV):
|
|
74
|
+
self.config = config
|
|
75
|
+
self.csv = csv
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def name(self) -> str:
|
|
80
|
+
"""Strategy name."""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def build_prompt(self, context: IdeationContext, ids: List[str], temp_csv_basename: str) -> str:
|
|
85
|
+
"""Build the AI prompt."""
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
def generate(self, context: IdeationContext, count: int) -> List[Idea]:
|
|
89
|
+
"""Generate ideas using this strategy."""
|
|
90
|
+
if count <= 0:
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
print(f"[IDEATE] Running {self.name} strategy for {count} ideas", file=sys.stderr)
|
|
94
|
+
|
|
95
|
+
# Get next IDs
|
|
96
|
+
ids = self.csv.get_next_ids(context.generation, count)
|
|
97
|
+
print(f"[IDEATE] Using IDs: {', '.join(ids)}", file=sys.stderr)
|
|
98
|
+
|
|
99
|
+
# Create temp CSV with stub rows
|
|
100
|
+
temp_csv = Path(self.config.evolution_dir) / f"temp-csv-{os.getpid()}.csv"
|
|
101
|
+
shutil.copy(self.config.csv_path, temp_csv)
|
|
102
|
+
|
|
103
|
+
# Add stub rows
|
|
104
|
+
with open(temp_csv, 'a') as f:
|
|
105
|
+
for id in ids:
|
|
106
|
+
parent = self._get_default_parent(context)
|
|
107
|
+
f.write(f'{id},{parent},"[PLACEHOLDER: Replace with algorithmic idea]",,pending\n')
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
# Build prompt
|
|
111
|
+
prompt = self.build_prompt(context, ids, temp_csv.name)
|
|
112
|
+
|
|
113
|
+
# Call AI
|
|
114
|
+
output, model = call_ai(prompt, command="ideate", working_dir=self.config.evolution_dir)
|
|
115
|
+
|
|
116
|
+
# Parse results from modified CSV
|
|
117
|
+
ideas = self._parse_results(temp_csv, ids)
|
|
118
|
+
|
|
119
|
+
if ideas:
|
|
120
|
+
# Record model used
|
|
121
|
+
for idea in ideas:
|
|
122
|
+
idea.strategy = f"{self.name} ({model})"
|
|
123
|
+
|
|
124
|
+
return ideas
|
|
125
|
+
|
|
126
|
+
except AIError as e:
|
|
127
|
+
print(f"[IDEATE] AI error in {self.name}: {e}", file=sys.stderr)
|
|
128
|
+
return []
|
|
129
|
+
finally:
|
|
130
|
+
temp_csv.unlink(missing_ok=True)
|
|
131
|
+
|
|
132
|
+
def _get_default_parent(self, context: IdeationContext) -> str:
|
|
133
|
+
"""Get default parent ID for this strategy."""
|
|
134
|
+
if context.top_performers:
|
|
135
|
+
return context.top_performers[0]['id']
|
|
136
|
+
return ""
|
|
137
|
+
|
|
138
|
+
def _parse_results(self, temp_csv: Path, expected_ids: List[str]) -> List[Idea]:
|
|
139
|
+
"""Parse ideas from modified CSV."""
|
|
140
|
+
ideas = []
|
|
141
|
+
|
|
142
|
+
with open(temp_csv) as f:
|
|
143
|
+
import csv
|
|
144
|
+
reader = csv.reader(f)
|
|
145
|
+
for row in reader:
|
|
146
|
+
if len(row) >= 3:
|
|
147
|
+
id = row[0].strip().strip('"')
|
|
148
|
+
if id in expected_ids:
|
|
149
|
+
based_on = row[1].strip() if len(row) > 1 else ""
|
|
150
|
+
description = row[2].strip().strip('"')
|
|
151
|
+
# Skip if still placeholder
|
|
152
|
+
if "PLACEHOLDER" not in description and description:
|
|
153
|
+
ideas.append(Idea(
|
|
154
|
+
id=id,
|
|
155
|
+
based_on_id=based_on,
|
|
156
|
+
description=description,
|
|
157
|
+
strategy=self.name
|
|
158
|
+
))
|
|
159
|
+
|
|
160
|
+
return ideas
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class NovelExplorationStrategy(IdeationStrategy):
|
|
164
|
+
"""Generate novel, creative ideas not based on existing algorithms."""
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def name(self) -> str:
|
|
168
|
+
return "novel_exploration"
|
|
169
|
+
|
|
170
|
+
def _get_default_parent(self, context: IdeationContext) -> str:
|
|
171
|
+
return "" # Novel ideas have no parent
|
|
172
|
+
|
|
173
|
+
def build_prompt(self, context: IdeationContext, ids: List[str], temp_csv_basename: str) -> str:
|
|
174
|
+
return f"""{get_git_protection_warning()}
|
|
175
|
+
|
|
176
|
+
I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: {temp_csv_basename}
|
|
177
|
+
|
|
178
|
+
Current evolution context:
|
|
179
|
+
- Generation: {context.generation}
|
|
180
|
+
- Brief: {context.brief_content[:500]}
|
|
181
|
+
|
|
182
|
+
CRITICAL TASK:
|
|
183
|
+
The CSV file already contains stub rows with these IDs: {', '.join(ids)}
|
|
184
|
+
Each stub row has a PLACEHOLDER description.
|
|
185
|
+
Your job is to REPLACE each PLACEHOLDER with a real algorithmic idea description.
|
|
186
|
+
|
|
187
|
+
IMPORTANT FILE READING INSTRUCTIONS:
|
|
188
|
+
Read ONLY the last 20-30 lines of the CSV file to see the placeholder rows.
|
|
189
|
+
DO NOT read the entire file - use offset and limit parameters.
|
|
190
|
+
|
|
191
|
+
CRITICAL INSTRUCTIONS:
|
|
192
|
+
1. Read ONLY the last 20-30 lines of the CSV to see the placeholder rows
|
|
193
|
+
2. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
|
|
194
|
+
3. DO NOT CHANGE THE IDs - they are already correct
|
|
195
|
+
4. Use the Edit tool to replace EACH PLACEHOLDER text with a real algorithmic idea
|
|
196
|
+
5. ALWAYS wrap the description field in double quotes
|
|
197
|
+
6. Each description should be one clear sentence describing a novel algorithmic approach
|
|
198
|
+
7. Focus on creative, ambitious ideas that haven't been tried yet
|
|
199
|
+
|
|
200
|
+
IMPORTANT: Use your file editing tools (Edit/MultiEdit) to modify the CSV file directly."""
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class HillClimbingStrategy(IdeationStrategy):
|
|
204
|
+
"""Generate incremental improvements to top performers."""
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def name(self) -> str:
|
|
208
|
+
return "hill_climbing"
|
|
209
|
+
|
|
210
|
+
def build_prompt(self, context: IdeationContext, ids: List[str], temp_csv_basename: str) -> str:
|
|
211
|
+
top_str = "\n".join(
|
|
212
|
+
f" {p['id']}: {p['description'][:100]}... (score: {p['performance']})"
|
|
213
|
+
for p in context.top_performers[:5]
|
|
214
|
+
)
|
|
215
|
+
valid_parents = ",".join(p['id'] for p in context.top_performers[:5])
|
|
216
|
+
|
|
217
|
+
return f"""{get_git_protection_warning()}
|
|
218
|
+
|
|
219
|
+
I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: {temp_csv_basename}
|
|
220
|
+
|
|
221
|
+
IMPORTANT: You MUST use one of these exact parent IDs: {valid_parents}
|
|
222
|
+
|
|
223
|
+
Successful algorithms to tune:
|
|
224
|
+
{top_str}
|
|
225
|
+
|
|
226
|
+
CRITICAL TASK:
|
|
227
|
+
The CSV file already contains stub rows with these IDs: {', '.join(ids)}
|
|
228
|
+
Your job is to REPLACE each PLACEHOLDER with a parameter tuning idea.
|
|
229
|
+
|
|
230
|
+
INSTRUCTIONS:
|
|
231
|
+
1. Read ONLY the last 20-30 lines of the CSV file
|
|
232
|
+
2. Each idea should be a small parameter adjustment or optimization
|
|
233
|
+
3. Reference which parent you're improving and what specifically you're changing
|
|
234
|
+
4. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
|
|
235
|
+
5. ALWAYS wrap descriptions in double quotes
|
|
236
|
+
6. Use the Edit tool to modify the file directly"""
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class StructuralMutationStrategy(IdeationStrategy):
|
|
240
|
+
"""Generate structural changes to algorithms."""
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def name(self) -> str:
|
|
244
|
+
return "structural_mutation"
|
|
245
|
+
|
|
246
|
+
def build_prompt(self, context: IdeationContext, ids: List[str], temp_csv_basename: str) -> str:
|
|
247
|
+
top_str = "\n".join(
|
|
248
|
+
f" {p['id']}: {p['description'][:100]}..."
|
|
249
|
+
for p in context.top_performers[:5]
|
|
250
|
+
)
|
|
251
|
+
valid_parents = ",".join(p['id'] for p in context.top_performers[:5])
|
|
252
|
+
|
|
253
|
+
return f"""{get_git_protection_warning()}
|
|
254
|
+
|
|
255
|
+
I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: {temp_csv_basename}
|
|
256
|
+
|
|
257
|
+
IMPORTANT: You MUST use one of these exact parent IDs: {valid_parents}
|
|
258
|
+
|
|
259
|
+
Top algorithms for structural changes:
|
|
260
|
+
{top_str}
|
|
261
|
+
|
|
262
|
+
CRITICAL TASK:
|
|
263
|
+
The CSV file already contains stub rows with these IDs: {', '.join(ids)}
|
|
264
|
+
Your job is to REPLACE each PLACEHOLDER with a structural mutation idea.
|
|
265
|
+
|
|
266
|
+
INSTRUCTIONS:
|
|
267
|
+
1. Read ONLY the last 20-30 lines of the CSV file
|
|
268
|
+
2. Each idea should involve a significant architectural change
|
|
269
|
+
3. Examples: adding new features, changing data flow, combining techniques
|
|
270
|
+
4. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
|
|
271
|
+
5. ALWAYS wrap descriptions in double quotes
|
|
272
|
+
6. Use the Edit tool to modify the file directly"""
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class CrossoverStrategy(IdeationStrategy):
|
|
276
|
+
"""Generate crossover ideas combining multiple algorithms."""
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def name(self) -> str:
|
|
280
|
+
return "crossover"
|
|
281
|
+
|
|
282
|
+
def build_prompt(self, context: IdeationContext, ids: List[str], temp_csv_basename: str) -> str:
|
|
283
|
+
top_str = "\n".join(
|
|
284
|
+
f" {p['id']}: {p['description'][:100]}..."
|
|
285
|
+
for p in context.top_performers[:5]
|
|
286
|
+
)
|
|
287
|
+
valid_parents = ",".join(p['id'] for p in context.top_performers[:5])
|
|
288
|
+
|
|
289
|
+
return f"""{get_git_protection_warning()}
|
|
290
|
+
|
|
291
|
+
I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: {temp_csv_basename}
|
|
292
|
+
|
|
293
|
+
IMPORTANT: Reference multiple parents from: {valid_parents}
|
|
294
|
+
|
|
295
|
+
Top algorithms to combine:
|
|
296
|
+
{top_str}
|
|
297
|
+
|
|
298
|
+
CRITICAL TASK:
|
|
299
|
+
The CSV file already contains stub rows with these IDs: {', '.join(ids)}
|
|
300
|
+
Your job is to REPLACE each PLACEHOLDER with a crossover idea.
|
|
301
|
+
|
|
302
|
+
INSTRUCTIONS:
|
|
303
|
+
1. Read ONLY the last 20-30 lines of the CSV file
|
|
304
|
+
2. Each idea should combine elements from 2+ top algorithms
|
|
305
|
+
3. In parent_id, list the main parent (use comma-separated for multiple)
|
|
306
|
+
4. Describe how you're combining the approaches
|
|
307
|
+
5. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
|
|
308
|
+
6. ALWAYS wrap descriptions in double quotes
|
|
309
|
+
7. Use the Edit tool to modify the file directly"""
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class Ideator:
|
|
313
|
+
"""Main ideation controller."""
|
|
314
|
+
|
|
315
|
+
def __init__(self, config: IdeationConfig):
|
|
316
|
+
self.config = config
|
|
317
|
+
self.csv = EvolutionCSV(config.csv_path)
|
|
318
|
+
|
|
319
|
+
# Initialize embedding cache for novelty filtering
|
|
320
|
+
if config.novelty_enabled:
|
|
321
|
+
cache_path = Path(config.evolution_dir) / "embeddings_cache.json"
|
|
322
|
+
set_cache_file(str(cache_path))
|
|
323
|
+
print(f"[IDEATE] Embedding cache: {cache_path}", file=sys.stderr)
|
|
324
|
+
|
|
325
|
+
# Initialize strategies
|
|
326
|
+
self.strategies = [
|
|
327
|
+
(NovelExplorationStrategy(config, self.csv), config.novel_exploration),
|
|
328
|
+
(HillClimbingStrategy(config, self.csv), config.hill_climbing),
|
|
329
|
+
(StructuralMutationStrategy(config, self.csv), config.structural_mutation),
|
|
330
|
+
(CrossoverStrategy(config, self.csv), config.crossover_hybrid),
|
|
331
|
+
]
|
|
332
|
+
|
|
333
|
+
def get_context(self) -> IdeationContext:
|
|
334
|
+
"""Build ideation context."""
|
|
335
|
+
with EvolutionCSV(self.config.csv_path) as csv:
|
|
336
|
+
top_performers = csv.get_top_performers(self.config.num_elites)
|
|
337
|
+
existing_descriptions = csv.get_all_descriptions()
|
|
338
|
+
generation = csv.get_highest_generation() + 1
|
|
339
|
+
|
|
340
|
+
# Read brief
|
|
341
|
+
brief_content = ""
|
|
342
|
+
if Path(self.config.brief_path).exists():
|
|
343
|
+
brief_content = Path(self.config.brief_path).read_text()[:1000]
|
|
344
|
+
|
|
345
|
+
return IdeationContext(
|
|
346
|
+
generation=generation,
|
|
347
|
+
top_performers=top_performers,
|
|
348
|
+
brief_content=brief_content,
|
|
349
|
+
existing_descriptions=existing_descriptions,
|
|
350
|
+
config=self.config
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def check_novelty(self, description: str, existing: List[str]) -> Tuple[bool, float]:
|
|
354
|
+
"""Check if description is novel enough."""
|
|
355
|
+
if not self.config.novelty_enabled:
|
|
356
|
+
return True, 0.0
|
|
357
|
+
|
|
358
|
+
if not existing:
|
|
359
|
+
return True, 0.0
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
is_novel, max_sim = check_embedding_novelty(
|
|
363
|
+
description,
|
|
364
|
+
existing,
|
|
365
|
+
threshold=self.config.novelty_threshold
|
|
366
|
+
)
|
|
367
|
+
return is_novel, max_sim
|
|
368
|
+
except Exception as e:
|
|
369
|
+
print(f"[IDEATE] Novelty check failed: {e}", file=sys.stderr)
|
|
370
|
+
return True, 0.0 # Allow if check fails
|
|
371
|
+
|
|
372
|
+
def run(self) -> int:
|
|
373
|
+
"""Run ideation. Returns number of ideas generated."""
|
|
374
|
+
context = self.get_context()
|
|
375
|
+
print(f"[IDEATE] Starting generation {context.generation}", file=sys.stderr)
|
|
376
|
+
print(f"[IDEATE] Top performers: {len(context.top_performers)}", file=sys.stderr)
|
|
377
|
+
|
|
378
|
+
all_ideas: List[Idea] = []
|
|
379
|
+
strategies_succeeded = 0
|
|
380
|
+
|
|
381
|
+
for strategy, count in self.strategies:
|
|
382
|
+
if count <= 0:
|
|
383
|
+
continue
|
|
384
|
+
|
|
385
|
+
ideas = strategy.generate(context, count)
|
|
386
|
+
|
|
387
|
+
if ideas:
|
|
388
|
+
strategies_succeeded += 1
|
|
389
|
+
|
|
390
|
+
# Filter for novelty
|
|
391
|
+
novel_ideas = []
|
|
392
|
+
for idea in ideas:
|
|
393
|
+
is_novel, similarity = self.check_novelty(
|
|
394
|
+
idea.description,
|
|
395
|
+
context.existing_descriptions + [i.description for i in all_ideas]
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
if is_novel:
|
|
399
|
+
novel_ideas.append(idea)
|
|
400
|
+
print(f"[IDEATE] Accepted: {idea.id} (sim={similarity:.2%})", file=sys.stderr)
|
|
401
|
+
else:
|
|
402
|
+
print(f"[IDEATE] Rejected (too similar {similarity:.2%}): {idea.description[:50]}...", file=sys.stderr)
|
|
403
|
+
|
|
404
|
+
all_ideas.extend(novel_ideas)
|
|
405
|
+
|
|
406
|
+
# Add ideas to CSV
|
|
407
|
+
if all_ideas:
|
|
408
|
+
with EvolutionCSV(self.config.csv_path) as csv:
|
|
409
|
+
candidates = [
|
|
410
|
+
{
|
|
411
|
+
'id': idea.id,
|
|
412
|
+
'basedOnId': idea.based_on_id,
|
|
413
|
+
'description': idea.description,
|
|
414
|
+
'status': 'pending',
|
|
415
|
+
'idea-LLM': idea.strategy
|
|
416
|
+
}
|
|
417
|
+
for idea in all_ideas
|
|
418
|
+
]
|
|
419
|
+
added = csv.append_candidates(candidates)
|
|
420
|
+
print(f"[IDEATE] Added {added} ideas to CSV", file=sys.stderr)
|
|
421
|
+
|
|
422
|
+
print(f"[IDEATE] Strategies succeeded: {strategies_succeeded}/{len([s for s, c in self.strategies if c > 0])}", file=sys.stderr)
|
|
423
|
+
print(f"[IDEATE] Total ideas generated: {len(all_ideas)}", file=sys.stderr)
|
|
424
|
+
|
|
425
|
+
# Final cache save
|
|
426
|
+
if self.config.novelty_enabled:
|
|
427
|
+
save_cache()
|
|
428
|
+
|
|
429
|
+
return len(all_ideas)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def load_config(config_path: Optional[str] = None) -> IdeationConfig:
|
|
433
|
+
"""Load configuration from YAML."""
|
|
434
|
+
import yaml
|
|
435
|
+
|
|
436
|
+
# Find config
|
|
437
|
+
if config_path:
|
|
438
|
+
yaml_path = Path(config_path)
|
|
439
|
+
elif os.environ.get('CLAUDE_EVOLVE_CONFIG'):
|
|
440
|
+
yaml_path = Path(os.environ['CLAUDE_EVOLVE_CONFIG'])
|
|
441
|
+
else:
|
|
442
|
+
yaml_path = Path('evolution/config.yaml')
|
|
443
|
+
if not yaml_path.exists():
|
|
444
|
+
yaml_path = Path('config.yaml')
|
|
445
|
+
|
|
446
|
+
if not yaml_path.exists():
|
|
447
|
+
raise FileNotFoundError(f"Config not found: {yaml_path}")
|
|
448
|
+
|
|
449
|
+
with open(yaml_path) as f:
|
|
450
|
+
data = yaml.safe_load(f) or {}
|
|
451
|
+
|
|
452
|
+
base_dir = yaml_path.parent
|
|
453
|
+
|
|
454
|
+
def resolve(path: str) -> str:
|
|
455
|
+
p = Path(path)
|
|
456
|
+
if not p.is_absolute():
|
|
457
|
+
p = base_dir / p
|
|
458
|
+
return str(p.resolve())
|
|
459
|
+
|
|
460
|
+
ideation = data.get('ideation', {})
|
|
461
|
+
novelty = data.get('novelty', {})
|
|
462
|
+
|
|
463
|
+
return IdeationConfig(
|
|
464
|
+
csv_path=resolve(data.get('csv_file', 'evolution.csv')),
|
|
465
|
+
evolution_dir=str(base_dir.resolve()),
|
|
466
|
+
brief_path=resolve(data.get('brief_file', 'BRIEF.md')),
|
|
467
|
+
algorithm_path=resolve(data.get('algorithm_file', 'algorithm.py')),
|
|
468
|
+
total_ideas=ideation.get('total_ideas', 15),
|
|
469
|
+
novel_exploration=ideation.get('novel_exploration', 3),
|
|
470
|
+
hill_climbing=ideation.get('hill_climbing', 5),
|
|
471
|
+
structural_mutation=ideation.get('structural_mutation', 3),
|
|
472
|
+
crossover_hybrid=ideation.get('crossover_hybrid', 4),
|
|
473
|
+
num_elites=ideation.get('num_elites', 3),
|
|
474
|
+
novelty_enabled=novelty.get('enabled', True),
|
|
475
|
+
novelty_threshold=novelty.get('threshold', 0.92)
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def main():
|
|
480
|
+
parser = argparse.ArgumentParser(description='Claude Evolve Ideation')
|
|
481
|
+
parser.add_argument('--config', help='Path to config.yaml')
|
|
482
|
+
parser.add_argument('--count', type=int, help='Override total idea count')
|
|
483
|
+
args = parser.parse_args()
|
|
484
|
+
|
|
485
|
+
try:
|
|
486
|
+
config = load_config(args.config)
|
|
487
|
+
|
|
488
|
+
ideator = Ideator(config)
|
|
489
|
+
count = ideator.run()
|
|
490
|
+
|
|
491
|
+
if count > 0:
|
|
492
|
+
print(f"Generated {count} ideas", file=sys.stderr)
|
|
493
|
+
sys.exit(0)
|
|
494
|
+
else:
|
|
495
|
+
print("No ideas generated", file=sys.stderr)
|
|
496
|
+
sys.exit(1)
|
|
497
|
+
|
|
498
|
+
except FileNotFoundError as e:
|
|
499
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
500
|
+
sys.exit(1)
|
|
501
|
+
except Exception as e:
|
|
502
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
503
|
+
import traceback
|
|
504
|
+
traceback.print_exc()
|
|
505
|
+
sys.exit(1)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
if __name__ == '__main__':
|
|
509
|
+
main()
|