code_healer 0.1.24 → 0.1.32
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 +4 -4
- data/CHANGELOG.md +46 -0
- data/lib/code_healer/claude_code_evolution_handler.rb +141 -53
- data/lib/code_healer/config_manager.rb +16 -23
- data/lib/code_healer/core.rb +36 -25
- data/lib/code_healer/healing_job.rb +33 -29
- data/lib/code_healer/healing_workspace_manager.rb +132 -118
- data/lib/code_healer/mcp_server.rb +8 -7
- data/lib/code_healer/presentation_logger.rb +114 -0
- data/lib/code_healer/setup.rb +9 -45
- data/lib/code_healer/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a36985eccac2d4e1f55a411b9872478f6ec9e2f5285ebe969f6a839e600e37a
|
4
|
+
data.tar.gz: 7f1c43c592b9b6f6ed953ac70cc13855048d7f62222b65ddbf56b047a711ece5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 790508da0467e26544b1d89bab7caece0bc0afd0e8bc10cf1aee0166704d75a5ef717cab75049b32b6c51e296339df1e8a036194684b100c7ca7e840de5a8230
|
7
|
+
data.tar.gz: a34f3ec6ba73155e4adba2ed08ed30a3581dc587ec1d91df2386a69653ec46a83351d2f03bfae02e525e432c64410ea119e9293c44493b08210e7e4a4ba824bd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
## [0.1.32] - 2025-09-15
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- Targeted RSpec test-fix loop after code changes in `ClaudeCodeEvolutionHandler`:
|
5
|
+
- Runs specs for files related to recent modifications
|
6
|
+
- Parses failures and re-invokes Claude with failure summary
|
7
|
+
- Iterates up to `test_fix.max_iterations` (default 2)
|
8
|
+
- Configuration: `test_fix.max_iterations` with sensible default in `ConfigManager` and `setup.rb`.
|
9
|
+
|
10
|
+
### Changed
|
11
|
+
- Removed demo mode code and comments across gem: prompts, setup script, logger phrasing, and workspace manager.
|
12
|
+
- Switched class targeting to excluded-classes-only model; removed `allowed_classes` usage in config managers and setup generation.
|
13
|
+
|
14
|
+
### Notes
|
15
|
+
- Backwards compatible; no breaking API changes. Configure `test_fix.max_iterations` in `config/code_healer.yml` to tune retries.
|
16
|
+
|
1
17
|
# Changelog
|
2
18
|
|
3
19
|
All notable changes to this project will be documented in this file.
|
@@ -7,6 +23,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
23
|
|
8
24
|
## [0.1.24] - 2025-08-31
|
9
25
|
|
26
|
+
## [0.1.26] - 2025-09-03
|
27
|
+
|
28
|
+
### Added
|
29
|
+
- **Enhanced PresentationLogger**: Improved with backtrace truncation, timing display, and specialized action methods
|
30
|
+
- **Smart Value Truncation**: Long arrays and strings are intelligently truncated for clean presentation
|
31
|
+
- **Timing Integration**: Total healing time displayed in final outcome
|
32
|
+
- **Action-Specific Logging**: `claude_action`, `workspace_action`, `git_action` methods for contextual logging
|
33
|
+
|
34
|
+
### Changed
|
35
|
+
- **Workspace Logs**: Dramatically reduced verbosity while maintaining key information
|
36
|
+
- **Backtrace Display**: Shows only first 3 relevant lines with clean formatting
|
37
|
+
- **Git Operations**: Streamlined git status and diff output for presentation clarity
|
38
|
+
- **Outcome Display**: Enhanced with timing and better formatting
|
39
|
+
|
40
|
+
### Notes
|
41
|
+
- Perfect for conference demos with `CODE_HEALER_VERBOSE=true` for detailed debugging
|
42
|
+
- No API changes; safe minor release
|
43
|
+
|
44
|
+
## [0.1.25] - 2025-09-03
|
45
|
+
|
46
|
+
### Added
|
47
|
+
- PresentationLogger: clean, emoji-labeled, presentation-friendly logs with optional verbose mode (`CODE_HEALER_VERBOSE=true`)
|
48
|
+
- HealingJob and ClaudeCodeEvolutionHandler now use PresentationLogger for concise output
|
49
|
+
|
50
|
+
### Changed
|
51
|
+
- Suppressed noisy prints in core healing flow in favor of high-signal steps and outcomes
|
52
|
+
|
53
|
+
### Notes
|
54
|
+
- No API changes; safe minor release. Ideal for conference demos.
|
55
|
+
|
10
56
|
### Added
|
11
57
|
- **Aggressive File Filtering**: Implemented comprehensive filtering to prevent `tmp/`, `log/`, and other temporary files from being committed
|
12
58
|
- **Pre-Commit Validation**: Added workspace validation before commit to ensure no temporary files slip through
|
@@ -1,49 +1,44 @@
|
|
1
1
|
require 'timeout'
|
2
2
|
require 'open3'
|
3
|
+
require_relative 'presentation_logger'
|
3
4
|
|
4
5
|
module CodeHealer
|
5
6
|
class ClaudeCodeEvolutionHandler
|
6
7
|
class << self
|
7
8
|
def handle_error_with_claude_code(error, class_name, method_name, file_path)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
PresentationLogger.section("Claude Code Terminal – Evolution")
|
10
|
+
PresentationLogger.kv("Error", "#{error.class}: #{error.message}")
|
11
|
+
PresentationLogger.kv("Target", "#{class_name}##{method_name}")
|
12
|
+
PresentationLogger.detail("File: #{file_path}")
|
12
13
|
|
13
14
|
begin
|
14
|
-
# Build concise
|
15
|
+
# Build concise prompt
|
15
16
|
prompt = BusinessContextManager.build_claude_code_prompt(
|
16
17
|
error, class_name, method_name, file_path
|
17
18
|
)
|
19
|
+
|
18
20
|
prompt << "\n\nStrict instructions:" \
|
19
21
|
"\n- Do NOT scan the entire codebase." \
|
20
22
|
"\n- Work only with the provided file/method context and backtrace." \
|
21
23
|
"\n- Return a unified diff (no prose)." \
|
22
|
-
"\n- Keep changes minimal and safe."
|
23
|
-
"\n- Do NOT create or run tests." if CodeHealer::ConfigManager.demo_mode?
|
24
|
+
"\n- Keep changes minimal and safe."
|
24
25
|
|
25
26
|
# Execute Claude Code command
|
26
27
|
success = execute_claude_code_fix(prompt, class_name, method_name)
|
27
28
|
|
28
29
|
if success
|
29
|
-
|
30
|
-
# Reload modified files
|
30
|
+
PresentationLogger.success("Claude run completed")
|
31
31
|
reload_modified_files
|
32
|
-
|
33
|
-
|
34
|
-
# Note: Git operations are now handled by the isolated workspace manager
|
35
|
-
# to prevent duplication and ensure proper isolation
|
36
|
-
puts "🔄 Git operations will be handled by isolated workspace manager..."
|
37
|
-
|
38
|
-
return true
|
32
|
+
# Run targeted RSpec and iterate on failures
|
33
|
+
return run_tests_and_iterate_fixes(class_name, method_name)
|
39
34
|
else
|
40
|
-
|
35
|
+
PresentationLogger.error("Claude run failed")
|
41
36
|
return false
|
42
37
|
end
|
43
38
|
|
44
39
|
rescue => e
|
45
|
-
|
46
|
-
|
40
|
+
PresentationLogger.error("Claude evolution error: #{e.message}")
|
41
|
+
PresentationLogger.detail("Backtrace: #{Array(e.backtrace).first(5).join("\n")}")
|
47
42
|
return false
|
48
43
|
end
|
49
44
|
end
|
@@ -56,24 +51,24 @@ module CodeHealer
|
|
56
51
|
# Build command
|
57
52
|
command = build_claude_command(prompt, config)
|
58
53
|
|
59
|
-
|
60
|
-
puts "Command: #{command}"
|
61
|
-
puts "Timeout: #{config['timeout']} seconds"
|
54
|
+
PresentationLogger.claude_action("Executing Claude Code (timeout: #{config['timeout']}s)")
|
62
55
|
|
63
56
|
begin
|
64
57
|
# Execute with timeout
|
65
58
|
Timeout.timeout(config['timeout']) do
|
66
59
|
stdout, stderr, status = Open3.capture3(command)
|
67
60
|
|
68
|
-
puts "📤 Claude Code Output:"
|
69
61
|
if stdout && !stdout.empty?
|
70
|
-
|
71
|
-
|
62
|
+
PresentationLogger.success("Response received from Claude")
|
63
|
+
PresentationLogger.detail(stdout)
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
# Business context references are intentionally not logged
|
72
68
|
|
73
69
|
# Check if Claude Code is asking for permission
|
74
70
|
if stdout.include?("permission") || stdout.include?("grant") || stdout.include?("edit")
|
75
|
-
|
76
|
-
puts "💡 Make sure to grant Edit permissions when prompted"
|
71
|
+
PresentationLogger.warn("Claude requested edit permissions. Ensure permissions are granted.")
|
77
72
|
end
|
78
73
|
|
79
74
|
# Check if fix was applied
|
@@ -81,28 +76,28 @@ module CodeHealer
|
|
81
76
|
puts "🎯 Fix appears to be ready - checking if files were modified..."
|
82
77
|
end
|
83
78
|
else
|
84
|
-
|
79
|
+
PresentationLogger.warn("No output received from Claude")
|
85
80
|
end
|
86
81
|
|
87
82
|
if stderr && !stderr.empty?
|
88
|
-
|
89
|
-
|
83
|
+
PresentationLogger.warn("Claude warnings/errors present")
|
84
|
+
PresentationLogger.detail(stderr)
|
90
85
|
end
|
91
86
|
|
92
87
|
if status.success?
|
93
|
-
|
88
|
+
PresentationLogger.success("Claude execution succeeded")
|
94
89
|
return true
|
95
90
|
else
|
96
|
-
|
91
|
+
PresentationLogger.error("Claude execution failed (status #{status.exitstatus})")
|
97
92
|
return false
|
98
93
|
end
|
99
94
|
end
|
100
95
|
|
101
96
|
rescue Timeout::Error
|
102
|
-
|
97
|
+
PresentationLogger.error("Claude execution timed out after #{config['timeout']}s")
|
103
98
|
return false
|
104
99
|
rescue => e
|
105
|
-
|
100
|
+
PresentationLogger.error("Claude execution error: #{e.message}")
|
106
101
|
return false
|
107
102
|
end
|
108
103
|
end
|
@@ -117,28 +112,121 @@ module CodeHealer
|
|
117
112
|
# Replace placeholder
|
118
113
|
command = command_template.gsub('{prompt}', escaped_prompt)
|
119
114
|
|
120
|
-
# Add
|
121
|
-
if
|
122
|
-
command += " --append-system-prompt '
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
if config['max_file_changes']
|
129
|
-
command += " --append-system-prompt 'Limit changes to #{config['max_file_changes']} files maximum'"
|
130
|
-
end
|
115
|
+
# Add limits and testing hints
|
116
|
+
if config['include_tests']
|
117
|
+
command += " --append-system-prompt 'Include tests when fixing the code'"
|
118
|
+
end
|
119
|
+
|
120
|
+
if config['max_file_changes']
|
121
|
+
command += " --append-system-prompt 'Limit changes to #{config['max_file_changes']} files maximum'"
|
131
122
|
end
|
132
123
|
|
133
|
-
# Add business context instructions
|
134
|
-
command += " --append-system-prompt 'CRITICAL: Before fixing any code,
|
135
|
-
|
124
|
+
# Add business context instructions and require a delimited summary we can parse (Confluence only)
|
125
|
+
# command += " --append-system-prompt 'CRITICAL: Before fixing any code, use the Atlassian MCP tools to fetch business context from Confluence ONLY. Summarize the relevant findings in a concise, bullet list between the markers <<CONTEXT_START>> and <<CONTEXT_END>>. Include Confluence page titles and links, and key rules. Keep to <=5 bullets. Then proceed with the fix.'"
|
126
|
+
|
127
|
+
# Explicit Confluence page fetch (env override with default fallback)
|
128
|
+
explicit_confluence_page_id = (ENV['CONFLUENCE_PAGE_ID'] || '4949770295').to_s.strip
|
129
|
+
unless explicit_confluence_page_id.empty?
|
130
|
+
command += " --append-system-prompt 'CRITICAL: Before fixing any code, Explicitly fetch Confluence page ID #{explicit_confluence_page_id} using Atlassian MCP (mcp__atlassian), extract applicable business logic/rules, and APPLY those rules in the fix.'"
|
131
|
+
end
|
132
|
+
|
136
133
|
# Return command
|
137
134
|
command
|
138
135
|
end
|
136
|
+
|
137
|
+
# Run targeted specs for changed files and iterate fixes up to a configured limit
|
138
|
+
def run_tests_and_iterate_fixes(class_name, method_name)
|
139
|
+
max_iters = ConfigManager.max_test_fix_iterations
|
140
|
+
it = 0
|
141
|
+
loop do
|
142
|
+
it += 1
|
143
|
+
PresentationLogger.step("RSpec run #{it}/#{max_iters}")
|
144
|
+
failures = run_targeted_rspec_for_changes
|
145
|
+
if failures.nil?
|
146
|
+
PresentationLogger.warn("No RSpec detected or no changed files with specs. Skipping test loop.")
|
147
|
+
return true
|
148
|
+
end
|
149
|
+
if failures.empty?
|
150
|
+
PresentationLogger.success("All targeted specs passed")
|
151
|
+
return true
|
152
|
+
end
|
153
|
+
PresentationLogger.warn("Failures detected (#{failures.size}). Attempting fix iteration...")
|
154
|
+
break if it >= max_iters
|
155
|
+
attempt_fix_from_failures(failures, class_name, method_name)
|
156
|
+
end
|
157
|
+
PresentationLogger.warn("Reached max test-fix iterations (#{max_iters}).")
|
158
|
+
false
|
159
|
+
end
|
160
|
+
|
161
|
+
def run_targeted_rspec_for_changes
|
162
|
+
changed = get_recently_modified_files.select { |f| f.end_with?('.rb') }
|
163
|
+
spec_files = changed.map { |f| f.sub(%r{^app/}, 'spec/').sub(/\.rb\z/, '_spec.rb') }
|
164
|
+
spec_files.select! { |s| File.exist?(s) }
|
165
|
+
return nil if spec_files.empty? || !File.exist?('spec')
|
166
|
+
cmd = ["bundle exec rspec --format documentation --no-color", spec_files.map { |s| "'#{s}'" }.join(' ')].join(' ')
|
167
|
+
stdout, stderr, status = Open3.capture3(cmd)
|
168
|
+
PresentationLogger.detail(stdout) if stdout && !stdout.empty?
|
169
|
+
PresentationLogger.warn(stderr) if stderr && !stderr.empty?
|
170
|
+
parse_rspec_failures(stdout)
|
171
|
+
rescue => e
|
172
|
+
PresentationLogger.warn("RSpec execution failed: #{e.message}")
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
|
176
|
+
def parse_rspec_failures(output)
|
177
|
+
return [] unless output
|
178
|
+
failures = []
|
179
|
+
current = nil
|
180
|
+
output.each_line do |line|
|
181
|
+
if line =~ /^\s*\d+\)\s+(.*)$/
|
182
|
+
current = { title: $1.strip, details: [] }
|
183
|
+
failures << current
|
184
|
+
elsif current
|
185
|
+
current[:details] << line
|
186
|
+
end
|
187
|
+
end
|
188
|
+
failures
|
189
|
+
end
|
190
|
+
|
191
|
+
def attempt_fix_from_failures(failures, class_name, method_name)
|
192
|
+
summary = failures.map { |f| "- #{f[:title]}\n #{f[:details].first(5).join}" }.join("\n")
|
193
|
+
PresentationLogger.claude_action("Sending failure summary to Claude for iterative fix")
|
194
|
+
iterative_prompt = <<~PROMPT
|
195
|
+
The previous fix compiled, but targeted RSpec tests failed. Here is a concise failure summary:\n\n#{summary}\n\nUpdate the relevant code to make these tests pass. Return only a unified diff. Keep changes minimal and safe.
|
196
|
+
PROMPT
|
197
|
+
config = ConfigManager.claude_code_settings
|
198
|
+
command = build_claude_command(iterative_prompt, config)
|
199
|
+
stdout, stderr, status = Open3.capture3(command)
|
200
|
+
PresentationLogger.detail(stdout) if stdout && !stdout.empty?
|
201
|
+
PresentationLogger.warn(stderr) if stderr && !stderr.empty?
|
202
|
+
if status.success?
|
203
|
+
PresentationLogger.success("Iterative Claude run succeeded")
|
204
|
+
reload_modified_files
|
205
|
+
true
|
206
|
+
else
|
207
|
+
PresentationLogger.error("Iterative Claude run failed (status #{status.exitstatus})")
|
208
|
+
false
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Parse Claude Terminal output for Confluence links/titles (Confluence only)
|
213
|
+
def extract_business_context_references(text)
|
214
|
+
refs = []
|
215
|
+
return refs unless text
|
216
|
+
|
217
|
+
# Match Confluence URLs
|
218
|
+
confluence_regex = /(https?:\/\/[^\s]+confluence[^\s]+\/(display|spaces|pages)\/[^\s)"']+)/i
|
219
|
+
text.scan(confluence_regex).each do |match|
|
220
|
+
url = match[0]
|
221
|
+
title = url.split('/').last.gsub('-', ' ')[0..80]
|
222
|
+
refs << { source: 'Confluence', display: "#{title} (#{url})" }
|
223
|
+
end
|
224
|
+
|
225
|
+
refs.uniq { |r| r[:display] }
|
226
|
+
end
|
139
227
|
|
140
228
|
def reload_modified_files
|
141
|
-
|
229
|
+
PresentationLogger.step("Reloading modified files")
|
142
230
|
|
143
231
|
# Get list of recently modified files (last 5 minutes)
|
144
232
|
recent_files = get_recently_modified_files
|
@@ -147,14 +235,14 @@ module CodeHealer
|
|
147
235
|
if file_path.include?('/app/')
|
148
236
|
begin
|
149
237
|
load file_path
|
150
|
-
|
238
|
+
PresentationLogger.detail("Reloaded: #{file_path}")
|
151
239
|
rescue => e
|
152
|
-
|
240
|
+
PresentationLogger.warn("Failed to reload #{file_path}: #{e.message}")
|
153
241
|
end
|
154
242
|
end
|
155
243
|
end
|
156
244
|
|
157
|
-
|
245
|
+
PresentationLogger.detail("File reloading completed")
|
158
246
|
end
|
159
247
|
|
160
248
|
def get_recently_modified_files
|
@@ -191,7 +279,7 @@ module CodeHealer
|
|
191
279
|
f.puts(log_entry.to_json)
|
192
280
|
end
|
193
281
|
|
194
|
-
|
282
|
+
PresentationLogger.detail("Evolution attempt logged to #{log_file}")
|
195
283
|
end
|
196
284
|
|
197
285
|
|
@@ -34,8 +34,9 @@ module CodeHealer
|
|
34
34
|
config['allowed_error_types'] || []
|
35
35
|
end
|
36
36
|
|
37
|
+
# Deprecated: allowed classes are no longer used. We rely solely on excluded_classes.
|
37
38
|
def allowed_classes
|
38
|
-
|
39
|
+
[]
|
39
40
|
end
|
40
41
|
|
41
42
|
def excluded_classes
|
@@ -45,8 +46,7 @@ module CodeHealer
|
|
45
46
|
def can_evolve_class?(class_name)
|
46
47
|
return false unless enabled?
|
47
48
|
return false if excluded_classes.include?(class_name)
|
48
|
-
|
49
|
-
allowed_classes.include?(class_name)
|
49
|
+
true
|
50
50
|
end
|
51
51
|
|
52
52
|
def can_handle_error?(error)
|
@@ -82,6 +82,15 @@ module CodeHealer
|
|
82
82
|
config['claude_code'] || {}
|
83
83
|
end
|
84
84
|
|
85
|
+
# Test-fix iteration settings
|
86
|
+
def test_fix_settings
|
87
|
+
config['test_fix'] || {}
|
88
|
+
end
|
89
|
+
|
90
|
+
def max_test_fix_iterations
|
91
|
+
(test_fix_settings['max_iterations'] || 2).to_i
|
92
|
+
end
|
93
|
+
|
85
94
|
def claude_persist_session?
|
86
95
|
claude_code_settings['persist_session'] == true
|
87
96
|
end
|
@@ -163,21 +172,6 @@ module CodeHealer
|
|
163
172
|
config['api'] || {}
|
164
173
|
end
|
165
174
|
|
166
|
-
# Demo Configuration
|
167
|
-
def demo_settings
|
168
|
-
config['demo'] || {}
|
169
|
-
end
|
170
|
-
|
171
|
-
def demo_mode?
|
172
|
-
demo_settings['enabled'] == true
|
173
|
-
end
|
174
|
-
|
175
|
-
def demo_skip_tests?
|
176
|
-
demo_mode? && demo_settings['skip_tests'] != false
|
177
|
-
end
|
178
|
-
|
179
|
-
|
180
|
-
|
181
175
|
def git_settings
|
182
176
|
config['git'] || {}
|
183
177
|
end
|
@@ -337,7 +331,6 @@ module CodeHealer
|
|
337
331
|
'max_evolutions_per_day' => 10,
|
338
332
|
'auto_generate_tests' => true,
|
339
333
|
'allowed_error_types' => ['ZeroDivisionError', 'NoMethodError', 'ArgumentError', 'TypeError'],
|
340
|
-
'allowed_classes' => ['User', 'Order', 'PaymentProcessor'],
|
341
334
|
'excluded_classes' => ['ApplicationController', 'ApplicationRecord', 'ApplicationJob', 'ApplicationMailer'],
|
342
335
|
'evolution_strategy' => {
|
343
336
|
'method' => 'api',
|
@@ -355,6 +348,9 @@ module CodeHealer
|
|
355
348
|
'spec/business_context_specs.rb'
|
356
349
|
]
|
357
350
|
},
|
351
|
+
'test_fix' => {
|
352
|
+
'max_iterations' => 2
|
353
|
+
},
|
358
354
|
'business_context' => {
|
359
355
|
'enabled' => true,
|
360
356
|
'sources' => ['docs/business_rules.md']
|
@@ -365,10 +361,7 @@ module CodeHealer
|
|
365
361
|
'max_tokens' => 2000,
|
366
362
|
'temperature' => 0.1
|
367
363
|
},
|
368
|
-
|
369
|
-
'enabled' => false,
|
370
|
-
'skip_tests' => true
|
371
|
-
},
|
364
|
+
|
372
365
|
'git' => {
|
373
366
|
'auto_commit' => true,
|
374
367
|
'auto_push' => true,
|
data/lib/code_healer/core.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'git'
|
3
3
|
require 'octokit'
|
4
|
+
require_relative 'presentation_logger'
|
4
5
|
|
5
6
|
module CodeHealer
|
6
7
|
class Core
|
@@ -180,15 +181,25 @@ module CodeHealer
|
|
180
181
|
end
|
181
182
|
|
182
183
|
def patch_classes_for_evolution
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
184
|
+
# Patch all autoloaded classes except excluded ones
|
185
|
+
excluded = CodeHealer::ConfigManager.excluded_classes
|
186
|
+
# Attempt to iterate over loaded application classes
|
187
|
+
if defined?(Rails) && Rails.application
|
188
|
+
app_paths = [Rails.root.join('app', 'models'), Rails.root.join('app', 'controllers')]
|
189
|
+
app_paths.each do |path|
|
190
|
+
Dir.glob(File.join(path.to_s, '**', '*.rb')).each do |file|
|
191
|
+
begin
|
192
|
+
relative = file.sub(Rails.root.join('app').to_s + '/', '')
|
193
|
+
class_name = relative.gsub('.rb', '').split('/').map(&:classify).join('::')
|
194
|
+
next if class_name.nil? || class_name.empty?
|
195
|
+
next if excluded.include?(class_name)
|
196
|
+
klass = class_name.constantize rescue nil
|
197
|
+
next unless klass.is_a?(Class) || klass.is_a?(Module)
|
198
|
+
patch_class_for_evolution(klass)
|
199
|
+
rescue => e
|
200
|
+
puts "⚠️ Skipping #{file}: #{e.message}"
|
201
|
+
end
|
202
|
+
end
|
192
203
|
end
|
193
204
|
end
|
194
205
|
end
|
@@ -229,26 +240,26 @@ module CodeHealer
|
|
229
240
|
def extract_from_backtrace(backtrace)
|
230
241
|
return [nil, nil] unless backtrace
|
231
242
|
|
232
|
-
|
233
|
-
|
234
|
-
backtrace.first(5).each_with_index { |line, i|
|
243
|
+
PresentationLogger.detail("Starting backtrace analysis...")
|
244
|
+
PresentationLogger.detail("First 5 backtrace lines:")
|
245
|
+
backtrace.first(5).each_with_index { |line, i| PresentationLogger.detail(" #{i}: #{line}") }
|
235
246
|
|
236
247
|
# Use the exact working implementation from SelfRuby
|
237
248
|
core_methods = %w[* + - / % ** == != < > <= >= <=> === =~ !~ & | ^ ~ << >> [] []= `]
|
238
249
|
app_file_line = backtrace.find { |line| line.include?('/app/') }
|
239
250
|
return [nil, nil] unless app_file_line
|
240
251
|
|
241
|
-
|
252
|
+
PresentationLogger.detail("Found app file line: #{app_file_line}")
|
242
253
|
|
243
254
|
if app_file_line =~ /(.+):(\d+):in `(.+)'/
|
244
255
|
file_path = $1
|
245
256
|
method_name = $3
|
246
257
|
|
247
|
-
|
258
|
+
PresentationLogger.detail("Extracted file_path=#{file_path}, method_name=#{method_name}")
|
248
259
|
|
249
260
|
# If it's a core method, look deeper in the backtrace
|
250
261
|
if core_methods.include?(method_name)
|
251
|
-
|
262
|
+
PresentationLogger.detail("#{method_name} is a core method, looking deeper...")
|
252
263
|
deeper_app_line = backtrace.find do |line|
|
253
264
|
line.include?('/app/') &&
|
254
265
|
line =~ /in `(.+)'/ &&
|
@@ -260,14 +271,14 @@ module CodeHealer
|
|
260
271
|
end
|
261
272
|
|
262
273
|
if deeper_app_line
|
263
|
-
|
274
|
+
PresentationLogger.detail("Found deeper app line: #{deeper_app_line}")
|
264
275
|
if deeper_app_line =~ /(.+):(\d+):in `(.+)'/
|
265
276
|
file_path = $1
|
266
277
|
method_name = $3
|
267
|
-
|
278
|
+
PresentationLogger.detail("Updated to file_path=#{file_path}, method_name=#{method_name}")
|
268
279
|
end
|
269
280
|
else
|
270
|
-
|
281
|
+
PresentationLogger.detail("No deeper app line found")
|
271
282
|
end
|
272
283
|
end
|
273
284
|
|
@@ -279,7 +290,7 @@ module CodeHealer
|
|
279
290
|
method_name.include?('reduce') ||
|
280
291
|
method_name.include?('sum')
|
281
292
|
)
|
282
|
-
|
293
|
+
PresentationLogger.detail("#{method_name} is a block/iterator, looking for containing method...")
|
283
294
|
# Look for the FIRST valid method in the backtrace, not just any method
|
284
295
|
containing_line = backtrace.find do |line|
|
285
296
|
line.include?('/app/') &&
|
@@ -294,14 +305,14 @@ module CodeHealer
|
|
294
305
|
end
|
295
306
|
|
296
307
|
if containing_line
|
297
|
-
|
308
|
+
PresentationLogger.detail("Found containing line: #{containing_line}")
|
298
309
|
if containing_line =~ /(.+):(\d+):in `(.+)'/
|
299
310
|
file_path = $1
|
300
311
|
method_name = $3
|
301
|
-
|
312
|
+
PresentationLogger.detail("Updated to file_path=#{file_path}, method_name=#{method_name}")
|
302
313
|
end
|
303
314
|
else
|
304
|
-
|
315
|
+
PresentationLogger.detail("No containing line found")
|
305
316
|
end
|
306
317
|
end
|
307
318
|
|
@@ -320,13 +331,13 @@ module CodeHealer
|
|
320
331
|
end
|
321
332
|
end
|
322
333
|
|
323
|
-
|
324
|
-
|
334
|
+
PresentationLogger.detail("Final result - class_name=#{class_name}, method_name=#{method_name}")
|
335
|
+
PresentationLogger.detail("Extracted: #{class_name}##{method_name} from #{file_path}")
|
325
336
|
return [class_name, method_name]
|
326
337
|
end
|
327
338
|
end
|
328
339
|
|
329
|
-
|
340
|
+
PresentationLogger.detail("No valid method found in backtrace")
|
330
341
|
[nil, nil]
|
331
342
|
end
|
332
343
|
|