chaos_to_the_rescue 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.
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example: Using ChaosGuidance and Verification Features
4
+ #
5
+ # This example demonstrates three key features:
6
+ # 1. Providing guidance when defining methods (chaos_guidance)
7
+ # 2. Adding guidance to existing methods (additional_chaos_guidance)
8
+ # 3. Verifying method outputs with optional auto-fix (verify!)
9
+
10
+ require "chaos_to_the_rescue"
11
+
12
+ # Configure the gem
13
+ ChaosToTheRescue.configure do |config|
14
+ config.enabled = true
15
+ config.auto_define_methods = true
16
+ config.allow_everything!
17
+ config.openai_api_key = ENV["OPENAI_API_KEY"] # or use ENV variable
18
+ end
19
+
20
+ # Example 1: Provide guidance when defining methods
21
+ # ==================================================
22
+ class Calculator
23
+ include ChaosToTheRescue::ChaosRescue
24
+ chaos_rescue_enabled!
25
+
26
+ # Add guidance BEFORE the method is generated
27
+ # This helps the LLM understand edge cases and requirements
28
+ chaos_guidance :pow, <<~GUIDANCE
29
+ This method should correctly handle negative bases:
30
+ - Negative base with even exponent should return positive: (-5)^2 = 25
31
+ - Negative base with odd exponent should return negative: (-5)^3 = -125
32
+
33
+ Common mistake: Using base.abs which always returns positive results.
34
+ Correct approach: Use base ** exponent directly.
35
+ GUIDANCE
36
+
37
+ # When this method is generated, the LLM will see the guidance above
38
+ # and create a correct implementation
39
+ end
40
+
41
+ # Example 2: Add guidance to existing methods
42
+ # ============================================
43
+ class ScientificCalculator
44
+ include ChaosToTheRescue::ChaosRescue
45
+ chaos_rescue_enabled!
46
+
47
+ # Already defined method with a bug
48
+ def sqrt(n)
49
+ Math.sqrt(n) # Bug: doesn't handle negative numbers
50
+ end
51
+
52
+ # Add guidance after the method is defined
53
+ # This is useful for documenting expected behavior for verification
54
+ additional_chaos_guidance :sqrt, <<~GUIDANCE
55
+ For negative inputs, this should return a complex number:
56
+ sqrt(-4) should return (0+2i), not NaN or raise an error.
57
+
58
+ Use Complex math when input is negative.
59
+ GUIDANCE
60
+ end
61
+
62
+ # You can also add global guidance in configuration
63
+ ChaosToTheRescue.configure do |config|
64
+ config.method_guidance[:divide] = <<~GUIDANCE
65
+ Handle division by zero gracefully:
66
+ - Return Float::INFINITY for positive numerator
67
+ - Return -Float::INFINITY for negative numerator
68
+ - Return Float::NAN for 0/0
69
+ GUIDANCE
70
+ end
71
+
72
+ # Example 3: Verify method outputs
73
+ # =================================
74
+
75
+ # 3a. Simple verification without auto-fix
76
+ puts "=== Example 3a: Simple Verification ==="
77
+ result = Calculator.verify_chaos(:pow, -5, 2)
78
+
79
+ if result.passed?
80
+ puts "✓ Method works correctly!"
81
+ puts " Diagnosis: #{result.diagnosis}"
82
+ else
83
+ puts "✗ Method has issues:"
84
+ puts " Diagnosis: #{result.diagnosis}"
85
+ puts " Suggestion: #{result.suggestion}"
86
+ puts " Actual result: #{result.actual_result}"
87
+ end
88
+
89
+ # 3b. Verification with auto-fix enabled
90
+ puts "\n=== Example 3b: Verification with Auto-Fix ==="
91
+ calc = ScientificCalculator.new
92
+
93
+ # This will verify the method and automatically apply a fix if needed
94
+ result = ScientificCalculator.verify_chaos(:sqrt, -4, auto_apply: true)
95
+
96
+ if result.failed?
97
+ puts "✗ Original implementation was incorrect"
98
+ puts " Diagnosis: #{result.diagnosis}"
99
+ puts "🔧 Fix has been automatically applied!"
100
+ puts " Fixed code:\n#{result.fixed_code}"
101
+
102
+ # Try again with the fixed implementation
103
+ new_result = calc.sqrt(-4)
104
+ puts " New result: #{new_result}"
105
+ end
106
+
107
+ # 3c. Verification using block syntax
108
+ puts "\n=== Example 3c: Block-based Verification ==="
109
+
110
+ # More convenient syntax for one-off verifications
111
+ result = ChaosToTheRescue.verify do
112
+ Calculator.new.pow(-5, 3)
113
+ end
114
+
115
+ puts result.passed? ? "✓ Passed" : "✗ Failed: #{result.diagnosis}"
116
+
117
+ # 3d. Verification with auto-fix using block syntax
118
+ puts "\n=== Example 3d: Block-based Verification with Auto-Fix ==="
119
+
120
+ result = ChaosToTheRescue.verify(auto_apply: true) do
121
+ Calculator.new.pow(-5, 2)
122
+ end
123
+
124
+ if result.failed?
125
+ puts "Method was incorrect and has been fixed!"
126
+ puts "Actual result was: #{result.actual_result}"
127
+ end
128
+
129
+ # Example 4: Comprehensive workflow
130
+ # ==================================
131
+ puts "\n=== Example 4: Comprehensive Workflow ==="
132
+
133
+ class MathLibrary
134
+ include ChaosToTheRescue::ChaosRescue
135
+ chaos_rescue_enabled!
136
+
137
+ # Step 1: Add guidance for a method that will be generated
138
+ chaos_guidance :factorial, <<~GUIDANCE
139
+ Calculate factorial recursively or iteratively.
140
+ - factorial(0) = 1
141
+ - factorial(1) = 1
142
+ - factorial(5) = 120
143
+ - Should handle negative inputs by raising ArgumentError
144
+ - Should handle large inputs efficiently (consider iterative approach)
145
+ GUIDANCE
146
+
147
+ chaos_guidance :fibonacci, <<~GUIDANCE
148
+ Calculate the nth Fibonacci number.
149
+ - fibonacci(0) = 0
150
+ - fibonacci(1) = 1
151
+ - fibonacci(10) = 55
152
+ - Use memoization or dynamic programming for efficiency
153
+ GUIDANCE
154
+ end
155
+
156
+ # Step 2: Call the methods (they'll be generated with guidance)
157
+ math = MathLibrary.new
158
+
159
+ begin
160
+ result1 = math.factorial(5)
161
+ puts "factorial(5) = #{result1}"
162
+ rescue => e
163
+ puts "Error: #{e.message}"
164
+ end
165
+
166
+ begin
167
+ result2 = math.fibonacci(10)
168
+ puts "fibonacci(10) = #{result2}"
169
+ rescue => e
170
+ puts "Error: #{e.message}"
171
+ end
172
+
173
+ # Step 3: Verify the implementations
174
+ puts "\nVerifying implementations..."
175
+
176
+ verification1 = MathLibrary.verify_chaos(:factorial, 5)
177
+ puts "factorial verification: #{verification1.passed? ? "✓" : "✗"}"
178
+ puts " Diagnosis: #{verification1.diagnosis}"
179
+
180
+ verification2 = MathLibrary.verify_chaos(:fibonacci, 10, auto_apply: true)
181
+ puts "fibonacci verification: #{verification2.passed? ? "✓" : "✗ (auto-fixed)"}"
182
+ puts " Diagnosis: #{verification2.diagnosis}"
183
+
184
+ # Example 5: Testing edge cases
185
+ # ==============================
186
+ puts "\n=== Example 5: Testing Edge Cases ==="
187
+
188
+ class EdgeCaseCalculator
189
+ include ChaosToTheRescue::ChaosRescue
190
+ chaos_rescue_enabled!
191
+
192
+ chaos_guidance :safe_divide, <<~GUIDANCE
193
+ Division with edge case handling:
194
+ - safe_divide(10, 2) = 5.0
195
+ - safe_divide(10, 0) should return nil or Float::INFINITY (not raise error)
196
+ - safe_divide(-10, 0) should return -Float::INFINITY
197
+ - safe_divide(0, 0) should return nil or Float::NAN
198
+ GUIDANCE
199
+
200
+ def safe_divide(a, b)
201
+ # Intentionally buggy - doesn't handle division by zero
202
+ a / b
203
+ end
204
+ end
205
+
206
+ # Verify with specific test cases
207
+ puts "Testing safe_divide(10, 0)..."
208
+ result = ChaosToTheRescue.verify do
209
+ EdgeCaseCalculator.new.safe_divide(10, 0)
210
+ end
211
+
212
+ if result.failed?
213
+ puts "✗ Edge case not handled:"
214
+ puts " #{result.diagnosis}"
215
+ puts " Suggestion: #{result.suggestion}"
216
+ end
217
+
218
+ puts "\n" + "=" * 60
219
+ puts "Examples complete!"
220
+ puts "=" * 60
221
+ puts "\nKey takeaways:"
222
+ puts "1. Use 'chaos_guidance' to provide hints for method generation"
223
+ puts "2. Use 'additional_chaos_guidance' (alias) for existing methods"
224
+ puts "3. Use 'verify_chaos' or 'ChaosToTheRescue.verify' to check outputs"
225
+ puts "4. Use 'auto_apply: true' to automatically fix incorrect implementations"
226
+ puts "5. Guidance helps LLMs understand edge cases and requirements"
@@ -0,0 +1,336 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChaosToTheRescue
4
+ # Core module that provides method_missing-based LLM method generation
5
+ module ChaosRescue
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ base.class_variable_set(:@@chaos_rescue_enabled, false)
9
+ base.class_variable_set(:@@chaos_method_guidance, {})
10
+ end
11
+
12
+ # Class methods for enabling/disabling ChaosRescue per class
13
+ module ClassMethods
14
+ # Enable ChaosRescue for this class specifically
15
+ def chaos_rescue_enabled!
16
+ class_variable_set(:@@chaos_rescue_enabled, true)
17
+ end
18
+
19
+ # Disable ChaosRescue for this class specifically
20
+ def chaos_rescue_disabled!
21
+ class_variable_set(:@@chaos_rescue_enabled, false)
22
+ end
23
+
24
+ # Check if ChaosRescue is enabled for this class
25
+ # @return [Boolean]
26
+ def chaos_rescue_enabled?
27
+ class_variable_get(:@@chaos_rescue_enabled)
28
+ end
29
+
30
+ # Add guidance for a method to help the LLM generate correct implementations
31
+ # @param method_name [Symbol] the method name
32
+ # @param guidance [String] guidance text for the LLM
33
+ # @return [void]
34
+ def chaos_guidance(method_name, guidance)
35
+ guidance_hash = class_variable_get(:@@chaos_method_guidance)
36
+ guidance_hash[method_name.to_sym] = guidance
37
+ class_variable_set(:@@chaos_method_guidance, guidance_hash)
38
+ ChaosToTheRescue.logger.debug("Added guidance for #{name}##{method_name}")
39
+ end
40
+
41
+ # Alias for adding guidance to existing methods
42
+ alias_method :additional_chaos_guidance, :chaos_guidance
43
+
44
+ # Get guidance for a method
45
+ # @param method_name [Symbol] the method name
46
+ # @return [String, nil] the guidance text or nil
47
+ def chaos_method_guidance
48
+ class_variable_get(:@@chaos_method_guidance)
49
+ end
50
+
51
+ # Verify a method call
52
+ # @param method_name [Symbol] the method name
53
+ # @param args [Array] arguments to pass
54
+ # @param auto_apply [Boolean] whether to automatically apply fixes
55
+ # @return [Verifier::VerificationResult]
56
+ def verify_chaos(method_name, *args, auto_apply: false, **kwargs)
57
+ verifier = Verifier.new
58
+ instance = new
59
+ verifier.verify(
60
+ object: instance,
61
+ method_name: method_name,
62
+ args: args,
63
+ kwargs: kwargs,
64
+ auto_apply: auto_apply
65
+ )
66
+ end
67
+
68
+ # Handle missing class methods by generating them via LLM
69
+ def method_missing(method_name, *args, **kwargs, &block)
70
+ # Check if enabled (globally or per-class)
71
+ unless class_enabled?
72
+ ChaosToTheRescue.logger.debug("ChaosRescue disabled for #{name}.#{method_name}")
73
+ return super
74
+ end
75
+
76
+ # Check if method name is allowed
77
+ unless ChaosToTheRescue.configuration.method_allowed?(method_name)
78
+ ChaosToTheRescue.logger.debug("Method #{method_name} not in allowlist or patterns")
79
+ return super
80
+ end
81
+
82
+ ChaosToTheRescue.logger.info("Attempting to generate class method: #{name}.#{method_name}")
83
+
84
+ begin
85
+ # Generate the method code via LLM
86
+ result = generate_class_method(method_name, args, kwargs)
87
+
88
+ # If auto_define_methods is enabled, define the method on the class
89
+ if ChaosToTheRescue.configuration.auto_define_methods && result[:ruby_code]
90
+ define_generated_class_method(method_name, result[:ruby_code])
91
+
92
+ # Call the newly defined method
93
+ ChaosToTheRescue.logger.info("🚀 Executing generated class method: #{name}.#{method_name}")
94
+ if kwargs.empty?
95
+ send(method_name, *args, &block)
96
+ else
97
+ send(method_name, *args, **kwargs, &block)
98
+ end
99
+ else
100
+ # Return the generated code without executing it
101
+ ChaosToTheRescue.logger.info("Generated class method code (not auto-defined): #{result[:ruby_code]}")
102
+ result
103
+ end
104
+ rescue LLMClient::LLMNotAvailableError => e
105
+ ChaosToTheRescue.logger.error("LLM not available: #{e.message}")
106
+ raise
107
+ rescue => e
108
+ ChaosToTheRescue.logger.error("Failed to generate class method #{method_name}: #{e.message}")
109
+ super
110
+ end
111
+ end
112
+
113
+ # Properly handle respond_to? for missing class methods
114
+ def respond_to_missing?(method_name, include_private = false)
115
+ if class_enabled? && ChaosToTheRescue.configuration.method_allowed?(method_name)
116
+ true
117
+ else
118
+ super
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ # Check if ChaosRescue is enabled for this class
125
+ # @return [Boolean]
126
+ def class_enabled?
127
+ # Check global configuration first
128
+ return false unless ChaosToTheRescue.configuration.enabled
129
+
130
+ # Then check class-level setting
131
+ chaos_rescue_enabled?
132
+ end
133
+
134
+ # Generate class method code via LLM client
135
+ # @param method_name [Symbol] the method name
136
+ # @param args [Array] the arguments passed
137
+ # @param kwargs [Hash] the keyword arguments passed
138
+ # @return [Hash] the generated method info
139
+ def generate_class_method(method_name, args, kwargs)
140
+ # Get guidance if available
141
+ guidance = get_method_guidance(method_name)
142
+
143
+ context = {
144
+ method_name: method_name,
145
+ class_name: name,
146
+ class_method: true,
147
+ args: args,
148
+ kwargs: kwargs,
149
+ guidance: guidance,
150
+ additional_context: build_class_context_string(method_name, args, kwargs)
151
+ }
152
+
153
+ llm_client.generate_method_code(context)
154
+ end
155
+
156
+ # Define the generated class method
157
+ # @param method_name [Symbol] the method name
158
+ # @param code [String] the Ruby code to define
159
+ def define_generated_class_method(method_name, code)
160
+ ChaosToTheRescue.logger.info("🤖 Defining generated class method: #{name}.#{method_name}")
161
+ ChaosToTheRescue.logger.debug("Generated code:\n#{code}")
162
+
163
+ begin
164
+ # WARNING: This executes LLM-generated code!
165
+ # The code should contain a class definition that reopens the class and adds the class method
166
+ # We eval it in the context of the main object to allow class definitions
167
+ eval(code, TOPLEVEL_BINDING)
168
+
169
+ ChaosToTheRescue.logger.info("✅ Successfully defined #{name}.#{method_name}")
170
+ rescue => e
171
+ ChaosToTheRescue.logger.error("❌ Failed to define class method #{method_name}: #{e.message}")
172
+ ChaosToTheRescue.logger.error("Backtrace: #{e.backtrace.first(5).join("\n")}")
173
+ raise SyntaxError, "Failed to define generated class method #{method_name}: #{e.message}"
174
+ end
175
+ end
176
+
177
+ # Build a context string for class methods
178
+ # @param method_name [Symbol] the method name
179
+ # @param args [Array] the arguments
180
+ # @param kwargs [Hash] the keyword arguments
181
+ # @return [String] a descriptive context string
182
+ def build_class_context_string(method_name, args, kwargs)
183
+ parts = ["Class Method: #{method_name}"]
184
+ parts << "Args: #{args.inspect}" unless args.empty?
185
+ parts << "Kwargs: #{kwargs.inspect}" unless kwargs.empty?
186
+ parts.join(", ")
187
+ end
188
+
189
+ # Get guidance for a method
190
+ # @param method_name [Symbol] the method name
191
+ # @return [String, nil] guidance text or nil
192
+ def get_method_guidance(method_name)
193
+ # Check class-level guidance first
194
+ class_guidance = class_variable_get(:@@chaos_method_guidance)[method_name.to_sym]
195
+ return class_guidance if class_guidance
196
+
197
+ # Check global configuration guidance
198
+ ChaosToTheRescue.configuration.method_guidance[method_name.to_sym]
199
+ end
200
+
201
+ # Get the LLM client instance
202
+ # @return [LLMClient]
203
+ def llm_client
204
+ @llm_client ||= LLMClient.new
205
+ end
206
+ end
207
+
208
+ private
209
+
210
+ # Handle missing methods by generating them via LLM
211
+ def method_missing(method_name, *args, **kwargs, &block)
212
+ # Check if enabled (globally or per-class)
213
+ unless enabled_for_class?
214
+ ChaosToTheRescue.logger.debug("ChaosRescue disabled for #{self.class.name}##{method_name}")
215
+ return super
216
+ end
217
+
218
+ # Check if method name is allowed
219
+ unless ChaosToTheRescue.configuration.method_allowed?(method_name)
220
+ ChaosToTheRescue.logger.debug("Method #{method_name} not in allowlist or patterns")
221
+ return super
222
+ end
223
+
224
+ ChaosToTheRescue.logger.info("Attempting to generate method: #{self.class.name}##{method_name}")
225
+
226
+ begin
227
+ # Generate the method code via LLM
228
+ result = generate_method(method_name, args, kwargs)
229
+
230
+ # If auto_define_methods is enabled, define the method on the class
231
+ if ChaosToTheRescue.configuration.auto_define_methods && result[:ruby_code]
232
+ define_generated_method(method_name, result[:ruby_code])
233
+
234
+ # Call the newly defined method
235
+ ChaosToTheRescue.logger.info("🚀 Executing generated method: #{self.class.name}##{method_name}")
236
+ if kwargs.empty?
237
+ send(method_name, *args, &block)
238
+ else
239
+ send(method_name, *args, **kwargs, &block)
240
+ end
241
+ else
242
+ # Return the generated code without executing it
243
+ ChaosToTheRescue.logger.info("Generated method code (not auto-defined): #{result[:ruby_code]}")
244
+ result
245
+ end
246
+ rescue LLMClient::LLMNotAvailableError => e
247
+ ChaosToTheRescue.logger.error("LLM not available: #{e.message}")
248
+ raise
249
+ rescue => e
250
+ ChaosToTheRescue.logger.error("Failed to generate method #{method_name}: #{e.message}")
251
+ super
252
+ end
253
+ end
254
+
255
+ # Properly handle respond_to? for missing methods
256
+ def respond_to_missing?(method_name, include_private = false)
257
+ if enabled_for_class? && ChaosToTheRescue.configuration.method_allowed?(method_name)
258
+ true
259
+ else
260
+ super
261
+ end
262
+ end
263
+
264
+ # Generate method code via LLM client
265
+ # @param method_name [Symbol] the method name
266
+ # @param args [Array] the arguments passed
267
+ # @param kwargs [Hash] the keyword arguments passed
268
+ # @return [Hash] the generated method info
269
+ def generate_method(method_name, args, kwargs)
270
+ # Get guidance if available
271
+ guidance = self.class.respond_to?(:chaos_method_guidance) ?
272
+ self.class.chaos_method_guidance[method_name.to_sym] : nil
273
+ guidance ||= ChaosToTheRescue.configuration.method_guidance[method_name.to_sym]
274
+
275
+ context = {
276
+ method_name: method_name,
277
+ class_name: self.class.name,
278
+ args: args,
279
+ kwargs: kwargs,
280
+ guidance: guidance,
281
+ additional_context: build_context_string(method_name, args, kwargs)
282
+ }
283
+
284
+ llm_client.generate_method_code(context)
285
+ end
286
+
287
+ # Define the generated method on the singleton class
288
+ # @param method_name [Symbol] the method name
289
+ # @param code [String] the Ruby code to define
290
+ def define_generated_method(method_name, code)
291
+ ChaosToTheRescue.logger.info("🤖 Defining generated method: #{self.class.name}##{method_name}")
292
+ ChaosToTheRescue.logger.debug("Generated code:\n#{code}")
293
+
294
+ begin
295
+ # WARNING: This executes LLM-generated code!
296
+ # The code should contain a class definition that reopens the class and adds the method
297
+ # We eval it in the context of the main object to allow class definitions
298
+ eval(code, TOPLEVEL_BINDING)
299
+
300
+ ChaosToTheRescue.logger.info("✅ Successfully defined #{self.class.name}##{method_name}")
301
+ rescue => e
302
+ ChaosToTheRescue.logger.error("❌ Failed to define method #{method_name}: #{e.message}")
303
+ ChaosToTheRescue.logger.error("Backtrace: #{e.backtrace.first(5).join("\n")}")
304
+ raise SyntaxError, "Failed to define generated method #{method_name}: #{e.message}"
305
+ end
306
+ end
307
+
308
+ # Build a context string from method name and arguments
309
+ # @param method_name [Symbol] the method name
310
+ # @param args [Array] the arguments
311
+ # @param kwargs [Hash] the keyword arguments
312
+ # @return [String] a descriptive context string
313
+ def build_context_string(method_name, args, kwargs)
314
+ parts = ["Method: #{method_name}"]
315
+ parts << "Args: #{args.inspect}" unless args.empty?
316
+ parts << "Kwargs: #{kwargs.inspect}" unless kwargs.empty?
317
+ parts.join(", ")
318
+ end
319
+
320
+ # Check if ChaosRescue is enabled for this class
321
+ # @return [Boolean]
322
+ def enabled_for_class?
323
+ # Check global configuration first
324
+ return false unless ChaosToTheRescue.configuration.enabled
325
+
326
+ # Then check class-level setting
327
+ self.class.chaos_rescue_enabled?
328
+ end
329
+
330
+ # Get the LLM client instance
331
+ # @return [LLMClient]
332
+ def llm_client
333
+ @llm_client ||= LLMClient.new
334
+ end
335
+ end
336
+ end