code_healer 0.1.24 โ 0.1.26
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 +30 -0
- data/lib/code_healer/claude_code_evolution_handler.rb +26 -29
- data/lib/code_healer/healing_job.rb +33 -29
- data/lib/code_healer/healing_workspace_manager.rb +54 -69
- data/lib/code_healer/presentation_logger.rb +114 -0
- 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: 20c5c060c65cfee68cf65f2478e6335b1927f7e810fdcf6dd1c6796e29b49f65
|
4
|
+
data.tar.gz: 38aad1427647fe628c1a0aa2824fe09b206d6ac752ef6b1407a41ddd15560383
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb2f391d38dfb0e444ff60e66d92625409f09c8389705a08c3ca3783f9aa56ae1d10dffc36109472cf2158c1e44b3df1eaba6100263f57d7d82bec713d58bbe6
|
7
|
+
data.tar.gz: 7864c4f255da0a1682d8c5787009998fe0e554a29fadbaf486f84d3c7664cdb12596381d8affa303110dc68a7b42d83c00f10da94c7f5f7368bc8bed88d13189
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [0.1.24] - 2025-08-31
|
9
9
|
|
10
|
+
## [0.1.26] - 2025-09-03
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- **Enhanced PresentationLogger**: Improved with backtrace truncation, timing display, and specialized action methods
|
14
|
+
- **Smart Value Truncation**: Long arrays and strings are intelligently truncated for clean presentation
|
15
|
+
- **Timing Integration**: Total healing time displayed in final outcome
|
16
|
+
- **Action-Specific Logging**: `claude_action`, `workspace_action`, `git_action` methods for contextual logging
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
- **Workspace Logs**: Dramatically reduced verbosity while maintaining key information
|
20
|
+
- **Backtrace Display**: Shows only first 3 relevant lines with clean formatting
|
21
|
+
- **Git Operations**: Streamlined git status and diff output for presentation clarity
|
22
|
+
- **Outcome Display**: Enhanced with timing and better formatting
|
23
|
+
|
24
|
+
### Notes
|
25
|
+
- Perfect for conference demos with `CODE_HEALER_VERBOSE=true` for detailed debugging
|
26
|
+
- No API changes; safe minor release
|
27
|
+
|
28
|
+
## [0.1.25] - 2025-09-03
|
29
|
+
|
30
|
+
### Added
|
31
|
+
- PresentationLogger: clean, emoji-labeled, presentation-friendly logs with optional verbose mode (`CODE_HEALER_VERBOSE=true`)
|
32
|
+
- HealingJob and ClaudeCodeEvolutionHandler now use PresentationLogger for concise output
|
33
|
+
|
34
|
+
### Changed
|
35
|
+
- Suppressed noisy prints in core healing flow in favor of high-signal steps and outcomes
|
36
|
+
|
37
|
+
### Notes
|
38
|
+
- No API changes; safe minor release. Ideal for conference demos.
|
39
|
+
|
10
40
|
### Added
|
11
41
|
- **Aggressive File Filtering**: Implemented comprehensive filtering to prevent `tmp/`, `log/`, and other temporary files from being committed
|
12
42
|
- **Pre-Commit Validation**: Added workspace validation before commit to ensure no temporary files slip through
|
@@ -1,14 +1,15 @@
|
|
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
15
|
# Build concise, demo-optimized prompt (no repo-wide scan, no tests)
|
@@ -26,24 +27,24 @@ module CodeHealer
|
|
26
27
|
success = execute_claude_code_fix(prompt, class_name, method_name)
|
27
28
|
|
28
29
|
if success
|
29
|
-
|
30
|
+
PresentationLogger.success("Claude run completed")
|
30
31
|
# Reload modified files
|
31
32
|
reload_modified_files
|
32
33
|
|
33
34
|
# ๐ Trigger Git operations (commit, push, PR creation)
|
34
35
|
# Note: Git operations are now handled by the isolated workspace manager
|
35
36
|
# to prevent duplication and ensure proper isolation
|
36
|
-
|
37
|
+
PresentationLogger.info("Git operations handled by workspace manager")
|
37
38
|
|
38
39
|
return true
|
39
40
|
else
|
40
|
-
|
41
|
+
PresentationLogger.error("Claude run failed")
|
41
42
|
return false
|
42
43
|
end
|
43
44
|
|
44
45
|
rescue => e
|
45
|
-
|
46
|
-
|
46
|
+
PresentationLogger.error("Claude evolution error: #{e.message}")
|
47
|
+
PresentationLogger.detail("Backtrace: #{Array(e.backtrace).first(5).join("\n")}")
|
47
48
|
return false
|
48
49
|
end
|
49
50
|
end
|
@@ -56,24 +57,20 @@ module CodeHealer
|
|
56
57
|
# Build command
|
57
58
|
command = build_claude_command(prompt, config)
|
58
59
|
|
59
|
-
|
60
|
-
puts "Command: #{command}"
|
61
|
-
puts "Timeout: #{config['timeout']} seconds"
|
60
|
+
PresentationLogger.claude_action("Executing Claude Code (timeout: #{config['timeout']}s)")
|
62
61
|
|
63
62
|
begin
|
64
63
|
# Execute with timeout
|
65
64
|
Timeout.timeout(config['timeout']) do
|
66
65
|
stdout, stderr, status = Open3.capture3(command)
|
67
66
|
|
68
|
-
puts "๐ค Claude Code Output:"
|
69
67
|
if stdout && !stdout.empty?
|
70
|
-
|
71
|
-
|
68
|
+
PresentationLogger.success("Response received from Claude")
|
69
|
+
PresentationLogger.detail(stdout)
|
72
70
|
|
73
71
|
# Check if Claude Code is asking for permission
|
74
72
|
if stdout.include?("permission") || stdout.include?("grant") || stdout.include?("edit")
|
75
|
-
|
76
|
-
puts "๐ก Make sure to grant Edit permissions when prompted"
|
73
|
+
PresentationLogger.warn("Claude requested edit permissions. Ensure permissions are granted.")
|
77
74
|
end
|
78
75
|
|
79
76
|
# Check if fix was applied
|
@@ -81,28 +78,28 @@ module CodeHealer
|
|
81
78
|
puts "๐ฏ Fix appears to be ready - checking if files were modified..."
|
82
79
|
end
|
83
80
|
else
|
84
|
-
|
81
|
+
PresentationLogger.warn("No output received from Claude")
|
85
82
|
end
|
86
83
|
|
87
84
|
if stderr && !stderr.empty?
|
88
|
-
|
89
|
-
|
85
|
+
PresentationLogger.warn("Claude warnings/errors present")
|
86
|
+
PresentationLogger.detail(stderr)
|
90
87
|
end
|
91
88
|
|
92
89
|
if status.success?
|
93
|
-
|
90
|
+
PresentationLogger.success("Claude execution succeeded")
|
94
91
|
return true
|
95
92
|
else
|
96
|
-
|
93
|
+
PresentationLogger.error("Claude execution failed (status #{status.exitstatus})")
|
97
94
|
return false
|
98
95
|
end
|
99
96
|
end
|
100
97
|
|
101
98
|
rescue Timeout::Error
|
102
|
-
|
99
|
+
PresentationLogger.error("Claude execution timed out after #{config['timeout']}s")
|
103
100
|
return false
|
104
101
|
rescue => e
|
105
|
-
|
102
|
+
PresentationLogger.error("Claude execution error: #{e.message}")
|
106
103
|
return false
|
107
104
|
end
|
108
105
|
end
|
@@ -138,7 +135,7 @@ module CodeHealer
|
|
138
135
|
end
|
139
136
|
|
140
137
|
def reload_modified_files
|
141
|
-
|
138
|
+
PresentationLogger.step("Reloading modified files")
|
142
139
|
|
143
140
|
# Get list of recently modified files (last 5 minutes)
|
144
141
|
recent_files = get_recently_modified_files
|
@@ -147,14 +144,14 @@ module CodeHealer
|
|
147
144
|
if file_path.include?('/app/')
|
148
145
|
begin
|
149
146
|
load file_path
|
150
|
-
|
147
|
+
PresentationLogger.detail("Reloaded: #{file_path}")
|
151
148
|
rescue => e
|
152
|
-
|
149
|
+
PresentationLogger.warn("Failed to reload #{file_path}: #{e.message}")
|
153
150
|
end
|
154
151
|
end
|
155
152
|
end
|
156
153
|
|
157
|
-
|
154
|
+
PresentationLogger.detail("File reloading completed")
|
158
155
|
end
|
159
156
|
|
160
157
|
def get_recently_modified_files
|
@@ -191,7 +188,7 @@ module CodeHealer
|
|
191
188
|
f.puts(log_entry.to_json)
|
192
189
|
end
|
193
190
|
|
194
|
-
|
191
|
+
PresentationLogger.detail("Evolution attempt logged to #{log_file}")
|
195
192
|
end
|
196
193
|
|
197
194
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'sidekiq'
|
2
2
|
require 'open3'
|
3
|
+
require_relative 'presentation_logger'
|
3
4
|
|
4
5
|
module CodeHealer
|
5
6
|
class HealingJob
|
@@ -8,15 +9,15 @@ module CodeHealer
|
|
8
9
|
sidekiq_options retry: 3, backtrace: true, queue: 'evolution'
|
9
10
|
|
10
11
|
def perform(*args)
|
11
|
-
|
12
|
-
|
12
|
+
start_time = Time.now
|
13
|
+
PresentationLogger.section("CodeHealer โ Healing Job")
|
13
14
|
|
14
|
-
|
15
15
|
# Support both legacy and new invocation styles
|
16
16
|
error, class_name, method_name, evolution_method, backtrace = parse_args(args)
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
PresentationLogger.kv("Error", "#{error.class}: #{error.message}")
|
18
|
+
PresentationLogger.kv("Target", "#{class_name}##{method_name}")
|
19
|
+
PresentationLogger.kv("Strategy", evolution_method)
|
20
|
+
PresentationLogger.backtrace(backtrace)
|
20
21
|
|
21
22
|
# Track start metric
|
22
23
|
healing_id = MetricsCollector.generate_healing_id
|
@@ -30,13 +31,13 @@ module CodeHealer
|
|
30
31
|
)
|
31
32
|
MetricsCollector.track_error_occurrence(healing_id, Time.current)
|
32
33
|
|
33
|
-
|
34
|
+
PresentationLogger.success("Healing started for #{class_name}##{method_name}")
|
34
35
|
|
35
|
-
|
36
|
+
PresentationLogger.step("Creating isolated healing workspace")
|
36
37
|
# Create isolated healing workspace
|
37
38
|
workspace_path = create_healing_workspace(class_name, method_name)
|
38
39
|
MetricsCollector.track_workspace_creation(healing_id, workspace_path)
|
39
|
-
|
40
|
+
PresentationLogger.kv("Workspace", workspace_path)
|
40
41
|
|
41
42
|
ai_time_ms = nil
|
42
43
|
git_time_ms = nil
|
@@ -46,7 +47,7 @@ module CodeHealer
|
|
46
47
|
failure_reason = nil
|
47
48
|
|
48
49
|
begin
|
49
|
-
|
50
|
+
PresentationLogger.step("Applying fixes in isolated environment")
|
50
51
|
# Apply fixes in isolated environment
|
51
52
|
ai_started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
52
53
|
success = apply_fixes_in_workspace(workspace_path, error, class_name, method_name, evolution_method)
|
@@ -83,17 +84,18 @@ module CodeHealer
|
|
83
84
|
false # pr_created
|
84
85
|
)
|
85
86
|
overall_success = true
|
86
|
-
|
87
|
+
PresentationLogger.success("Fixes applied and validated")
|
88
|
+
PresentationLogger.kv("Branch", healing_branch)
|
87
89
|
else
|
88
90
|
overall_success = false
|
89
91
|
failure_reason ||= 'healing_branch_creation_failed'
|
90
|
-
|
92
|
+
PresentationLogger.warn("Fixes applied and tested, but merge failed")
|
91
93
|
end
|
92
94
|
else
|
93
95
|
overall_success = false
|
94
96
|
syntax_valid = false
|
95
97
|
failure_reason ||= 'workspace_tests_failed_or_syntax_error'
|
96
|
-
|
98
|
+
PresentationLogger.warn("Fixes applied but validation failed; skipping merge")
|
97
99
|
end
|
98
100
|
else
|
99
101
|
overall_success = false
|
@@ -105,7 +107,7 @@ module CodeHealer
|
|
105
107
|
ai_provider_for(evolution_method),
|
106
108
|
failure_reason
|
107
109
|
)
|
108
|
-
|
110
|
+
PresentationLogger.error("Failed to apply fixes in workspace")
|
109
111
|
end
|
110
112
|
ensure
|
111
113
|
# Persist timing metrics if captured
|
@@ -122,10 +124,12 @@ module CodeHealer
|
|
122
124
|
cleanup_workspace(workspace_path)
|
123
125
|
end
|
124
126
|
|
125
|
-
|
127
|
+
total_time = ((Time.now - start_time) * 1000).round
|
128
|
+
timing = "#{total_time}ms total"
|
129
|
+
PresentationLogger.outcome(success: overall_success, branch: healing_branch, pr_url: nil, reason: failure_reason, timing: timing)
|
126
130
|
rescue => e
|
127
|
-
|
128
|
-
|
131
|
+
PresentationLogger.error("Evolution Job Failed: #{e.message}")
|
132
|
+
PresentationLogger.detail("Backtrace: #{Array(e.backtrace).first(5).join("\n")}")
|
129
133
|
raise e # Re-raise to trigger Sidekiq retry
|
130
134
|
end
|
131
135
|
|
@@ -222,7 +226,7 @@ module CodeHealer
|
|
222
226
|
end
|
223
227
|
|
224
228
|
def create_healing_workspace(class_name, method_name)
|
225
|
-
|
229
|
+
PresentationLogger.detail("Preparing workspace for #{class_name}##{method_name}")
|
226
230
|
|
227
231
|
# Create persistent workspace and checkout to target branch
|
228
232
|
workspace_path = CodeHealer::HealingWorkspaceManager.create_healing_workspace(
|
@@ -230,12 +234,12 @@ module CodeHealer
|
|
230
234
|
CodeHealer::ConfigManager.pr_target_branch # Use configured target branch
|
231
235
|
)
|
232
236
|
|
233
|
-
|
237
|
+
PresentationLogger.detail("Workspace ready: #{workspace_path}")
|
234
238
|
workspace_path
|
235
239
|
end
|
236
240
|
|
237
241
|
def apply_fixes_in_workspace(workspace_path, error, class_name, method_name, evolution_method)
|
238
|
-
|
242
|
+
PresentationLogger.detail("Applying fixes in workspace #{workspace_path}")
|
239
243
|
|
240
244
|
case evolution_method
|
241
245
|
when 'claude_code_terminal'
|
@@ -251,13 +255,13 @@ module CodeHealer
|
|
251
255
|
end
|
252
256
|
handle_api_evolution_in_workspace(workspace_path, error, class_name, method_name)
|
253
257
|
else
|
254
|
-
|
258
|
+
PresentationLogger.error("Unknown evolution method: #{evolution_method}")
|
255
259
|
false
|
256
260
|
end
|
257
261
|
end
|
258
262
|
|
259
263
|
def handle_claude_code_evolution_in_workspace(workspace_path, error, class_name, method_name)
|
260
|
-
|
264
|
+
PresentationLogger.step("Claude Code Terminal evolution")
|
261
265
|
|
262
266
|
# Change to workspace directory for Claude Code operations
|
263
267
|
Dir.chdir(workspace_path) do
|
@@ -266,17 +270,17 @@ module CodeHealer
|
|
266
270
|
)
|
267
271
|
|
268
272
|
if success
|
269
|
-
|
273
|
+
PresentationLogger.success("Claude evolution succeeded")
|
270
274
|
true
|
271
275
|
else
|
272
|
-
|
276
|
+
PresentationLogger.error("Claude evolution failed")
|
273
277
|
false
|
274
278
|
end
|
275
279
|
end
|
276
280
|
end
|
277
281
|
|
278
282
|
def handle_api_evolution_in_workspace(workspace_path, error, class_name, method_name)
|
279
|
-
|
283
|
+
PresentationLogger.step("OpenAI API evolution")
|
280
284
|
|
281
285
|
# Load business context for API evolution
|
282
286
|
business_context = CodeHealer::BusinessContextManager.get_context_for_error(
|
@@ -286,7 +290,7 @@ module CodeHealer
|
|
286
290
|
# Optionally record business context used
|
287
291
|
# MetricsCollector.track_business_context(healing_id, business_context) # healing_id not accessible here
|
288
292
|
|
289
|
-
|
293
|
+
PresentationLogger.detail("Business context loaded for API evolution")
|
290
294
|
|
291
295
|
# Change to workspace directory for API operations
|
292
296
|
Dir.chdir(workspace_path) do
|
@@ -295,10 +299,10 @@ module CodeHealer
|
|
295
299
|
)
|
296
300
|
|
297
301
|
if success
|
298
|
-
|
302
|
+
PresentationLogger.success("API evolution succeeded")
|
299
303
|
true
|
300
304
|
else
|
301
|
-
|
305
|
+
PresentationLogger.error("API evolution failed")
|
302
306
|
false
|
303
307
|
end
|
304
308
|
end
|
@@ -307,7 +311,7 @@ module CodeHealer
|
|
307
311
|
def cleanup_workspace(workspace_path)
|
308
312
|
return unless workspace_path && Dir.exist?(workspace_path)
|
309
313
|
|
310
|
-
|
314
|
+
PresentationLogger.step("Cleaning up workspace")
|
311
315
|
CodeHealer::HealingWorkspaceManager.cleanup_workspace(workspace_path)
|
312
316
|
end
|
313
317
|
|
@@ -1,20 +1,18 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'securerandom'
|
3
|
+
require_relative 'presentation_logger'
|
3
4
|
|
4
5
|
module CodeHealer
|
5
6
|
# Manages isolated healing workspaces for safe code evolution
|
6
7
|
class HealingWorkspaceManager
|
7
8
|
class << self
|
8
9
|
def create_healing_workspace(repo_path, branch_name = nil)
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
PresentationLogger.workspace_action("Preparing isolated workspace")
|
11
|
+
PresentationLogger.detail("Repo: #{repo_path}")
|
12
|
+
PresentationLogger.detail("Target branch: #{branch_name || 'default'}")
|
12
13
|
|
13
14
|
config = CodeHealer::ConfigManager.code_heal_directory_config
|
14
|
-
puts "๐ฅ [WORKSPACE] Raw config: #{config.inspect}"
|
15
|
-
|
16
15
|
base_path = config['path'] || config[:path] || '/tmp/code_healer_workspaces'
|
17
|
-
puts "๐ฅ [WORKSPACE] Base heal dir: #{base_path}"
|
18
16
|
|
19
17
|
# Use persistent workspace ID based on repo (if enabled)
|
20
18
|
if CodeHealer::ConfigManager.persistent_workspaces_enabled?
|
@@ -28,39 +26,36 @@ module CodeHealer
|
|
28
26
|
end
|
29
27
|
|
30
28
|
if CodeHealer::ConfigManager.persistent_workspaces_enabled?
|
31
|
-
|
29
|
+
PresentationLogger.detail("Using persistent workspace: #{workspace_id}")
|
32
30
|
else
|
33
|
-
|
31
|
+
PresentationLogger.detail("Creating temporary workspace: #{workspace_id}")
|
34
32
|
end
|
35
|
-
puts "๐ฅ [WORKSPACE] Full workspace path: #{workspace_path}"
|
36
33
|
|
37
34
|
begin
|
38
|
-
puts "๐ฅ [WORKSPACE] Creating base directory..."
|
39
35
|
# Ensure code heal directory exists
|
40
36
|
FileUtils.mkdir_p(base_path)
|
41
|
-
puts "๐ฅ [WORKSPACE] Base directory created/verified: #{base_path}"
|
42
37
|
|
43
38
|
# Check if workspace already exists
|
44
39
|
if Dir.exist?(workspace_path) && Dir.exist?(File.join(workspace_path, '.git'))
|
45
40
|
if CodeHealer::ConfigManager.persistent_workspaces_enabled?
|
46
|
-
|
41
|
+
PresentationLogger.detail("Reusing existing workspace, checking out target branch")
|
47
42
|
checkout_to_branch(workspace_path, branch_name, repo_path)
|
48
43
|
else
|
49
|
-
|
44
|
+
PresentationLogger.detail("Recreating workspace (persistent mode disabled)")
|
50
45
|
cleanup_workspace(workspace_path, true)
|
51
46
|
create_persistent_workspace(repo_path, workspace_path, branch_name)
|
52
47
|
end
|
53
48
|
else
|
54
|
-
|
49
|
+
PresentationLogger.detail("Creating new workspace")
|
55
50
|
create_persistent_workspace(repo_path, workspace_path, branch_name)
|
56
51
|
end
|
57
52
|
|
58
|
-
|
59
|
-
|
60
|
-
|
53
|
+
PresentationLogger.success("Workspace ready")
|
54
|
+
PresentationLogger.detail("Path: #{workspace_path}")
|
55
|
+
PresentationLogger.detail("Branch: #{get_current_branch(workspace_path)}")
|
61
56
|
workspace_path
|
62
57
|
rescue => e
|
63
|
-
|
58
|
+
PresentationLogger.error("Failed to create workspace: #{e.message}")
|
64
59
|
# Don't cleanup persistent workspace on error
|
65
60
|
raise e
|
66
61
|
end
|
@@ -111,36 +106,42 @@ module CodeHealer
|
|
111
106
|
def test_fixes_in_workspace(workspace_path)
|
112
107
|
config = CodeHealer::ConfigManager.code_heal_directory_config
|
113
108
|
|
114
|
-
|
109
|
+
PresentationLogger.step("Validating fixes")
|
115
110
|
|
116
111
|
begin
|
117
112
|
# Change to workspace directory
|
118
113
|
Dir.chdir(workspace_path) do
|
119
114
|
# Run basic syntax check
|
120
115
|
syntax_check = system("ruby -c #{find_ruby_files.join(' ')} 2>/dev/null")
|
121
|
-
|
116
|
+
unless syntax_check
|
117
|
+
PresentationLogger.error("Syntax validation failed")
|
118
|
+
return false
|
119
|
+
end
|
122
120
|
|
123
121
|
# Optionally skip heavy tests in demo mode
|
124
122
|
unless CodeHealer::ConfigManager.demo_skip_tests?
|
125
123
|
# Run tests if available
|
126
124
|
if File.exist?('Gemfile')
|
127
125
|
bundle_check = system("bundle check >/dev/null 2>&1")
|
128
|
-
|
126
|
+
unless bundle_check
|
127
|
+
PresentationLogger.error("Bundle check failed")
|
128
|
+
return false
|
129
|
+
end
|
129
130
|
|
130
131
|
# Run tests if RSpec is available
|
131
132
|
if File.exist?('spec') || File.exist?('test')
|
132
133
|
test_result = system("bundle exec rspec --dry-run >/dev/null 2>&1") ||
|
133
134
|
system("bundle exec rake test:prepare >/dev/null 2>&1")
|
134
|
-
|
135
|
+
PresentationLogger.detail("Test preparation: #{test_result ? 'passed' : 'skipped'}")
|
135
136
|
end
|
136
137
|
end
|
137
138
|
end
|
138
139
|
|
139
|
-
|
140
|
+
PresentationLogger.success("Validation passed")
|
140
141
|
true
|
141
142
|
end
|
142
143
|
rescue => e
|
143
|
-
|
144
|
+
PresentationLogger.error("Validation failed: #{e.message}")
|
144
145
|
false
|
145
146
|
end
|
146
147
|
end
|
@@ -183,16 +184,14 @@ module CodeHealer
|
|
183
184
|
end
|
184
185
|
|
185
186
|
def create_healing_branch(repo_path, workspace_path, branch_name)
|
186
|
-
|
187
|
+
PresentationLogger.git_action("Creating healing branch and PR")
|
187
188
|
|
188
189
|
begin
|
189
190
|
# All Git operations happen in the isolated workspace
|
190
191
|
Dir.chdir(workspace_path) do
|
191
|
-
|
192
|
-
|
193
|
-
#
|
194
|
-
puts "๐ฟ [WORKSPACE] Git remote origin: #{`git config --get remote.origin.url`.strip}"
|
195
|
-
puts "๐ฟ [WORKSPACE] Current branch: #{`git branch --show-current`.strip}"
|
192
|
+
PresentationLogger.detail("Working in isolated workspace")
|
193
|
+
PresentationLogger.detail("Remote: #{`git config --get remote.origin.url`.strip}")
|
194
|
+
PresentationLogger.detail("Current branch: #{`git branch --show-current`.strip}")
|
196
195
|
|
197
196
|
# Ensure we're on the target branch
|
198
197
|
system("git checkout #{branch_name}")
|
@@ -202,94 +201,80 @@ module CodeHealer
|
|
202
201
|
healing_branch = "code-healer-fix-#{Time.now.to_i}"
|
203
202
|
system("git checkout -b #{healing_branch}")
|
204
203
|
|
205
|
-
# Check Git status
|
206
|
-
puts "๐ [WORKSPACE] Git status in workspace:"
|
207
|
-
system("git status --porcelain")
|
208
|
-
|
209
204
|
# Add all changes (the fixes are already applied in the workspace)
|
210
205
|
add_only_relevant_files(workspace_path)
|
211
206
|
|
212
207
|
# Validate workspace before commit to ensure no temporary files
|
213
208
|
unless validate_workspace_for_commit(workspace_path)
|
214
|
-
|
209
|
+
PresentationLogger.detail("Retrying file addition after validation")
|
215
210
|
add_only_relevant_files(workspace_path)
|
216
211
|
end
|
217
212
|
|
218
213
|
# Check if there are changes to commit
|
219
214
|
if system("git diff --cached --quiet") == false
|
220
|
-
|
215
|
+
PresentationLogger.detail("Committing changes to healing branch")
|
221
216
|
commit_message = "Fix applied by CodeHealer: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
222
217
|
system("git commit -m '#{commit_message}'")
|
223
218
|
|
224
219
|
# Push healing branch from workspace
|
225
|
-
|
220
|
+
PresentationLogger.detail("Pushing healing branch")
|
226
221
|
system("git push origin #{healing_branch}")
|
227
222
|
|
228
|
-
|
229
|
-
|
230
|
-
puts "๐ All changes committed in isolated workspace"
|
223
|
+
PresentationLogger.success("Branch created and pushed: #{healing_branch}")
|
224
|
+
PresentationLogger.detail("Main repository remains untouched")
|
231
225
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
226
|
+
# Create pull request if auto-create is enabled
|
227
|
+
if should_create_pull_request?
|
228
|
+
PresentationLogger.detail("Creating pull request")
|
229
|
+
pr_url = create_pull_request(healing_branch, branch_name)
|
230
|
+
if pr_url
|
231
|
+
PresentationLogger.success("Pull request created: #{pr_url}")
|
232
|
+
else
|
233
|
+
PresentationLogger.warn("Failed to create pull request")
|
234
|
+
end
|
235
|
+
else
|
236
|
+
PresentationLogger.info("Review changes and create PR when ready")
|
237
|
+
end
|
244
238
|
|
245
239
|
healing_branch
|
246
240
|
else
|
247
|
-
|
248
|
-
|
249
|
-
puts " - The fixes were not applied to the workspace"
|
250
|
-
puts " - There was an issue with the healing process"
|
241
|
+
PresentationLogger.warn("No changes detected in workspace")
|
242
|
+
PresentationLogger.detail("This might indicate fixes were not applied or there was an issue")
|
251
243
|
|
252
244
|
# Delete the empty branch
|
253
245
|
system("git checkout #{branch_name}")
|
254
246
|
system("git branch -D #{healing_branch}")
|
255
|
-
|
247
|
+
PresentationLogger.detail("Deleted empty healing branch: #{healing_branch}")
|
256
248
|
nil
|
257
249
|
end
|
258
250
|
end
|
259
251
|
rescue => e
|
260
|
-
|
252
|
+
PresentationLogger.error("Failed to create healing branch: #{e.message}")
|
261
253
|
nil
|
262
254
|
end
|
263
255
|
end
|
264
256
|
|
265
257
|
def cleanup_workspace(workspace_path, force = false)
|
266
|
-
puts "๐งน [WORKSPACE] Starting workspace cleanup..."
|
267
|
-
puts "๐งน [WORKSPACE] Target: #{workspace_path}"
|
268
|
-
puts "๐งน [WORKSPACE] Force cleanup: #{force}"
|
269
|
-
puts "๐งน [WORKSPACE] Exists: #{Dir.exist?(workspace_path)}"
|
270
|
-
|
271
258
|
return unless Dir.exist?(workspace_path)
|
272
259
|
|
273
260
|
# Check if this is a persistent workspace
|
274
261
|
is_persistent = workspace_path.include?('persistent_')
|
275
262
|
|
276
263
|
if is_persistent && !force
|
277
|
-
|
278
|
-
puts "๐งน [WORKSPACE] Use force=true to override"
|
264
|
+
PresentationLogger.detail("Skipping cleanup of persistent workspace")
|
279
265
|
return
|
280
266
|
end
|
281
267
|
|
268
|
+
PresentationLogger.detail("Cleaning up workspace")
|
269
|
+
|
282
270
|
# Remove .git directory first to avoid conflicts
|
283
271
|
git_dir = File.join(workspace_path, '.git')
|
284
272
|
if Dir.exist?(git_dir)
|
285
|
-
puts "๐งน [WORKSPACE] Removing .git directory to prevent conflicts..."
|
286
273
|
FileUtils.rm_rf(git_dir)
|
287
274
|
end
|
288
275
|
|
289
|
-
puts "๐งน [WORKSPACE] Removing workspace directory..."
|
290
276
|
FileUtils.rm_rf(workspace_path)
|
291
|
-
|
292
|
-
puts "๐งน [WORKSPACE] Directory still exists: #{Dir.exist?(workspace_path)}"
|
277
|
+
PresentationLogger.detail("Workspace cleanup completed")
|
293
278
|
end
|
294
279
|
|
295
280
|
def cleanup_expired_workspaces
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module CodeHealer
|
2
|
+
# Presentation-focused logger for conference demos and clean operator output
|
3
|
+
class PresentationLogger
|
4
|
+
class << self
|
5
|
+
def verbose?
|
6
|
+
env_flag = ENV.fetch('CODE_HEALER_VERBOSE', 'false')
|
7
|
+
env_flag.to_s.downcase == 'true'
|
8
|
+
end
|
9
|
+
|
10
|
+
def section(title)
|
11
|
+
divider
|
12
|
+
puts "\n๐ค #{title}\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
def step(message)
|
16
|
+
puts "โก๏ธ #{message}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def info(message)
|
20
|
+
puts "โน๏ธ #{message}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def success(message)
|
24
|
+
puts "โ
#{message}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def warn(message)
|
28
|
+
puts "โ ๏ธ #{message}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def error(message)
|
32
|
+
puts "โ #{message}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def detail(message)
|
36
|
+
return unless verbose?
|
37
|
+
puts " ยท #{message}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def kv(label, value)
|
41
|
+
# Truncate long values for presentation
|
42
|
+
display_value = case value
|
43
|
+
when Array
|
44
|
+
if value.length > 3
|
45
|
+
"#{value.first(2).join(', ')}... (#{value.length} total)"
|
46
|
+
else
|
47
|
+
value.join(', ')
|
48
|
+
end
|
49
|
+
when String
|
50
|
+
value.length > 100 ? "#{value[0..97]}..." : value
|
51
|
+
else
|
52
|
+
value.to_s
|
53
|
+
end
|
54
|
+
puts " โข #{label}: #{display_value}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def backtrace(backtrace_array)
|
58
|
+
return unless backtrace_array&.any?
|
59
|
+
|
60
|
+
# Show only the first 3 relevant lines for presentation
|
61
|
+
relevant_lines = backtrace_array.first(3).map do |line|
|
62
|
+
# Extract just the file and line number for cleaner display
|
63
|
+
if line.match?(/^(.+\.rb):(\d+):in/)
|
64
|
+
file = File.basename($1)
|
65
|
+
line_num = $2
|
66
|
+
method = line.match(/in `(.+)'/)&.[](1) || 'unknown'
|
67
|
+
"#{file}:#{line_num} in #{method}"
|
68
|
+
else
|
69
|
+
line
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
puts " โข Backtrace: #{relevant_lines.join(' โ ')}"
|
74
|
+
detail("Full backtrace available with CODE_HEALER_VERBOSE=true") if backtrace_array.length > 3
|
75
|
+
end
|
76
|
+
|
77
|
+
def time(label, ms)
|
78
|
+
puts "โฑ๏ธ #{label}: #{ms} ms"
|
79
|
+
end
|
80
|
+
|
81
|
+
def divider
|
82
|
+
puts "\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
def outcome(success:, branch: nil, pr_url: nil, reason: nil, timing: nil)
|
86
|
+
if success
|
87
|
+
success_msg = "๐ Healing complete"
|
88
|
+
success_msg << " (#{timing})" if timing
|
89
|
+
success(success_msg)
|
90
|
+
puts " โข Branch: #{branch}" if branch
|
91
|
+
puts " โข PR: #{pr_url}" if pr_url
|
92
|
+
else
|
93
|
+
error_msg = "๐ฅ Healing failed"
|
94
|
+
error_msg << " (#{reason})" if reason
|
95
|
+
error(error_msg)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def claude_action(action)
|
100
|
+
puts "๐ค #{action}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def workspace_action(action)
|
104
|
+
puts "๐๏ธ #{action}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def git_action(action)
|
108
|
+
puts "๐ #{action}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
data/lib/code_healer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: code_healer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.26
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Deepan Kumar
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -401,6 +401,7 @@ files:
|
|
401
401
|
- lib/code_healer/mcp_server.rb
|
402
402
|
- lib/code_healer/mcp_tools.rb
|
403
403
|
- lib/code_healer/models/healing_metric.rb
|
404
|
+
- lib/code_healer/presentation_logger.rb
|
404
405
|
- lib/code_healer/pull_request_creator.rb
|
405
406
|
- lib/code_healer/routes.rb
|
406
407
|
- lib/code_healer/services/metrics_collector.rb
|