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,122 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module CodeHealer
|
4
|
+
class SimpleGlobalHandler
|
5
|
+
def self.setup_global_error_handling
|
6
|
+
# Set up a global exception handler for unhandled exceptions
|
7
|
+
Thread.current[:self_evolving_handler] = true
|
8
|
+
|
9
|
+
# Override the default exception handler
|
10
|
+
at_exit do
|
11
|
+
if $! && !Thread.current[:self_evolving_handler]
|
12
|
+
handle_unhandled_exception($!)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.handle_unhandled_exception(error)
|
18
|
+
puts "\n=== Global Error Handler Triggered ==="
|
19
|
+
puts "Error: #{error.class} - #{error.message}"
|
20
|
+
|
21
|
+
# Get the backtrace to find the file and method
|
22
|
+
if error.backtrace && error.backtrace.first
|
23
|
+
file_line = error.backtrace.first
|
24
|
+
if file_line =~ /(.+):(\d+):in `(.+)'/
|
25
|
+
file_path = $1
|
26
|
+
line_number = $2
|
27
|
+
method_name = $3
|
28
|
+
|
29
|
+
# Try to determine the class name from the file path
|
30
|
+
class_name = determine_class_name(file_path)
|
31
|
+
|
32
|
+
if class_name && method_name && should_handle_error?(error, class_name)
|
33
|
+
puts "File: #{file_path}"
|
34
|
+
puts "Class: #{class_name}"
|
35
|
+
puts "Method: #{method_name}"
|
36
|
+
|
37
|
+
# Handle the error using our evolution system
|
38
|
+
handle_method_error(error, class_name, method_name, file_path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.should_handle_error?(error, class_name)
|
45
|
+
# Only handle errors for our application classes, not system classes
|
46
|
+
return false if class_name.nil?
|
47
|
+
return false if ['String', 'Array', 'Hash', 'Integer', 'Float', 'Object', 'Class', 'Module'].include?(class_name)
|
48
|
+
return false if class_name.start_with?('RSpec') || class_name.start_with?('Minitest')
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.handle_method_error(error, class_name, method_name, file_path = nil)
|
53
|
+
puts "\n=== Method Error Handler Triggered ==="
|
54
|
+
puts "Error: #{error.class} - #{error.message}"
|
55
|
+
puts "Class: #{class_name}"
|
56
|
+
puts "Method: #{method_name}"
|
57
|
+
|
58
|
+
# If file_path is not provided, try to find it
|
59
|
+
unless file_path
|
60
|
+
file_path = find_file_for_class(class_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
if file_path && File.exist?(file_path)
|
64
|
+
puts "File: #{file_path}"
|
65
|
+
|
66
|
+
# Use our evolution system to fix the method
|
67
|
+
success = CodeHealer::ReliableEvolution.handle_error(
|
68
|
+
error,
|
69
|
+
class_name,
|
70
|
+
method_name,
|
71
|
+
file_path
|
72
|
+
)
|
73
|
+
|
74
|
+
if success
|
75
|
+
puts "✅ Method evolution successful!"
|
76
|
+
# Reload the class to get the updated method
|
77
|
+
load file_path
|
78
|
+
else
|
79
|
+
puts "❌ Method evolution failed"
|
80
|
+
end
|
81
|
+
else
|
82
|
+
puts "Could not find file for class: #{class_name}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def self.determine_class_name(file_path)
|
89
|
+
return nil unless File.exist?(file_path)
|
90
|
+
|
91
|
+
content = File.read(file_path)
|
92
|
+
if content =~ /class\s+(\w+)/
|
93
|
+
return $1
|
94
|
+
end
|
95
|
+
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.find_file_for_class(class_name)
|
100
|
+
# Look in common Rails directories
|
101
|
+
search_paths = [
|
102
|
+
'app/models',
|
103
|
+
'app/controllers',
|
104
|
+
'app/services',
|
105
|
+
'lib'
|
106
|
+
]
|
107
|
+
|
108
|
+
search_paths.each do |path|
|
109
|
+
if Dir.exist?(path)
|
110
|
+
Dir.glob("#{path}/**/*.rb").each do |file|
|
111
|
+
content = File.read(file)
|
112
|
+
if content =~ /class\s+#{class_name}/
|
113
|
+
return file
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,515 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CodeHealer
|
4
|
+
class SimpleHealer
|
5
|
+
class << self
|
6
|
+
def handle_error_with_mcp_intelligence(error, class_name, method_name, file_path, business_context = {})
|
7
|
+
puts "🧠 Starting MCP-powered intelligent healing..."
|
8
|
+
|
9
|
+
# Initialize MCP server if available
|
10
|
+
if defined?(CodeHealer::McpServer)
|
11
|
+
begin
|
12
|
+
CodeHealer::McpServer.initialize_server
|
13
|
+
puts "✅ MCP Server initialized successfully with tools"
|
14
|
+
rescue => e
|
15
|
+
puts "⚠️ MCP Server initialization failed: #{e.message}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Analyze the error using MCP tools
|
20
|
+
analysis = analyze_error_with_mcp(error, class_name, method_name, business_context)
|
21
|
+
|
22
|
+
if analysis
|
23
|
+
# Generate fix using MCP
|
24
|
+
fix = generate_fix_with_mcp(error, analysis, class_name, method_name, business_context)
|
25
|
+
|
26
|
+
if fix
|
27
|
+
# Apply the fix
|
28
|
+
apply_fix_to_code(fix, class_name, method_name)
|
29
|
+
else
|
30
|
+
puts "⚠️ Failed to generate fix with MCP"
|
31
|
+
end
|
32
|
+
else
|
33
|
+
puts "⚠️ Failed to analyze error with MCP"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def analyze_error_with_mcp(error, class_name, method_name, business_context)
|
40
|
+
puts "🧠 MCP analyzing error: #{error.class} - #{error.message}"
|
41
|
+
|
42
|
+
if defined?(CodeHealer::McpServer)
|
43
|
+
begin
|
44
|
+
context = {
|
45
|
+
class_name: class_name,
|
46
|
+
method_name: method_name,
|
47
|
+
error: error,
|
48
|
+
business_context: business_context
|
49
|
+
}
|
50
|
+
|
51
|
+
analysis = CodeHealer::McpServer.analyze_error(error, context)
|
52
|
+
puts "✅ MCP analysis complete"
|
53
|
+
return analysis
|
54
|
+
rescue => e
|
55
|
+
puts "⚠️ MCP analysis failed: #{e.message}"
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
else
|
59
|
+
puts "⚠️ MCP Server not available"
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def generate_fix_with_mcp(error, analysis, class_name, method_name, business_context)
|
65
|
+
puts "🧠 MCP generating contextual fix..."
|
66
|
+
|
67
|
+
if defined?(CodeHealer::McpServer)
|
68
|
+
begin
|
69
|
+
context = {
|
70
|
+
class_name: class_name,
|
71
|
+
method_name: method_name,
|
72
|
+
error: error,
|
73
|
+
analysis: analysis,
|
74
|
+
business_context: business_context
|
75
|
+
}
|
76
|
+
|
77
|
+
fix = CodeHealer::McpServer.generate_contextual_fix(error, analysis, context)
|
78
|
+
puts "✅ MCP generated intelligent fix"
|
79
|
+
return fix
|
80
|
+
rescue => e
|
81
|
+
puts "⚠️ MCP fix generation failed: #{e.message}"
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
else
|
85
|
+
puts "⚠️ MCP Server not available"
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def apply_fix_to_code(fix, class_name, method_name)
|
91
|
+
puts "🔧 Applying fix to code..."
|
92
|
+
|
93
|
+
begin
|
94
|
+
# Extract the new code from the fix
|
95
|
+
new_code = extract_code_from_fix(fix)
|
96
|
+
|
97
|
+
if new_code
|
98
|
+
# Find the source file
|
99
|
+
file_path = find_source_file(class_name)
|
100
|
+
|
101
|
+
if file_path && File.exist?(file_path)
|
102
|
+
# Apply the fix to the file
|
103
|
+
success = patch_file_with_fix(file_path, method_name, new_code)
|
104
|
+
|
105
|
+
if success
|
106
|
+
puts "✅ Fix successfully applied to #{file_path}"
|
107
|
+
|
108
|
+
# Reload the class to apply changes
|
109
|
+
reload_class(class_name)
|
110
|
+
|
111
|
+
# Create Git commit if configured
|
112
|
+
create_git_commit(class_name, method_name, fix)
|
113
|
+
|
114
|
+
return true
|
115
|
+
else
|
116
|
+
puts "⚠️ Failed to apply fix to file"
|
117
|
+
return false
|
118
|
+
end
|
119
|
+
else
|
120
|
+
puts "⚠️ Source file not found for #{class_name}"
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
else
|
124
|
+
puts "⚠️ No valid code found in fix"
|
125
|
+
return false
|
126
|
+
end
|
127
|
+
rescue => e
|
128
|
+
puts "❌ Error applying fix: #{e.message}"
|
129
|
+
puts e.backtrace.first(3)
|
130
|
+
return false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def extract_code_from_fix(fix)
|
137
|
+
case fix
|
138
|
+
when Hash
|
139
|
+
fix[:new_code] || fix[:code] || fix['new_code'] || fix['code']
|
140
|
+
when String
|
141
|
+
fix
|
142
|
+
else
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def find_source_file(class_name)
|
148
|
+
# Look for the class file in common Rails locations
|
149
|
+
possible_paths = [
|
150
|
+
"app/models/#{class_name.underscore}.rb",
|
151
|
+
"app/controllers/#{class_name.underscore}_controller.rb",
|
152
|
+
"app/services/#{class_name.underscore}.rb",
|
153
|
+
"app/lib/#{class_name.underscore}.rb"
|
154
|
+
]
|
155
|
+
|
156
|
+
possible_paths.find { |path| File.exist?(Rails.root.join(path)) }
|
157
|
+
end
|
158
|
+
|
159
|
+
def patch_file_with_fix(file_path, method_name, new_code)
|
160
|
+
full_path = Rails.root.join(file_path)
|
161
|
+
current_content = File.read(full_path)
|
162
|
+
|
163
|
+
puts "Applying fix for #{method_name}..."
|
164
|
+
puts "Current content length: #{current_content.length}"
|
165
|
+
|
166
|
+
# Get the new method code and clean it up
|
167
|
+
new_method_code = new_code.to_s.strip
|
168
|
+
puts "Generated new method: #{new_method_code}"
|
169
|
+
|
170
|
+
# Clean up the new method code - remove extra indentation
|
171
|
+
method_lines = new_method_code.split("\n")
|
172
|
+
# Find the base indentation of the first line
|
173
|
+
base_indent = method_lines.first.match(/^(\s*)/)[1].length
|
174
|
+
# Remove the base indentation from all lines
|
175
|
+
cleaned_method_lines = method_lines.map do |line|
|
176
|
+
if line.strip.empty?
|
177
|
+
line
|
178
|
+
else
|
179
|
+
line_indent = line.match(/^(\s*)/)[1].length
|
180
|
+
if line_indent >= base_indent
|
181
|
+
line[base_indent..-1]
|
182
|
+
else
|
183
|
+
line
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
cleaned_method_code = cleaned_method_lines.join("\n")
|
188
|
+
|
189
|
+
# Use a simpler and more reliable method replacement approach
|
190
|
+
lines = current_content.split("\n")
|
191
|
+
new_lines = []
|
192
|
+
method_start_index = nil
|
193
|
+
method_indent = 0
|
194
|
+
|
195
|
+
# Find the method start
|
196
|
+
lines.each_with_index do |line, index|
|
197
|
+
if line.strip.start_with?("def #{method_name}")
|
198
|
+
method_start_index = index
|
199
|
+
method_indent = line.match(/^(\s*)/)[1].length
|
200
|
+
break
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
if method_start_index.nil?
|
205
|
+
puts "❌ Could not find method #{method_name} in file"
|
206
|
+
return false
|
207
|
+
end
|
208
|
+
|
209
|
+
# Find the method end using a simple approach
|
210
|
+
method_end_index = method_start_index
|
211
|
+
indent_level = method_indent
|
212
|
+
|
213
|
+
lines[method_start_index..-1].each_with_index do |line, relative_index|
|
214
|
+
actual_index = method_start_index + relative_index
|
215
|
+
current_indent = line.match(/^(\s*)/)[1].length
|
216
|
+
|
217
|
+
# Skip the method definition line
|
218
|
+
if relative_index == 0
|
219
|
+
next
|
220
|
+
end
|
221
|
+
|
222
|
+
# If we find an 'end' at the same or lower indentation level, we're done
|
223
|
+
if line.strip == 'end' && current_indent <= indent_level
|
224
|
+
method_end_index = actual_index
|
225
|
+
break
|
226
|
+
end
|
227
|
+
|
228
|
+
# If we find another method definition at the same level, we're done
|
229
|
+
if line.strip.start_with?('def ') && current_indent == indent_level
|
230
|
+
method_end_index = actual_index - 1
|
231
|
+
break
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Build the new content
|
236
|
+
new_lines = lines[0...method_start_index]
|
237
|
+
|
238
|
+
# Add the new method with proper indentation
|
239
|
+
method_indent_str = ' ' * method_indent
|
240
|
+
cleaned_method_lines.each do |method_line|
|
241
|
+
if method_line.strip.empty?
|
242
|
+
new_lines << method_line
|
243
|
+
else
|
244
|
+
new_lines << method_indent_str + method_line
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Add the rest of the file after the method
|
249
|
+
new_lines += lines[(method_end_index + 1)..-1]
|
250
|
+
|
251
|
+
new_content = new_lines.join("\n")
|
252
|
+
puts "New content length: #{new_content.length}"
|
253
|
+
|
254
|
+
# Validate syntax before applying
|
255
|
+
unless valid_ruby_syntax?(new_content)
|
256
|
+
puts "❌ Syntax validation failed, reverting to original content"
|
257
|
+
return false
|
258
|
+
end
|
259
|
+
|
260
|
+
# Write the updated content
|
261
|
+
File.write(full_path, new_content)
|
262
|
+
puts "📝 Updated method #{method_name} in #{file_path}"
|
263
|
+
true
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
268
|
+
def valid_ruby_syntax?(content)
|
269
|
+
begin
|
270
|
+
# Try to parse the Ruby code
|
271
|
+
RubyVM::InstructionSequence.compile(content)
|
272
|
+
true
|
273
|
+
rescue SyntaxError => e
|
274
|
+
puts " Syntax error: #{e.message}"
|
275
|
+
false
|
276
|
+
rescue => e
|
277
|
+
puts " Validation error: #{e.message}"
|
278
|
+
false
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def reload_class(class_name)
|
283
|
+
begin
|
284
|
+
# Remove the constant to force reload
|
285
|
+
Object.send(:remove_const, class_name.to_sym) if Object.const_defined?(class_name.to_sym)
|
286
|
+
|
287
|
+
# Reload the file
|
288
|
+
load Rails.root.join(find_source_file(class_name))
|
289
|
+
|
290
|
+
puts "🔄 Successfully reloaded #{class_name}"
|
291
|
+
rescue => e
|
292
|
+
puts "⚠️ Failed to reload #{class_name}: #{e.message}"
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def create_git_commit(class_name, method_name, fix)
|
297
|
+
return unless git_configured?
|
298
|
+
|
299
|
+
begin
|
300
|
+
git = Git.open(Rails.root.to_s)
|
301
|
+
|
302
|
+
# Create Git branch using configuration
|
303
|
+
branch_prefix = CodeHealer::ConfigManager.branch_prefix
|
304
|
+
branch_name = "#{branch_prefix}/#{class_name.downcase}-#{method_name}-#{Time.now.to_i}"
|
305
|
+
puts "🌿 Creating new branch: #{branch_name}"
|
306
|
+
|
307
|
+
# Create and checkout new branch
|
308
|
+
git.branch(branch_name).checkout
|
309
|
+
puts "✅ Created and checked out branch: #{branch_name}"
|
310
|
+
|
311
|
+
# Create a descriptive commit message using template
|
312
|
+
commit_message = generate_commit_message_from_template(class_name, method_name, fix)
|
313
|
+
|
314
|
+
# Add and commit the changes
|
315
|
+
git.add('.')
|
316
|
+
git.commit(commit_message)
|
317
|
+
|
318
|
+
puts "📝 Git commit created: #{commit_message}"
|
319
|
+
|
320
|
+
# Push if configured
|
321
|
+
if CodeHealer::ConfigManager.auto_push?
|
322
|
+
push_changes(git, branch_name)
|
323
|
+
end
|
324
|
+
|
325
|
+
# Create pull request if configured
|
326
|
+
if CodeHealer::ConfigManager.auto_create_pr?
|
327
|
+
create_pull_request(branch_name, class_name, method_name, fix)
|
328
|
+
end
|
329
|
+
|
330
|
+
rescue => e
|
331
|
+
puts "⚠️ Git operations failed: #{e.message}"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def git_configured?
|
336
|
+
File.exist?(Rails.root.join('.git'))
|
337
|
+
end
|
338
|
+
|
339
|
+
def generate_commit_message_from_template(class_name, method_name, fix)
|
340
|
+
template = CodeHealer::ConfigManager.commit_message_template
|
341
|
+
|
342
|
+
# Replace placeholders in template
|
343
|
+
message = template.gsub('{class_name}', class_name.to_s)
|
344
|
+
.gsub('{method_name}', method_name.to_s)
|
345
|
+
.gsub('{error_type}', fix[:error_type] || fix['error_type'] || 'Unknown')
|
346
|
+
|
347
|
+
# Add additional context
|
348
|
+
message += "\n\nGenerated by CodeHealer gem"
|
349
|
+
if fix[:description] || fix['description']
|
350
|
+
message += "\nDescription: #{fix[:description] || fix['description']}"
|
351
|
+
end
|
352
|
+
if fix[:risk_level] || fix['risk_level']
|
353
|
+
message += "\nRisk Level: #{fix[:risk_level] || fix['risk_level']}"
|
354
|
+
end
|
355
|
+
|
356
|
+
message
|
357
|
+
end
|
358
|
+
|
359
|
+
def push_changes(git, branch_name)
|
360
|
+
begin
|
361
|
+
git.push('origin', branch_name)
|
362
|
+
puts "🚀 Changes pushed to origin/#{branch_name}"
|
363
|
+
rescue => e
|
364
|
+
puts "⚠️ Failed to push changes: #{e.message}"
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def create_pull_request(branch_name, class_name, method_name, fix)
|
369
|
+
puts "🔗 Creating pull request for branch: #{branch_name}"
|
370
|
+
|
371
|
+
# Get configuration
|
372
|
+
target_branch = CodeHealer::ConfigManager.pr_target_branch
|
373
|
+
labels = CodeHealer::ConfigManager.pr_labels
|
374
|
+
|
375
|
+
puts "📋 Target branch: #{target_branch}"
|
376
|
+
puts "🏷️ Labels: #{labels.join(', ')}"
|
377
|
+
|
378
|
+
begin
|
379
|
+
# Validate GitHub configuration
|
380
|
+
unless validate_github_config
|
381
|
+
return false
|
382
|
+
end
|
383
|
+
|
384
|
+
# Initialize GitHub client
|
385
|
+
github_client = Octokit::Client.new(access_token: ENV['GITHUB_TOKEN'])
|
386
|
+
|
387
|
+
# Test GitHub connection
|
388
|
+
begin
|
389
|
+
github_client.user
|
390
|
+
puts "✅ GitHub connection successful"
|
391
|
+
rescue Octokit::Error => e
|
392
|
+
puts "❌ GitHub authentication failed: #{e.message}"
|
393
|
+
return false
|
394
|
+
end
|
395
|
+
|
396
|
+
# Create pull request title and body
|
397
|
+
title = "Fix #{class_name}##{method_name}: #{fix[:error_type] || 'AI-generated fix'}"
|
398
|
+
body = generate_pr_body(class_name, method_name, fix, branch_name)
|
399
|
+
|
400
|
+
puts "📝 Creating PR: #{title}"
|
401
|
+
puts "📄 PR Body length: #{body.length} characters"
|
402
|
+
|
403
|
+
# Create the pull request
|
404
|
+
pr = github_client.create_pull_request(
|
405
|
+
ENV['GITHUB_REPOSITORY'],
|
406
|
+
target_branch,
|
407
|
+
branch_name,
|
408
|
+
title,
|
409
|
+
body
|
410
|
+
)
|
411
|
+
|
412
|
+
# Add labels to the PR
|
413
|
+
if labels.any?
|
414
|
+
begin
|
415
|
+
github_client.add_labels_to_an_issue(ENV['GITHUB_REPOSITORY'], pr.number, labels)
|
416
|
+
puts "🏷️ Added labels: #{labels.join(', ')}"
|
417
|
+
rescue => e
|
418
|
+
puts "⚠️ Warning: Could not add labels: #{e.message}"
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
puts "✅ Pull request created successfully!"
|
423
|
+
puts "🔗 PR URL: #{pr.html_url}"
|
424
|
+
puts "🔢 PR Number: ##{pr.number}"
|
425
|
+
|
426
|
+
return true
|
427
|
+
|
428
|
+
rescue Octokit::Error => e
|
429
|
+
puts "❌ GitHub API error: #{e.message}"
|
430
|
+
if e.respond_to?(:errors) && e.errors.any?
|
431
|
+
puts "📍 Error details:"
|
432
|
+
e.errors.each { |error| puts " - #{error}" }
|
433
|
+
end
|
434
|
+
return false
|
435
|
+
rescue => e
|
436
|
+
puts "❌ Unexpected error creating PR: #{e.message}"
|
437
|
+
puts "📍 Backtrace: #{e.backtrace.first(3)}"
|
438
|
+
return false
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
private
|
443
|
+
|
444
|
+
def validate_github_config
|
445
|
+
github_token = ENV['GITHUB_TOKEN']
|
446
|
+
github_repo = ENV['GITHUB_REPOSITORY']
|
447
|
+
|
448
|
+
unless github_token
|
449
|
+
puts "❌ Missing GITHUB_TOKEN environment variable"
|
450
|
+
puts "💡 Set it in your .env file or export it in your shell"
|
451
|
+
return false
|
452
|
+
end
|
453
|
+
|
454
|
+
unless github_repo
|
455
|
+
puts "❌ Missing GITHUB_REPOSITORY environment variable"
|
456
|
+
puts "💡 Set it in your .env file (format: username/repository)"
|
457
|
+
return false
|
458
|
+
end
|
459
|
+
|
460
|
+
unless github_repo.include?('/')
|
461
|
+
puts "❌ Invalid GITHUB_REPOSITORY format: #{github_repo}"
|
462
|
+
puts "💡 Should be in format: username/repository"
|
463
|
+
return false
|
464
|
+
end
|
465
|
+
|
466
|
+
puts "✅ GitHub configuration validated"
|
467
|
+
true
|
468
|
+
end
|
469
|
+
|
470
|
+
def generate_pr_body(class_name, method_name, fix, branch_name)
|
471
|
+
body = <<~MARKDOWN
|
472
|
+
## 🤖 AI-Powered Code Fix
|
473
|
+
|
474
|
+
This pull request was automatically generated by **CodeHealer** - an AI-powered code healing system.
|
475
|
+
|
476
|
+
### 📋 Fix Details
|
477
|
+
- **Class**: `#{class_name}`
|
478
|
+
- **Method**: `#{method_name}`
|
479
|
+
- **Error Type**: #{fix[:error_type] || 'Unknown'}
|
480
|
+
- **Branch**: `#{branch_name}`
|
481
|
+
|
482
|
+
### 🔧 What Was Fixed
|
483
|
+
#{fix[:description] || 'AI-generated fix for the specified error'}
|
484
|
+
|
485
|
+
### 🧠 How It Works
|
486
|
+
CodeHealer detected an error in your application and automatically:
|
487
|
+
1. Analyzed the error using AI
|
488
|
+
2. Generated a production-ready fix
|
489
|
+
3. Applied the fix to your code
|
490
|
+
4. Created this pull request for review
|
491
|
+
|
492
|
+
### ✅ Safety Features
|
493
|
+
- Code syntax validated before application
|
494
|
+
- Business context awareness integrated
|
495
|
+
- Comprehensive error handling added
|
496
|
+
- Logging and monitoring included
|
497
|
+
|
498
|
+
### 🔍 Review Checklist
|
499
|
+
- [ ] Code follows your project's style guidelines
|
500
|
+
- [ ] Error handling is appropriate for your use case
|
501
|
+
- [ ] Business logic aligns with your requirements
|
502
|
+
- [ ] Tests pass (if applicable)
|
503
|
+
|
504
|
+
### 🚀 Next Steps
|
505
|
+
Review the changes and merge when ready. The fix is already applied to your code and ready for testing.
|
506
|
+
|
507
|
+
---
|
508
|
+
*Generated by CodeHealer v#{CodeHealer::VERSION}*
|
509
|
+
MARKDOWN
|
510
|
+
|
511
|
+
body.strip
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|