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,153 @@
|
|
1
|
+
module CodeHealer
|
2
|
+
# Context-Aware Prompt Builder for Large Applications
|
3
|
+
# Builds intelligent prompts using rich business context
|
4
|
+
class ContextAwarePromptBuilder
|
5
|
+
class << self
|
6
|
+
def build_error_fix_prompt(error, class_name, method_name, context)
|
7
|
+
business_context = context[:business_context]
|
8
|
+
domain_context = context[:domain_context]
|
9
|
+
method_context = context[:method_specific_context]
|
10
|
+
|
11
|
+
<<~PROMPT
|
12
|
+
You are an expert Ruby developer and code evolution specialist for a #{business_context['project']['business_domain']} application.
|
13
|
+
|
14
|
+
## ERROR DETAILS
|
15
|
+
Type: #{error.class.name}
|
16
|
+
Message: #{error.message}
|
17
|
+
Class: #{class_name}
|
18
|
+
Method: #{method_name}
|
19
|
+
|
20
|
+
## BUSINESS CONTEXT
|
21
|
+
#{build_business_context_section(business_context, domain_context)}
|
22
|
+
|
23
|
+
## METHOD-SPECIFIC CONTEXT
|
24
|
+
#{build_method_context_section(method_context, method_name)}
|
25
|
+
|
26
|
+
## DOMAIN REQUIREMENTS
|
27
|
+
#{build_domain_requirements_section(domain_context, business_context)}
|
28
|
+
|
29
|
+
## CODING STANDARDS
|
30
|
+
#{build_coding_standards_section(business_context)}
|
31
|
+
|
32
|
+
## ERROR-SPECIFIC REQUIREMENTS
|
33
|
+
#{build_error_specific_requirements(error, business_context)}
|
34
|
+
|
35
|
+
## CODE REQUIREMENTS
|
36
|
+
- Generate ONLY the complete method implementation (def #{method_name}...end)
|
37
|
+
- Include comprehensive error handling specific to #{error.class.name}
|
38
|
+
- Add business-appropriate logging using Rails.logger
|
39
|
+
- Include input validation and parameter checking
|
40
|
+
- Follow Ruby best practices and conventions
|
41
|
+
- Ensure the fix is production-ready and secure
|
42
|
+
- Add performance considerations where relevant
|
43
|
+
- Include proper return values and error responses
|
44
|
+
- Use the exact method name: #{method_name}
|
45
|
+
- Consider domain-specific business rules and compliance requirements
|
46
|
+
|
47
|
+
Generate a complete, intelligent fix for the #{method_name} method that specifically addresses the #{error.class.name}:
|
48
|
+
PROMPT
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def build_business_context_section(business_context, domain_context)
|
54
|
+
return "No business context available." unless business_context
|
55
|
+
|
56
|
+
<<~CONTEXT
|
57
|
+
**Project**: #{business_context['project']['description']}
|
58
|
+
**Domain**: #{domain_context || 'General'}
|
59
|
+
**Criticality**: #{business_context['class_specific']&.dig('business_criticality') || 'medium'}
|
60
|
+
**Data Privacy**: #{business_context['class_specific']&.dig('data_privacy') || 'standard'}
|
61
|
+
**SLA Requirements**: #{business_context['domain_config']&.dig('sla_requirements') || '99.9%'}
|
62
|
+
**Compliance**: #{business_context['domain_config']&.dig('compliance')&.join(', ') || 'Standard'}
|
63
|
+
CONTEXT
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_method_context_section(method_context, method_name)
|
67
|
+
return "No method-specific context available." unless method_context
|
68
|
+
|
69
|
+
<<~METHOD
|
70
|
+
**Method Description**: #{method_context['description'] || "Auto-inferred method: #{method_name}"}
|
71
|
+
**Error Handling Strategy**: #{method_context['error_handling'] || 'graceful_handling'}
|
72
|
+
**Logging Level**: #{method_context['logging'] || 'info'}
|
73
|
+
**Return Values**: #{format_return_values(method_context['return_values'])}
|
74
|
+
METHOD
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_domain_requirements_section(domain_context, business_context)
|
78
|
+
return "No domain-specific requirements." unless domain_context
|
79
|
+
|
80
|
+
domain_config = business_context['domain_config']
|
81
|
+
return "No domain configuration available." unless domain_config
|
82
|
+
|
83
|
+
<<~DOMAIN
|
84
|
+
**Domain**: #{domain_context}
|
85
|
+
**Description**: #{domain_config['description']}
|
86
|
+
**Criticality**: #{domain_config['criticality']}
|
87
|
+
**Data Consistency**: #{domain_config['data_consistency'] || 'standard'}
|
88
|
+
**Security Level**: #{domain_config['security'] || 'standard'}
|
89
|
+
**Performance Requirements**: #{format_performance_requirements(domain_context, business_context)}
|
90
|
+
DOMAIN
|
91
|
+
end
|
92
|
+
|
93
|
+
def build_coding_standards_section(business_context)
|
94
|
+
return "No coding standards defined." unless business_context['coding_standards']
|
95
|
+
|
96
|
+
standards = business_context['coding_standards']
|
97
|
+
|
98
|
+
<<~STANDARDS
|
99
|
+
**Error Handling**: #{standards['error_handling']}
|
100
|
+
**Logging**: #{standards['logging']}
|
101
|
+
**Validation**: #{standards['validation']}
|
102
|
+
**Performance**: #{standards['performance']}
|
103
|
+
**Security**: #{standards['security']}
|
104
|
+
**Documentation**: #{standards['documentation']}
|
105
|
+
**Testing**: #{standards['testing']}
|
106
|
+
STANDARDS
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_error_specific_requirements(error, business_context)
|
110
|
+
# Previously extracted structured error rules removed. Use textual markdown/business context only.
|
111
|
+
error_rules = nil
|
112
|
+
|
113
|
+
if error_rules
|
114
|
+
<<~ERROR
|
115
|
+
**Strategy**: #{error_rules['strategy']}
|
116
|
+
**Return Value**: #{error_rules['return_value']}
|
117
|
+
**Logging**: #{error_rules['logging']}
|
118
|
+
**User Experience**: #{error_rules['user_experience']}
|
119
|
+
**Business Impact**: #{error_rules['business_impact']}
|
120
|
+
**Default Return**: #{error_rules['default_return']}
|
121
|
+
ERROR
|
122
|
+
else
|
123
|
+
<<~ERROR
|
124
|
+
**Strategy**: defensive_programming
|
125
|
+
**Return Value**: nil_or_default
|
126
|
+
**Logging**: error_level
|
127
|
+
**User Experience**: graceful_degradation
|
128
|
+
**Business Impact**: variable
|
129
|
+
ERROR
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def format_return_values(return_values)
|
134
|
+
return "Standard return values" unless return_values
|
135
|
+
|
136
|
+
return_values.map { |key, value| "#{key}: #{value}" }.join(", ")
|
137
|
+
end
|
138
|
+
|
139
|
+
def format_performance_requirements(domain_context, business_context)
|
140
|
+
performance_config = business_context['performance_requirements']&.dig(domain_context)
|
141
|
+
|
142
|
+
if performance_config
|
143
|
+
"Response Time: #{performance_config['response_time']}, " \
|
144
|
+
"Memory: #{performance_config['memory_usage']}, " \
|
145
|
+
"CPU: #{performance_config['cpu_usage']}, " \
|
146
|
+
"DB Queries: #{performance_config['database_queries']}"
|
147
|
+
else
|
148
|
+
"Standard performance requirements"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,513 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'git'
|
3
|
+
require 'octokit'
|
4
|
+
|
5
|
+
module CodeHealer
|
6
|
+
class Core
|
7
|
+
class << self
|
8
|
+
def initialize(config)
|
9
|
+
puts "🏥 Initializing CodeHealer with configuration..."
|
10
|
+
# Store configuration globally
|
11
|
+
@config = config
|
12
|
+
puts "✅ CodeHealer initialized successfully!"
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup_error_handling
|
16
|
+
puts "🔧 Setting up error handling..."
|
17
|
+
|
18
|
+
# Set up Rails error handling for web requests
|
19
|
+
if defined?(Rails) && Rails.application
|
20
|
+
# Subscribe to Rails error reporter
|
21
|
+
Rails.error.subscribe(WebRequestErrorSubscriber.new)
|
22
|
+
puts "✅ Web request error handling set up"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set up console error handling
|
26
|
+
if defined?(IRB) || defined?(Pry)
|
27
|
+
# Patch classes to catch console errors
|
28
|
+
patch_classes_for_evolution
|
29
|
+
puts "✅ Console error handling set up"
|
30
|
+
end
|
31
|
+
|
32
|
+
puts "✅ Error handling setup complete!"
|
33
|
+
end
|
34
|
+
|
35
|
+
def evolve_method(klass, method_name, context)
|
36
|
+
puts "Starting evolution process for #{klass.name}##{method_name}"
|
37
|
+
|
38
|
+
# Get the file path
|
39
|
+
file_path = Rails.root.join('app', 'models', "#{klass.name.underscore}.rb")
|
40
|
+
puts "File path: #{file_path}"
|
41
|
+
|
42
|
+
# Read the current content
|
43
|
+
current_content = File.read(file_path)
|
44
|
+
puts "Current content length: #{current_content.length}"
|
45
|
+
|
46
|
+
# Get the original method
|
47
|
+
original_method = klass.instance_method(method_name)
|
48
|
+
|
49
|
+
# Try to get the method source
|
50
|
+
begin
|
51
|
+
original_source = original_method.source
|
52
|
+
rescue
|
53
|
+
# If we can't get the source, try to find it in the file
|
54
|
+
if current_content =~ /def\s+#{method_name}.*?end/m
|
55
|
+
original_source = $&
|
56
|
+
else
|
57
|
+
# If we still can't find it, use the error context to generate a new method
|
58
|
+
original_source = generate_method_from_error(klass, method_name, context)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
puts "Original method source: #{original_source}"
|
63
|
+
|
64
|
+
# Generate new method implementation
|
65
|
+
new_method = generate_method(klass, method_name, context, original_source)
|
66
|
+
puts "Generated new method: #{new_method}"
|
67
|
+
|
68
|
+
# Replace the method in the file content
|
69
|
+
new_content = current_content.gsub(original_source, new_method)
|
70
|
+
puts "New content length: #{new_content.length}"
|
71
|
+
|
72
|
+
# Create a new branch
|
73
|
+
branch_name = "evolve/#{klass.name.underscore}-#{method_name}-#{Time.now.to_i}"
|
74
|
+
puts "Creating new branch: #{branch_name}"
|
75
|
+
|
76
|
+
# Initialize git if not already initialized
|
77
|
+
unless File.exist?(Rails.root.join('.git'))
|
78
|
+
puts "Initializing git repository..."
|
79
|
+
git = Git.init(Rails.root.to_s)
|
80
|
+
git.add('.')
|
81
|
+
git.commit('Initial commit')
|
82
|
+
end
|
83
|
+
|
84
|
+
# Create and checkout new branch
|
85
|
+
git = Git.open(Rails.root.to_s)
|
86
|
+
git.branch(branch_name).checkout
|
87
|
+
|
88
|
+
# Write the new content
|
89
|
+
File.write(file_path, new_content)
|
90
|
+
puts "Updated file: #{file_path}"
|
91
|
+
|
92
|
+
# Commit changes
|
93
|
+
git.add(file_path)
|
94
|
+
commit_message = "Evolve #{klass.name}##{method_name} to handle #{context[:error].class}"
|
95
|
+
git.commit(commit_message)
|
96
|
+
|
97
|
+
# Push branch
|
98
|
+
git.push('origin', branch_name)
|
99
|
+
puts "Pushed branch: #{branch_name}"
|
100
|
+
|
101
|
+
# Create PR
|
102
|
+
client = Octokit::Client.new(access_token: ENV['GITHUB_TOKEN'])
|
103
|
+
repo = ENV['GITHUB_REPOSITORY'] || 'your-username/your-repo'
|
104
|
+
|
105
|
+
pr_title = "Evolve #{klass.name}##{method_name}"
|
106
|
+
pr_body = <<~MARKDOWN
|
107
|
+
This PR contains an evolved version of `#{klass.name}##{method_name}` to handle the following error:
|
108
|
+
|
109
|
+
```
|
110
|
+
#{context[:error].class}: #{context[:error].message}
|
111
|
+
```
|
112
|
+
|
113
|
+
Changes:
|
114
|
+
- Added error handling for #{context[:error].class}
|
115
|
+
- Improved method robustness
|
116
|
+
- Added logging for debugging
|
117
|
+
MARKDOWN
|
118
|
+
|
119
|
+
pr = client.create_pull_request(
|
120
|
+
repo,
|
121
|
+
'main',
|
122
|
+
branch_name,
|
123
|
+
pr_title,
|
124
|
+
pr_body
|
125
|
+
)
|
126
|
+
puts "Created PR: #{pr.html_url}"
|
127
|
+
|
128
|
+
# Return the new method implementation
|
129
|
+
new_method
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def generate_method_from_error(klass, method_name, context)
|
135
|
+
# Get the error message and backtrace
|
136
|
+
error = context[:error]
|
137
|
+
backtrace = error.backtrace.first
|
138
|
+
|
139
|
+
# Extract the line number from the backtrace
|
140
|
+
if backtrace =~ /:(\d+):/
|
141
|
+
line_number = $1.to_i
|
142
|
+
end
|
143
|
+
|
144
|
+
# Generate a basic method structure
|
145
|
+
<<~RUBY
|
146
|
+
def #{method_name}(#{original_method.parameters.map { |type, name| name }.join(', ')})
|
147
|
+
#{error.message}
|
148
|
+
end
|
149
|
+
RUBY
|
150
|
+
end
|
151
|
+
|
152
|
+
def generate_method(klass, method_name, context, original_source)
|
153
|
+
# Get the original method
|
154
|
+
original_method = klass.instance_method(method_name)
|
155
|
+
|
156
|
+
# Extract the method body (everything between the first and last line)
|
157
|
+
method_lines = original_source.split("\n")
|
158
|
+
method_body = method_lines[1..-2].join("\n")
|
159
|
+
|
160
|
+
# Generate new method with error handling
|
161
|
+
new_method = <<~RUBY
|
162
|
+
def #{method_name}(#{original_method.parameters.map { |type, name| name }.join(', ')})
|
163
|
+
begin
|
164
|
+
#{method_body}
|
165
|
+
rescue NoMethodError => e
|
166
|
+
if e.message.include?('undefined method') && e.message.include?('nil')
|
167
|
+
# Handle nil object errors
|
168
|
+
puts "Handling nil object error: \#{e.message}"
|
169
|
+
return 0.05 # Default discount for nil cases
|
170
|
+
end
|
171
|
+
raise e
|
172
|
+
rescue => e
|
173
|
+
puts "Unexpected error in #{method_name}: \#{e.message}"
|
174
|
+
raise e
|
175
|
+
end
|
176
|
+
end
|
177
|
+
RUBY
|
178
|
+
|
179
|
+
new_method
|
180
|
+
end
|
181
|
+
|
182
|
+
def patch_classes_for_evolution
|
183
|
+
# Get allowed classes from config
|
184
|
+
allowed_classes = CodeHealer::ConfigManager.allowed_classes
|
185
|
+
|
186
|
+
allowed_classes.each do |class_name|
|
187
|
+
begin
|
188
|
+
klass = class_name.constantize
|
189
|
+
patch_class_for_evolution(klass)
|
190
|
+
rescue NameError => e
|
191
|
+
puts "⚠️ Could not load class #{class_name}: #{e.message}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def patch_class_for_evolution(klass)
|
197
|
+
# This will be called when errors occur in the class
|
198
|
+
# For now, just log that we're ready to handle errors
|
199
|
+
puts "🔍 Ready to handle errors in #{klass.name}"
|
200
|
+
end
|
201
|
+
|
202
|
+
# Web request error subscriber
|
203
|
+
class WebRequestErrorSubscriber
|
204
|
+
def report(error, handled:, severity:, context:, source: nil)
|
205
|
+
return unless should_handle_error?(error, context)
|
206
|
+
|
207
|
+
puts "🚨 Web request error caught: #{error.class} - #{error.message}"
|
208
|
+
|
209
|
+
# Extract class and method from error backtrace
|
210
|
+
class_name, method_name = extract_from_backtrace(error.backtrace)
|
211
|
+
|
212
|
+
if class_name && method_name
|
213
|
+
puts "🔧 Triggering healing for #{class_name}##{method_name}"
|
214
|
+
|
215
|
+
# Queue healing job
|
216
|
+
queue_evolution_job(error, class_name, method_name)
|
217
|
+
else
|
218
|
+
puts "⚠️ Could not extract class/method from backtrace"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def should_handle_error?(error, context)
|
225
|
+
# Always try to handle errors - we'll extract class from backtrace
|
226
|
+
true
|
227
|
+
end
|
228
|
+
|
229
|
+
def extract_from_backtrace(backtrace)
|
230
|
+
return [nil, nil] unless backtrace
|
231
|
+
|
232
|
+
puts "🔍 DEBUG: Starting backtrace analysis..."
|
233
|
+
puts "🔍 DEBUG: First 5 backtrace lines:"
|
234
|
+
backtrace.first(5).each_with_index { |line, i| puts " #{i}: #{line}" }
|
235
|
+
|
236
|
+
# Use the exact working implementation from SelfRuby
|
237
|
+
core_methods = %w[* + - / % ** == != < > <= >= <=> === =~ !~ & | ^ ~ << >> [] []= `]
|
238
|
+
app_file_line = backtrace.find { |line| line.include?('/app/') }
|
239
|
+
return [nil, nil] unless app_file_line
|
240
|
+
|
241
|
+
puts "🔍 DEBUG: Found app file line: #{app_file_line}"
|
242
|
+
|
243
|
+
if app_file_line =~ /(.+):(\d+):in `(.+)'/
|
244
|
+
file_path = $1
|
245
|
+
method_name = $3
|
246
|
+
|
247
|
+
puts "🔍 DEBUG: Extracted file_path=#{file_path}, method_name=#{method_name}"
|
248
|
+
|
249
|
+
# If it's a core method, look deeper in the backtrace
|
250
|
+
if core_methods.include?(method_name)
|
251
|
+
puts "🔍 DEBUG: #{method_name} is a core method, looking deeper..."
|
252
|
+
deeper_app_line = backtrace.find do |line|
|
253
|
+
line.include?('/app/') &&
|
254
|
+
line =~ /in `(.+)'/ &&
|
255
|
+
!core_methods.include?($1) &&
|
256
|
+
!$1.include?('block in') &&
|
257
|
+
!$1.include?('each') &&
|
258
|
+
!$1.include?('map') &&
|
259
|
+
!$1.include?('reduce')
|
260
|
+
end
|
261
|
+
|
262
|
+
if deeper_app_line
|
263
|
+
puts "🔍 DEBUG: Found deeper app line: #{deeper_app_line}"
|
264
|
+
if deeper_app_line =~ /(.+):(\d+):in `(.+)'/
|
265
|
+
file_path = $1
|
266
|
+
method_name = $3
|
267
|
+
puts "🔍 DEBUG: Updated to file_path=#{file_path}, method_name=#{method_name}"
|
268
|
+
end
|
269
|
+
else
|
270
|
+
puts "🔍 DEBUG: No deeper app line found"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# If it's still a block or iterator, look for the containing method
|
275
|
+
if method_name && (
|
276
|
+
method_name.include?('block in') ||
|
277
|
+
method_name.include?('each') ||
|
278
|
+
method_name.include?('map') ||
|
279
|
+
method_name.include?('reduce') ||
|
280
|
+
method_name.include?('sum')
|
281
|
+
)
|
282
|
+
puts "🔍 DEBUG: #{method_name} is a block/iterator, looking for containing method..."
|
283
|
+
# Look for the FIRST valid method in the backtrace, not just any method
|
284
|
+
containing_line = backtrace.find do |line|
|
285
|
+
line.include?('/app/') &&
|
286
|
+
line =~ /in `(.+)'/ &&
|
287
|
+
!core_methods.include?($1) &&
|
288
|
+
!$1.include?('block in') &&
|
289
|
+
!$1.include?('each') &&
|
290
|
+
!$1.include?('map') &&
|
291
|
+
!$1.include?('reduce') &&
|
292
|
+
!$1.include?('sum') &&
|
293
|
+
!$1.include?('*') # Also skip the core method we started with
|
294
|
+
end
|
295
|
+
|
296
|
+
if containing_line
|
297
|
+
puts "🔍 DEBUG: Found containing line: #{containing_line}"
|
298
|
+
if containing_line =~ /(.+):(\d+):in `(.+)'/
|
299
|
+
file_path = $1
|
300
|
+
method_name = $3
|
301
|
+
puts "🔍 DEBUG: Updated to file_path=#{file_path}, method_name=#{method_name}"
|
302
|
+
end
|
303
|
+
else
|
304
|
+
puts "🔍 DEBUG: No containing line found"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Extract class name from file path
|
309
|
+
if file_path && method_name
|
310
|
+
if file_path.include?('app/models/')
|
311
|
+
file_name = file_path.split('/').last.gsub('.rb', '')
|
312
|
+
class_name = file_name.classify
|
313
|
+
elsif file_path.include?('app/controllers/')
|
314
|
+
file_name = file_path.split('/').last.gsub('.rb', '')
|
315
|
+
if file_name.include?('/')
|
316
|
+
parts = file_name.split('/')
|
317
|
+
class_name = parts.map(&:classify).join('::')
|
318
|
+
else
|
319
|
+
class_name = file_name.classify
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
puts "🔍 DEBUG: Final result - class_name=#{class_name}, method_name=#{method_name}"
|
324
|
+
puts "🔍 Extracted: #{class_name}##{method_name} from #{file_path}"
|
325
|
+
return [class_name, method_name]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
puts "🔍 DEBUG: No valid method found in backtrace"
|
330
|
+
[nil, nil]
|
331
|
+
end
|
332
|
+
|
333
|
+
def queue_evolution_job(error, class_name, method_name)
|
334
|
+
# Queue the healing job in Sidekiq
|
335
|
+
if defined?(CodeHealer::HealingJob)
|
336
|
+
# Get evolution method from configuration
|
337
|
+
evolution_method = CodeHealer::ConfigManager.evolution_method
|
338
|
+
puts "🔄 Using evolution method: #{evolution_method}"
|
339
|
+
|
340
|
+
CodeHealer::HealingJob.perform_async(
|
341
|
+
error.class.name,
|
342
|
+
error.message,
|
343
|
+
class_name.to_s,
|
344
|
+
method_name.to_s,
|
345
|
+
evolution_method, # Use configured evolution method
|
346
|
+
error.backtrace # Pass the full backtrace for better analysis
|
347
|
+
)
|
348
|
+
puts "✅ Healing job queued for #{class_name}##{method_name}"
|
349
|
+
else
|
350
|
+
puts "⚠️ HealingJob not available"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
attr_reader :klass, :method_name, :context, :logger
|
357
|
+
|
358
|
+
def initialize(klass, method_name, context = {})
|
359
|
+
@klass = klass
|
360
|
+
@method_name = method_name
|
361
|
+
@context = context
|
362
|
+
@logger = Logger.new(Rails.root.join('log', 'self_evolving.log'))
|
363
|
+
end
|
364
|
+
|
365
|
+
def evolve
|
366
|
+
begin
|
367
|
+
puts "Starting evolution for #{klass.name}##{method_name}"
|
368
|
+
puts "Error: #{context[:error].class} - #{context[:error].message}"
|
369
|
+
|
370
|
+
# Get the original method
|
371
|
+
original_method = klass.instance_method(method_name)
|
372
|
+
puts "Original method parameters: #{original_method.parameters.inspect}"
|
373
|
+
|
374
|
+
# Generate the fixed method
|
375
|
+
new_method = generate_method(original_method)
|
376
|
+
puts "Generated new method:"
|
377
|
+
puts new_method
|
378
|
+
|
379
|
+
# Apply the changes directly
|
380
|
+
if apply_changes(new_method)
|
381
|
+
puts "Changes applied successfully"
|
382
|
+
true
|
383
|
+
else
|
384
|
+
puts "Failed to apply changes"
|
385
|
+
false
|
386
|
+
end
|
387
|
+
rescue => e
|
388
|
+
logger.error("Evolution failed: #{e.message}")
|
389
|
+
puts "Evolution failed: #{e.message}"
|
390
|
+
puts e.backtrace
|
391
|
+
false
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
private
|
396
|
+
|
397
|
+
def generate_method(original_method)
|
398
|
+
# Get the actual parameters from the original method
|
399
|
+
parameters = original_method.parameters
|
400
|
+
param_list = parameters.map { |type, name| name || "arg#{type}" }.join(', ')
|
401
|
+
|
402
|
+
# Use the source from context or try to get it
|
403
|
+
source = context[:source]
|
404
|
+
unless source
|
405
|
+
begin
|
406
|
+
source = original_method.source
|
407
|
+
puts "Original source: #{source}"
|
408
|
+
rescue
|
409
|
+
# If we can't get the source, read it from the file
|
410
|
+
file_path = Rails.root.join('app', 'models', "#{klass.name.underscore}.rb")
|
411
|
+
content = File.read(file_path)
|
412
|
+
if content =~ /def\s+#{method_name}.*?end/m
|
413
|
+
source = $&
|
414
|
+
puts "Found source in file: #{source}"
|
415
|
+
else
|
416
|
+
source = nil
|
417
|
+
puts "Could not find source in file"
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
case context[:error]
|
423
|
+
when NoMethodError
|
424
|
+
# For nil errors, add nil checks
|
425
|
+
<<~RUBY
|
426
|
+
def #{method_name}(#{param_list})
|
427
|
+
# Original method with nil checks
|
428
|
+
if user.purchase_history.nil? || user.purchase_history.empty?
|
429
|
+
0.05
|
430
|
+
else
|
431
|
+
last_purchase = user.purchase_history.last
|
432
|
+
last_purchase && last_purchase[:amount] > 1000 ? 0.1 : 0.05
|
433
|
+
end
|
434
|
+
end
|
435
|
+
RUBY
|
436
|
+
else
|
437
|
+
# For other errors, add basic error handling
|
438
|
+
if source
|
439
|
+
# Extract the method body from the source
|
440
|
+
if source =~ /def\s+#{method_name}.*?\n(.*?)end/m
|
441
|
+
method_body = $1
|
442
|
+
<<~RUBY
|
443
|
+
def #{method_name}(#{param_list})
|
444
|
+
begin
|
445
|
+
#{method_body}
|
446
|
+
rescue => e
|
447
|
+
logger.error("Error in #{method_name}: \#{e.message}")
|
448
|
+
raise e
|
449
|
+
end
|
450
|
+
end
|
451
|
+
RUBY
|
452
|
+
else
|
453
|
+
# If we couldn't extract the body, use the whole source
|
454
|
+
<<~RUBY
|
455
|
+
def #{method_name}(#{param_list})
|
456
|
+
begin
|
457
|
+
#{source}
|
458
|
+
rescue => e
|
459
|
+
logger.error("Error in #{method_name}: \#{e.message}")
|
460
|
+
raise e
|
461
|
+
end
|
462
|
+
end
|
463
|
+
RUBY
|
464
|
+
end
|
465
|
+
else
|
466
|
+
# If we couldn't get the source, use a generic error handler
|
467
|
+
<<~RUBY
|
468
|
+
def #{method_name}(#{param_list})
|
469
|
+
begin
|
470
|
+
# Original method implementation
|
471
|
+
super
|
472
|
+
rescue => e
|
473
|
+
logger.error("Error in #{method_name}: \#{e.message}")
|
474
|
+
raise e
|
475
|
+
end
|
476
|
+
end
|
477
|
+
RUBY
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
def apply_changes(new_method)
|
483
|
+
# Write the new method to the file
|
484
|
+
file_path = Rails.root.join('app', 'models', "#{klass.name.underscore}.rb")
|
485
|
+
puts "Updating file: #{file_path}"
|
486
|
+
|
487
|
+
content = File.read(file_path)
|
488
|
+
puts "Current file content:"
|
489
|
+
puts content
|
490
|
+
|
491
|
+
# Find the original method definition
|
492
|
+
method_pattern = /def\s+#{method_name}.*?end/m
|
493
|
+
if content =~ method_pattern
|
494
|
+
new_content = content.gsub(method_pattern, new_method)
|
495
|
+
puts "New file content:"
|
496
|
+
puts new_content
|
497
|
+
|
498
|
+
# Write the changes
|
499
|
+
File.write(file_path, new_content)
|
500
|
+
puts "File updated successfully"
|
501
|
+
|
502
|
+
# Reload the class to apply changes
|
503
|
+
klass.class_eval(new_method)
|
504
|
+
puts "Class reloaded with new method"
|
505
|
+
|
506
|
+
true
|
507
|
+
else
|
508
|
+
puts "Could not find method #{method_name} in #{file_path}"
|
509
|
+
false
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|