claude-evolve 1.11.1 → 1.11.2
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/lib/__pycache__/ai_cli.cpython-310.pyc +0 -0
- package/lib/__pycache__/ai_cli.cpython-314.pyc +0 -0
- package/lib/__pycache__/embedding.cpython-310.pyc +0 -0
- package/lib/__pycache__/embedding.cpython-314.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-310.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-314.pyc +0 -0
- package/lib/__pycache__/evolve_ideate.cpython-310.pyc +0 -0
- package/lib/__pycache__/evolve_ideate.cpython-314.pyc +0 -0
- package/lib/__pycache__/log.cpython-310.pyc +0 -0
- package/lib/ai-cli.sh +3 -2
- package/lib/evolution_csv.py +33 -0
- package/lib/evolve_ideate.py +68 -26
- package/lib/evolve_run.py +4 -1
- package/lib/evolve_worker.py +4 -1
- package/lib/log.py +78 -1
- package/package.json +1 -1
- package/lib/__pycache__/ai_cli.cpython-311.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-311.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-313.pyc +0 -0
- package/lib/__pycache__/evolve_run.cpython-311.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/__pycache__/llm_bandit.cpython-314.pyc +0 -0
- package/lib/__pycache__/log.cpython-311.pyc +0 -0
- package/lib/__pycache__/log.cpython-314.pyc +0 -0
- package/lib/__pycache__/meta_learning.cpython-314.pyc +0 -0
- package/lib/__pycache__/sandbox_wrapper.cpython-314.pyc +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/ai-cli.sh
CHANGED
|
@@ -55,6 +55,7 @@ EOF
|
|
|
55
55
|
call_ai_model_configured() {
|
|
56
56
|
local model_name="$1"
|
|
57
57
|
local prompt="$2"
|
|
58
|
+
local codex_gpt5_model="${CODEX_GPT5_MODEL:-gpt-5.3}"
|
|
58
59
|
|
|
59
60
|
# Record start time
|
|
60
61
|
local start_time=$(date +%s)
|
|
@@ -96,12 +97,12 @@ $prompt"
|
|
|
96
97
|
;;
|
|
97
98
|
gpt5high)
|
|
98
99
|
local ai_output
|
|
99
|
-
ai_output=$(timeout -k 30 600 codex exec -m
|
|
100
|
+
ai_output=$(timeout -k 30 600 codex exec -m "$codex_gpt5_model" -c model_reasoning_effort="high" --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
|
|
100
101
|
local ai_exit_code=$?
|
|
101
102
|
;;
|
|
102
103
|
gpt5)
|
|
103
104
|
local ai_output
|
|
104
|
-
ai_output=$(timeout -k 30 600 codex exec -m
|
|
105
|
+
ai_output=$(timeout -k 30 600 codex exec -m "$codex_gpt5_model" --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
|
|
105
106
|
local ai_exit_code=$?
|
|
106
107
|
;;
|
|
107
108
|
o3high)
|
package/lib/evolution_csv.py
CHANGED
|
@@ -747,6 +747,39 @@ class EvolutionCSV:
|
|
|
747
747
|
|
|
748
748
|
return max_gen
|
|
749
749
|
|
|
750
|
+
def get_generation_count(self, generation: int) -> int:
|
|
751
|
+
"""
|
|
752
|
+
Count the number of items in a specific generation.
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
generation: Generation number
|
|
756
|
+
|
|
757
|
+
Returns:
|
|
758
|
+
Number of items in that generation
|
|
759
|
+
"""
|
|
760
|
+
rows = self._read_csv()
|
|
761
|
+
if not rows:
|
|
762
|
+
return 0
|
|
763
|
+
|
|
764
|
+
gen_prefix = f"gen{generation:02d}" if generation < 100 else f"gen{generation}"
|
|
765
|
+
has_header = rows and rows[0] and rows[0][0].lower() == 'id'
|
|
766
|
+
start_idx = 1 if has_header else 0
|
|
767
|
+
|
|
768
|
+
count = 0
|
|
769
|
+
for row in rows[start_idx:]:
|
|
770
|
+
if not self.is_valid_candidate_row(row):
|
|
771
|
+
continue
|
|
772
|
+
|
|
773
|
+
candidate_id = row[0].strip().strip('"')
|
|
774
|
+
# Must match exactly gen{N}- to avoid gen92 matching gen920
|
|
775
|
+
if candidate_id.startswith(gen_prefix + '-'):
|
|
776
|
+
# Extra check: ensure the part after gen prefix is the dash
|
|
777
|
+
gen_part = candidate_id.split('-')[0]
|
|
778
|
+
if gen_part == gen_prefix:
|
|
779
|
+
count += 1
|
|
780
|
+
|
|
781
|
+
return count
|
|
782
|
+
|
|
750
783
|
def get_next_id(self, generation: int) -> str:
|
|
751
784
|
"""
|
|
752
785
|
Get the next available ID for a generation.
|
package/lib/evolve_ideate.py
CHANGED
|
@@ -25,6 +25,11 @@ sys.path.insert(0, str(SCRIPT_DIR.parent))
|
|
|
25
25
|
from lib.evolution_csv import EvolutionCSV
|
|
26
26
|
from lib.ai_cli import call_ai_with_backoff, get_git_protection_warning, AIError
|
|
27
27
|
from lib.embedding import check_novelty as check_embedding_novelty, get_embedding, set_cache_file, save_cache
|
|
28
|
+
from lib.log import init_file_logging, log as _log_base
|
|
29
|
+
|
|
30
|
+
def _log(msg: str):
|
|
31
|
+
"""Log with IDEATE prefix."""
|
|
32
|
+
_log_base(msg, prefix="IDEATE")
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
def sample_parent_powerlaw(parents: List[Dict], alpha: float = 1.0) -> Dict:
|
|
@@ -178,34 +183,54 @@ class IdeationStrategy(ABC):
|
|
|
178
183
|
parent = self._get_default_parent(context)
|
|
179
184
|
f.write(f'{id},{parent},"[PLACEHOLDER: Replace with algorithmic idea]",,pending\n')
|
|
180
185
|
|
|
186
|
+
# AIDEV-NOTE: Retry loop for when AI "succeeds" but doesn't edit the file
|
|
187
|
+
# Non-agentic models (kimi, grok) return text but can't edit files
|
|
188
|
+
max_parse_retries = 3
|
|
181
189
|
try:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
for parse_attempt in range(max_parse_retries):
|
|
191
|
+
try:
|
|
192
|
+
# Build prompt
|
|
193
|
+
prompt = self.build_prompt(context, ids, temp_csv.name)
|
|
194
|
+
|
|
195
|
+
# Call AI with round-based retry and backoff
|
|
196
|
+
output, model = call_ai_with_backoff(
|
|
197
|
+
prompt,
|
|
198
|
+
command="ideate",
|
|
199
|
+
working_dir=self.config.evolution_dir,
|
|
200
|
+
max_rounds=max_rounds,
|
|
201
|
+
initial_wait=initial_wait,
|
|
202
|
+
max_wait=max_wait
|
|
203
|
+
)
|
|
194
204
|
|
|
195
|
-
|
|
196
|
-
|
|
205
|
+
# Parse results from modified CSV
|
|
206
|
+
ideas = self._parse_results(temp_csv, ids)
|
|
197
207
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
208
|
+
if ideas:
|
|
209
|
+
# Record model used
|
|
210
|
+
for idea in ideas:
|
|
211
|
+
idea.strategy = f"{self.name} ({model})"
|
|
212
|
+
return ideas
|
|
213
|
+
else:
|
|
214
|
+
# AI returned but didn't edit the file - retry with different model
|
|
215
|
+
if parse_attempt < max_parse_retries - 1:
|
|
216
|
+
print(f"[IDEATE] {model} didn't edit file (attempt {parse_attempt + 1}/{max_parse_retries}), retrying...", file=sys.stderr)
|
|
217
|
+
# Reset temp CSV for next attempt
|
|
218
|
+
shutil.copy(self.config.csv_path, temp_csv)
|
|
219
|
+
with open(temp_csv, 'a') as f:
|
|
220
|
+
for id in ids:
|
|
221
|
+
parent = self._get_default_parent(context)
|
|
222
|
+
f.write(f'{id},{parent},"[PLACEHOLDER: Replace with algorithmic idea]",,pending\n')
|
|
223
|
+
continue
|
|
224
|
+
else:
|
|
225
|
+
print(f"[IDEATE] AI completed but no ideas parsed after {max_parse_retries} attempts", file=sys.stderr)
|
|
226
|
+
return []
|
|
227
|
+
|
|
228
|
+
except AIError as e:
|
|
229
|
+
print(f"[IDEATE] All retries exhausted in {self.name}: {e}", file=sys.stderr)
|
|
230
|
+
return []
|
|
231
|
+
|
|
232
|
+
# Should not reach here
|
|
233
|
+
print(f"[IDEATE] {self.name} failed after all attempts", file=sys.stderr)
|
|
209
234
|
return []
|
|
210
235
|
|
|
211
236
|
finally:
|
|
@@ -427,7 +452,21 @@ class Ideator:
|
|
|
427
452
|
with EvolutionCSV(self.config.csv_path) as csv:
|
|
428
453
|
top_performers = csv.get_top_performers(self.config.num_elites)
|
|
429
454
|
existing_descriptions = csv.get_all_descriptions()
|
|
430
|
-
|
|
455
|
+
|
|
456
|
+
# AIDEV-NOTE: Check if highest generation is complete before creating new one
|
|
457
|
+
# This prevents sparse generations from accumulating
|
|
458
|
+
highest_gen = csv.get_highest_generation()
|
|
459
|
+
if highest_gen > 0:
|
|
460
|
+
gen_count = csv.get_generation_count(highest_gen)
|
|
461
|
+
if gen_count < self.config.total_ideas:
|
|
462
|
+
# Continue incomplete generation
|
|
463
|
+
generation = highest_gen
|
|
464
|
+
print(f"[IDEATE] Continuing incomplete gen{highest_gen} ({gen_count}/{self.config.total_ideas} items)", file=sys.stderr)
|
|
465
|
+
else:
|
|
466
|
+
# Start new generation
|
|
467
|
+
generation = highest_gen + 1
|
|
468
|
+
else:
|
|
469
|
+
generation = 1
|
|
431
470
|
|
|
432
471
|
# Read brief
|
|
433
472
|
brief_content = ""
|
|
@@ -605,6 +644,9 @@ def main():
|
|
|
605
644
|
try:
|
|
606
645
|
config = load_config(args.config)
|
|
607
646
|
|
|
647
|
+
# Initialize file logging in the evolution directory
|
|
648
|
+
init_file_logging(config.evolution_dir)
|
|
649
|
+
|
|
608
650
|
ideator = Ideator(config)
|
|
609
651
|
count = ideator.run()
|
|
610
652
|
|
package/lib/evolve_run.py
CHANGED
|
@@ -26,7 +26,7 @@ SCRIPT_DIR = Path(__file__).parent
|
|
|
26
26
|
sys.path.insert(0, str(SCRIPT_DIR.parent))
|
|
27
27
|
|
|
28
28
|
from lib.evolution_csv import EvolutionCSV
|
|
29
|
-
from lib.log import log, log_error, log_warn, set_prefix
|
|
29
|
+
from lib.log import log, log_error, log_warn, set_prefix, init_file_logging
|
|
30
30
|
from lib.meta_learning import process_new_generations
|
|
31
31
|
set_prefix("RUN")
|
|
32
32
|
|
|
@@ -388,6 +388,9 @@ def main():
|
|
|
388
388
|
try:
|
|
389
389
|
config = load_config(args.config)
|
|
390
390
|
|
|
391
|
+
# Initialize file logging in the evolution directory
|
|
392
|
+
init_file_logging(config.evolution_dir)
|
|
393
|
+
|
|
391
394
|
if args.sequential:
|
|
392
395
|
config.max_workers = 1
|
|
393
396
|
elif args.parallel:
|
package/lib/evolve_worker.py
CHANGED
|
@@ -30,7 +30,7 @@ from typing import Optional, Tuple, Dict, Any
|
|
|
30
30
|
SCRIPT_DIR = Path(__file__).parent
|
|
31
31
|
sys.path.insert(0, str(SCRIPT_DIR.parent))
|
|
32
32
|
|
|
33
|
-
from lib.log import log, log_error, log_warn, log_debug, set_prefix
|
|
33
|
+
from lib.log import log, log_error, log_warn, log_debug, set_prefix, init_file_logging
|
|
34
34
|
set_prefix("WORKER")
|
|
35
35
|
|
|
36
36
|
from lib.evolution_csv import EvolutionCSV
|
|
@@ -750,6 +750,9 @@ def main():
|
|
|
750
750
|
if args.timeout:
|
|
751
751
|
config.timeout_seconds = args.timeout
|
|
752
752
|
|
|
753
|
+
# Initialize file logging in the evolution directory
|
|
754
|
+
init_file_logging(config.evolution_dir)
|
|
755
|
+
|
|
753
756
|
worker = Worker(config)
|
|
754
757
|
sys.exit(worker.run())
|
|
755
758
|
|
package/lib/log.py
CHANGED
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Simple timestamped logging for claude-evolve.
|
|
4
4
|
Uses stderr with flush=True for real-time output.
|
|
5
|
+
Also writes to a log file if configured.
|
|
6
|
+
|
|
7
|
+
AIDEV-NOTE: Set CLAUDE_EVOLVE_LOG_DIR or call init_file_logging() to enable file logging.
|
|
5
8
|
"""
|
|
6
9
|
|
|
7
10
|
import os
|
|
8
11
|
import sys
|
|
9
12
|
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
10
15
|
|
|
11
16
|
# Default prefix, can be set per-module
|
|
12
17
|
_prefix = "EVOLVE"
|
|
13
18
|
|
|
19
|
+
# File logging
|
|
20
|
+
_log_file = None
|
|
21
|
+
_log_dir = None
|
|
22
|
+
|
|
14
23
|
|
|
15
24
|
def set_prefix(prefix: str):
|
|
16
25
|
"""Set the log prefix (e.g., 'WORKER', 'IDEATE', 'RUN')."""
|
|
@@ -18,12 +27,64 @@ def set_prefix(prefix: str):
|
|
|
18
27
|
_prefix = prefix
|
|
19
28
|
|
|
20
29
|
|
|
30
|
+
def init_file_logging(log_dir: Optional[str] = None):
|
|
31
|
+
"""
|
|
32
|
+
Initialize file logging to the specified directory.
|
|
33
|
+
|
|
34
|
+
Creates a log file named evolve_YYYYMMDD.log in the log directory.
|
|
35
|
+
If log_dir is None, uses CLAUDE_EVOLVE_LOG_DIR env var or the current directory.
|
|
36
|
+
"""
|
|
37
|
+
global _log_file, _log_dir
|
|
38
|
+
|
|
39
|
+
if log_dir:
|
|
40
|
+
_log_dir = Path(log_dir)
|
|
41
|
+
elif os.environ.get('CLAUDE_EVOLVE_LOG_DIR'):
|
|
42
|
+
_log_dir = Path(os.environ['CLAUDE_EVOLVE_LOG_DIR'])
|
|
43
|
+
elif os.environ.get('CLAUDE_EVOLVE_CONFIG'):
|
|
44
|
+
# Use the directory containing the config file
|
|
45
|
+
_log_dir = Path(os.environ['CLAUDE_EVOLVE_CONFIG']).parent
|
|
46
|
+
else:
|
|
47
|
+
_log_dir = Path.cwd()
|
|
48
|
+
|
|
49
|
+
_log_dir.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
# Create daily log file
|
|
52
|
+
date_str = datetime.now().strftime("%Y%m%d")
|
|
53
|
+
log_path = _log_dir / f"evolve_{date_str}.log"
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
_log_file = open(log_path, 'a', buffering=1) # Line buffered
|
|
57
|
+
log(f"File logging initialized: {log_path}")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"[WARN] Could not open log file {log_path}: {e}", file=sys.stderr)
|
|
60
|
+
_log_file = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _write_to_file(msg: str):
|
|
64
|
+
"""Write message to log file if configured."""
|
|
65
|
+
global _log_file
|
|
66
|
+
if _log_file:
|
|
67
|
+
try:
|
|
68
|
+
_log_file.write(msg + "\n")
|
|
69
|
+
_log_file.flush()
|
|
70
|
+
except Exception:
|
|
71
|
+
pass # Don't fail on log write errors
|
|
72
|
+
|
|
73
|
+
|
|
21
74
|
def log(msg: str, prefix: str = None):
|
|
22
75
|
"""Log with timestamp. Always flushes for real-time output."""
|
|
23
76
|
ts = datetime.now().strftime("%H:%M:%S")
|
|
77
|
+
date_ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
24
78
|
p = prefix or _prefix
|
|
25
79
|
pid = os.getpid()
|
|
26
|
-
|
|
80
|
+
|
|
81
|
+
# Console output (short timestamp)
|
|
82
|
+
console_msg = f"[{ts}] [{p}-{pid}] {msg}"
|
|
83
|
+
print(console_msg, file=sys.stderr, flush=True)
|
|
84
|
+
|
|
85
|
+
# File output (full timestamp)
|
|
86
|
+
file_msg = f"[{date_ts}] [{p}-{pid}] {msg}"
|
|
87
|
+
_write_to_file(file_msg)
|
|
27
88
|
|
|
28
89
|
|
|
29
90
|
def log_debug(msg: str, prefix: str = None):
|
|
@@ -40,3 +101,19 @@ def log_error(msg: str, prefix: str = None):
|
|
|
40
101
|
def log_warn(msg: str, prefix: str = None):
|
|
41
102
|
"""Log warning message."""
|
|
42
103
|
log(f"[WARN] {msg}", prefix)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def close_log():
|
|
107
|
+
"""Close the log file."""
|
|
108
|
+
global _log_file
|
|
109
|
+
if _log_file:
|
|
110
|
+
try:
|
|
111
|
+
_log_file.close()
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
_log_file = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Auto-initialize file logging if env var is set
|
|
118
|
+
if os.environ.get('CLAUDE_EVOLVE_LOG_DIR') or os.environ.get('CLAUDE_EVOLVE_CONFIG'):
|
|
119
|
+
init_file_logging()
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|