claude-evolve 1.5.2 → 1.5.4

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.
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CSV format fixer for claude-evolve
4
+ Ensures proper quoting of CSV fields, especially descriptions
5
+ """
6
+
7
+ import csv
8
+ import sys
9
+
10
+ def fix_csv_format(input_file, output_file):
11
+ """
12
+ Read a CSV file and ensure all fields are properly quoted.
13
+ The csv module handles quoting automatically based on content.
14
+ """
15
+ with open(input_file, 'r') as infile:
16
+ reader = csv.reader(infile)
17
+ rows = list(reader)
18
+
19
+ with open(output_file, 'w', newline='') as outfile:
20
+ writer = csv.writer(outfile, quoting=csv.QUOTE_NONNUMERIC)
21
+
22
+ # Write all rows - csv.writer handles quoting automatically
23
+ for row in rows:
24
+ writer.writerow(row)
25
+
26
+ if __name__ == "__main__":
27
+ if len(sys.argv) != 3:
28
+ print("Usage: csv_fixer.py <input_file> <output_file>", file=sys.stderr)
29
+ sys.exit(1)
30
+
31
+ try:
32
+ fix_csv_format(sys.argv[1], sys.argv[2])
33
+ except Exception as e:
34
+ print(f"Error fixing CSV: {e}", file=sys.stderr)
35
+ sys.exit(1)
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Memory-limited execution wrapper for claude-evolve evaluations.
4
+
5
+ This script runs a command with memory limits to prevent runaway algorithms
6
+ from consuming all system memory and crashing the machine.
7
+ """
8
+ import sys
9
+ import os
10
+ import subprocess
11
+ import signal
12
+ import time
13
+ import resource
14
+ from typing import Optional
15
+
16
+ def set_memory_limit(limit_mb: int) -> None:
17
+ """Set memory limit in MB using resource module."""
18
+ try:
19
+ # Convert MB to bytes
20
+ limit_bytes = limit_mb * 1024 * 1024
21
+
22
+ # Set virtual memory limit (address space)
23
+ # On macOS this is the most reliable way to limit memory
24
+ resource.setrlimit(resource.RLIMIT_AS, (limit_bytes, limit_bytes))
25
+
26
+ # Also try to set data segment limit if available
27
+ try:
28
+ resource.setrlimit(resource.RLIMIT_DATA, (limit_bytes, limit_bytes))
29
+ except (OSError, ValueError):
30
+ # Not available on all systems
31
+ pass
32
+
33
+ print(f"[MEMORY] Set memory limit to {limit_mb}MB", file=sys.stderr)
34
+
35
+ except (OSError, ValueError) as e:
36
+ print(f"[MEMORY] Warning: Could not set memory limit: {e}", file=sys.stderr)
37
+
38
+ def monitor_memory_usage_native(process: subprocess.Popen, limit_mb: int) -> Optional[str]:
39
+ """Monitor process memory usage using native tools and kill if it exceeds limits."""
40
+ # print(f"[MEMORY] Starting native monitoring for PID {process.pid} with limit {limit_mb}MB", file=sys.stderr)
41
+
42
+ while process.poll() is None:
43
+ try:
44
+ # Use ps command to get memory usage
45
+ ps_result = subprocess.run(
46
+ ["ps", "-o", "rss=", "-p", str(process.pid)],
47
+ capture_output=True,
48
+ text=True,
49
+ timeout=1
50
+ )
51
+
52
+ if ps_result.returncode == 0 and ps_result.stdout.strip():
53
+ # ps returns RSS in KB, convert to MB
54
+ memory_kb = int(ps_result.stdout.strip())
55
+ memory_mb = memory_kb / 1024
56
+
57
+ # print(f"[MEMORY] PID {process.pid} using {memory_mb:.1f}MB (limit: {limit_mb}MB)", file=sys.stderr)
58
+
59
+ if memory_mb > limit_mb:
60
+ print(f"[MEMORY] Process exceeded {limit_mb}MB limit (using {memory_mb:.1f}MB), terminating", file=sys.stderr)
61
+ # Kill the entire process group
62
+ try:
63
+ os.killpg(os.getpgid(process.pid), signal.SIGTERM)
64
+ time.sleep(2) # Give it time to cleanup
65
+ if process.poll() is None:
66
+ os.killpg(os.getpgid(process.pid), signal.SIGKILL)
67
+ except ProcessLookupError:
68
+ pass
69
+ return f"Memory limit exceeded: {memory_mb:.1f}MB > {limit_mb}MB"
70
+
71
+ time.sleep(0.5) # Check every 500ms
72
+
73
+ except (subprocess.TimeoutExpired, ValueError, ProcessLookupError):
74
+ # Process might have terminated or ps command failed
75
+ time.sleep(0.5)
76
+ continue
77
+
78
+ # print(f"[MEMORY] Monitoring stopped for PID {process.pid}", file=sys.stderr)
79
+ return None
80
+
81
+ def monitor_memory_usage(process: subprocess.Popen, limit_mb: int) -> Optional[str]:
82
+ """Monitor process memory usage and kill if it exceeds limits."""
83
+ try:
84
+ import psutil
85
+ ps_process = psutil.Process(process.pid)
86
+
87
+ while process.poll() is None:
88
+ try:
89
+ # Get memory usage in MB
90
+ memory_info = ps_process.memory_info()
91
+ memory_mb = memory_info.rss / (1024 * 1024)
92
+
93
+ if memory_mb > limit_mb:
94
+ print(f"[MEMORY] Process exceeded {limit_mb}MB limit (using {memory_mb:.1f}MB), terminating", file=sys.stderr)
95
+ # Kill the entire process group
96
+ try:
97
+ os.killpg(os.getpgid(process.pid), signal.SIGTERM)
98
+ time.sleep(2) # Give it time to cleanup
99
+ if process.poll() is None:
100
+ os.killpg(os.getpgid(process.pid), signal.SIGKILL)
101
+ except ProcessLookupError:
102
+ pass
103
+ return f"Memory limit exceeded: {memory_mb:.1f}MB > {limit_mb}MB"
104
+
105
+ time.sleep(0.5) # Check every 500ms
106
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
107
+ # Process already terminated
108
+ break
109
+ except ImportError:
110
+ # psutil not available, use native monitoring
111
+ return monitor_memory_usage_native(process, limit_mb)
112
+
113
+ return None
114
+
115
+ def main():
116
+ if len(sys.argv) < 3:
117
+ print("Usage: memory_limit_wrapper.py <memory_limit_mb> <command> [args...]", file=sys.stderr)
118
+ sys.exit(1)
119
+
120
+ try:
121
+ memory_limit_mb = int(sys.argv[1])
122
+ except ValueError:
123
+ print(f"Error: Invalid memory limit '{sys.argv[1]}' - must be integer MB", file=sys.stderr)
124
+ sys.exit(1)
125
+
126
+ command = sys.argv[2:]
127
+
128
+ if memory_limit_mb <= 0:
129
+ print("[MEMORY] No memory limit set (0 or negative value)", file=sys.stderr)
130
+ # Just exec the command directly without limits
131
+ os.execvp(command[0], command)
132
+
133
+ # Set memory limits for this process (inherited by subprocess)
134
+ set_memory_limit(memory_limit_mb)
135
+
136
+ try:
137
+ # Start process in new process group for easier cleanup
138
+ process = subprocess.Popen(
139
+ command,
140
+ preexec_fn=os.setsid, # Create new process group
141
+ stdout=subprocess.PIPE,
142
+ stderr=subprocess.STDOUT,
143
+ universal_newlines=True,
144
+ bufsize=1 # Line buffered
145
+ )
146
+
147
+ # Monitor memory usage in background
148
+ memory_error = None
149
+ import threading
150
+
151
+ def memory_monitor():
152
+ nonlocal memory_error
153
+ memory_error = monitor_memory_usage(process, memory_limit_mb)
154
+
155
+ monitor_thread = threading.Thread(target=memory_monitor, daemon=True)
156
+ monitor_thread.start()
157
+
158
+ # Stream output in real-time
159
+ while True:
160
+ output = process.stdout.readline()
161
+ if output == '' and process.poll() is not None:
162
+ break
163
+ if output:
164
+ print(output.rstrip())
165
+ sys.stdout.flush()
166
+
167
+ # Wait for completion
168
+ return_code = process.wait()
169
+
170
+ # Check if we killed it due to memory
171
+ if memory_error:
172
+ print(f"[MEMORY] {memory_error}", file=sys.stderr)
173
+ sys.exit(137) # 128 + SIGKILL
174
+
175
+ sys.exit(return_code)
176
+
177
+ except FileNotFoundError:
178
+ print(f"Error: Command not found: {command[0]}", file=sys.stderr)
179
+ sys.exit(127)
180
+ except KeyboardInterrupt:
181
+ print("[MEMORY] Interrupted by user", file=sys.stderr)
182
+ try:
183
+ os.killpg(os.getpgid(process.pid), signal.SIGTERM)
184
+ except:
185
+ pass
186
+ sys.exit(130)
187
+ except Exception as e:
188
+ print(f"[MEMORY] Unexpected error: {e}", file=sys.stderr)
189
+ sys.exit(1)
190
+
191
+ if __name__ == "__main__":
192
+ main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-evolve",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",
@@ -46,6 +46,11 @@ auto_ideate: true
46
46
  # Maximum number of retries for failed candidates before marking as permanently failed
47
47
  max_retries: 3
48
48
 
49
+ # Memory protection configuration
50
+ # Memory limit in MB for evaluation processes (0 = no limit)
51
+ # This prevents runaway algorithms from consuming all system memory
52
+ memory_limit_mb: 2048
53
+
49
54
  # Parallel execution configuration
50
55
  parallel:
51
56
  # Enable parallel execution of evolution candidates
@@ -59,16 +64,11 @@ parallel:
59
64
 
60
65
  # LLM/AI CLI configuration
61
66
  llm_cli:
62
- # How to run each CLI for each LLM option
63
- # {{PROMPT}} will be replaced with the actual prompt text
64
- o3: 'codex exec -m o3 --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
65
- codex: 'codex exec --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
66
- gemini: 'gemini -y -p "{{PROMPT}}"'
67
- opus: 'claude --dangerously-skip-permissions --model opus -p "{{PROMPT}}"'
68
- sonnet: 'claude --dangerously-skip-permissions --model sonnet -p "{{PROMPT}}"'
69
-
70
67
  # What to run for each sub-command
71
68
  # Models are tried in order, with round-robin distribution across candidates
72
69
  # You can repeat models for weighted selection (e.g., "sonnet sonnet gemini" for 2:1 ratio)
73
- run: sonnet gemini
74
- ideate: opus o3
70
+
71
+ # commented out because these change over time; if you want to fix them in a particular
72
+ # configuration, uncomment them and set them
73
+ #run: sonnet
74
+ #ideate: gemini gpt-5 opus