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/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 gpt-5.2 -c model_reasoning_effort="high" --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
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 gpt-5.2 --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
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)
@@ -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.
@@ -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
- # Build prompt
183
- prompt = self.build_prompt(context, ids, temp_csv.name)
184
-
185
- # Call AI with round-based retry and backoff
186
- output, model = call_ai_with_backoff(
187
- prompt,
188
- command="ideate",
189
- working_dir=self.config.evolution_dir,
190
- max_rounds=max_rounds,
191
- initial_wait=initial_wait,
192
- max_wait=max_wait
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
- # Parse results from modified CSV
196
- ideas = self._parse_results(temp_csv, ids)
205
+ # Parse results from modified CSV
206
+ ideas = self._parse_results(temp_csv, ids)
197
207
 
198
- if ideas:
199
- # Record model used
200
- for idea in ideas:
201
- idea.strategy = f"{self.name} ({model})"
202
- return ideas
203
- else:
204
- print(f"[IDEATE] AI completed but no ideas parsed from output", file=sys.stderr)
205
- return []
206
-
207
- except AIError as e:
208
- print(f"[IDEATE] All retries exhausted in {self.name}: {e}", file=sys.stderr)
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
- generation = csv.get_highest_generation() + 1
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:
@@ -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
- print(f"[{ts}] [{p}-{pid}] {msg}", file=sys.stderr, flush=True)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-evolve",
3
- "version": "1.11.1",
3
+ "version": "1.11.2",
4
4
  "bin": {
5
5
  "claude-evolve": "bin/claude-evolve",
6
6
  "claude-evolve-main": "bin/claude-evolve-main",
Binary file
Binary file