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,273 @@
1
+ module CodeHealer
2
+ # Dynamic Business Context Loader for Large Applications
3
+ # Intelligently loads and provides business context for any class/method
4
+ class BusinessContextLoader
5
+ class << self
6
+ def load_context_for_class(class_name)
7
+ context = load_base_context
8
+ domain_context = find_domain_for_class(class_name)
9
+
10
+ if domain_context
11
+ context.merge!(domain_context)
12
+ context[:class_specific] = load_class_specific_context(class_name, domain_context)
13
+ end
14
+
15
+ context
16
+ end
17
+
18
+ def load_context_for_method(class_name, method_name)
19
+ class_context = load_context_for_class(class_name)
20
+ method_context = load_method_specific_context(class_name, method_name, class_context)
21
+
22
+ class_context.merge!(method_context)
23
+ end
24
+
25
+ def find_domain_for_class(class_name)
26
+ domains = load_domains_from_config
27
+
28
+ # Direct class match
29
+ domains.each do |domain_name, domain_config|
30
+ if domain_config['classes']&.key?(class_name)
31
+ return {
32
+ domain: domain_name,
33
+ domain_config: domain_config,
34
+ class_config: domain_config['classes'][class_name]
35
+ }
36
+ end
37
+ end
38
+
39
+ # Pattern matching for similar classes
40
+ domains.each do |domain_name, domain_config|
41
+ if matches_domain_pattern?(class_name, domain_name)
42
+ return {
43
+ domain: domain_name,
44
+ domain_config: domain_config,
45
+ class_config: infer_class_config(class_name, domain_config)
46
+ }
47
+ end
48
+ end
49
+
50
+ # Default domain based on class name patterns
51
+ default_domain = infer_default_domain(class_name)
52
+ if default_domain && domains[default_domain]
53
+ return {
54
+ domain: default_domain,
55
+ domain_config: domains[default_domain],
56
+ class_config: infer_class_config(class_name, domains[default_domain])
57
+ }
58
+ end
59
+
60
+ nil
61
+ end
62
+
63
+ def load_method_specific_context(class_name, method_name, class_context)
64
+ return {} unless class_context[:class_config]
65
+
66
+ method_config = class_context[:class_config]['methods']&.dig(method_name)
67
+
68
+ if method_config
69
+ {
70
+ method_specific: method_config,
71
+ error_handling: method_config['error_handling'],
72
+ logging: method_config['logging'],
73
+ return_values: method_config['return_values']
74
+ }
75
+ else
76
+ # Infer method context based on method name patterns
77
+ infer_method_context(method_name, class_context)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def load_base_context
84
+ business_context_file = Rails.root.join('config', 'business_context.yml')
85
+
86
+ if File.exist?(business_context_file)
87
+ YAML.load_file(business_context_file)
88
+ else
89
+ default_context
90
+ end
91
+ end
92
+
93
+ def load_domains_from_config
94
+ context = load_base_context
95
+ context['domains'] || {}
96
+ end
97
+
98
+ def load_class_specific_context(class_name, domain_context)
99
+ domain_context[:class_config] || infer_class_config(class_name, domain_context[:domain_config])
100
+ end
101
+
102
+ def matches_domain_pattern?(class_name, domain_name)
103
+ patterns = {
104
+ 'user_management' => /User|Session|Auth|Profile/,
105
+ 'inventory_management' => /Product|Inventory|Stock|Catalog/,
106
+ 'order_management' => /Order|Cart|Checkout|Fulfillment/,
107
+ 'payment_processing' => /Payment|Transaction|Gateway|Refund/,
108
+ 'analytics_reporting' => /Analytics|Report|Metrics|Dashboard/
109
+ }
110
+
111
+ pattern = patterns[domain_name]
112
+ pattern&.match?(class_name)
113
+ end
114
+
115
+ def infer_default_domain(class_name)
116
+ case class_name
117
+ when /User|Session|Auth/
118
+ 'user_management'
119
+ when /Product|Inventory|Stock/
120
+ 'inventory_management'
121
+ when /Order|Cart|Checkout/
122
+ 'order_management'
123
+ when /Payment|Transaction/
124
+ 'payment_processing'
125
+ when /Analytics|Report|Metrics/
126
+ 'analytics_reporting'
127
+ else
128
+ 'general'
129
+ end
130
+ end
131
+
132
+ def infer_class_config(class_name, domain_config)
133
+ {
134
+ 'description' => "Auto-inferred configuration for #{class_name}",
135
+ 'business_criticality' => domain_config['criticality'] || 'medium',
136
+ 'data_privacy' => domain_config['data_sensitivity'] || 'standard',
137
+ 'methods' => infer_methods_for_class(class_name, domain_config)
138
+ }
139
+ end
140
+
141
+ def infer_methods_for_class(class_name, domain_config)
142
+ # Common method patterns by domain
143
+ patterns = {
144
+ 'user_management' => {
145
+ 'authenticate' => {
146
+ 'description' => 'User authentication',
147
+ 'error_handling' => 'secure_fallback',
148
+ 'logging' => 'security_audit'
149
+ },
150
+ 'update_profile' => {
151
+ 'description' => 'Update user profile',
152
+ 'error_handling' => 'validation_errors',
153
+ 'logging' => 'audit_trail'
154
+ }
155
+ },
156
+ 'inventory_management' => {
157
+ 'update_stock' => {
158
+ 'description' => 'Update inventory stock',
159
+ 'error_handling' => 'stock_validation',
160
+ 'logging' => 'inventory_audit'
161
+ },
162
+ 'calculate_availability' => {
163
+ 'description' => 'Calculate product availability',
164
+ 'error_handling' => 'graceful_fallback',
165
+ 'logging' => 'inventory_metrics'
166
+ }
167
+ },
168
+ 'order_management' => {
169
+ 'process_payment' => {
170
+ 'description' => 'Process order payment',
171
+ 'error_handling' => 'payment_failure',
172
+ 'logging' => 'payment_audit'
173
+ },
174
+ 'update_status' => {
175
+ 'description' => 'Update order status',
176
+ 'error_handling' => 'status_validation',
177
+ 'logging' => 'order_audit'
178
+ }
179
+ }
180
+ }
181
+
182
+ domain_name = domain_config['description']&.downcase&.gsub(/\s+/, '_')
183
+ patterns[domain_name] || {}
184
+ end
185
+
186
+ def infer_method_context(method_name, class_context)
187
+ # Infer based on method name patterns
188
+ case method_name.to_s
189
+ when /authenticate|login/
190
+ {
191
+ method_specific: {
192
+ 'description' => 'Authentication method',
193
+ 'error_handling' => 'secure_fallback',
194
+ 'logging' => 'security_audit',
195
+ 'return_values' => {
196
+ 'success' => 'user_object',
197
+ 'failure' => 'nil_with_logging'
198
+ }
199
+ }
200
+ }
201
+ when /update|save/
202
+ {
203
+ method_specific: {
204
+ 'description' => 'Update method',
205
+ 'error_handling' => 'validation_errors',
206
+ 'logging' => 'audit_trail',
207
+ 'return_values' => {
208
+ 'success' => 'boolean',
209
+ 'validation_failed' => 'errors_hash'
210
+ }
211
+ }
212
+ }
213
+ when /calculate|compute/
214
+ {
215
+ method_specific: {
216
+ 'description' => 'Calculation method',
217
+ 'error_handling' => 'calculation_error',
218
+ 'logging' => 'metrics',
219
+ 'return_values' => {
220
+ 'success' => 'numeric_result',
221
+ 'error' => 'nil_or_default'
222
+ }
223
+ }
224
+ }
225
+ when /process|execute/
226
+ {
227
+ method_specific: {
228
+ 'description' => 'Processing method',
229
+ 'error_handling' => 'process_failure',
230
+ 'logging' => 'process_audit',
231
+ 'return_values' => {
232
+ 'success' => 'result_object',
233
+ 'failure' => 'error_object'
234
+ }
235
+ }
236
+ }
237
+ else
238
+ {
239
+ method_specific: {
240
+ 'description' => "Auto-inferred method: #{method_name}",
241
+ 'error_handling' => 'graceful_handling',
242
+ 'logging' => 'info',
243
+ 'return_values' => {
244
+ 'success' => 'method_result',
245
+ 'error' => 'nil_or_default'
246
+ }
247
+ }
248
+ }
249
+ end
250
+ end
251
+
252
+ def default_context
253
+ {
254
+ 'project' => {
255
+ 'type' => 'Rails Application',
256
+ 'business_domain' => 'General Application',
257
+ 'description' => 'Default business context'
258
+ },
259
+ 'coding_standards' => {
260
+ 'error_handling' => 'comprehensive',
261
+ 'logging' => 'structured',
262
+ 'validation' => 'strict',
263
+ 'performance' => 'optimized',
264
+ 'security' => 'high'
265
+ },
266
+ 'business_rules' => {
267
+ # Structured error_resolution removed in favor of markdown requirements
268
+ }
269
+ }
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,297 @@
1
+ require 'yaml'
2
+ require 'json'
3
+
4
+ module CodeHealer
5
+ class BusinessContextManager
6
+ class << self
7
+ def get_context_for_error(error, class_name, method_name)
8
+ return {} unless ConfigManager.business_context_enabled?
9
+
10
+ # Load business context from markdown files (same as Claude Code)
11
+ markdown_context = load_business_context_from_markdown_simple
12
+
13
+ context = {
14
+ class_context: get_class_business_context(class_name),
15
+ method_context: get_method_business_context(class_name, method_name),
16
+ error_context: get_error_context(error),
17
+ related_context: get_related_context(class_name, method_name),
18
+ domain_patterns: get_domain_patterns(class_name),
19
+ markdown_requirements: markdown_context # Add markdown content
20
+ }
21
+
22
+ context.compact
23
+ end
24
+
25
+ def get_class_business_context(class_name)
26
+ business_context = ConfigManager.business_context_settings
27
+
28
+ # Extract class-specific context
29
+ class_context = business_context[class_name] || {}
30
+
31
+ # Add domain information
32
+ domain_info = {
33
+ domain: class_context['domain'] || 'General Business Logic',
34
+ key_rules: class_context['key_rules'] || [],
35
+ validation_patterns: class_context['validation_patterns'] || []
36
+ }
37
+
38
+ domain_info
39
+ end
40
+
41
+ def get_method_business_context(class_name, method_name)
42
+ # Method-specific business rules
43
+ method_rules = {
44
+ 'calculate_discount' => {
45
+ rules: ['Discount cannot exceed 50% of subtotal', 'VIP customers get maximum 15%'],
46
+ constraints: ['Positive amounts only', 'Valid customer tier required']
47
+ },
48
+ 'process_order' => {
49
+ rules: ['Orders must have valid items', 'Customer validation required'],
50
+ constraints: ['Positive quantities', 'Valid payment method']
51
+ },
52
+ 'validate_payment' => {
53
+ rules: ['Card number must be valid', 'Expiry date must be future'],
54
+ constraints: ['Valid CVV format', 'Supported payment method']
55
+ }
56
+ }
57
+
58
+ method_rules[method_name] || {}
59
+ end
60
+
61
+ def get_error_context(error)
62
+ {
63
+ type: error.class.name,
64
+ message: error.message,
65
+ common_causes: get_common_error_causes(error.class),
66
+ suggested_fixes: get_suggested_fixes(error.class)
67
+ }
68
+ end
69
+
70
+ def get_common_error_causes(error_class)
71
+ causes = {
72
+ 'ArgumentError' => [
73
+ 'Invalid parameter types',
74
+ 'Missing required parameters',
75
+ 'Parameter validation failed'
76
+ ],
77
+ 'NoMethodError' => [
78
+ 'Method not defined',
79
+ 'Incorrect method name',
80
+ 'Missing method implementation'
81
+ ],
82
+ 'TypeError' => [
83
+ 'Incompatible data types',
84
+ 'Nil value where object expected',
85
+ 'Type conversion failed'
86
+ ],
87
+ 'NameError' => [
88
+ 'Undefined variable or constant',
89
+ 'Scope issue with variable',
90
+ 'Missing import or require'
91
+ ]
92
+ }
93
+
94
+ causes[error_class.name] || ['Unknown error cause']
95
+ end
96
+
97
+ def get_suggested_fixes(error_class)
98
+ fixes = {
99
+ 'ArgumentError' => [
100
+ 'Add parameter validation',
101
+ 'Check parameter types',
102
+ 'Provide default values'
103
+ ],
104
+ 'NoMethodError' => [
105
+ 'Implement missing method',
106
+ 'Check method spelling',
107
+ 'Verify method exists in parent class'
108
+ ],
109
+ 'TypeError' => [
110
+ 'Add type checking',
111
+ 'Handle nil values',
112
+ 'Convert types appropriately'
113
+ ],
114
+ 'NameError' => [
115
+ 'Define missing variable',
116
+ 'Check variable scope',
117
+ 'Add missing imports'
118
+ ]
119
+ }
120
+
121
+ fixes[error_class.name] || ['Review error and implement appropriate fix']
122
+ end
123
+
124
+ def get_related_context(class_name, method_name)
125
+ # Find related classes and methods
126
+ related = {
127
+ 'OrderProcessor' => ['User', 'Payment', 'Inventory'],
128
+ 'User' => ['Order', 'Profile', 'Authentication'],
129
+ 'OrderProcessorV2' => ['OrderProcessor', 'AdvancedValidation', 'BusinessRules']
130
+ }
131
+
132
+ {
133
+ related_classes: related[class_name] || [],
134
+ related_methods: get_related_methods(method_name),
135
+ dependencies: get_class_dependencies(class_name)
136
+ }
137
+ end
138
+
139
+ def get_related_methods(method_name)
140
+ # Method relationships
141
+ relationships = {
142
+ 'calculate_discount' => ['validate_customer', 'calculate_loyalty_points', 'apply_coupon'],
143
+ 'process_order' => ['validate_inventory', 'process_payment', 'send_confirmation'],
144
+ 'validate_payment' => ['validate_card', 'check_balance', 'process_transaction']
145
+ }
146
+
147
+ relationships[method_name] || []
148
+ end
149
+
150
+ def get_class_dependencies(class_name)
151
+ # Class dependencies
152
+ dependencies = {
153
+ 'OrderProcessor' => ['User', 'Payment', 'Inventory'],
154
+ 'User' => ['Order', 'Profile'],
155
+ 'OrderProcessorV2' => ['OrderProcessor', 'AdvancedValidation']
156
+ }
157
+
158
+ dependencies[class_name] || []
159
+ end
160
+
161
+ def get_domain_patterns(class_name)
162
+ # Domain-specific patterns
163
+ patterns = {
164
+ 'OrderProcessor' => {
165
+ validation_pattern: 'Input validation → Business rule check → Processing → Result',
166
+ error_handling: 'Graceful degradation with meaningful error messages',
167
+ logging: 'Comprehensive logging for debugging and monitoring'
168
+ },
169
+ 'User' => {
170
+ validation_pattern: 'Data validation → Security check → Database operation → Response',
171
+ error_handling: 'User-friendly error messages with guidance',
172
+ logging: 'Security-focused logging without sensitive data'
173
+ }
174
+ }
175
+
176
+ patterns[class_name] || {}
177
+ end
178
+
179
+ def build_claude_code_prompt(error, class_name, method_name, file_path)
180
+ # Load business context from markdown files
181
+ business_context = load_business_context_from_markdown_simple
182
+
183
+ prompt = <<~PROMPT
184
+ I have a Ruby on Rails application with an error that needs fixing.
185
+
186
+ ## Error Details:
187
+ - **Type:** #{error.class.name}
188
+ - **Message:** #{error.message}
189
+ - **Class:** #{class_name}
190
+ - **Method:** #{method_name}
191
+ - **File:** #{file_path}
192
+
193
+ ## Complete Backtrace (for root cause analysis):
194
+ ```
195
+ #{error.backtrace&.join("\n") || "No backtrace available"}
196
+ ```
197
+
198
+ ## Business Context (from requirements):
199
+ #{business_context}
200
+
201
+ ## Instructions:
202
+ Please:
203
+ 1. Analyze the error and understand the root cause
204
+ 2. **IMPORTANT: Check if the fix requires changes to multiple files or if the root cause is in a different file**
205
+ 3. Fix the issue considering the business rules above
206
+ 4. Ensure the fix doesn't break other functionality
207
+ 5. Follow Rails conventions and patterns
208
+ 6. Make sure to write testcases for the fix
209
+
210
+ ## IMPORTANT: Full Codebase Access & Multi-File Fixes
211
+ You have permission to edit ANY files in the codebase. Please:
212
+ - **Don't limit yourself to just the file where the error occurred**
213
+ - **Check related files, dependencies, and the broader codebase**
214
+ - **If the fix requires multiple file changes, make ALL necessary changes**
215
+ - **Look for root causes that might be in different files**
216
+ - **Edit any file that needs to be modified to resolve the issue completely**
217
+ - Ensure the fix follows business rules and validation patterns
218
+
219
+ ## Multi-File Fix Strategy:
220
+ - **Analyze the complete backtrace above** to understand the full error chain
221
+ - **Start from the bottom of the backtrace** (where the error originated)
222
+ - **Work your way up** to see how the error propagated through different files
223
+ - **Identify ALL files in the call stack** that need modification
224
+ - **Look for the root cause** - it might be in a different file than where the error was caught
225
+ - **Make comprehensive changes** across the codebase if needed
226
+ - **Don't just patch the symptom** - fix the underlying issue
227
+ - **Check each file in the backtrace** for potential fixes
228
+
229
+ Use your full codebase access to provide the best solution.
230
+ PROMPT
231
+
232
+ prompt.strip
233
+ end
234
+
235
+ def build_api_prompt(error, class_name, method_name, file_path)
236
+ context = get_context_for_error(error, class_name, method_name)
237
+
238
+ prompt = <<~PROMPT
239
+ Fix this Ruby on Rails error:
240
+
241
+ Error: #{context[:error_context][:type]} - #{context[:error_context][:message]}
242
+ Class: #{class_name}
243
+ Method: #{method_name}
244
+ File: #{file_path}
245
+
246
+ Business Context:
247
+ - Domain: #{context[:class_context][:domain]}
248
+ - Key Rules: #{context[:class_context][:key_rules].join("; ")}
249
+ - Validation: #{context[:class_context][:validation_patterns].join("; ")}
250
+
251
+ Method Rules: #{context[:method_context][:rules]&.join("; ") || "None"}
252
+
253
+ Common Causes: #{context[:error_context][:common_causes].join("; ")}
254
+ Suggested Fixes: #{context[:error_context][:suggested_fixes].join("; ")}
255
+
256
+ ## Business Requirements (from markdown):
257
+ #{context[:markdown_requirements]}
258
+
259
+ Provide a production-ready fix that handles the error gracefully while maintaining business logic integrity.
260
+ PROMPT
261
+
262
+ prompt.strip
263
+ end
264
+
265
+ private
266
+
267
+ def load_business_context_from_markdown_simple
268
+ # Look for business requirements in the docs directory (created by setup script)
269
+ docs_path = 'docs'
270
+ business_rules_path = 'docs/business_rules.md'
271
+
272
+ # First try the specific business_rules.md file
273
+ if File.exist?(business_rules_path)
274
+ content = File.read(business_rules_path)
275
+ puts "📋 Loaded business context from: #{business_rules_path}"
276
+ return content.strip
277
+ end
278
+
279
+ # Fallback: look for any markdown files in docs directory
280
+ if Dir.exist?(docs_path)
281
+ markdown_files = Dir.glob("#{docs_path}/**/*.md")
282
+
283
+ if markdown_files.any?
284
+ # Load the first markdown file
285
+ content = File.read(markdown_files.first)
286
+ puts "📋 Loaded business context from: #{markdown_files.first}"
287
+ return content.strip
288
+ end
289
+ end
290
+
291
+ # Fallback to basic business context
292
+ puts "⚠️ No business context markdown files found, using fallback"
293
+ return "Follow standard business logic and error handling practices."
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,94 @@
1
+ module CodeHealer
2
+ class BusinessLogicGenerator
3
+ class << self
4
+ def generate_from_description(description, target_class, method_name)
5
+ new(description, target_class, method_name).generate
6
+ end
7
+ end
8
+
9
+ attr_reader :description, :target_class, :method_name, :logger
10
+
11
+ def initialize(description, target_class, method_name)
12
+ @description = description
13
+ @target_class = target_class
14
+ @method_name = method_name
15
+ @logger = Logger.new(Rails.root.join('log', 'self_evolving.log'))
16
+ end
17
+
18
+ def generate
19
+ return false unless valid_description?
20
+
21
+ begin
22
+ method_definition = generate_method_definition
23
+ return false unless valid_method?(method_definition)
24
+
25
+ success = Core.evolve_method(target_class, method_name, {
26
+ description: description,
27
+ generated_at: Time.current
28
+ })
29
+
30
+ if success
31
+ log_successful_generation
32
+ true
33
+ else
34
+ log_failed_generation
35
+ false
36
+ end
37
+ rescue => e
38
+ logger.error("Business logic generation failed: #{e.message}")
39
+ false
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def valid_description?
46
+ # Implement your validation logic here
47
+ # For example: minimum length, required keywords, etc.
48
+ description.present? && description.length >= 10
49
+ end
50
+
51
+ def generate_method_definition
52
+ # This is where you'd integrate with your AI system
53
+ # For now, we'll return a simple placeholder
54
+ <<~RUBY
55
+ def #{method_name}
56
+ # Generated from description: #{description}
57
+ # TODO: Implement actual business logic
58
+ true
59
+ end
60
+ RUBY
61
+ end
62
+
63
+ def valid_method?(method)
64
+ # Basic syntax validation
65
+ begin
66
+ RubyVM::InstructionSequence.compile(method)
67
+ true
68
+ rescue SyntaxError => e
69
+ logger.error("Invalid method syntax: #{e.message}")
70
+ false
71
+ end
72
+ end
73
+
74
+ def log_successful_generation
75
+ logger.info({
76
+ timestamp: Time.current,
77
+ event: 'successful_generation',
78
+ class: target_class.name,
79
+ method: method_name,
80
+ description: description
81
+ }.to_json)
82
+ end
83
+
84
+ def log_failed_generation
85
+ logger.info({
86
+ timestamp: Time.current,
87
+ event: 'failed_generation',
88
+ class: target_class.name,
89
+ method: method_name,
90
+ description: description
91
+ }.to_json)
92
+ end
93
+ end
94
+ end