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