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.
- checksums.yaml +7 -0
- data/.claude/CLAUDE.md +3 -0
- data/.claude/memory.sqlite3 +0 -0
- data/.claude/rules/claude_memory.generated.md +8 -0
- data/.claude/settings.local.json +9 -0
- data/.devcontainer/.env.example +12 -0
- data/.devcontainer/Dockerfile.standalone +30 -0
- data/.devcontainer/README.md +207 -0
- data/.devcontainer/devcontainer.json +54 -0
- data/.devcontainer/docker-compose.yml +32 -0
- data/CHANGELOG.md +18 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/FEATURES.md +306 -0
- data/LICENSE.txt +21 -0
- data/README.md +502 -0
- data/Rakefile +10 -0
- data/examples/guidance_and_verification.rb +226 -0
- data/lib/chaos_to_the_rescue/chaos_rescue.rb +336 -0
- data/lib/chaos_to_the_rescue/configuration.rb +157 -0
- data/lib/chaos_to_the_rescue/llm_client.rb +182 -0
- data/lib/chaos_to_the_rescue/logger.rb +82 -0
- data/lib/chaos_to_the_rescue/railtie.rb +24 -0
- data/lib/chaos_to_the_rescue/redactor.rb +76 -0
- data/lib/chaos_to_the_rescue/rescue_from.rb +156 -0
- data/lib/chaos_to_the_rescue/verifier.rb +277 -0
- data/lib/chaos_to_the_rescue/version.rb +5 -0
- data/lib/chaos_to_the_rescue.rb +57 -0
- data/lib/generators/chaos_to_the_rescue/install_generator.rb +37 -0
- data/lib/generators/chaos_to_the_rescue/templates/chaos_to_the_rescue.rb +97 -0
- data/sig/chaos_to_the_rescue.rbs +4 -0
- metadata +89 -0
|
@@ -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
|