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,141 @@
1
+ module CodeHealer
2
+ class ErrorHandler
3
+ class << self
4
+ def handle_error(error, context = {})
5
+ new(error, context).handle
6
+ end
7
+ end
8
+
9
+ attr_reader :error, :context, :logger
10
+
11
+ def initialize(error, context = {})
12
+ @error = error
13
+ @context = context
14
+ @logger = Logger.new(Rails.root.join('log', 'self_evolving.log'))
15
+ logger.debug("Initializing ErrorHandler with error: #{error.inspect}")
16
+ end
17
+
18
+ def handle
19
+ logger.debug("Starting error handling for: #{error.class}")
20
+ return false unless should_handle_error?
21
+
22
+ begin
23
+ evolution_context = build_evolution_context
24
+ logger.debug("Evolution context: #{evolution_context.inspect}")
25
+
26
+ # If we can't determine the class or method, create a test class
27
+ target_class = error_class || create_test_class
28
+ target_method = error_method || 'handle_error'
29
+
30
+ logger.debug("Target class: #{target_class}, Target method: #{target_method}")
31
+
32
+ success = Core.evolve_method(target_class, target_method, evolution_context)
33
+
34
+ if success
35
+ log_successful_repair
36
+ true
37
+ else
38
+ log_failed_repair
39
+ false
40
+ end
41
+ rescue => e
42
+ logger.error("Error handling failed: #{e.message}")
43
+ logger.error(e.backtrace.join("\n"))
44
+ false
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def should_handle_error?
51
+ # During testing, handle all errors
52
+ return true if Rails.env.test? || Rails.env.development?
53
+
54
+ # In production, only handle specific errors
55
+ error.is_a?(NoMethodError) || error.is_a?(ZeroDivisionError)
56
+ end
57
+
58
+ def error_class
59
+ if error.respond_to?(:backtrace_locations)
60
+ # Get the first backtrace location
61
+ location = error.backtrace_locations&.first
62
+ return create_test_class unless location
63
+
64
+ # Get the label (method name) from the backtrace
65
+ label = location.label
66
+ return create_test_class unless label
67
+
68
+ # Try to get the class name from the label
69
+ if label.include?('#')
70
+ class_name = label.split('#').first
71
+ # Only try to constantize if it looks like a valid class name
72
+ if class_name =~ /^[A-Z]/
73
+ begin
74
+ return class_name.constantize
75
+ rescue NameError
76
+ logger.debug("Could not constantize class name: #{class_name}")
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ # If we couldn't determine the class, use the test class
83
+ create_test_class
84
+ end
85
+
86
+ def error_method
87
+ if error.respond_to?(:backtrace_locations)
88
+ location = error.backtrace_locations&.first
89
+ return 'handle_error' unless location
90
+
91
+ label = location.label
92
+ return 'handle_error' unless label
93
+
94
+ if label.include?('#')
95
+ method_name = label.split('#').last
96
+ return method_name if method_name =~ /^[a-z]/
97
+ end
98
+ end
99
+
100
+ 'handle_error'
101
+ end
102
+
103
+ def create_test_class
104
+ # Create a test class if we can't determine the original class
105
+ Class.new do
106
+ def self.name
107
+ 'TestClass'
108
+ end
109
+ end
110
+ end
111
+
112
+ def build_evolution_context
113
+ {
114
+ error_type: error.class.name,
115
+ error_message: error.to_s,
116
+ backtrace: error.respond_to?(:backtrace) ? error.backtrace&.first(5) : [],
117
+ context: context
118
+ }
119
+ end
120
+
121
+ def log_successful_repair
122
+ logger.info({
123
+ timestamp: Time.current,
124
+ event: 'successful_repair',
125
+ error_type: error.class.name,
126
+ class: error_class&.name,
127
+ method: error_method
128
+ }.to_json)
129
+ end
130
+
131
+ def log_failed_repair
132
+ logger.info({
133
+ timestamp: Time.current,
134
+ event: 'failed_repair',
135
+ error_type: error.class.name,
136
+ class: error_class&.name,
137
+ method: error_method
138
+ }.to_json)
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,99 @@
1
+ class EvolutionJob
2
+ include Sidekiq::Job
3
+
4
+ sidekiq_options retry: 3, backtrace: true, queue: 'evolution'
5
+
6
+ def perform(error_data, class_name, method_name, file_path)
7
+ puts "🚀 Evolution Job Started: #{class_name}##{method_name}"
8
+
9
+ # Reconstruct the error object
10
+ error = reconstruct_error(error_data)
11
+
12
+ # Determine evolution strategy
13
+ evolution_method = CodeHealer::ConfigManager.evolution_method
14
+
15
+ case evolution_method
16
+ when 'claude_code_terminal'
17
+ handle_claude_code_evolution(error, class_name, method_name, file_path)
18
+ when 'api'
19
+ handle_api_evolution(error, class_name, method_name, file_path)
20
+ when 'hybrid'
21
+ handle_hybrid_evolution(error, class_name, method_name, file_path)
22
+ else
23
+ puts "❌ Unknown evolution method: #{evolution_method}"
24
+ end
25
+
26
+ puts "✅ Evolution Job Completed: #{class_name}##{method_name}"
27
+ rescue => e
28
+ puts "❌ Evolution Job Failed: #{e.message}"
29
+ puts "📍 Backtrace: #{e.backtrace.first(5)}"
30
+ raise e # Re-raise to trigger Sidekiq retry
31
+ end
32
+
33
+ private
34
+
35
+ def reconstruct_error(error_data)
36
+ # Reconstruct the error object from serialized data
37
+ error_class = Object.const_get(error_data['class'])
38
+ error = error_class.new(error_data['message'])
39
+
40
+ # Restore backtrace if available
41
+ if error_data['backtrace']
42
+ error.set_backtrace(error_data['backtrace'])
43
+ end
44
+
45
+ error
46
+ end
47
+
48
+ def handle_claude_code_evolution(error, class_name, method_name, file_path)
49
+ puts "🤖 Using Claude Code Terminal for evolution..."
50
+
51
+ # Use the existing Claude Code evolution handler (it's a class method)
52
+ success = CodeHealer::ClaudeCodeEvolutionHandler.handle_error_with_claude_code(error, class_name, method_name, file_path)
53
+
54
+ if success
55
+ puts "✅ Claude Code evolution completed successfully!"
56
+ else
57
+ puts "❌ Claude Code evolution failed"
58
+ raise "Claude Code evolution failed for #{class_name}##{method_name}"
59
+ end
60
+ end
61
+
62
+ def handle_api_evolution(error, class_name, method_name, file_path)
63
+ puts "🌐 Using OpenAI API for evolution..."
64
+
65
+ # Load business context for API evolution
66
+ business_context = CodeHealer::BusinessContextManager.get_context_for_error(
67
+ error, class_name, method_name
68
+ )
69
+
70
+ puts "📋 Business context loaded for API evolution"
71
+
72
+ # Use the existing MCP evolution handler with business context
73
+ success = CodeHealer::SimpleEvolution.handle_error_with_mcp_intelligence(
74
+ error, class_name, method_name, file_path, business_context
75
+ )
76
+
77
+ if success
78
+ puts "✅ API evolution completed successfully!"
79
+ else
80
+ puts "❌ API evolution failed"
81
+ raise "API evolution failed for #{class_name}##{method_name}"
82
+ end
83
+ end
84
+
85
+ def handle_hybrid_evolution(error, class_name, method_name, file_path)
86
+ puts "🔄 Using Hybrid approach for evolution..."
87
+
88
+ begin
89
+ # Try Claude Code first
90
+ success = handle_claude_code_evolution(error, class_name, method_name, file_path)
91
+ return if success
92
+ rescue => e
93
+ puts "⚠️ Claude Code failed, falling back to API: #{e.message}"
94
+ end
95
+
96
+ # Fallback to API
97
+ handle_api_evolution(error, class_name, method_name, file_path)
98
+ end
99
+ end
@@ -0,0 +1,130 @@
1
+ require 'git'
2
+
3
+ module CodeHealer
4
+ class GlobalHandler
5
+ def self.setup_global_error_handling
6
+ # Override the default exception handler for unhandled exceptions
7
+ at_exit do
8
+ if $!
9
+ handle_global_error($!)
10
+ end
11
+ end
12
+
13
+ # Set up a global error handler for specific error types
14
+ setup_method_error_handling
15
+ end
16
+
17
+ def self.setup_method_error_handling
18
+ # Override method_missing to catch NoMethodError
19
+ Object.class_eval do
20
+ alias_method :original_method_missing, :method_missing
21
+
22
+ def method_missing(method_name, *args, &block)
23
+ begin
24
+ original_method_missing(method_name, *args, &block)
25
+ rescue NoMethodError => e
26
+ CodeHealer::GlobalHandler.handle_method_error(e, self.class, method_name)
27
+ raise e # Re-raise the error after handling
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def self.handle_global_error(error)
34
+ puts "\n=== Global Error Handler Triggered ==="
35
+ puts "Error: #{error.class} - #{error.message}"
36
+
37
+ # Get the backtrace to find the file and method
38
+ if error.backtrace && error.backtrace.first
39
+ file_line = error.backtrace.first
40
+ if file_line =~ /(.+):(\d+):in `(.+)'/
41
+ file_path = $1
42
+ line_number = $2
43
+ method_name = $3
44
+
45
+ # Try to determine the class name from the file path
46
+ class_name = determine_class_name(file_path)
47
+
48
+ if class_name && method_name
49
+ puts "File: #{file_path}"
50
+ puts "Class: #{class_name}"
51
+ puts "Method: #{method_name}"
52
+
53
+ # Handle the error using our evolution system
54
+ handle_method_error(error, class_name, method_name, file_path)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def self.handle_method_error(error, class_name, method_name, file_path = nil)
61
+ puts "\n=== Method Error Handler Triggered ==="
62
+ puts "Error: #{error.class} - #{error.message}"
63
+ puts "Class: #{class_name}"
64
+ puts "Method: #{method_name}"
65
+
66
+ # If file_path is not provided, try to find it
67
+ unless file_path
68
+ file_path = find_file_for_class(class_name)
69
+ end
70
+
71
+ if file_path && File.exist?(file_path)
72
+ puts "File: #{file_path}"
73
+
74
+ # Use our evolution system to fix the method
75
+ success = CodeHealer::ReliableEvolution.handle_error(
76
+ error,
77
+ class_name,
78
+ method_name,
79
+ file_path
80
+ )
81
+
82
+ if success
83
+ puts "✅ Method evolution successful!"
84
+ # Reload the class to get the updated method
85
+ load file_path
86
+ else
87
+ puts "❌ Method evolution failed"
88
+ end
89
+ else
90
+ puts "Could not find file for class: #{class_name}"
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def self.determine_class_name(file_path)
97
+ return nil unless File.exist?(file_path)
98
+
99
+ content = File.read(file_path)
100
+ if content =~ /class\s+(\w+)/
101
+ return $1
102
+ end
103
+
104
+ nil
105
+ end
106
+
107
+ def self.find_file_for_class(class_name)
108
+ # Look in common Rails directories
109
+ search_paths = [
110
+ 'app/models',
111
+ 'app/controllers',
112
+ 'app/services',
113
+ 'lib'
114
+ ]
115
+
116
+ search_paths.each do |path|
117
+ if Dir.exist?(path)
118
+ Dir.glob("#{path}/**/*.rb").each do |file|
119
+ content = File.read(file)
120
+ if content =~ /class\s+#{class_name}/
121
+ return file
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ nil
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodeHealer
4
+ class HealingJob
5
+ include Sidekiq::Job
6
+
7
+ sidekiq_options retry: 3, backtrace: true, queue: 'evolution'
8
+
9
+ def perform(error_type, error_message, class_name, method_name, evolution_method = 'api', backtrace = nil)
10
+ puts "🚀 Evolution Job Started: #{class_name}##{method_name}"
11
+
12
+ # Reconstruct the error object with backtrace
13
+ error = reconstruct_error(error_type, error_message, backtrace)
14
+
15
+ # Determine evolution strategy
16
+ case evolution_method
17
+ when 'claude_code_terminal'
18
+ handle_claude_code_evolution(error, class_name, method_name)
19
+ when 'api'
20
+ handle_api_evolution(error, class_name, method_name)
21
+ when 'hybrid'
22
+ handle_hybrid_evolution(error, class_name, method_name)
23
+ else
24
+ puts "⚠️ Unknown evolution method: #{evolution_method}"
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def handle_claude_code_evolution(error, class_name, method_name)
31
+ puts "🤖 Using Claude Code Terminal for evolution..."
32
+
33
+ if defined?(CodeHealer::ClaudeCodeEvolutionHandler)
34
+ # For Claude Code: pass the full backtrace instead of file path
35
+ # Claude can analyze the backtrace and find files itself
36
+ puts "📋 Sending full backtrace to Claude Code for intelligent analysis"
37
+ puts "🔍 Backtrace length: #{error.backtrace&.length || 0} lines"
38
+
39
+ CodeHealer::ClaudeCodeEvolutionHandler.handle_error_with_claude_code(
40
+ error, class_name, method_name, nil
41
+ )
42
+ else
43
+ puts "⚠️ ClaudeCodeEvolutionHandler not available"
44
+ end
45
+ end
46
+
47
+ def handle_api_evolution(error, class_name, method_name)
48
+ puts "🌐 Using OpenAI API for evolution..."
49
+
50
+ # For API: extract file path since API doesn't have codebase access
51
+ file_path = extract_file_path_from_backtrace(error.backtrace)
52
+ puts "📁 File path for API: #{file_path || 'Not found'}"
53
+
54
+ # Load business context
55
+ if defined?(CodeHealer::BusinessContextManager)
56
+ business_context = CodeHealer::BusinessContextManager.get_context_for_error(
57
+ error, class_name, method_name
58
+ )
59
+ puts "📋 Business context loaded for API evolution"
60
+ else
61
+ business_context = {}
62
+ puts "⚠️ BusinessContextManager not available"
63
+ end
64
+
65
+ # Use SimpleHealer for API-based evolution
66
+ if defined?(CodeHealer::SimpleHealer)
67
+ CodeHealer::SimpleHealer.handle_error_with_mcp_intelligence(
68
+ error, class_name, method_name, file_path, business_context
69
+ )
70
+ else
71
+ puts "⚠️ SimpleHealer not available"
72
+ end
73
+ end
74
+
75
+ def handle_hybrid_evolution(error, class_name, method_name)
76
+ puts "🔄 Using hybrid evolution strategy..."
77
+
78
+ # Try Claude Code first, fallback to API
79
+ begin
80
+ handle_claude_code_evolution(error, class_name, method_name)
81
+ rescue => e
82
+ puts "⚠️ Claude Code evolution failed: #{e.message}"
83
+ puts "🔄 Falling back to API evolution..."
84
+ handle_api_evolution(error, class_name, method_name)
85
+ end
86
+ end
87
+
88
+ def extract_file_path_from_backtrace(backtrace)
89
+ return nil unless backtrace
90
+
91
+ core_methods = %w[* + - / % ** == != < > <= >= <=> === =~ !~ & | ^ ~ << >> [] []= `]
92
+ app_file_line = backtrace.find { |line| line.include?('/app/') }
93
+
94
+ return nil unless app_file_line
95
+
96
+ if app_file_line =~ /(.+):(\d+):in `(.+)'/
97
+ file_path = $1
98
+ method_name = $3
99
+
100
+ # Handle Ruby operators by looking deeper in the stack
101
+ if core_methods.include?(method_name)
102
+ deeper_app_line = backtrace.find do |line|
103
+ line.include?('/app/') &&
104
+ line =~ /in `(.+)'/ &&
105
+ !core_methods.include?($1) &&
106
+ !$1.include?('block in') &&
107
+ !$1.include?('each') &&
108
+ !$1.include?('map') &&
109
+ !$1.include?('reduce')
110
+ end
111
+
112
+ if deeper_app_line && deeper_app_line =~ /(.+):(\d+):in `(.+)'/
113
+ file_path = $1
114
+ method_name = $3
115
+ end
116
+ end
117
+
118
+ # Handle iterator methods and blocks
119
+ if method_name && (
120
+ method_name.include?('block in') ||
121
+ method_name.include?('each') ||
122
+ method_name.include?('map') ||
123
+ method_name.include?('reduce') ||
124
+ method_name.include?('sum')
125
+ )
126
+ containing_line = backtrace.find do |line|
127
+ line.include?('/app/') &&
128
+ line =~ /in `(.+)'/ &&
129
+ !$1.include?('block in') &&
130
+ !$1.include?('each') &&
131
+ !$1.include?('map') &&
132
+ !$1.include?('reduce') &&
133
+ !$1.include?('sum')
134
+ end
135
+
136
+ if containing_line && containing_line =~ /(.+):(\d+):in `(.+)'/
137
+ file_path = $1
138
+ method_name = $3
139
+ end
140
+ end
141
+
142
+ return file_path if file_path
143
+ end
144
+
145
+ nil
146
+ end
147
+
148
+ def reconstruct_error(error_type, error_message, backtrace = nil)
149
+ # Create a simple error object with the type and message
150
+ error_class = error_type.constantize rescue StandardError
151
+ error = error_class.new(error_message)
152
+
153
+ # Set the backtrace if provided
154
+ if backtrace
155
+ error.set_backtrace(backtrace)
156
+ puts "📋 Backtrace restored: #{backtrace.length} lines"
157
+ else
158
+ puts "⚠️ No backtrace provided"
159
+ end
160
+
161
+ error
162
+ rescue
163
+ # Fallback to generic error
164
+ StandardError.new(error_message)
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodeHealer
4
+ module MCP
5
+ # Base class for MCP tools
6
+ class Tool
7
+ class << self
8
+ def description(desc = nil)
9
+ @description = desc if desc
10
+ @description
11
+ end
12
+
13
+ def input_schema(schema = nil)
14
+ @input_schema = schema if schema
15
+ @input_schema
16
+ end
17
+
18
+ def annotations(annotations = nil)
19
+ @annotations = annotations if annotations
20
+ @annotations
21
+ end
22
+ end
23
+ end
24
+
25
+ # Response class for MCP tools
26
+ class Tool::Response
27
+ attr_reader :content
28
+
29
+ def initialize(content)
30
+ @content = content
31
+ end
32
+ end
33
+
34
+ # Base class for MCP prompts
35
+ class Prompt
36
+ class << self
37
+ def prompt_name(name = nil)
38
+ @prompt_name = name if name
39
+ @prompt_name
40
+ end
41
+
42
+ def description(desc = nil)
43
+ @description = desc if desc
44
+ @description
45
+ end
46
+
47
+ def arguments(args = nil)
48
+ @arguments = args if args
49
+ @arguments
50
+ end
51
+ end
52
+ end
53
+
54
+ # Argument class for MCP prompts
55
+ class Prompt::Argument
56
+ attr_reader :name, :description, :required
57
+
58
+ def initialize(name:, description:, required: false)
59
+ @name = name
60
+ @description = description
61
+ @required = required
62
+ end
63
+ end
64
+
65
+ # Result class for MCP prompts
66
+ class Prompt::Result
67
+ attr_reader :description, :messages
68
+
69
+ def initialize(description:, messages:)
70
+ @description = description
71
+ @messages = messages
72
+ end
73
+ end
74
+
75
+ # Message class for MCP prompts
76
+ class Prompt::Message
77
+ attr_reader :role, :content
78
+
79
+ def initialize(role:, content:)
80
+ @role = role
81
+ @content = content
82
+ end
83
+ end
84
+
85
+ # Content classes for MCP prompts
86
+ module Content
87
+ class Text
88
+ attr_reader :text
89
+
90
+ def initialize(text)
91
+ @text = text
92
+ end
93
+ end
94
+ end
95
+
96
+ # Server class for MCP
97
+ class Server
98
+ attr_reader :name, :version, :tools, :server_context
99
+
100
+ def initialize(name:, version:, tools:, server_context:)
101
+ @name = name
102
+ @version = version
103
+ @tools = tools
104
+ @server_context = server_context
105
+ end
106
+ end
107
+ end
108
+ end