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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +70 -0
  3. data/GEM_SUMMARY.md +307 -0
  4. data/README.md +281 -0
  5. data/code_healer.gemspec +77 -0
  6. data/config/code_healer.yml.example +104 -0
  7. data/docs/INSTALLATION.md +439 -0
  8. data/examples/basic_usage.rb +160 -0
  9. data/exe/code_healer-setup +7 -0
  10. data/lib/code_healer/application_job.rb +7 -0
  11. data/lib/code_healer/business_context_analyzer.rb +464 -0
  12. data/lib/code_healer/business_context_loader.rb +273 -0
  13. data/lib/code_healer/business_context_manager.rb +297 -0
  14. data/lib/code_healer/business_logic_generator.rb +94 -0
  15. data/lib/code_healer/business_rule_applier.rb +54 -0
  16. data/lib/code_healer/claude_code_evolution_handler.rb +224 -0
  17. data/lib/code_healer/claude_error_monitor.rb +48 -0
  18. data/lib/code_healer/config_manager.rb +275 -0
  19. data/lib/code_healer/context_aware_prompt_builder.rb +153 -0
  20. data/lib/code_healer/core.rb +513 -0
  21. data/lib/code_healer/error_handler.rb +141 -0
  22. data/lib/code_healer/evolution_job.rb +99 -0
  23. data/lib/code_healer/global_handler.rb +130 -0
  24. data/lib/code_healer/healing_job.rb +167 -0
  25. data/lib/code_healer/mcp.rb +108 -0
  26. data/lib/code_healer/mcp_prompts.rb +111 -0
  27. data/lib/code_healer/mcp_server.rb +389 -0
  28. data/lib/code_healer/mcp_tools.rb +2364 -0
  29. data/lib/code_healer/pull_request_creator.rb +143 -0
  30. data/lib/code_healer/setup.rb +390 -0
  31. data/lib/code_healer/simple_evolution.rb +737 -0
  32. data/lib/code_healer/simple_global_handler.rb +122 -0
  33. data/lib/code_healer/simple_healer.rb +515 -0
  34. data/lib/code_healer/terminal_integration.rb +87 -0
  35. data/lib/code_healer/usage_analyzer.rb +92 -0
  36. data/lib/code_healer/version.rb +5 -0
  37. data/lib/code_healer.rb +67 -0
  38. 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