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,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChaosToTheRescue
4
+ # Verifies that method outputs match expected behavior using LLM analysis
5
+ class Verifier
6
+ # Result of a verification check
7
+ VerificationResult = Struct.new(:success, :diagnosis, :suggestion, :fixed_code, :actual_result, keyword_init: true) do
8
+ def passed?
9
+ success
10
+ end
11
+
12
+ def failed?
13
+ !success
14
+ end
15
+ end
16
+
17
+ def initialize
18
+ @llm_client = LLMClient.new
19
+ @redactor = Redactor.new
20
+ end
21
+
22
+ # Verify a method call's output
23
+ # @param object [Object] the object to call the method on
24
+ # @param method_name [Symbol] the method to call
25
+ # @param args [Array] arguments to pass
26
+ # @param kwargs [Hash] keyword arguments to pass
27
+ # @param expected [Object] optional expected result (if known)
28
+ # @param auto_apply [Boolean] whether to automatically apply fixes
29
+ # @return [VerificationResult]
30
+ def verify(object:, method_name:, args: [], kwargs: {}, expected: nil, auto_apply: false)
31
+ ChaosToTheRescue.logger.info("🔍 Verifying #{object.class.name}##{method_name}")
32
+
33
+ # Call the method and capture the result
34
+ actual_result = if kwargs.empty?
35
+ object.send(method_name, *args)
36
+ else
37
+ object.send(method_name, *args, **kwargs)
38
+ end
39
+
40
+ # Get any stored guidance for this method
41
+ guidance = get_method_guidance(object.class, method_name)
42
+
43
+ # Get the method source if possible
44
+ method_source = extract_method_source(object.class, method_name)
45
+
46
+ # Ask LLM to verify the result
47
+ verification = verify_with_llm(
48
+ class_name: object.class.name,
49
+ method_name: method_name,
50
+ args: args,
51
+ kwargs: kwargs,
52
+ actual_result: actual_result,
53
+ expected_result: expected,
54
+ guidance: guidance,
55
+ method_source: method_source
56
+ )
57
+
58
+ if verification[:success]
59
+ ChaosToTheRescue.logger.info("✅ Verification passed for #{object.class.name}##{method_name}")
60
+ VerificationResult.new(
61
+ success: true,
62
+ diagnosis: verification[:diagnosis],
63
+ actual_result: actual_result
64
+ )
65
+ else
66
+ ChaosToTheRescue.logger.warn("❌ Verification failed for #{object.class.name}##{method_name}")
67
+ ChaosToTheRescue.logger.warn("Diagnosis: #{verification[:diagnosis]}")
68
+
69
+ result = VerificationResult.new(
70
+ success: false,
71
+ diagnosis: verification[:diagnosis],
72
+ suggestion: verification[:suggestion],
73
+ fixed_code: verification[:fixed_code],
74
+ actual_result: actual_result
75
+ )
76
+
77
+ # Auto-apply fix if requested
78
+ if auto_apply && verification[:fixed_code]
79
+ apply_fix(object.class, method_name, verification[:fixed_code])
80
+ ChaosToTheRescue.logger.info("🔧 Auto-applied fix for #{object.class.name}##{method_name}")
81
+ end
82
+
83
+ result
84
+ end
85
+ rescue => e
86
+ ChaosToTheRescue.logger.error("Failed to verify method: #{e.message}")
87
+ VerificationResult.new(
88
+ success: false,
89
+ diagnosis: "Verification failed: #{e.message}",
90
+ actual_result: actual_result
91
+ )
92
+ end
93
+
94
+ # Verify a method call using a block
95
+ # @param auto_apply [Boolean] whether to automatically apply fixes
96
+ # @yield the method call to verify
97
+ # @return [VerificationResult]
98
+ def verify_block(auto_apply: false, &block)
99
+ # Use TracePoint to capture the method call
100
+ method_info = capture_method_call(&block)
101
+
102
+ verify(
103
+ object: method_info[:object],
104
+ method_name: method_info[:method_name],
105
+ args: method_info[:args],
106
+ kwargs: method_info[:kwargs],
107
+ auto_apply: auto_apply
108
+ )
109
+ end
110
+
111
+ private
112
+
113
+ # Capture method call information using TracePoint
114
+ def capture_method_call(&block)
115
+ captured = nil
116
+
117
+ trace = TracePoint.new(:call) do |tp|
118
+ captured = {
119
+ object: tp.self,
120
+ method_name: tp.method_id,
121
+ args: tp.binding.local_variables.map { |v| tp.binding.local_variable_get(v) }.compact,
122
+ kwargs: {}
123
+ }
124
+ trace.disable
125
+ end
126
+
127
+ trace.enable
128
+ result = block.call
129
+ trace.disable
130
+
131
+ captured[:result] = result
132
+ captured
133
+ end
134
+
135
+ # Get guidance for a method
136
+ def get_method_guidance(klass, method_name)
137
+ return nil unless klass.respond_to?(:chaos_method_guidance)
138
+
139
+ klass.chaos_method_guidance[method_name]
140
+ end
141
+
142
+ # Extract method source code
143
+ def extract_method_source(klass, method_name)
144
+ method = klass.instance_method(method_name)
145
+ source_location = method.source_location
146
+ return nil unless source_location
147
+
148
+ file_path, line_number = source_location
149
+ return nil unless File.exist?(file_path)
150
+
151
+ # Read the file and extract the method
152
+ lines = File.readlines(file_path)
153
+ method_lines = extract_method_lines(lines, line_number)
154
+
155
+ method_lines.join
156
+ rescue => e
157
+ ChaosToTheRescue.logger.debug("Could not extract method source: #{e.message}")
158
+ nil
159
+ end
160
+
161
+ # Extract method lines from source file
162
+ def extract_method_lines(lines, start_line)
163
+ result = []
164
+ indent_level = nil
165
+ in_method = false
166
+
167
+ lines[start_line - 1..].each do |line|
168
+ if line.strip.start_with?("def ")
169
+ in_method = true
170
+ indent_level = line.index("def")
171
+ result << line
172
+ elsif in_method
173
+ current_indent = line.index(/\S/)
174
+ if current_indent && current_indent <= indent_level && line.strip == "end"
175
+ result << line
176
+ break
177
+ else
178
+ result << line
179
+ end
180
+ end
181
+ end
182
+
183
+ result
184
+ end
185
+
186
+ # Verify with LLM
187
+ def verify_with_llm(class_name:, method_name:, args:, kwargs:, actual_result:, expected_result:, guidance:, method_source:)
188
+ prompt = build_verification_prompt(
189
+ class_name: class_name,
190
+ method_name: method_name,
191
+ args: args,
192
+ kwargs: kwargs,
193
+ actual_result: actual_result,
194
+ expected_result: expected_result,
195
+ guidance: guidance,
196
+ method_source: method_source
197
+ )
198
+
199
+ redacted_prompt = @redactor.redact(prompt)
200
+ response = @llm_client.send(:call_llm, redacted_prompt)
201
+
202
+ parse_verification_response(response)
203
+ end
204
+
205
+ # Build verification prompt
206
+ def build_verification_prompt(class_name:, method_name:, args:, kwargs:, actual_result:, expected_result:, guidance:, method_source:)
207
+ <<~PROMPT
208
+ You are a Ruby code verification assistant. Analyze whether a method's output is correct.
209
+
210
+ Class: #{class_name}
211
+ Method: #{method_name}
212
+ Arguments: #{args.inspect}
213
+ #{"Keyword Arguments: #{kwargs.inspect}" unless kwargs.empty?}
214
+
215
+ Actual Result: #{actual_result.inspect}
216
+ #{"Expected Result: #{expected_result.inspect}" if expected_result}
217
+
218
+ #{guidance ? "Method Guidance:\n#{guidance}\n" : ""}
219
+
220
+ #{method_source ? "Method Source:\n```ruby\n#{method_source}\n```\n" : ""}
221
+
222
+ Based on the method name, arguments, and any provided guidance, determine if the actual result is correct.
223
+
224
+ Please respond in the following format:
225
+ SUCCESS: <true or false>
226
+ DIAGNOSIS: <explanation of whether the result is correct or what went wrong>
227
+ SUGGESTION: <if incorrect, suggest what the correct result should be>
228
+ FIXED_CODE:
229
+ <if incorrect and you have the source, provide fixed Ruby code>
230
+ PROMPT
231
+ end
232
+
233
+ # Parse verification response
234
+ def parse_verification_response(response)
235
+ lines = response.split("\n")
236
+
237
+ success_line = lines.find { |l| l.start_with?("SUCCESS:") }
238
+ success = success_line&.include?("true") || false
239
+
240
+ diagnosis_line = lines.find { |l| l.start_with?("DIAGNOSIS:") }
241
+ diagnosis = diagnosis_line&.sub("DIAGNOSIS:", "")&.strip || "No diagnosis provided"
242
+
243
+ suggestion_line = lines.find { |l| l.start_with?("SUGGESTION:") }
244
+ suggestion = suggestion_line&.sub("SUGGESTION:", "")&.strip
245
+
246
+ # Extract fixed code if present
247
+ code_start = lines.index { |l| l.strip == "FIXED_CODE:" }
248
+ fixed_code = if code_start
249
+ lines[(code_start + 1)..].join("\n").strip
250
+ else
251
+ nil
252
+ end
253
+
254
+ {
255
+ success: success,
256
+ diagnosis: diagnosis,
257
+ suggestion: suggestion,
258
+ fixed_code: fixed_code
259
+ }
260
+ end
261
+
262
+ # Apply a fix to a method
263
+ def apply_fix(klass, method_name, fixed_code)
264
+ ChaosToTheRescue.logger.info("🔧 Applying fix to #{klass.name}##{method_name}")
265
+ ChaosToTheRescue.logger.debug("Fixed code:\n#{fixed_code}")
266
+
267
+ begin
268
+ # WARNING: This executes LLM-generated code!
269
+ eval(fixed_code, TOPLEVEL_BINDING)
270
+ ChaosToTheRescue.logger.info("✅ Successfully applied fix to #{klass.name}##{method_name}")
271
+ rescue => e
272
+ ChaosToTheRescue.logger.error("❌ Failed to apply fix: #{e.message}")
273
+ raise
274
+ end
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChaosToTheRescue
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "chaos_to_the_rescue/version"
4
+ require_relative "chaos_to_the_rescue/configuration"
5
+ require_relative "chaos_to_the_rescue/redactor"
6
+ require_relative "chaos_to_the_rescue/logger"
7
+ require_relative "chaos_to_the_rescue/llm_client"
8
+ require_relative "chaos_to_the_rescue/verifier"
9
+ require_relative "chaos_to_the_rescue/chaos_rescue"
10
+
11
+ # Load Rails integration if Rails is available
12
+ if defined?(Rails)
13
+ require_relative "chaos_to_the_rescue/railtie"
14
+ end
15
+
16
+ module ChaosToTheRescue
17
+ class Error < StandardError; end
18
+
19
+ class << self
20
+ # Get the global configuration instance
21
+ # @return [Configuration]
22
+ def configuration
23
+ @configuration ||= Configuration.new
24
+ end
25
+
26
+ # Configure the gem with a block
27
+ # @yield [Configuration] the configuration object
28
+ def configure
29
+ configuration.configure do |config|
30
+ yield config
31
+ end
32
+ end
33
+
34
+ # Reset configuration to defaults
35
+ def reset_configuration!
36
+ @configuration = Configuration.new
37
+ end
38
+
39
+ # Get the global logger instance
40
+ # @return [Logger]
41
+ def logger
42
+ @logger ||= ChaosToTheRescue::Logger.instance
43
+ end
44
+
45
+ # Verify a method call using a block
46
+ # @param auto_apply [Boolean] whether to automatically apply fixes
47
+ # @yield the method call to verify
48
+ # @return [Verifier::VerificationResult]
49
+ # @example
50
+ # result = ChaosToTheRescue.verify { Calculator.new.pow(-5, 2) }
51
+ # puts result.diagnosis if result.failed?
52
+ def verify(auto_apply: false, &block)
53
+ verifier = Verifier.new
54
+ verifier.verify_block(auto_apply: auto_apply, &block)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module ChaosToTheRescue
6
+ module Generators
7
+ # Rails generator for installing ChaosToTheRescue initializer
8
+ class InstallGenerator < Rails::Generators::Base
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Creates a ChaosToTheRescue initializer in config/initializers"
12
+
13
+ def copy_initializer_file
14
+ copy_file "chaos_to_the_rescue.rb", "config/initializers/chaos_to_the_rescue.rb"
15
+ end
16
+
17
+ def show_readme
18
+ readme "INSTALL_README" if behavior == :invoke
19
+ end
20
+
21
+ private
22
+
23
+ def readme(filename)
24
+ say "\n" + "=" * 80
25
+ say "ChaosToTheRescue has been installed!"
26
+ say "=" * 80
27
+ say "\nConfiguration file created at: config/initializers/chaos_to_the_rescue.rb"
28
+ say "\nIMPORTANT: ChaosToTheRescue is DISABLED by default for safety."
29
+ say "Edit the initializer to enable it and configure your preferences."
30
+ say "\nTo use LLM features, add to your Gemfile:"
31
+ say " gem 'ruby_llm', '~> 0.1'"
32
+ say "\nDocumentation: https://github.com/codenamev/chaos_to_the_rescue"
33
+ say "=" * 80 + "\n"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ChaosToTheRescue Configuration
4
+ #
5
+ # This gem provides LLM-powered method generation and Rails exception rescue suggestions.
6
+ # It is DISABLED by default for safety. Enable only in development/test environments.
7
+ #
8
+ # Documentation: https://github.com/codenamev/chaos_to_the_rescue
9
+
10
+ ChaosToTheRescue.configure do |config|
11
+ # === CORE SETTINGS ===
12
+
13
+ # Enable/disable the gem globally (default: false)
14
+ # IMPORTANT: Only enable in development/test environments!
15
+ config.enabled = Rails.env.development? || Rails.env.test?
16
+
17
+ # Automatically define generated methods on classes (default: false)
18
+ # When false, generated code is only returned as a string
19
+ # When true, methods are dynamically defined (use with caution!)
20
+ # config.auto_define_methods = false
21
+
22
+ # === METHOD GENERATION ALLOWLIST ===
23
+
24
+ # Regex patterns for allowed method names
25
+ # Only methods matching these patterns will be generated
26
+ # Example: Allow methods starting with "calc_"
27
+ # config.allowed_method_name_patterns = [/^calc_/, /^compute_/]
28
+ config.allowed_method_name_patterns = []
29
+
30
+ # Explicit allowlist of method names (as strings or symbols)
31
+ # Example: Allow specific method names
32
+ # config.allowlist = [:my_dynamic_method, "another_method"]
33
+ config.allowlist = []
34
+
35
+ # === LLM SETTINGS ===
36
+
37
+ # LLM model to use (default: "gpt-4o-mini")
38
+ # Requires ruby_llm gem: gem "ruby_llm", "~> 0.1"
39
+ # config.model = "gpt-4o-mini"
40
+
41
+ # Maximum characters to send in prompts (default: 8000)
42
+ # Prevents accidentally sending huge prompts to LLM
43
+ # config.max_prompt_chars = 8000
44
+
45
+ # === LOGGING SETTINGS ===
46
+
47
+ # Log level (:debug, :info, :warn, :error, :fatal)
48
+ # config.log_level = :info
49
+
50
+ # Enable logging suggestions to file (default: true)
51
+ # config.log_suggestions = true
52
+
53
+ # Path for suggestion log file (default: "tmp/chaos_to_the_rescue_suggestions.log")
54
+ # config.suggestions_log_path = "tmp/chaos_to_the_rescue_suggestions.log"
55
+
56
+ # === SECURITY SETTINGS ===
57
+
58
+ # Additional regex patterns to redact from prompts
59
+ # Default patterns already cover: ENV vars, AWS keys, OpenAI tokens,
60
+ # GitHub tokens, Rails credentials, and generic API_KEY/SECRET/TOKEN patterns
61
+ #
62
+ # Add custom patterns if you have other sensitive data:
63
+ # config.redact_patterns << /CUSTOM_SECRET[=:]\s*['"]?([^'"\s]+)['"]?/i
64
+ end
65
+
66
+ # === USAGE EXAMPLES ===
67
+
68
+ # 1. Method Generation with ChaosRescue module:
69
+ #
70
+ # class Calculator
71
+ # include ChaosToTheRescue::ChaosRescue
72
+ # chaos_rescue_enabled! # Enable for this class
73
+ # end
74
+ #
75
+ # calc = Calculator.new
76
+ # calc.calc_fibonacci(10) # Method generated on-the-fly if allowlisted
77
+ #
78
+
79
+ # 2. Rails rescue_from integration:
80
+ #
81
+ # class ApplicationController < ActionController::Base
82
+ # include ChaosToTheRescue::RescueFrom
83
+ #
84
+ # rescue_from StandardError do |exception|
85
+ # suggestion = chaos_suggest_fix(
86
+ # exception,
87
+ # guidance: "This is a payment processing error"
88
+ # )
89
+ #
90
+ # # Use suggestion to fix the issue or log it
91
+ # Rails.logger.info("Suggestion: #{suggestion[:title]}")
92
+ # Rails.logger.info("Diagnosis: #{suggestion[:diagnosis]}")
93
+ #
94
+ # # Render error page
95
+ # render "errors/500", status: :internal_server_error
96
+ # end
97
+ # end
@@ -0,0 +1,4 @@
1
+ module ChaosToTheRescue
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chaos_to_the_rescue
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Valentino Stoll
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: logger
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.6'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.6'
26
+ description: ChaosToTheRescue uses LLMs to generate missing methods on-the-fly and
27
+ suggest fixes for Rails exceptions. Features comprehensive security (secret redaction,
28
+ opt-in behavior, no auto-execution) and is disabled by default for production safety.
29
+ email:
30
+ - v@codenamev.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".claude/CLAUDE.md"
36
+ - ".claude/memory.sqlite3"
37
+ - ".claude/rules/claude_memory.generated.md"
38
+ - ".claude/settings.local.json"
39
+ - ".devcontainer/.env.example"
40
+ - ".devcontainer/Dockerfile.standalone"
41
+ - ".devcontainer/README.md"
42
+ - ".devcontainer/devcontainer.json"
43
+ - ".devcontainer/docker-compose.yml"
44
+ - CHANGELOG.md
45
+ - CODE_OF_CONDUCT.md
46
+ - FEATURES.md
47
+ - LICENSE.txt
48
+ - README.md
49
+ - Rakefile
50
+ - examples/guidance_and_verification.rb
51
+ - lib/chaos_to_the_rescue.rb
52
+ - lib/chaos_to_the_rescue/chaos_rescue.rb
53
+ - lib/chaos_to_the_rescue/configuration.rb
54
+ - lib/chaos_to_the_rescue/llm_client.rb
55
+ - lib/chaos_to_the_rescue/logger.rb
56
+ - lib/chaos_to_the_rescue/railtie.rb
57
+ - lib/chaos_to_the_rescue/redactor.rb
58
+ - lib/chaos_to_the_rescue/rescue_from.rb
59
+ - lib/chaos_to_the_rescue/verifier.rb
60
+ - lib/chaos_to_the_rescue/version.rb
61
+ - lib/generators/chaos_to_the_rescue/install_generator.rb
62
+ - lib/generators/chaos_to_the_rescue/templates/chaos_to_the_rescue.rb
63
+ - sig/chaos_to_the_rescue.rbs
64
+ homepage: https://github.com/codenamev/chaos_to_the_rescue
65
+ licenses:
66
+ - MIT
67
+ metadata:
68
+ allowed_push_host: https://rubygems.org
69
+ homepage_uri: https://github.com/codenamev/chaos_to_the_rescue
70
+ source_code_uri: https://github.com/codenamev/chaos_to_the_rescue
71
+ changelog_uri: https://github.com/codenamev/chaos_to_the_rescue/blob/main/CHANGELOG.md
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 3.2.0
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubygems_version: 4.0.4
87
+ specification_version: 4
88
+ summary: Safe-by-default LLM-powered method generation and Rails error rescue suggestions
89
+ test_files: []