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,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
|