code_healer 0.1.3 โ 0.1.6
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/lib/code_healer/config_manager.rb +28 -0
- data/lib/code_healer/evolution_job.rb +118 -48
- data/lib/code_healer/healing_job.rb +160 -140
- data/lib/code_healer/healing_workspace_manager.rb +296 -0
- data/lib/code_healer/setup.rb +219 -65
- data/lib/code_healer/version.rb +1 -1
- data/lib/code_healer.rb +1 -0
- metadata +3 -2
@@ -0,0 +1,296 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module CodeHealer
|
5
|
+
# Manages isolated healing workspaces for safe code evolution
|
6
|
+
class HealingWorkspaceManager
|
7
|
+
class << self
|
8
|
+
def create_healing_workspace(repo_path, branch_name = nil)
|
9
|
+
puts "๐ฅ [WORKSPACE] Starting workspace creation..."
|
10
|
+
puts "๐ฅ [WORKSPACE] Repo path: #{repo_path}"
|
11
|
+
puts "๐ฅ [WORKSPACE] Branch name: #{branch_name || 'current'}"
|
12
|
+
|
13
|
+
config = CodeHealer::ConfigManager.code_heal_directory_config
|
14
|
+
puts "๐ฅ [WORKSPACE] Raw config: #{config.inspect}"
|
15
|
+
|
16
|
+
base_path = config['path'] || config[:path] || '/tmp/code_healer_workspaces'
|
17
|
+
puts "๐ฅ [WORKSPACE] Base heal dir: #{base_path}"
|
18
|
+
|
19
|
+
# Create unique workspace directory
|
20
|
+
workspace_id = "healing_#{Time.now.to_i}_#{SecureRandom.hex(4)}"
|
21
|
+
workspace_path = File.join(base_path, workspace_id)
|
22
|
+
|
23
|
+
puts "๐ฅ [WORKSPACE] Workspace ID: #{workspace_id}"
|
24
|
+
puts "๐ฅ [WORKSPACE] Full workspace path: #{workspace_path}"
|
25
|
+
|
26
|
+
begin
|
27
|
+
puts "๐ฅ [WORKSPACE] Creating base directory..."
|
28
|
+
# Ensure code heal directory exists
|
29
|
+
FileUtils.mkdir_p(base_path)
|
30
|
+
puts "๐ฅ [WORKSPACE] Base directory created/verified: #{base_path}"
|
31
|
+
|
32
|
+
# Clone current branch to workspace
|
33
|
+
strategy = clone_strategy
|
34
|
+
puts "๐ฅ [WORKSPACE] Clone strategy: #{strategy}"
|
35
|
+
|
36
|
+
if strategy == "branch"
|
37
|
+
puts "๐ฅ [WORKSPACE] Using branch-only cloning..."
|
38
|
+
clone_current_branch(repo_path, workspace_path, branch_name)
|
39
|
+
else
|
40
|
+
puts "๐ฅ [WORKSPACE] Using full repo cloning..."
|
41
|
+
clone_full_repo(repo_path, workspace_path, branch_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
puts "๐ฅ [WORKSPACE] Workspace creation completed successfully!"
|
45
|
+
puts "๐ฅ [WORKSPACE] Final workspace path: #{workspace_path}"
|
46
|
+
puts "๐ฅ [WORKSPACE] Workspace contents: #{Dir.entries(workspace_path).join(', ')}"
|
47
|
+
workspace_path
|
48
|
+
rescue => e
|
49
|
+
puts "โ Failed to create healing workspace: #{e.message}"
|
50
|
+
cleanup_workspace(workspace_path) if Dir.exist?(workspace_path)
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def apply_fixes_in_workspace(workspace_path, fixes, class_name, method_name)
|
56
|
+
puts "๐ง [WORKSPACE] Starting fix application..."
|
57
|
+
puts "๐ง [WORKSPACE] Workspace: #{workspace_path}"
|
58
|
+
puts "๐ง [WORKSPACE] Class: #{class_name}, Method: #{method_name}"
|
59
|
+
puts "๐ง [WORKSPACE] Fixes to apply: #{fixes.inspect}"
|
60
|
+
|
61
|
+
begin
|
62
|
+
puts "๐ง [WORKSPACE] Processing #{fixes.length} fixes..."
|
63
|
+
# Apply each fix to the workspace
|
64
|
+
fixes.each_with_index do |fix, index|
|
65
|
+
puts "๐ง [WORKSPACE] Processing fix #{index + 1}: #{fix.inspect}"
|
66
|
+
file_path = File.join(workspace_path, fix[:file_path])
|
67
|
+
puts "๐ง [WORKSPACE] Target file: #{file_path}"
|
68
|
+
puts "๐ง [WORKSPACE] File exists: #{File.exist?(file_path)}"
|
69
|
+
|
70
|
+
next unless File.exist?(file_path)
|
71
|
+
|
72
|
+
puts "๐ง [WORKSPACE] Creating backup..."
|
73
|
+
# Backup original file
|
74
|
+
backup_file(file_path)
|
75
|
+
|
76
|
+
puts "๐ง [WORKSPACE] Applying fix to file..."
|
77
|
+
# Apply the fix
|
78
|
+
apply_fix_to_file(file_path, fix[:new_code], class_name, method_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
puts "โ
Fixes applied successfully in workspace"
|
82
|
+
true
|
83
|
+
rescue => e
|
84
|
+
puts "โ Failed to apply fixes in workspace: #{e.message}"
|
85
|
+
false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_fixes_in_workspace(workspace_path)
|
90
|
+
config = CodeHealer::ConfigManager.code_heal_directory_config
|
91
|
+
|
92
|
+
puts "๐งช Testing fixes in workspace: #{workspace_path}"
|
93
|
+
|
94
|
+
begin
|
95
|
+
# Change to workspace directory
|
96
|
+
Dir.chdir(workspace_path) do
|
97
|
+
# Run basic syntax check
|
98
|
+
syntax_check = system("ruby -c #{find_ruby_files.join(' ')} 2>/dev/null")
|
99
|
+
return false unless syntax_check
|
100
|
+
|
101
|
+
# Run tests if available
|
102
|
+
if File.exist?('Gemfile')
|
103
|
+
bundle_check = system("bundle check >/dev/null 2>&1")
|
104
|
+
return false unless bundle_check
|
105
|
+
|
106
|
+
# Run tests if RSpec is available
|
107
|
+
if File.exist?('spec') || File.exist?('test')
|
108
|
+
test_result = system("bundle exec rspec --dry-run >/dev/null 2>&1") ||
|
109
|
+
system("bundle exec rake test:prepare >/dev/null 2>&1")
|
110
|
+
puts "๐งช Test preparation: #{test_result ? 'โ
' : 'โ ๏ธ'}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
puts "โ
Workspace validation passed"
|
115
|
+
true
|
116
|
+
end
|
117
|
+
rescue => e
|
118
|
+
puts "โ Workspace validation failed: #{e.message}"
|
119
|
+
false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def merge_fixes_back(repo_path, workspace_path, branch_name)
|
124
|
+
puts "๐ Merging fixes back to main repository"
|
125
|
+
|
126
|
+
begin
|
127
|
+
# Create healing branch in main repo
|
128
|
+
Dir.chdir(repo_path) do
|
129
|
+
# Ensure we're on the target branch
|
130
|
+
system("git checkout #{branch_name}")
|
131
|
+
system("git pull origin #{branch_name}")
|
132
|
+
|
133
|
+
# Create healing branch
|
134
|
+
healing_branch = "code-healer-fix-#{Time.now.to_i}"
|
135
|
+
system("git checkout -b #{healing_branch}")
|
136
|
+
|
137
|
+
# Copy fixed files from workspace
|
138
|
+
copy_fixed_files(workspace_path, repo_path)
|
139
|
+
|
140
|
+
# Commit changes
|
141
|
+
system("git add .")
|
142
|
+
commit_message = "Fix applied by CodeHealer: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
143
|
+
system("git commit -m '#{commit_message}'")
|
144
|
+
|
145
|
+
# Push branch
|
146
|
+
system("git push origin #{healing_branch}")
|
147
|
+
|
148
|
+
puts "โ
Healing branch created: #{healing_branch}"
|
149
|
+
healing_branch
|
150
|
+
end
|
151
|
+
rescue => e
|
152
|
+
puts "โ Failed to merge fixes back: #{e.message}"
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def cleanup_workspace(workspace_path)
|
158
|
+
puts "๐งน [WORKSPACE] Starting workspace cleanup..."
|
159
|
+
puts "๐งน [WORKSPACE] Target: #{workspace_path}"
|
160
|
+
puts "๐งน [WORKSPACE] Exists: #{Dir.exist?(workspace_path)}"
|
161
|
+
|
162
|
+
return unless Dir.exist?(workspace_path)
|
163
|
+
|
164
|
+
puts "๐งน [WORKSPACE] Removing workspace directory..."
|
165
|
+
FileUtils.rm_rf(workspace_path)
|
166
|
+
puts "๐งน [WORKSPACE] Workspace cleanup completed"
|
167
|
+
puts "๐งน [WORKSPACE] Directory still exists: #{Dir.exist?(workspace_path)}"
|
168
|
+
end
|
169
|
+
|
170
|
+
def cleanup_expired_workspaces
|
171
|
+
config = CodeHealer::ConfigManager.code_heal_directory_config
|
172
|
+
auto_cleanup = config['auto_cleanup']
|
173
|
+
auto_cleanup = config[:auto_cleanup] if auto_cleanup.nil?
|
174
|
+
return unless auto_cleanup
|
175
|
+
|
176
|
+
puts "๐งน Cleaning up expired healing workspaces"
|
177
|
+
|
178
|
+
base_path = config['path'] || config[:path] || '/tmp/code_healer_workspaces'
|
179
|
+
Dir.glob(File.join(base_path, "healing_*")).each do |workspace_path|
|
180
|
+
next unless Dir.exist?(workspace_path)
|
181
|
+
|
182
|
+
# Check if workspace is expired
|
183
|
+
hours = config['cleanup_after_hours'] || config[:cleanup_after_hours]
|
184
|
+
hours = hours.to_i if hours
|
185
|
+
if workspace_expired?(workspace_path, hours)
|
186
|
+
cleanup_workspace(workspace_path)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def clone_strategy
|
194
|
+
cfg = CodeHealer::ConfigManager.code_heal_directory_config
|
195
|
+
cfg['clone_strategy'] || cfg[:clone_strategy] || "branch"
|
196
|
+
end
|
197
|
+
|
198
|
+
def clone_current_branch(repo_path, workspace_path, branch_name)
|
199
|
+
puts "๐ฟ [WORKSPACE] Starting branch cloning..."
|
200
|
+
Dir.chdir(repo_path) do
|
201
|
+
current_branch = branch_name || `git branch --show-current`.strip
|
202
|
+
puts "๐ฟ [WORKSPACE] Current branch: #{current_branch}"
|
203
|
+
puts "๐ฟ [WORKSPACE] Executing: git clone --single-branch --branch #{current_branch} #{repo_path} #{workspace_path}"
|
204
|
+
|
205
|
+
# Clone only the current branch
|
206
|
+
result = system("git clone --single-branch --branch #{current_branch} #{repo_path} #{workspace_path}")
|
207
|
+
puts "๐ฟ [WORKSPACE] Clone result: #{result ? 'SUCCESS' : 'FAILED'}"
|
208
|
+
|
209
|
+
if result
|
210
|
+
puts "๐ฟ [WORKSPACE] Removing .git to avoid conflicts..."
|
211
|
+
# Remove .git to avoid conflicts
|
212
|
+
FileUtils.rm_rf(File.join(workspace_path, '.git'))
|
213
|
+
puts "๐ฟ [WORKSPACE] .git removed successfully"
|
214
|
+
else
|
215
|
+
puts "๐ฟ [WORKSPACE] Clone failed, checking workspace..."
|
216
|
+
puts "๐ฟ [WORKSPACE] Workspace exists: #{Dir.exist?(workspace_path)}"
|
217
|
+
puts "๐ฟ [WORKSPACE] Workspace contents: #{Dir.exist?(workspace_path) ? Dir.entries(workspace_path).join(', ') : 'N/A'}"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def clone_full_repo(repo_path, workspace_path, branch_name)
|
223
|
+
puts "๐ฟ [WORKSPACE] Starting full repo cloning..."
|
224
|
+
Dir.chdir(repo_path) do
|
225
|
+
current_branch = branch_name || `git branch --show-current`.strip
|
226
|
+
puts "๐ฟ [WORKSPACE] Target branch: #{current_branch}"
|
227
|
+
puts "๐ฟ [WORKSPACE] Executing: git clone #{repo_path} #{workspace_path}"
|
228
|
+
|
229
|
+
# Clone full repo
|
230
|
+
result = system("git clone #{repo_path} #{workspace_path}")
|
231
|
+
puts "๐ฟ [WORKSPACE] Clone result: #{result ? 'SUCCESS' : 'FAILED'}"
|
232
|
+
|
233
|
+
if result
|
234
|
+
puts "๐ฟ [WORKSPACE] Switching to branch: #{current_branch}"
|
235
|
+
# Switch to specific branch
|
236
|
+
Dir.chdir(workspace_path) do
|
237
|
+
checkout_result = system("git checkout #{current_branch}")
|
238
|
+
puts "๐ฟ [WORKSPACE] Checkout result: #{checkout_result ? 'SUCCESS' : 'FAILED'}"
|
239
|
+
end
|
240
|
+
else
|
241
|
+
puts "๐ฟ [WORKSPACE] Full repo clone failed"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def backup_file(file_path)
|
247
|
+
backup_path = "#{file_path}.code_healer_backup"
|
248
|
+
FileUtils.cp(file_path, backup_path)
|
249
|
+
end
|
250
|
+
|
251
|
+
def apply_fix_to_file(file_path, new_code, class_name, method_name)
|
252
|
+
content = File.read(file_path)
|
253
|
+
|
254
|
+
# Find and replace the method
|
255
|
+
method_pattern = /def\s+#{Regexp.escape(method_name)}\s*\([^)]*\)(.*?)end/m
|
256
|
+
if content.match(method_pattern)
|
257
|
+
content.gsub!(method_pattern, new_code)
|
258
|
+
File.write(file_path, content)
|
259
|
+
puts "โ
Applied fix to #{File.basename(file_path)}##{method_name}"
|
260
|
+
else
|
261
|
+
puts "โ ๏ธ Could not find method #{method_name} in #{File.basename(file_path)}"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def find_ruby_files
|
266
|
+
Dir.glob("**/*.rb")
|
267
|
+
end
|
268
|
+
|
269
|
+
def copy_fixed_files(workspace_path, repo_path)
|
270
|
+
# Copy all Ruby files from workspace to repo
|
271
|
+
Dir.glob(File.join(workspace_path, "**/*.rb")).each do |workspace_file|
|
272
|
+
relative_path = workspace_file.sub(workspace_path + "/", "")
|
273
|
+
repo_file = File.join(repo_path, relative_path)
|
274
|
+
|
275
|
+
if File.exist?(repo_file)
|
276
|
+
FileUtils.cp(workspace_file, repo_file)
|
277
|
+
puts "๐ Copied fixed file: #{relative_path}"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def workspace_expired?(workspace_path, hours)
|
283
|
+
return false unless hours && hours > 0
|
284
|
+
|
285
|
+
# Extract timestamp from workspace name
|
286
|
+
if workspace_path =~ /healing_(\d+)/
|
287
|
+
timestamp = $1.to_i
|
288
|
+
age_hours = (Time.now.to_i - timestamp) / 3600
|
289
|
+
age_hours > hours
|
290
|
+
else
|
291
|
+
false
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
data/lib/code_healer/setup.rb
CHANGED
@@ -7,6 +7,7 @@
|
|
7
7
|
require 'fileutils'
|
8
8
|
require 'yaml'
|
9
9
|
require 'optparse'
|
10
|
+
require 'octokit'
|
10
11
|
|
11
12
|
# Parse command line options
|
12
13
|
options = {}
|
@@ -88,6 +89,109 @@ def read_file_content(file_path)
|
|
88
89
|
File.read(file_path) if file_exists?(file_path)
|
89
90
|
end
|
90
91
|
|
92
|
+
def normalize_repository_url(repository_input, github_token = nil)
|
93
|
+
return nil if repository_input.nil?
|
94
|
+
input = repository_input.strip
|
95
|
+
return nil if input.empty?
|
96
|
+
|
97
|
+
# If it's already a URL, return as-is
|
98
|
+
if input.include?('://')
|
99
|
+
return input
|
100
|
+
end
|
101
|
+
|
102
|
+
# Build HTTPS URL from owner/repo
|
103
|
+
base = "https://github.com/#{input}.git"
|
104
|
+
return base if github_token.nil? || github_token.strip.empty?
|
105
|
+
|
106
|
+
# Embed token for validation clone only (do not log the token)
|
107
|
+
"https://#{github_token}@github.com/#{input}.git"
|
108
|
+
end
|
109
|
+
|
110
|
+
# Permission validation method
|
111
|
+
def validate_code_heal_directory_permissions(directory_path, repository_url, github_token = nil)
|
112
|
+
puts " ๐ Checking directory: #{directory_path}"
|
113
|
+
|
114
|
+
# Check if directory exists or can be created
|
115
|
+
begin
|
116
|
+
if Dir.exist?(directory_path)
|
117
|
+
puts " โ
Directory exists"
|
118
|
+
else
|
119
|
+
puts " ๐จ Creating directory..."
|
120
|
+
Dir.mkdir(directory_path)
|
121
|
+
puts " โ
Directory created successfully"
|
122
|
+
end
|
123
|
+
rescue => e
|
124
|
+
puts " โ Cannot create directory: #{e.message}"
|
125
|
+
return false
|
126
|
+
end
|
127
|
+
|
128
|
+
# Check write permissions
|
129
|
+
begin
|
130
|
+
test_file = File.join(directory_path, '.permission_test')
|
131
|
+
File.write(test_file, 'test')
|
132
|
+
File.delete(test_file)
|
133
|
+
puts " โ
Write permissions verified"
|
134
|
+
rescue => e
|
135
|
+
puts " โ Write permission failed: #{e.message}"
|
136
|
+
return false
|
137
|
+
end
|
138
|
+
|
139
|
+
# If we have a token and a repo, verify push permissions via GitHub API
|
140
|
+
if github_token && !github_token.strip.empty? && repository_url && !repository_url.strip.empty?
|
141
|
+
begin
|
142
|
+
client = Octokit::Client.new(access_token: github_token)
|
143
|
+
repo_full_name = if repository_url.include?('github.com')
|
144
|
+
path = repository_url.split('github.com/').last.to_s
|
145
|
+
path.sub(/\.git\z/, '')
|
146
|
+
else
|
147
|
+
repository_url
|
148
|
+
end
|
149
|
+
repo = client.repository(repo_full_name)
|
150
|
+
perms = (repo.respond_to?(:permissions) ? repo.permissions : repo[:permissions]) || {}
|
151
|
+
can_push = perms[:push] == true || perms['push'] == true
|
152
|
+
if can_push
|
153
|
+
puts " โ
GitHub token has push permission to #{repo_full_name}"
|
154
|
+
else
|
155
|
+
puts " โ ๏ธ GitHub token does not have push permission to #{repo_full_name}"
|
156
|
+
end
|
157
|
+
rescue => e
|
158
|
+
puts " โ ๏ธ Could not verify push permission via GitHub API: #{e.message}"
|
159
|
+
end
|
160
|
+
else
|
161
|
+
puts " โ ๏ธ Skipping GitHub push-permission check (missing token or repository)"
|
162
|
+
end
|
163
|
+
|
164
|
+
# Check if we can clone the repository (read access)
|
165
|
+
puts " ๐ Testing repository access..."
|
166
|
+
begin
|
167
|
+
require 'git'
|
168
|
+
require 'fileutils'
|
169
|
+
|
170
|
+
# Create a temporary test directory
|
171
|
+
test_dir = File.join(directory_path, 'test_clone_' + Time.now.to_i.to_s)
|
172
|
+
|
173
|
+
# Try to clone the repository
|
174
|
+
puts " ๐ฅ Attempting to clone repository..."
|
175
|
+
# Avoid printing token in logs
|
176
|
+
safe_url = repository_url.to_s.gsub(/:\/\/[A-Za-z0-9_\-]+@/, '://***@')
|
177
|
+
# Perform clone
|
178
|
+
Git.clone(repository_url, test_dir, depth: 1)
|
179
|
+
|
180
|
+
# Clean up test clone
|
181
|
+
FileUtils.rm_rf(test_dir)
|
182
|
+
puts " โ
Repository access verified (#{safe_url})"
|
183
|
+
|
184
|
+
return true
|
185
|
+
rescue => e
|
186
|
+
puts " โ Repository access failed: #{e.message}"
|
187
|
+
puts " ๐ก Make sure:"
|
188
|
+
puts " - Your GitHub token has repo access"
|
189
|
+
puts " - The repository URL is correct (e.g., https://github.com/owner/repo.git or owner/repo)"
|
190
|
+
puts " - You have network access to GitHub"
|
191
|
+
return false
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
91
195
|
# Main setup logic
|
92
196
|
puts "๐ฅ Welcome to CodeHealer Setup! ๐"
|
93
197
|
puts "=" * 50
|
@@ -119,7 +223,7 @@ else
|
|
119
223
|
puts "๐ Would add 'code_healer' to Gemfile"
|
120
224
|
else
|
121
225
|
File.write(gemfile_path, new_gemfile_content)
|
122
|
-
|
226
|
+
puts "โ
Added 'code_healer' to Gemfile"
|
123
227
|
end
|
124
228
|
end
|
125
229
|
|
@@ -145,7 +249,8 @@ puts "Get one at: https://github.com/settings/tokens"
|
|
145
249
|
puts
|
146
250
|
|
147
251
|
github_token = ask_for_input("Enter your GitHub personal access token (or press Enter to skip for now):")
|
148
|
-
github_repo = ask_for_input("Enter your GitHub repository (username/repo):")
|
252
|
+
github_repo = ask_for_input("Enter your GitHub repository (username/repo or full URL):")
|
253
|
+
github_repo_url = normalize_repository_url(github_repo, github_token)
|
149
254
|
|
150
255
|
# Git Branch Configuration
|
151
256
|
puts
|
@@ -153,6 +258,40 @@ puts "๐ฟ Git Branch Configuration:"
|
|
153
258
|
branch_prefix = ask_for_input("Enter branch prefix for healing branches (default: evolve):", default: "evolve")
|
154
259
|
pr_target_branch = ask_for_input("Enter target branch for pull requests (default: main):", default: "main")
|
155
260
|
|
261
|
+
# Code Heal Directory Configuration
|
262
|
+
puts
|
263
|
+
puts "๐ฅ Code Heal Directory Configuration:"
|
264
|
+
puts "This directory will store isolated copies of your code for safe healing."
|
265
|
+
puts "CodeHealer will clone your current branch here before making fixes."
|
266
|
+
puts
|
267
|
+
|
268
|
+
code_heal_directory = ask_for_input("Enter code heal directory path (default: /tmp/code_healer_workspaces):", default: "/tmp/code_healer_workspaces")
|
269
|
+
|
270
|
+
# Validate code heal directory permissions
|
271
|
+
puts "๐ Validating code heal directory permissions..."
|
272
|
+
if github_repo_url && !github_repo_url.strip.empty?
|
273
|
+
if validate_code_heal_directory_permissions(code_heal_directory, github_repo_url, github_token)
|
274
|
+
puts "โ
Code heal directory permissions validated successfully!"
|
275
|
+
else
|
276
|
+
puts "โ Code heal directory permission validation failed!"
|
277
|
+
puts "Please ensure the directory has proper write permissions and can access the repository."
|
278
|
+
code_heal_directory = ask_for_input("Enter a different code heal directory path:", default: "/tmp/code_healer_workspaces")
|
279
|
+
|
280
|
+
# Retry validation
|
281
|
+
if validate_code_heal_directory_permissions(code_heal_directory, github_repo_url, github_token)
|
282
|
+
puts "โ
Code heal directory permissions validated successfully!"
|
283
|
+
else
|
284
|
+
puts "โ ๏ธ Permission validation failed again. You may need to fix permissions manually."
|
285
|
+
end
|
286
|
+
end
|
287
|
+
else
|
288
|
+
puts "โ ๏ธ Skipping repository access validation (no repository URL provided)"
|
289
|
+
puts " Directory permissions will be validated when you run the actual setup"
|
290
|
+
end
|
291
|
+
|
292
|
+
auto_cleanup = ask_for_yes_no("Automatically clean up healing workspaces after use?", default: true)
|
293
|
+
cleanup_after_hours = ask_for_input("Clean up workspaces after how many hours? (default: 24):", default: "24")
|
294
|
+
|
156
295
|
# Business Context
|
157
296
|
puts
|
158
297
|
puts "๐ผ Business Context Setup:"
|
@@ -187,12 +326,12 @@ puts "๐ Step 3: Creating Configuration Files"
|
|
187
326
|
puts
|
188
327
|
|
189
328
|
# Create actual .env file (will be ignored by git)
|
190
|
-
env_content = <<~ENV
|
329
|
+
env_content = <<~ENV
|
191
330
|
# CodeHealer Configuration
|
192
|
-
|
331
|
+
# OpenAI Configuration
|
193
332
|
OPENAI_API_KEY=#{openai_key}
|
194
|
-
|
195
|
-
|
333
|
+
|
334
|
+
# GitHub Configuration
|
196
335
|
GITHUB_TOKEN=#{github_token}
|
197
336
|
GITHUB_REPOSITORY=#{github_repo}
|
198
337
|
|
@@ -203,81 +342,81 @@ ENV
|
|
203
342
|
create_file_with_content('.env', env_content, dry_run: options[:dry_run])
|
204
343
|
|
205
344
|
# Create code_healer.yml
|
206
|
-
config_content = <<~YAML
|
207
|
-
|
208
|
-
|
209
|
-
|
345
|
+
config_content = <<~YAML
|
346
|
+
# CodeHealer Configuration
|
347
|
+
enabled: true
|
348
|
+
|
210
349
|
# Allowed classes for healing (customize as needed)
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
350
|
+
allowed_classes:
|
351
|
+
- User
|
352
|
+
- Order
|
353
|
+
- PaymentProcessor
|
215
354
|
- OrderProcessor
|
216
|
-
|
355
|
+
|
217
356
|
# Excluded classes (never touch these)
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
357
|
+
excluded_classes:
|
358
|
+
- ApplicationController
|
359
|
+
- ApplicationRecord
|
360
|
+
- ApplicationJob
|
361
|
+
- ApplicationMailer
|
223
362
|
- ApplicationHelper
|
224
|
-
|
225
|
-
|
226
|
-
|
363
|
+
|
364
|
+
# Allowed error types for healing
|
365
|
+
allowed_error_types:
|
227
366
|
- ZeroDivisionError
|
228
367
|
- NoMethodError
|
229
|
-
|
368
|
+
- ArgumentError
|
230
369
|
- TypeError
|
231
|
-
|
232
|
-
|
233
|
-
|
370
|
+
- NameError
|
371
|
+
- ValidationError
|
372
|
+
|
234
373
|
# Evolution Strategy Configuration
|
235
|
-
|
374
|
+
evolution_strategy:
|
236
375
|
method: #{evolution_method} # Options: api, claude_code_terminal, hybrid
|
237
376
|
fallback_to_api: #{fallback_to_api} # If Claude Code fails, fall back to API
|
238
|
-
|
377
|
+
|
239
378
|
# Claude Code Terminal Configuration
|
240
|
-
|
379
|
+
claude_code:
|
241
380
|
enabled: #{evolution_method == 'claude_code_terminal' || evolution_method == 'hybrid'}
|
242
381
|
timeout: 300 # Timeout in seconds
|
243
|
-
|
244
|
-
|
382
|
+
max_file_changes: 10
|
383
|
+
include_tests: true
|
245
384
|
command_template: "claude --print '{prompt}' --output-format text --permission-mode acceptEdits --allowedTools Edit"
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
385
|
+
business_context_sources:
|
386
|
+
- "config/business_rules.yml"
|
387
|
+
- "docs/business_logic.md"
|
388
|
+
- "spec/business_context_specs.rb"
|
389
|
+
|
251
390
|
# Business Context Configuration
|
252
|
-
|
253
|
-
|
391
|
+
business_context:
|
392
|
+
enabled: true
|
254
393
|
sources:
|
255
394
|
- "docs/business_rules.md"
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
395
|
+
|
396
|
+
# OpenAI API configuration
|
397
|
+
api:
|
398
|
+
provider: openai
|
260
399
|
model: gpt-4
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
400
|
+
max_tokens: 2000
|
401
|
+
temperature: 0.1
|
402
|
+
|
403
|
+
# Git operations
|
404
|
+
git:
|
405
|
+
auto_commit: true
|
406
|
+
auto_push: true
|
268
407
|
branch_prefix: "#{branch_prefix}"
|
269
408
|
commit_message_template: 'Fix {{class_name}}\#\#{{method_name}}: {{error_type}}'
|
270
409
|
pr_target_branch: "#{pr_target_branch}"
|
271
410
|
|
272
411
|
# Pull Request Configuration
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
412
|
+
pull_request:
|
413
|
+
enabled: true
|
414
|
+
auto_create: true
|
415
|
+
labels:
|
416
|
+
- "auto-fix"
|
278
417
|
- "self-evolving"
|
279
|
-
|
280
|
-
|
418
|
+
- "bug-fix"
|
419
|
+
|
281
420
|
# Safety Configuration
|
282
421
|
safety:
|
283
422
|
backup_before_evolution: true
|
@@ -297,6 +436,14 @@ config_content = <<~YAML
|
|
297
436
|
max_concurrent_healing: 3
|
298
437
|
healing_timeout: 300
|
299
438
|
retry_attempts: 3
|
439
|
+
|
440
|
+
# Code Heal Directory Configuration
|
441
|
+
code_heal_directory:
|
442
|
+
path: "#{code_heal_directory}"
|
443
|
+
auto_cleanup: #{auto_cleanup}
|
444
|
+
cleanup_after_hours: #{cleanup_after_hours}
|
445
|
+
max_workspaces: 10
|
446
|
+
clone_strategy: "branch" # Options: branch, full_repo
|
300
447
|
YAML
|
301
448
|
|
302
449
|
create_file_with_content('config/code_healer.yml', config_content, dry_run: options[:dry_run])
|
@@ -350,6 +497,8 @@ YAML
|
|
350
497
|
|
351
498
|
create_file_with_content('config/sidekiq.yml', sidekiq_config_content, dry_run: options[:dry_run])
|
352
499
|
|
500
|
+
# Permission validation method moved to top of file
|
501
|
+
|
353
502
|
# Final instructions
|
354
503
|
puts
|
355
504
|
if options[:dry_run]
|
@@ -366,14 +515,19 @@ else
|
|
366
515
|
puts "4. Start your Rails server: rails s"
|
367
516
|
puts
|
368
517
|
puts "๐ Security Notes:"
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
518
|
+
puts " - .env file contains your actual API keys and is ignored by git"
|
519
|
+
puts " - .env.example is safe to commit and shows the required format"
|
520
|
+
puts " - Never commit .env files with real secrets to version control"
|
521
|
+
puts
|
522
|
+
puts "๐ฅ Code Heal Directory:"
|
523
|
+
puts " - Your code will be cloned to: #{code_heal_directory}"
|
524
|
+
puts " - This ensures safe, isolated healing without affecting your running server"
|
525
|
+
puts " - Workspaces are automatically cleaned up after #{cleanup_after_hours} hours"
|
526
|
+
puts
|
527
|
+
puts "โ๏ธ Configuration:"
|
528
|
+
puts " - code_healer.yml contains comprehensive settings with sensible defaults"
|
529
|
+
puts " - Customize the configuration file as needed for your project"
|
530
|
+
puts " - All features are pre-configured and ready to use"
|
377
531
|
puts
|
378
532
|
puts "๐ Environment Variables:"
|
379
533
|
puts " - Add 'gem \"dotenv-rails\"' to your Gemfile for automatic .env loading"
|
data/lib/code_healer/version.rb
CHANGED
data/lib/code_healer.rb
CHANGED
@@ -21,6 +21,7 @@ autoload :BusinessContextManager, "code_healer/business_context_manager"
|
|
21
21
|
autoload :ClaudeCodeEvolutionHandler, "code_healer/claude_code_evolution_handler"
|
22
22
|
autoload :SimpleHealer, "code_healer/simple_healer"
|
23
23
|
autoload :HealingJob, "code_healer/healing_job"
|
24
|
+
autoload :HealingWorkspaceManager, "code_healer/healing_workspace_manager"
|
24
25
|
autoload :PullRequestCreator, "code_healer/pull_request_creator"
|
25
26
|
autoload :McpServer, "code_healer/mcp_server"
|
26
27
|
autoload :McpTools, "code_healer/mcp_tools"
|