code_healer 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +70 -0
- data/GEM_SUMMARY.md +307 -0
- data/README.md +281 -0
- data/code_healer.gemspec +77 -0
- data/config/code_healer.yml.example +104 -0
- data/docs/INSTALLATION.md +439 -0
- data/examples/basic_usage.rb +160 -0
- data/exe/code_healer-setup +7 -0
- data/lib/code_healer/application_job.rb +7 -0
- data/lib/code_healer/business_context_analyzer.rb +464 -0
- data/lib/code_healer/business_context_loader.rb +273 -0
- data/lib/code_healer/business_context_manager.rb +297 -0
- data/lib/code_healer/business_logic_generator.rb +94 -0
- data/lib/code_healer/business_rule_applier.rb +54 -0
- data/lib/code_healer/claude_code_evolution_handler.rb +224 -0
- data/lib/code_healer/claude_error_monitor.rb +48 -0
- data/lib/code_healer/config_manager.rb +275 -0
- data/lib/code_healer/context_aware_prompt_builder.rb +153 -0
- data/lib/code_healer/core.rb +513 -0
- data/lib/code_healer/error_handler.rb +141 -0
- data/lib/code_healer/evolution_job.rb +99 -0
- data/lib/code_healer/global_handler.rb +130 -0
- data/lib/code_healer/healing_job.rb +167 -0
- data/lib/code_healer/mcp.rb +108 -0
- data/lib/code_healer/mcp_prompts.rb +111 -0
- data/lib/code_healer/mcp_server.rb +389 -0
- data/lib/code_healer/mcp_tools.rb +2364 -0
- data/lib/code_healer/pull_request_creator.rb +143 -0
- data/lib/code_healer/setup.rb +390 -0
- data/lib/code_healer/simple_evolution.rb +737 -0
- data/lib/code_healer/simple_global_handler.rb +122 -0
- data/lib/code_healer/simple_healer.rb +515 -0
- data/lib/code_healer/terminal_integration.rb +87 -0
- data/lib/code_healer/usage_analyzer.rb +92 -0
- data/lib/code_healer/version.rb +5 -0
- data/lib/code_healer.rb +67 -0
- metadata +411 -0
@@ -0,0 +1,737 @@
|
|
1
|
+
require 'git'
|
2
|
+
require_relative 'business_rule_applier'
|
3
|
+
require_relative 'business_context_analyzer'
|
4
|
+
|
5
|
+
module CodeHealer
|
6
|
+
class SimpleEvolution
|
7
|
+
def self.handle_error(error, class_name, method_name, file_path)
|
8
|
+
# Check if evolution is enabled and allowed
|
9
|
+
unless ConfigManager.enabled?
|
10
|
+
puts "Self-evolution is disabled"
|
11
|
+
return false
|
12
|
+
end
|
13
|
+
|
14
|
+
unless ConfigManager.can_evolve_class?(class_name)
|
15
|
+
puts "Class #{class_name} is not allowed to evolve"
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
|
19
|
+
unless ConfigManager.can_handle_error?(error)
|
20
|
+
puts "Error type #{error.class.name} is not allowed to be handled"
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "\n=== Self-Evolution Triggered ==="
|
25
|
+
puts "Error: #{error.class} - #{error.message}"
|
26
|
+
puts "Class: #{class_name}"
|
27
|
+
puts "Method: #{method_name}"
|
28
|
+
puts "File: #{file_path}"
|
29
|
+
|
30
|
+
# Analyze the error and generate a fix
|
31
|
+
fix = generate_fix(error, class_name, method_name)
|
32
|
+
|
33
|
+
if fix
|
34
|
+
# Apply the fix
|
35
|
+
apply_fix(file_path, fix, class_name, method_name, error)
|
36
|
+
return true
|
37
|
+
else
|
38
|
+
puts "Could not generate fix for this error"
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def self.repatch_evolved_class(class_name, file_path)
|
46
|
+
puts "🔄 Re-patching evolved class: #{class_name}"
|
47
|
+
|
48
|
+
begin
|
49
|
+
# Get the actual class object
|
50
|
+
klass = Object.const_get(class_name)
|
51
|
+
|
52
|
+
# Re-patch the class with error handling
|
53
|
+
klass.class_eval do
|
54
|
+
instance_methods(false).each do |method_name|
|
55
|
+
next if method_name == :initialize || method_name.to_s.start_with?('_')
|
56
|
+
|
57
|
+
original_method = instance_method(method_name)
|
58
|
+
remove_method(method_name)
|
59
|
+
|
60
|
+
define_method(method_name) do |*args, &block|
|
61
|
+
begin
|
62
|
+
original_method.bind(self).call(*args, &block)
|
63
|
+
rescue => e
|
64
|
+
puts "\n=== Error Caught in #{self.class.name}##{method_name} ==="
|
65
|
+
puts "Error: #{e.class} - #{e.message}"
|
66
|
+
|
67
|
+
# Handle the error with self-evolution
|
68
|
+
handle_console_error(e, self.class, method_name)
|
69
|
+
raise e
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
puts "✅ Successfully re-patched #{class_name} for error handling"
|
76
|
+
return true
|
77
|
+
|
78
|
+
rescue => e
|
79
|
+
puts "❌ Failed to re-patch #{class_name}: #{e.message}"
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.generate_fix(error, class_name, method_name)
|
85
|
+
case error
|
86
|
+
when ZeroDivisionError
|
87
|
+
generate_division_fix(class_name, method_name)
|
88
|
+
when NoMethodError
|
89
|
+
generate_method_fix(error, class_name, method_name)
|
90
|
+
else
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.generate_division_fix(class_name, method_name)
|
96
|
+
{
|
97
|
+
method_name: method_name,
|
98
|
+
new_code: <<~RUBY
|
99
|
+
def #{method_name}(a, b)
|
100
|
+
if b == 0
|
101
|
+
puts "Warning: Division by zero attempted. Returning 0."
|
102
|
+
return 0
|
103
|
+
end
|
104
|
+
a / b
|
105
|
+
end
|
106
|
+
RUBY
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.generate_method_fix(error, class_name, method_name)
|
111
|
+
if error.message.include?('undefined method')
|
112
|
+
{
|
113
|
+
method_name: method_name,
|
114
|
+
new_code: <<~RUBY
|
115
|
+
def #{method_name}(a, b)
|
116
|
+
puts "Warning: Method #{method_name} called but not implemented. Returning 0."
|
117
|
+
return 0
|
118
|
+
end
|
119
|
+
RUBY
|
120
|
+
}
|
121
|
+
else
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.apply_fix(file_path, fix, class_name, method_name, error)
|
127
|
+
puts "Applying fix for #{class_name}##{method_name}..."
|
128
|
+
|
129
|
+
# Read current file
|
130
|
+
current_content = File.read(file_path)
|
131
|
+
puts "Current content length: #{current_content.length}"
|
132
|
+
|
133
|
+
# Get the new method code and clean it up
|
134
|
+
new_method_code = fix['new_code'] || fix[:new_code]
|
135
|
+
puts "Generated new method: #{new_method_code}"
|
136
|
+
|
137
|
+
# 🔧 APPLY BUSINESS RULES TO THE GENERATED CODE
|
138
|
+
if defined?(BusinessContextAnalyzer) && defined?(BusinessRuleApplier)
|
139
|
+
begin
|
140
|
+
puts "🔍 Getting business context for business rule application..."
|
141
|
+
|
142
|
+
# Get the file path for business context analysis
|
143
|
+
file_path_for_context = file_path
|
144
|
+
|
145
|
+
# Analyze business context
|
146
|
+
business_analysis = BusinessContextAnalyzer.analyze_error_for_business_context(
|
147
|
+
error, class_name, method_name, file_path_for_context
|
148
|
+
)
|
149
|
+
|
150
|
+
# Apply business rules to the generated code
|
151
|
+
new_method_code = BusinessRuleApplier.apply_business_rules_to_code(
|
152
|
+
business_analysis, new_method_code, error, class_name, method_name
|
153
|
+
)
|
154
|
+
|
155
|
+
puts "✅ Business rules applied to generated code"
|
156
|
+
rescue => e
|
157
|
+
puts "⚠️ Warning: Could not apply business rules: #{e.message}"
|
158
|
+
puts " Continuing with original generated code..."
|
159
|
+
end
|
160
|
+
else
|
161
|
+
puts "ℹ️ Business rule applier not available, using original generated code"
|
162
|
+
end
|
163
|
+
|
164
|
+
# Clean up the new method code - remove extra indentation
|
165
|
+
method_lines = new_method_code.strip.split("\n")
|
166
|
+
# Find the base indentation of the first line
|
167
|
+
base_indent = method_lines.first.match(/^(\s*)/)[1].length
|
168
|
+
# Remove the base indentation from all lines
|
169
|
+
cleaned_method_lines = method_lines.map do |line|
|
170
|
+
if line.strip.empty?
|
171
|
+
line
|
172
|
+
else
|
173
|
+
line_indent = line.match(/^(\s*)/)[1].length
|
174
|
+
if line_indent >= base_indent
|
175
|
+
line[base_indent..-1]
|
176
|
+
else
|
177
|
+
line
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
cleaned_method_code = cleaned_method_lines.join("\n")
|
182
|
+
|
183
|
+
# Use a simpler and more reliable method replacement approach
|
184
|
+
lines = current_content.split("\n")
|
185
|
+
new_lines = []
|
186
|
+
method_start_index = nil
|
187
|
+
method_indent = 0
|
188
|
+
|
189
|
+
# Find the method start
|
190
|
+
lines.each_with_index do |line, index|
|
191
|
+
if line.strip.start_with?("def #{fix['method_name'] || fix[:method_name]}")
|
192
|
+
method_start_index = index
|
193
|
+
method_indent = line.match(/^(\s*)/)[1].length
|
194
|
+
break
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
if method_start_index.nil?
|
199
|
+
puts "❌ Could not find method #{fix['method_name'] || fix[:method_name]} in file"
|
200
|
+
return false
|
201
|
+
end
|
202
|
+
|
203
|
+
# Find the method end using a simple approach
|
204
|
+
method_end_index = method_start_index
|
205
|
+
indent_level = method_indent
|
206
|
+
|
207
|
+
lines[method_start_index..-1].each_with_index do |line, relative_index|
|
208
|
+
actual_index = method_start_index + relative_index
|
209
|
+
current_indent = line.match(/^(\s*)/)[1].length
|
210
|
+
|
211
|
+
# Skip the method definition line
|
212
|
+
if relative_index == 0
|
213
|
+
next
|
214
|
+
end
|
215
|
+
|
216
|
+
# If we find an 'end' at the same or lower indentation level, we're done
|
217
|
+
if line.strip == 'end' && current_indent <= indent_level
|
218
|
+
method_end_index = actual_index
|
219
|
+
break
|
220
|
+
end
|
221
|
+
|
222
|
+
# If we find another method definition at the same level, we're done
|
223
|
+
if line.strip.start_with?('def ') && current_indent == indent_level
|
224
|
+
method_end_index = actual_index - 1
|
225
|
+
break
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Build the new content
|
230
|
+
new_lines = lines[0...method_start_index]
|
231
|
+
|
232
|
+
# Add the new method with proper indentation
|
233
|
+
method_indent_str = ' ' * method_indent
|
234
|
+
cleaned_method_lines.each do |method_line|
|
235
|
+
if method_line.strip.empty?
|
236
|
+
new_lines << method_line
|
237
|
+
else
|
238
|
+
new_lines << method_indent_str + method_line
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Add the rest of the file after the method
|
243
|
+
new_lines += lines[(method_end_index + 1)..-1]
|
244
|
+
|
245
|
+
new_content = new_lines.join("\n")
|
246
|
+
puts "New content length: #{new_content.length}"
|
247
|
+
|
248
|
+
# Validate syntax before applying
|
249
|
+
unless validate_syntax(new_content)
|
250
|
+
puts "❌ Syntax validation failed, reverting to original content"
|
251
|
+
return false
|
252
|
+
end
|
253
|
+
|
254
|
+
# Create Git branch
|
255
|
+
git_settings = ConfigManager.git_settings
|
256
|
+
branch_prefix = git_settings['branch_prefix'] || 'evolve'
|
257
|
+
branch_name = "#{branch_prefix}/#{class_name.downcase}-#{method_name}-#{Time.now.to_i}"
|
258
|
+
puts "Creating new branch: #{branch_name}"
|
259
|
+
|
260
|
+
begin
|
261
|
+
git = Git.open(Dir.pwd)
|
262
|
+
|
263
|
+
# Create and checkout new branch
|
264
|
+
git.branch(branch_name).checkout
|
265
|
+
puts "Created and checked out branch: #{branch_name}"
|
266
|
+
|
267
|
+
# Write the updated file
|
268
|
+
File.write(file_path, new_content)
|
269
|
+
puts "Updated file: #{file_path}"
|
270
|
+
|
271
|
+
# Reload the class to get the updated method
|
272
|
+
load file_path
|
273
|
+
puts "🔄 Class reloaded with updated method!"
|
274
|
+
|
275
|
+
# Re-patch the evolved class to restore error handling
|
276
|
+
repatch_evolved_class(class_name, file_path)
|
277
|
+
|
278
|
+
# Commit the changes
|
279
|
+
commit_template = (git_settings['commit_message_template'] || 'Fix {class_name}##{method_name}: {error_type}').to_s
|
280
|
+
commit_message = commit_template.gsub('{class_name}', class_name.to_s)
|
281
|
+
.gsub('{method_name}', method_name.to_s)
|
282
|
+
.gsub('{error_type}', error.class.name.to_s)
|
283
|
+
|
284
|
+
puts "Commit message: #{commit_message}"
|
285
|
+
|
286
|
+
git.add(file_path)
|
287
|
+
puts "Added file to git"
|
288
|
+
|
289
|
+
# Use a simpler commit approach
|
290
|
+
begin
|
291
|
+
git.commit(commit_message)
|
292
|
+
puts "Committed changes to branch: #{branch_name}"
|
293
|
+
rescue => commit_error
|
294
|
+
puts "Commit failed with message: #{commit_message}"
|
295
|
+
puts "Commit error: #{commit_error.message}"
|
296
|
+
# Try with a simpler message
|
297
|
+
git.commit("Fix #{class_name}##{method_name}: #{error.class.name}")
|
298
|
+
puts "Committed with fallback message"
|
299
|
+
end
|
300
|
+
|
301
|
+
# Push to remote if enabled
|
302
|
+
if git_settings['auto_push']
|
303
|
+
begin
|
304
|
+
git.push('origin', branch_name)
|
305
|
+
puts "Pushed to remote repository"
|
306
|
+
|
307
|
+
# Create pull request if enabled
|
308
|
+
if ConfigManager.auto_create_pr?
|
309
|
+
puts "🎯 Attempting to create pull request..."
|
310
|
+
fix_description = get_fix_description(error, method_name)
|
311
|
+
pr_url = CodeHealer::PullRequestCreator.create_pull_request(
|
312
|
+
branch_name,
|
313
|
+
class_name,
|
314
|
+
method_name,
|
315
|
+
error,
|
316
|
+
fix_description,
|
317
|
+
file_path
|
318
|
+
)
|
319
|
+
if pr_url
|
320
|
+
puts "✅ Pull request created successfully: #{pr_url}"
|
321
|
+
else
|
322
|
+
puts "❌ Pull request creation failed"
|
323
|
+
end
|
324
|
+
else
|
325
|
+
puts "📝 PR creation is disabled in configuration"
|
326
|
+
end
|
327
|
+
rescue => e
|
328
|
+
puts "Push failed (expected without remote): #{e.message}"
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
return true
|
333
|
+
|
334
|
+
rescue => e
|
335
|
+
puts "Git operations failed: #{e.message}"
|
336
|
+
return false
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def self.get_fix_description(error, method_name)
|
341
|
+
case error
|
342
|
+
when ZeroDivisionError
|
343
|
+
"Added division by zero check to prevent crashes"
|
344
|
+
when NoMethodError
|
345
|
+
"Added method implementation with default return value"
|
346
|
+
when ArgumentError
|
347
|
+
"Added argument validation and error handling"
|
348
|
+
else
|
349
|
+
"Added error handling for #{error.class.name}"
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def self.validate_syntax(content)
|
354
|
+
begin
|
355
|
+
# Try to parse the content as Ruby code
|
356
|
+
RubyVM::InstructionSequence.compile(content)
|
357
|
+
puts "✅ Syntax validation passed"
|
358
|
+
true
|
359
|
+
rescue SyntaxError => e
|
360
|
+
puts "❌ Syntax error: #{e.message}"
|
361
|
+
false
|
362
|
+
rescue => e
|
363
|
+
puts "❌ Validation error: #{e.message}"
|
364
|
+
false
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# Intelligent MCP-powered error handler
|
369
|
+
def self.handle_error_with_mcp_intelligence(error, class_name, method_name, file_path, business_context = nil)
|
370
|
+
# Check if evolution is enabled and allowed
|
371
|
+
unless ConfigManager.enabled?
|
372
|
+
puts "Self-evolution is disabled"
|
373
|
+
return false
|
374
|
+
end
|
375
|
+
|
376
|
+
unless ConfigManager.can_evolve_class?(class_name)
|
377
|
+
puts "Class #{class_name} is not allowed to evolve"
|
378
|
+
return false
|
379
|
+
end
|
380
|
+
|
381
|
+
unless ConfigManager.can_handle_error?(error)
|
382
|
+
puts "Error type #{error.class.name} is not allowed to be handled"
|
383
|
+
return false
|
384
|
+
end
|
385
|
+
|
386
|
+
puts "\n=== MCP-Powered Self-Evolution Triggered ==="
|
387
|
+
puts "Error: #{error.class} - #{error.message}"
|
388
|
+
puts "Class: #{class_name}"
|
389
|
+
puts "Method: #{method_name}"
|
390
|
+
puts "File: #{file_path}"
|
391
|
+
puts "Business Context: #{business_context.inspect}" if business_context
|
392
|
+
|
393
|
+
|
394
|
+
# Initialize MCP server if not already done
|
395
|
+
MCPServer.initialize_server unless defined?(@mcp_initialized)
|
396
|
+
@mcp_initialized = true
|
397
|
+
|
398
|
+
# Get rich context from MCP, enhanced with business context if provided
|
399
|
+
puts "🧠 Getting codebase context from MCP..."
|
400
|
+
context = MCPServer.get_codebase_context(class_name, method_name)
|
401
|
+
|
402
|
+
# Enhance context with business context if provided
|
403
|
+
if business_context
|
404
|
+
puts "🧠 Enhancing context with business rules..."
|
405
|
+
context = context.merge({
|
406
|
+
'business_context' => business_context
|
407
|
+
})
|
408
|
+
end
|
409
|
+
|
410
|
+
# Analyze error with MCP intelligence
|
411
|
+
puts "🧠 Analyzing error with MCP..."
|
412
|
+
analysis = MCPServer.analyze_error(error, context)
|
413
|
+
|
414
|
+
# Generate intelligent, contextual fix
|
415
|
+
puts "🧠 Generating intelligent fix with MCP..."
|
416
|
+
fix = MCPServer.generate_contextual_fix(error, analysis, context)
|
417
|
+
|
418
|
+
# Debug: Check what fix contains
|
419
|
+
puts "🔍 Debug: Fix result = #{fix.inspect}"
|
420
|
+
|
421
|
+
# Validate fix with MCP
|
422
|
+
puts "🔍 Validating fix with MCP..."
|
423
|
+
validation = MCPServer.validate_fix(fix, context)
|
424
|
+
|
425
|
+
unless validation['approved'] || validation[:approved]
|
426
|
+
puts "❌ MCP validation failed: #{validation['recommendations'] || validation[:recommendations]}"
|
427
|
+
return false
|
428
|
+
end
|
429
|
+
|
430
|
+
puts "✅ MCP validation passed with confidence: #{validation['confidence_score'] || validation[:confidence_score]}"
|
431
|
+
|
432
|
+
if fix
|
433
|
+
if ConfigManager.require_approval?
|
434
|
+
# Approval required - create PR but don't apply fix locally
|
435
|
+
puts "\n🔒 Approval Required - Creating PR for Review"
|
436
|
+
puts "=" * 50
|
437
|
+
puts "The intelligent fix will be applied only after PR is merged."
|
438
|
+
puts "Check the created PR for review and approval."
|
439
|
+
|
440
|
+
# Create PR without applying fix locally
|
441
|
+
create_pr_without_local_fix(file_path, fix, class_name, method_name, error)
|
442
|
+
return true
|
443
|
+
else
|
444
|
+
# No approval required - apply fix immediately
|
445
|
+
apply_fix(file_path, fix, class_name, method_name, error)
|
446
|
+
return true
|
447
|
+
end
|
448
|
+
else
|
449
|
+
puts "Could not generate intelligent fix for this error"
|
450
|
+
return false
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
# Approval-aware error handler (legacy)
|
455
|
+
def self.handle_error_with_approval(error, class_name, method_name, file_path)
|
456
|
+
# Check if evolution is enabled and allowed
|
457
|
+
unless ConfigManager.enabled?
|
458
|
+
puts "Self-evolution is disabled"
|
459
|
+
return false
|
460
|
+
end
|
461
|
+
|
462
|
+
unless ConfigManager.can_evolve_class?(class_name)
|
463
|
+
puts "Class #{class_name} is not allowed to evolve"
|
464
|
+
return false
|
465
|
+
end
|
466
|
+
|
467
|
+
unless ConfigManager.can_handle_error?(error)
|
468
|
+
puts "Error type #{error.class.name} is not allowed to be handled"
|
469
|
+
return false
|
470
|
+
end
|
471
|
+
|
472
|
+
puts "\n=== Self-Evolution Triggered ==="
|
473
|
+
puts "Error: #{error.class} - #{error.message}"
|
474
|
+
puts "Class: #{class_name}"
|
475
|
+
puts "Method: #{method_name}"
|
476
|
+
puts "File: #{file_path}"
|
477
|
+
|
478
|
+
# Analyze the error and generate a fix
|
479
|
+
fix = generate_fix(error, class_name, method_name)
|
480
|
+
|
481
|
+
if fix
|
482
|
+
if ConfigManager.require_approval?
|
483
|
+
# Approval required - create PR but don't apply fix locally
|
484
|
+
puts "\n🔒 Approval Required - Creating PR for Review"
|
485
|
+
puts "=" * 50
|
486
|
+
puts "The fix will be applied only after PR is merged."
|
487
|
+
puts "Check the created PR for review and approval."
|
488
|
+
|
489
|
+
# Create PR without applying fix locally
|
490
|
+
create_pr_without_local_fix(file_path, fix, class_name, method_name, error)
|
491
|
+
return true
|
492
|
+
else
|
493
|
+
# No approval required - apply fix immediately
|
494
|
+
apply_fix(file_path, fix, class_name, method_name, error)
|
495
|
+
return true
|
496
|
+
end
|
497
|
+
else
|
498
|
+
puts "Could not generate fix for this error"
|
499
|
+
return false
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def self.create_pr_without_local_fix(file_path, fix, class_name, method_name, error)
|
504
|
+
puts "Creating PR for #{class_name}##{method_name}..."
|
505
|
+
|
506
|
+
# Create Git branch
|
507
|
+
git_settings = ConfigManager.git_settings
|
508
|
+
branch_prefix = git_settings['branch_prefix'] || 'evolve'
|
509
|
+
branch_name = "#{branch_prefix}/#{class_name.downcase}-#{method_name}-#{Time.now.to_i}"
|
510
|
+
puts "Creating new branch: #{branch_name}"
|
511
|
+
|
512
|
+
begin
|
513
|
+
git = Git.open(Dir.pwd)
|
514
|
+
|
515
|
+
# Store the original branch
|
516
|
+
original_branch = git.current_branch
|
517
|
+
puts "Original branch: #{original_branch}"
|
518
|
+
|
519
|
+
# Create and checkout new branch
|
520
|
+
git.branch(branch_name).checkout
|
521
|
+
puts "Created and checked out branch: #{branch_name}"
|
522
|
+
|
523
|
+
# Read current file content
|
524
|
+
current_content = File.read(file_path)
|
525
|
+
|
526
|
+
# Apply the fix to create the new content
|
527
|
+
lines = current_content.split("\n")
|
528
|
+
new_lines = []
|
529
|
+
in_method = false
|
530
|
+
method_indent = 0
|
531
|
+
skip_until_end = false
|
532
|
+
|
533
|
+
lines.each_with_index do |line, index|
|
534
|
+
if skip_until_end
|
535
|
+
if line.strip == 'end' && in_method
|
536
|
+
in_method = false
|
537
|
+
skip_until_end = false
|
538
|
+
# Add the new method content
|
539
|
+
new_lines.concat(fix[:new_code].strip.split("\n"))
|
540
|
+
end
|
541
|
+
next
|
542
|
+
end
|
543
|
+
|
544
|
+
if line.strip.start_with?("def #{fix[:method_name]}(")
|
545
|
+
in_method = true
|
546
|
+
method_indent = line.match(/^(\s*)/)[1].length
|
547
|
+
skip_until_end = true
|
548
|
+
next
|
549
|
+
end
|
550
|
+
|
551
|
+
new_lines << line
|
552
|
+
end
|
553
|
+
|
554
|
+
new_content = new_lines.join("\n")
|
555
|
+
|
556
|
+
# Write the updated file to the branch
|
557
|
+
File.write(file_path, new_content)
|
558
|
+
puts "Updated file in branch: #{file_path}"
|
559
|
+
|
560
|
+
# Use simple commit message
|
561
|
+
commit_message = "Fix #{method_name} method (requires approval)"
|
562
|
+
|
563
|
+
puts "Commit message: #{commit_message}"
|
564
|
+
|
565
|
+
git.add(file_path)
|
566
|
+
puts "Added file to git"
|
567
|
+
|
568
|
+
# Commit the changes
|
569
|
+
commit_success = false
|
570
|
+
begin
|
571
|
+
git.commit(commit_message)
|
572
|
+
puts "Committed changes to branch: #{branch_name}"
|
573
|
+
commit_success = true
|
574
|
+
rescue => commit_error
|
575
|
+
puts "Commit failed: #{commit_error.message}"
|
576
|
+
# Try using system git command as fallback
|
577
|
+
begin
|
578
|
+
system("git add #{file_path}")
|
579
|
+
system("git commit -m '#{commit_message}'")
|
580
|
+
if $?.success?
|
581
|
+
puts "Committed using system git command"
|
582
|
+
commit_success = true
|
583
|
+
else
|
584
|
+
puts "System git commit also failed"
|
585
|
+
end
|
586
|
+
rescue => system_error
|
587
|
+
puts "System git command failed: #{system_error.message}"
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
# Push to remote if enabled and commit was successful
|
592
|
+
push_success = false
|
593
|
+
if git_settings['auto_push'] && commit_success
|
594
|
+
begin
|
595
|
+
git.push('origin', branch_name)
|
596
|
+
puts "Pushed to remote repository"
|
597
|
+
push_success = true
|
598
|
+
rescue => e
|
599
|
+
puts "Git gem push failed: #{e.message}"
|
600
|
+
# Try using system git command as fallback
|
601
|
+
begin
|
602
|
+
system("git push origin #{branch_name}")
|
603
|
+
if $?.success?
|
604
|
+
puts "Pushed using system git command"
|
605
|
+
push_success = true
|
606
|
+
else
|
607
|
+
puts "System git push also failed"
|
608
|
+
end
|
609
|
+
rescue => system_error
|
610
|
+
puts "System git push failed: #{system_error.message}"
|
611
|
+
end
|
612
|
+
end
|
613
|
+
elsif !commit_success
|
614
|
+
puts "Push skipped - commit failed"
|
615
|
+
end
|
616
|
+
|
617
|
+
# Create pull request if enabled
|
618
|
+
if ConfigManager.auto_create_pr? && push_success
|
619
|
+
puts "🎯 Creating pull request for approval..."
|
620
|
+
fix_description = get_fix_description(error, method_name)
|
621
|
+
pr_url = PullRequestCreator.create_pull_request(
|
622
|
+
branch_name,
|
623
|
+
class_name,
|
624
|
+
method_name,
|
625
|
+
error,
|
626
|
+
fix_description,
|
627
|
+
file_path
|
628
|
+
)
|
629
|
+
if pr_url
|
630
|
+
puts "✅ Pull request created successfully: #{pr_url}"
|
631
|
+
puts "🔒 Fix will be applied only after PR is merged."
|
632
|
+
puts "📋 Review the PR and merge when ready."
|
633
|
+
else
|
634
|
+
puts "❌ Pull request creation failed"
|
635
|
+
end
|
636
|
+
elsif ConfigManager.auto_create_pr? && !push_success
|
637
|
+
puts "📝 PR creation skipped - push to remote failed"
|
638
|
+
else
|
639
|
+
puts "📝 PR creation is disabled in configuration"
|
640
|
+
end
|
641
|
+
|
642
|
+
# Switch back to original branch
|
643
|
+
git.checkout(original_branch)
|
644
|
+
puts "Switched back to original branch: #{original_branch}"
|
645
|
+
|
646
|
+
return true
|
647
|
+
|
648
|
+
rescue => e
|
649
|
+
puts "Git operations failed: #{e.message}"
|
650
|
+
return false
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
# Dedicated method for Git operations (used by Claude Code evolution)
|
655
|
+
def self.handle_git_operations_for_claude(error, class_name, method_name, file_path)
|
656
|
+
puts "🚀 Starting Git operations for Claude Code evolution..."
|
657
|
+
|
658
|
+
begin
|
659
|
+
git_settings = ConfigManager.git_settings
|
660
|
+
branch_prefix = git_settings['branch_prefix'] || 'evolve'
|
661
|
+
branch_name = "#{branch_prefix}/#{class_name.downcase}-#{method_name}-#{Time.now.to_i}"
|
662
|
+
puts "Creating new branch: #{branch_name}"
|
663
|
+
|
664
|
+
git = Git.open(Dir.pwd)
|
665
|
+
|
666
|
+
# Create and checkout new branch
|
667
|
+
git.branch(branch_name).checkout
|
668
|
+
puts "Created and checked out branch: #{branch_name}"
|
669
|
+
|
670
|
+
# Add all modified files (Claude Code may have modified multiple files)
|
671
|
+
git.add('.')
|
672
|
+
puts "Added all modified files to git"
|
673
|
+
|
674
|
+
# Commit the changes
|
675
|
+
commit_template = (git_settings['commit_message_template'] || 'Fix {class_name}##{method_name}: {error_type}').to_s
|
676
|
+
commit_message = commit_template.gsub('{class_name}', class_name.to_s)
|
677
|
+
.gsub('{method_name}', method_name.to_s)
|
678
|
+
.gsub('{error_type}', error.class.name.to_s)
|
679
|
+
|
680
|
+
puts "Commit message: #{commit_message}"
|
681
|
+
|
682
|
+
begin
|
683
|
+
git.commit(commit_message)
|
684
|
+
puts "Committed changes to branch: #{branch_name}"
|
685
|
+
rescue => commit_error
|
686
|
+
puts "Commit failed, trying fallback message..."
|
687
|
+
git.commit("Fix #{class_name}##{method_name}: #{error.class.name}")
|
688
|
+
puts "Committed with fallback message"
|
689
|
+
end
|
690
|
+
|
691
|
+
# Push to remote if enabled
|
692
|
+
if git_settings['auto_push']
|
693
|
+
begin
|
694
|
+
git.push('origin', branch_name)
|
695
|
+
puts "Pushed to remote repository"
|
696
|
+
|
697
|
+
# Create pull request if enabled
|
698
|
+
if ConfigManager.auto_create_pr?
|
699
|
+
puts "🎯 Attempting to create pull request..."
|
700
|
+
fix_description = get_fix_description(error, method_name)
|
701
|
+
pr_url = CodeHealer::PullRequestCreator.create_pull_request(
|
702
|
+
branch_name,
|
703
|
+
class_name,
|
704
|
+
method_name,
|
705
|
+
error,
|
706
|
+
fix_description,
|
707
|
+
file_path
|
708
|
+
)
|
709
|
+
if pr_url
|
710
|
+
puts "✅ Pull request created successfully: #{pr_url}"
|
711
|
+
return true
|
712
|
+
else
|
713
|
+
puts "❌ Pull request creation failed"
|
714
|
+
return false
|
715
|
+
end
|
716
|
+
else
|
717
|
+
puts "📝 PR creation is disabled in configuration"
|
718
|
+
return true
|
719
|
+
end
|
720
|
+
rescue => e
|
721
|
+
puts "Push failed: #{e.message}"
|
722
|
+
return false
|
723
|
+
end
|
724
|
+
else
|
725
|
+
puts "📝 Auto-push is disabled in configuration"
|
726
|
+
return true
|
727
|
+
end
|
728
|
+
|
729
|
+
rescue => e
|
730
|
+
puts "Git operations failed: #{e.message}"
|
731
|
+
return false
|
732
|
+
end
|
733
|
+
end
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
737
|
+
require_relative 'mcp_server'
|