code_healer 0.1.3 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcc597b56e69d320ba09b31cf5841c188a7b9599275c0ab5d7a8f4a352fc4142
4
- data.tar.gz: c20f8a3e50018e4ecc4c5d7b42c77c68522dfaf97435eb7dc99a8e83d7f10b89
3
+ metadata.gz: 053ae42e7bf5fe56e20fefb00f6a5474bf2e4e74622738f81b45073bfe735da3
4
+ data.tar.gz: e3349ed04f2cb27fec077f0003f5bde4b63b9a9a220158bbfb32b864ea1af75b
5
5
  SHA512:
6
- metadata.gz: d7c683615b16fabf415d946c5b37b0bbaa13ef491b50d7a803d95688005bc406a07dfa333df4d9808631d68bb4073257577ec5d6f15bcce38379a3b720ef62c5
7
- data.tar.gz: 79f822c7bea5d3b3d59a80201454bd7191b383aa4bb6ae56954cc0e32f42b35f5cbdc4da2ce2f3939ec3a4c00b1c71a4dd23dae654745fe29a6cf2c9a92668ec
6
+ metadata.gz: 2bac061a4947215ba717645dccf034ebf1e130eb98f58861d4f88ad8249d17cd83380853b965a1b7f539a5c6af9bbeefd795eb45b521023f447ac99cc222f8e1
7
+ data.tar.gz: da855be2022f1f6e1cd67dd48ea8a42985444b20895560fd34fe506734d2837fde99a374411c07f10622657b2acd5fc4ed329a5f04990aea6b30575ef0f3b5c2
data/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.5] - 2025-01-14
9
+
10
+ ### Fixed
11
+ - **Duplicate HealingJob class definition** that was preventing isolated healing workspace system from working
12
+ - **Class loading conflict** between old and new healing logic
13
+ - **Isolated healing workspace system** now properly activated
14
+
15
+ ## [0.1.4] - 2025-01-14
16
+
17
+ ### Added
18
+ - **Comprehensive logging** for isolated healing workspace system
19
+ - **Detailed workspace creation logs** showing each step of the process
20
+ - **Clone operation logging** with success/failure status
21
+ - **Fix application logging** in isolated environment
22
+ - **Workspace cleanup logging** for debugging
23
+
24
+ ### Fixed
25
+ - **Workspace configuration reading** to handle both string and symbol keys
26
+ - **Branch name sanitization** to prevent invalid Git branch names
27
+
8
28
  ## [0.1.3] - 2025-01-14
9
29
 
10
30
  ### Added
@@ -99,6 +99,27 @@ module CodeHealer
99
99
  def git_settings
100
100
  config['git'] || {}
101
101
  end
102
+
103
+ # Code Heal Directory Configuration
104
+ def code_heal_directory_config
105
+ config['code_heal_directory'] || {}
106
+ end
107
+
108
+ def code_heal_directory_path
109
+ code_heal_directory_config['path'] || '/tmp/code_healer_workspaces'
110
+ end
111
+
112
+ def auto_cleanup_workspaces?
113
+ code_heal_directory_config['auto_cleanup'] != false
114
+ end
115
+
116
+ def workspace_cleanup_after_hours
117
+ code_heal_directory_config['cleanup_after_hours'] || 24
118
+ end
119
+
120
+ def max_workspaces
121
+ code_heal_directory_config['max_workspaces'] || 10
122
+ end
102
123
 
103
124
  def pull_request_settings
104
125
  config['pull_request'] || {}
@@ -267,6 +288,13 @@ module CodeHealer
267
288
  'safety' => {
268
289
  'backup_before_evolution' => true,
269
290
  'rollback_on_syntax_error' => true
291
+ },
292
+ 'code_heal_directory' => {
293
+ 'path' => '/tmp/code_healer_workspaces',
294
+ 'auto_cleanup' => true,
295
+ 'cleanup_after_hours' => 24,
296
+ 'max_workspaces' => 10,
297
+ 'clone_strategy' => 'branch'
270
298
  }
271
299
  }
272
300
  end
@@ -9,18 +9,39 @@ class EvolutionJob
9
9
  # Reconstruct the error object
10
10
  error = reconstruct_error(error_data)
11
11
 
12
- # Determine evolution strategy
13
- evolution_method = CodeHealer::ConfigManager.evolution_method
12
+ # Create isolated healing workspace
13
+ workspace_path = create_healing_workspace(class_name, method_name)
14
14
 
15
- case evolution_method
16
- when 'claude_code_terminal'
17
- handle_claude_code_evolution(error, class_name, method_name, file_path)
18
- when 'api'
19
- handle_api_evolution(error, class_name, method_name, file_path)
20
- when 'hybrid'
21
- handle_hybrid_evolution(error, class_name, method_name, file_path)
22
- else
23
- puts "❌ Unknown evolution method: #{evolution_method}"
15
+ begin
16
+ # Apply fixes in isolated environment
17
+ success = apply_fixes_in_workspace(workspace_path, error, class_name, method_name)
18
+
19
+ if success
20
+ # Test fixes in isolated environment
21
+ test_success = CodeHealer::HealingWorkspaceManager.test_fixes_in_workspace(workspace_path)
22
+
23
+ if test_success
24
+ # Merge back to main repo
25
+ healing_branch = CodeHealer::HealingWorkspaceManager.merge_fixes_back(
26
+ Rails.root.to_s,
27
+ workspace_path,
28
+ CodeHealer::ConfigManager.git_settings['pr_target_branch'] || 'main'
29
+ )
30
+
31
+ if healing_branch
32
+ puts "✅ Fixes applied, tested, and merged successfully! Branch: #{healing_branch}"
33
+ else
34
+ puts "⚠️ Fixes applied and tested, but merge failed"
35
+ end
36
+ else
37
+ puts "⚠️ Fixes applied but failed tests, not merging back"
38
+ end
39
+ else
40
+ puts "❌ Failed to apply fixes in workspace"
41
+ end
42
+ ensure
43
+ # Clean up workspace
44
+ cleanup_workspace(workspace_path)
24
45
  end
25
46
 
26
47
  puts "✅ Evolution Job Completed: #{class_name}##{method_name}"
@@ -32,35 +53,59 @@ class EvolutionJob
32
53
 
33
54
  private
34
55
 
35
- def reconstruct_error(error_data)
36
- # Reconstruct the error object from serialized data
37
- error_class = Object.const_get(error_data['class'])
38
- error = error_class.new(error_data['message'])
56
+ def create_healing_workspace(class_name, method_name)
57
+ puts "🏥 Creating isolated healing workspace for #{class_name}##{method_name}"
39
58
 
40
- # Restore backtrace if available
41
- if error_data['backtrace']
42
- error.set_backtrace(error_data['backtrace'])
43
- end
59
+ # Create unique workspace
60
+ workspace_path = CodeHealer::HealingWorkspaceManager.create_healing_workspace(
61
+ Rails.root.to_s,
62
+ nil # Use current branch
63
+ )
44
64
 
45
- error
65
+ puts "✅ Healing workspace created: #{workspace_path}"
66
+ workspace_path
46
67
  end
47
-
48
- def handle_claude_code_evolution(error, class_name, method_name, file_path)
49
- puts "🤖 Using Claude Code Terminal for evolution..."
68
+
69
+ def apply_fixes_in_workspace(workspace_path, error, class_name, method_name)
70
+ puts "🔧 Applying fixes in isolated workspace"
50
71
 
51
- # Use the existing Claude Code evolution handler (it's a class method)
52
- success = CodeHealer::ClaudeCodeEvolutionHandler.handle_error_with_claude_code(error, class_name, method_name, file_path)
72
+ # Determine evolution strategy
73
+ evolution_method = CodeHealer::ConfigManager.evolution_method
53
74
 
54
- if success
55
- puts "✅ Claude Code evolution completed successfully!"
75
+ case evolution_method
76
+ when 'claude_code_terminal'
77
+ handle_claude_code_evolution_in_workspace(workspace_path, error, class_name, method_name)
78
+ when 'api'
79
+ handle_api_evolution_in_workspace(workspace_path, error, class_name, method_name)
80
+ when 'hybrid'
81
+ handle_hybrid_evolution_in_workspace(workspace_path, error, class_name, method_name)
56
82
  else
57
- puts "❌ Claude Code evolution failed"
58
- raise "Claude Code evolution failed for #{class_name}##{method_name}"
83
+ puts "❌ Unknown evolution method: #{evolution_method}"
84
+ false
59
85
  end
60
86
  end
61
-
62
- def handle_api_evolution(error, class_name, method_name, file_path)
63
- puts "🌐 Using OpenAI API for evolution..."
87
+
88
+ def handle_claude_code_evolution_in_workspace(workspace_path, error, class_name, method_name)
89
+ puts "🤖 Using Claude Code Terminal for evolution in workspace..."
90
+
91
+ # Change to workspace directory for Claude Code operations
92
+ Dir.chdir(workspace_path) do
93
+ success = CodeHealer::ClaudeCodeEvolutionHandler.handle_error_with_claude_code(
94
+ error, class_name, method_name, nil # file_path not needed in workspace
95
+ )
96
+
97
+ if success
98
+ puts "✅ Claude Code evolution completed successfully in workspace!"
99
+ true
100
+ else
101
+ puts "❌ Claude Code evolution failed in workspace"
102
+ false
103
+ end
104
+ end
105
+ end
106
+
107
+ def handle_api_evolution_in_workspace(workspace_path, error, class_name, method_name)
108
+ puts "🌐 Using OpenAI API for evolution in workspace..."
64
109
 
65
110
  # Load business context for API evolution
66
111
  business_context = CodeHealer::BusinessContextManager.get_context_for_error(
@@ -69,31 +114,56 @@ class EvolutionJob
69
114
 
70
115
  puts "📋 Business context loaded for API evolution"
71
116
 
72
- # Use the existing MCP evolution handler with business context
73
- success = CodeHealer::SimpleEvolution.handle_error_with_mcp_intelligence(
74
- error, class_name, method_name, file_path, business_context
75
- )
76
-
77
- if success
78
- puts "✅ API evolution completed successfully!"
79
- else
80
- puts "❌ API evolution failed"
81
- raise "API evolution failed for #{class_name}##{method_name}"
117
+ # Change to workspace directory for API operations
118
+ Dir.chdir(workspace_path) do
119
+ success = CodeHealer::SimpleEvolution.handle_error_with_mcp_intelligence(
120
+ error, class_name, method_name, nil, business_context # file_path not needed in workspace
121
+ )
122
+
123
+ if success
124
+ puts "✅ API evolution completed successfully in workspace!"
125
+ true
126
+ else
127
+ puts "❌ API evolution failed in workspace"
128
+ false
129
+ end
82
130
  end
83
131
  end
84
-
85
- def handle_hybrid_evolution(error, class_name, method_name, file_path)
86
- puts "🔄 Using Hybrid approach for evolution..."
132
+
133
+ def handle_hybrid_evolution_in_workspace(workspace_path, error, class_name, method_name)
134
+ puts "🔄 Using Hybrid approach for evolution in workspace..."
87
135
 
88
136
  begin
89
137
  # Try Claude Code first
90
- success = handle_claude_code_evolution(error, class_name, method_name, file_path)
91
- return if success
138
+ success = handle_claude_code_evolution_in_workspace(workspace_path, error, class_name, method_name)
139
+ return success if success
92
140
  rescue => e
93
141
  puts "⚠️ Claude Code failed, falling back to API: #{e.message}"
94
142
  end
95
143
 
96
144
  # Fallback to API
97
- handle_api_evolution(error, class_name, method_name, file_path)
145
+ handle_api_evolution_in_workspace(workspace_path, error, class_name, method_name)
146
+ end
147
+
148
+ def cleanup_workspace(workspace_path)
149
+ return unless workspace_path && Dir.exist?(workspace_path)
150
+
151
+ puts "🧹 Cleaning up healing workspace: #{workspace_path}"
152
+ CodeHealer::HealingWorkspaceManager.cleanup_workspace(workspace_path)
153
+ end
154
+
155
+ def reconstruct_error(error_data)
156
+ # Reconstruct the error object from serialized data
157
+ error_class = Object.const_get(error_data['class'])
158
+ error = error_class.new(error_data['message'])
159
+
160
+ # Restore backtrace if available
161
+ if error_data['backtrace']
162
+ error.set_backtrace(error_data['backtrace'])
163
+ end
164
+
165
+ error
98
166
  end
167
+
168
+
99
169
  end
@@ -1,167 +1,187 @@
1
- # frozen_string_literal: true
1
+ require 'sidekiq'
2
2
 
3
3
  module CodeHealer
4
4
  class HealingJob
5
5
  include Sidekiq::Job
6
-
6
+
7
7
  sidekiq_options retry: 3, backtrace: true, queue: 'evolution'
8
-
9
- def perform(error_type, error_message, class_name, method_name, evolution_method = 'api', backtrace = nil)
10
- puts "🚀 Evolution Job Started: #{class_name}##{method_name}"
8
+
9
+ def perform(*args)
10
+ puts "🚀 [HEALING_JOB] Starting job with args: #{args.inspect}"
11
11
 
12
- # Reconstruct the error object with backtrace
13
- error = reconstruct_error(error_type, error_message, backtrace)
12
+ # Support both legacy and new invocation styles
13
+ error, class_name, method_name, evolution_method, backtrace = parse_args(args)
14
14
 
15
- # Determine evolution strategy
16
- case evolution_method
17
- when 'claude_code_terminal'
18
- handle_claude_code_evolution(error, class_name, method_name)
19
- when 'api'
20
- handle_api_evolution(error, class_name, method_name)
21
- when 'hybrid'
22
- handle_hybrid_evolution(error, class_name, method_name)
23
- else
24
- puts "⚠️ Unknown evolution method: #{evolution_method}"
15
+ puts "🚀 [HEALING_JOB] Parsed args - Error: #{error.class}, Class: #{class_name}, Method: #{method_name}, Evolution: #{evolution_method}"
16
+ puts "🚀 [HEALING_JOB] Backtrace length: #{backtrace&.length || 0}"
17
+
18
+ puts "🚀 Evolution Job Started: #{class_name}##{method_name}"
19
+
20
+ puts "🏥 [HEALING_JOB] About to create isolated healing workspace..."
21
+ # Create isolated healing workspace
22
+ workspace_path = create_healing_workspace(class_name, method_name)
23
+ puts "🏥 [HEALING_JOB] Workspace created: #{workspace_path}"
24
+
25
+ begin
26
+ puts "🔧 [HEALING_JOB] About to apply fixes in isolated environment..."
27
+ # Apply fixes in isolated environment
28
+ success = apply_fixes_in_workspace(workspace_path, error, class_name, method_name, evolution_method)
29
+
30
+ if success
31
+ # Test fixes in isolated environment
32
+ test_success = CodeHealer::HealingWorkspaceManager.test_fixes_in_workspace(workspace_path)
33
+
34
+ if test_success
35
+ # Merge back to main repo
36
+ healing_branch = CodeHealer::HealingWorkspaceManager.merge_fixes_back(
37
+ Rails.root.to_s,
38
+ workspace_path,
39
+ CodeHealer::ConfigManager.git_settings['pr_target_branch'] || 'main'
40
+ )
41
+
42
+ if healing_branch
43
+ puts "✅ Fixes applied, tested, and merged successfully! Branch: #{healing_branch}"
44
+ else
45
+ puts "⚠️ Fixes applied and tested, but merge failed"
46
+ end
47
+ else
48
+ puts "⚠️ Fixes applied but failed tests, not merging back"
49
+ end
50
+ else
51
+ puts "❌ Failed to apply fixes in workspace"
52
+ end
53
+ ensure
54
+ # Clean up workspace
55
+ cleanup_workspace(workspace_path)
25
56
  end
57
+
58
+ puts "✅ Evolution Job Completed: #{class_name}##{method_name}"
59
+ rescue => e
60
+ puts "❌ Evolution Job Failed: #{e.message}"
61
+ puts "📍 Backtrace: #{e.backtrace.first(5)}"
62
+ raise e # Re-raise to trigger Sidekiq retry
26
63
  end
27
-
64
+
28
65
  private
29
-
30
- def handle_claude_code_evolution(error, class_name, method_name)
31
- puts "🤖 Using Claude Code Terminal for evolution..."
32
-
33
- if defined?(CodeHealer::ClaudeCodeEvolutionHandler)
34
- # For Claude Code: pass the full backtrace instead of file path
35
- # Claude can analyze the backtrace and find files itself
36
- puts "📋 Sending full backtrace to Claude Code for intelligent analysis"
37
- puts "🔍 Backtrace length: #{error.backtrace&.length || 0} lines"
38
-
39
- CodeHealer::ClaudeCodeEvolutionHandler.handle_error_with_claude_code(
40
- error, class_name, method_name, nil
41
- )
66
+
67
+ def parse_args(args)
68
+ # Formats supported:
69
+ # 1) [error_class, error_message, class_name, method_name, evolution_method, backtrace]
70
+ # 2) [error_data_hash, class_name, method_name, file_path]
71
+ if args.length >= 6 && args[0].is_a?(String)
72
+ error_class, error_message, class_name, method_name, evolution_method, backtrace = args
73
+ error = reconstruct_error({ 'class' => error_class, 'message' => error_message, 'backtrace' => backtrace })
74
+ [error, class_name, method_name, evolution_method, backtrace]
75
+ elsif args.length == 4 && args[0].is_a?(Hash)
76
+ error_data, class_name, method_name, _file_path = args
77
+ error = reconstruct_error(error_data)
78
+ evolution_method = CodeHealer::ConfigManager.evolution_method
79
+ [error, class_name, method_name, evolution_method, error.backtrace]
42
80
  else
43
- puts "⚠️ ClaudeCodeEvolutionHandler not available"
81
+ raise ArgumentError, "Unsupported HealingJob arguments: #{args.inspect}"
44
82
  end
45
83
  end
46
-
47
- def handle_api_evolution(error, class_name, method_name)
48
- puts "🌐 Using OpenAI API for evolution..."
49
-
50
- # For API: extract file path since API doesn't have codebase access
51
- file_path = extract_file_path_from_backtrace(error.backtrace)
52
- puts "📁 File path for API: #{file_path || 'Not found'}"
53
-
54
- # Load business context
55
- if defined?(CodeHealer::BusinessContextManager)
56
- business_context = CodeHealer::BusinessContextManager.get_context_for_error(
57
- error, class_name, method_name
58
- )
59
- puts "📋 Business context loaded for API evolution"
60
- else
61
- business_context = {}
62
- puts "⚠️ BusinessContextManager not available"
63
- end
64
-
65
- # Use SimpleHealer for API-based evolution
66
- if defined?(CodeHealer::SimpleHealer)
67
- CodeHealer::SimpleHealer.handle_error_with_mcp_intelligence(
68
- error, class_name, method_name, file_path, business_context
69
- )
84
+
85
+ def create_healing_workspace(class_name, method_name)
86
+ puts "🏥 Creating isolated healing workspace for #{class_name}##{method_name}"
87
+
88
+ # Create unique workspace
89
+ workspace_path = CodeHealer::HealingWorkspaceManager.create_healing_workspace(
90
+ Rails.root.to_s,
91
+ nil # Use current branch
92
+ )
93
+
94
+ puts "✅ Healing workspace created: #{workspace_path}"
95
+ workspace_path
96
+ end
97
+
98
+ def apply_fixes_in_workspace(workspace_path, error, class_name, method_name, evolution_method)
99
+ puts "🔧 Applying fixes in isolated workspace"
100
+
101
+ case evolution_method
102
+ when 'claude_code_terminal'
103
+ handle_claude_code_evolution_in_workspace(workspace_path, error, class_name, method_name)
104
+ when 'api'
105
+ handle_api_evolution_in_workspace(workspace_path, error, class_name, method_name)
106
+ when 'hybrid'
107
+ begin
108
+ success = handle_claude_code_evolution_in_workspace(workspace_path, error, class_name, method_name)
109
+ return true if success
110
+ rescue => e
111
+ puts "⚠️ Claude Code failed, falling back to API: #{e.message}"
112
+ end
113
+ handle_api_evolution_in_workspace(workspace_path, error, class_name, method_name)
70
114
  else
71
- puts "⚠️ SimpleHealer not available"
115
+ puts " Unknown evolution method: #{evolution_method}"
116
+ false
72
117
  end
73
118
  end
74
-
75
- def handle_hybrid_evolution(error, class_name, method_name)
76
- puts "🔄 Using hybrid evolution strategy..."
77
-
78
- # Try Claude Code first, fallback to API
79
- begin
80
- handle_claude_code_evolution(error, class_name, method_name)
81
- rescue => e
82
- puts "⚠️ Claude Code evolution failed: #{e.message}"
83
- puts "🔄 Falling back to API evolution..."
84
- handle_api_evolution(error, class_name, method_name)
119
+
120
+ def handle_claude_code_evolution_in_workspace(workspace_path, error, class_name, method_name)
121
+ puts "🤖 Using Claude Code Terminal for evolution in workspace..."
122
+
123
+ # Change to workspace directory for Claude Code operations
124
+ Dir.chdir(workspace_path) do
125
+ success = CodeHealer::ClaudeCodeEvolutionHandler.handle_error_with_claude_code(
126
+ error, class_name, method_name, nil # file_path not needed in workspace
127
+ )
128
+
129
+ if success
130
+ puts "✅ Claude Code evolution completed successfully in workspace!"
131
+ true
132
+ else
133
+ puts "❌ Claude Code evolution failed in workspace"
134
+ false
135
+ end
85
136
  end
86
137
  end
87
-
88
- def extract_file_path_from_backtrace(backtrace)
89
- return nil unless backtrace
90
-
91
- core_methods = %w[* + - / % ** == != < > <= >= <=> === =~ !~ & | ^ ~ << >> [] []= `]
92
- app_file_line = backtrace.find { |line| line.include?('/app/') }
93
-
94
- return nil unless app_file_line
95
-
96
- if app_file_line =~ /(.+):(\d+):in `(.+)'/
97
- file_path = $1
98
- method_name = $3
99
-
100
- # Handle Ruby operators by looking deeper in the stack
101
- if core_methods.include?(method_name)
102
- deeper_app_line = backtrace.find do |line|
103
- line.include?('/app/') &&
104
- line =~ /in `(.+)'/ &&
105
- !core_methods.include?($1) &&
106
- !$1.include?('block in') &&
107
- !$1.include?('each') &&
108
- !$1.include?('map') &&
109
- !$1.include?('reduce')
110
- end
111
-
112
- if deeper_app_line && deeper_app_line =~ /(.+):(\d+):in `(.+)'/
113
- file_path = $1
114
- method_name = $3
115
- end
116
- end
117
-
118
- # Handle iterator methods and blocks
119
- if method_name && (
120
- method_name.include?('block in') ||
121
- method_name.include?('each') ||
122
- method_name.include?('map') ||
123
- method_name.include?('reduce') ||
124
- method_name.include?('sum')
138
+
139
+ def handle_api_evolution_in_workspace(workspace_path, error, class_name, method_name)
140
+ puts "🌐 Using OpenAI API for evolution in workspace..."
141
+
142
+ # Load business context for API evolution
143
+ business_context = CodeHealer::BusinessContextManager.get_context_for_error(
144
+ error, class_name, method_name
145
+ )
146
+
147
+ puts "📋 Business context loaded for API evolution"
148
+
149
+ # Change to workspace directory for API operations
150
+ Dir.chdir(workspace_path) do
151
+ success = CodeHealer::SimpleEvolution.handle_error_with_mcp_intelligence(
152
+ error, class_name, method_name, nil, business_context # file_path not needed in workspace
125
153
  )
126
- containing_line = backtrace.find do |line|
127
- line.include?('/app/') &&
128
- line =~ /in `(.+)'/ &&
129
- !$1.include?('block in') &&
130
- !$1.include?('each') &&
131
- !$1.include?('map') &&
132
- !$1.include?('reduce') &&
133
- !$1.include?('sum')
134
- end
135
-
136
- if containing_line && containing_line =~ /(.+):(\d+):in `(.+)'/
137
- file_path = $1
138
- method_name = $3
139
- end
154
+
155
+ if success
156
+ puts "✅ API evolution completed successfully in workspace!"
157
+ true
158
+ else
159
+ puts "❌ API evolution failed in workspace"
160
+ false
140
161
  end
141
-
142
- return file_path if file_path
143
162
  end
144
-
145
- nil
146
163
  end
147
-
148
- def reconstruct_error(error_type, error_message, backtrace = nil)
149
- # Create a simple error object with the type and message
150
- error_class = error_type.constantize rescue StandardError
151
- error = error_class.new(error_message)
152
-
153
- # Set the backtrace if provided
154
- if backtrace
155
- error.set_backtrace(backtrace)
156
- puts "📋 Backtrace restored: #{backtrace.length} lines"
157
- else
158
- puts "⚠️ No backtrace provided"
164
+
165
+ def cleanup_workspace(workspace_path)
166
+ return unless workspace_path && Dir.exist?(workspace_path)
167
+
168
+ puts "🧹 Cleaning up healing workspace: #{workspace_path}"
169
+ CodeHealer::HealingWorkspaceManager.cleanup_workspace(workspace_path)
170
+ end
171
+
172
+ def reconstruct_error(error_data)
173
+ # Reconstruct the error object from serialized data
174
+ error_class = Object.const_get(error_data['class'])
175
+ error = error_class.new(error_data['message'])
176
+
177
+ # Restore backtrace if available
178
+ if error_data['backtrace']
179
+ error.set_backtrace(error_data['backtrace'])
159
180
  end
160
-
181
+
161
182
  error
162
- rescue
163
- # Fallback to generic error
164
- StandardError.new(error_message)
165
183
  end
166
184
  end
167
185
  end
186
+
187
+ # This duplicate class has been removed to fix the isolated healing workspace system
@@ -0,0 +1,296 @@
1
+ require 'fileutils'
2
+ require 'securerandom'
3
+
4
+ module CodeHealer
5
+ # Manages isolated healing workspaces for safe code evolution
6
+ class HealingWorkspaceManager
7
+ class << self
8
+ def create_healing_workspace(repo_path, branch_name = nil)
9
+ puts "🏥 [WORKSPACE] Starting workspace creation..."
10
+ puts "🏥 [WORKSPACE] Repo path: #{repo_path}"
11
+ puts "🏥 [WORKSPACE] Branch name: #{branch_name || 'current'}"
12
+
13
+ config = CodeHealer::ConfigManager.code_heal_directory_config
14
+ puts "🏥 [WORKSPACE] Raw config: #{config.inspect}"
15
+
16
+ base_path = config['path'] || config[:path] || '/tmp/code_healer_workspaces'
17
+ puts "🏥 [WORKSPACE] Base heal dir: #{base_path}"
18
+
19
+ # Create unique workspace directory
20
+ workspace_id = "healing_#{Time.now.to_i}_#{SecureRandom.hex(4)}"
21
+ workspace_path = File.join(base_path, workspace_id)
22
+
23
+ puts "🏥 [WORKSPACE] Workspace ID: #{workspace_id}"
24
+ puts "🏥 [WORKSPACE] Full workspace path: #{workspace_path}"
25
+
26
+ begin
27
+ puts "🏥 [WORKSPACE] Creating base directory..."
28
+ # Ensure code heal directory exists
29
+ FileUtils.mkdir_p(base_path)
30
+ puts "🏥 [WORKSPACE] Base directory created/verified: #{base_path}"
31
+
32
+ # Clone current branch to workspace
33
+ strategy = clone_strategy
34
+ puts "🏥 [WORKSPACE] Clone strategy: #{strategy}"
35
+
36
+ if strategy == "branch"
37
+ puts "🏥 [WORKSPACE] Using branch-only cloning..."
38
+ clone_current_branch(repo_path, workspace_path, branch_name)
39
+ else
40
+ puts "🏥 [WORKSPACE] Using full repo cloning..."
41
+ clone_full_repo(repo_path, workspace_path, branch_name)
42
+ end
43
+
44
+ puts "🏥 [WORKSPACE] Workspace creation completed successfully!"
45
+ puts "🏥 [WORKSPACE] Final workspace path: #{workspace_path}"
46
+ puts "🏥 [WORKSPACE] Workspace contents: #{Dir.entries(workspace_path).join(', ')}"
47
+ workspace_path
48
+ rescue => e
49
+ puts "❌ Failed to create healing workspace: #{e.message}"
50
+ cleanup_workspace(workspace_path) if Dir.exist?(workspace_path)
51
+ raise e
52
+ end
53
+ end
54
+
55
+ def apply_fixes_in_workspace(workspace_path, fixes, class_name, method_name)
56
+ puts "🔧 [WORKSPACE] Starting fix application..."
57
+ puts "🔧 [WORKSPACE] Workspace: #{workspace_path}"
58
+ puts "🔧 [WORKSPACE] Class: #{class_name}, Method: #{method_name}"
59
+ puts "🔧 [WORKSPACE] Fixes to apply: #{fixes.inspect}"
60
+
61
+ begin
62
+ puts "🔧 [WORKSPACE] Processing #{fixes.length} fixes..."
63
+ # Apply each fix to the workspace
64
+ fixes.each_with_index do |fix, index|
65
+ puts "🔧 [WORKSPACE] Processing fix #{index + 1}: #{fix.inspect}"
66
+ file_path = File.join(workspace_path, fix[:file_path])
67
+ puts "🔧 [WORKSPACE] Target file: #{file_path}"
68
+ puts "🔧 [WORKSPACE] File exists: #{File.exist?(file_path)}"
69
+
70
+ next unless File.exist?(file_path)
71
+
72
+ puts "🔧 [WORKSPACE] Creating backup..."
73
+ # Backup original file
74
+ backup_file(file_path)
75
+
76
+ puts "🔧 [WORKSPACE] Applying fix to file..."
77
+ # Apply the fix
78
+ apply_fix_to_file(file_path, fix[:new_code], class_name, method_name)
79
+ end
80
+
81
+ puts "✅ Fixes applied successfully in workspace"
82
+ true
83
+ rescue => e
84
+ puts "❌ Failed to apply fixes in workspace: #{e.message}"
85
+ false
86
+ end
87
+ end
88
+
89
+ def test_fixes_in_workspace(workspace_path)
90
+ config = CodeHealer::ConfigManager.code_heal_directory_config
91
+
92
+ puts "🧪 Testing fixes in workspace: #{workspace_path}"
93
+
94
+ begin
95
+ # Change to workspace directory
96
+ Dir.chdir(workspace_path) do
97
+ # Run basic syntax check
98
+ syntax_check = system("ruby -c #{find_ruby_files.join(' ')} 2>/dev/null")
99
+ return false unless syntax_check
100
+
101
+ # Run tests if available
102
+ if File.exist?('Gemfile')
103
+ bundle_check = system("bundle check >/dev/null 2>&1")
104
+ return false unless bundle_check
105
+
106
+ # Run tests if RSpec is available
107
+ if File.exist?('spec') || File.exist?('test')
108
+ test_result = system("bundle exec rspec --dry-run >/dev/null 2>&1") ||
109
+ system("bundle exec rake test:prepare >/dev/null 2>&1")
110
+ puts "🧪 Test preparation: #{test_result ? '✅' : '⚠️'}"
111
+ end
112
+ end
113
+
114
+ puts "✅ Workspace validation passed"
115
+ true
116
+ end
117
+ rescue => e
118
+ puts "❌ Workspace validation failed: #{e.message}"
119
+ false
120
+ end
121
+ end
122
+
123
+ def merge_fixes_back(repo_path, workspace_path, branch_name)
124
+ puts "🔄 Merging fixes back to main repository"
125
+
126
+ begin
127
+ # Create healing branch in main repo
128
+ Dir.chdir(repo_path) do
129
+ # Ensure we're on the target branch
130
+ system("git checkout #{branch_name}")
131
+ system("git pull origin #{branch_name}")
132
+
133
+ # Create healing branch
134
+ healing_branch = "code-healer-fix-#{Time.now.to_i}"
135
+ system("git checkout -b #{healing_branch}")
136
+
137
+ # Copy fixed files from workspace
138
+ copy_fixed_files(workspace_path, repo_path)
139
+
140
+ # Commit changes
141
+ system("git add .")
142
+ commit_message = "Fix applied by CodeHealer: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
143
+ system("git commit -m '#{commit_message}'")
144
+
145
+ # Push branch
146
+ system("git push origin #{healing_branch}")
147
+
148
+ puts "✅ Healing branch created: #{healing_branch}"
149
+ healing_branch
150
+ end
151
+ rescue => e
152
+ puts "❌ Failed to merge fixes back: #{e.message}"
153
+ nil
154
+ end
155
+ end
156
+
157
+ def cleanup_workspace(workspace_path)
158
+ puts "🧹 [WORKSPACE] Starting workspace cleanup..."
159
+ puts "🧹 [WORKSPACE] Target: #{workspace_path}"
160
+ puts "🧹 [WORKSPACE] Exists: #{Dir.exist?(workspace_path)}"
161
+
162
+ return unless Dir.exist?(workspace_path)
163
+
164
+ puts "🧹 [WORKSPACE] Removing workspace directory..."
165
+ FileUtils.rm_rf(workspace_path)
166
+ puts "🧹 [WORKSPACE] Workspace cleanup completed"
167
+ puts "🧹 [WORKSPACE] Directory still exists: #{Dir.exist?(workspace_path)}"
168
+ end
169
+
170
+ def cleanup_expired_workspaces
171
+ config = CodeHealer::ConfigManager.code_heal_directory_config
172
+ auto_cleanup = config['auto_cleanup']
173
+ auto_cleanup = config[:auto_cleanup] if auto_cleanup.nil?
174
+ return unless auto_cleanup
175
+
176
+ puts "🧹 Cleaning up expired healing workspaces"
177
+
178
+ base_path = config['path'] || config[:path] || '/tmp/code_healer_workspaces'
179
+ Dir.glob(File.join(base_path, "healing_*")).each do |workspace_path|
180
+ next unless Dir.exist?(workspace_path)
181
+
182
+ # Check if workspace is expired
183
+ hours = config['cleanup_after_hours'] || config[:cleanup_after_hours]
184
+ hours = hours.to_i if hours
185
+ if workspace_expired?(workspace_path, hours)
186
+ cleanup_workspace(workspace_path)
187
+ end
188
+ end
189
+ end
190
+
191
+ private
192
+
193
+ def clone_strategy
194
+ cfg = CodeHealer::ConfigManager.code_heal_directory_config
195
+ cfg['clone_strategy'] || cfg[:clone_strategy] || "branch"
196
+ end
197
+
198
+ def clone_current_branch(repo_path, workspace_path, branch_name)
199
+ puts "🌿 [WORKSPACE] Starting branch cloning..."
200
+ Dir.chdir(repo_path) do
201
+ current_branch = branch_name || `git branch --show-current`.strip
202
+ puts "🌿 [WORKSPACE] Current branch: #{current_branch}"
203
+ puts "🌿 [WORKSPACE] Executing: git clone --single-branch --branch #{current_branch} #{repo_path} #{workspace_path}"
204
+
205
+ # Clone only the current branch
206
+ result = system("git clone --single-branch --branch #{current_branch} #{repo_path} #{workspace_path}")
207
+ puts "🌿 [WORKSPACE] Clone result: #{result ? 'SUCCESS' : 'FAILED'}"
208
+
209
+ if result
210
+ puts "🌿 [WORKSPACE] Removing .git to avoid conflicts..."
211
+ # Remove .git to avoid conflicts
212
+ FileUtils.rm_rf(File.join(workspace_path, '.git'))
213
+ puts "🌿 [WORKSPACE] .git removed successfully"
214
+ else
215
+ puts "🌿 [WORKSPACE] Clone failed, checking workspace..."
216
+ puts "🌿 [WORKSPACE] Workspace exists: #{Dir.exist?(workspace_path)}"
217
+ puts "🌿 [WORKSPACE] Workspace contents: #{Dir.exist?(workspace_path) ? Dir.entries(workspace_path).join(', ') : 'N/A'}"
218
+ end
219
+ end
220
+ end
221
+
222
+ def clone_full_repo(repo_path, workspace_path, branch_name)
223
+ puts "🌿 [WORKSPACE] Starting full repo cloning..."
224
+ Dir.chdir(repo_path) do
225
+ current_branch = branch_name || `git branch --show-current`.strip
226
+ puts "🌿 [WORKSPACE] Target branch: #{current_branch}"
227
+ puts "🌿 [WORKSPACE] Executing: git clone #{repo_path} #{workspace_path}"
228
+
229
+ # Clone full repo
230
+ result = system("git clone #{repo_path} #{workspace_path}")
231
+ puts "🌿 [WORKSPACE] Clone result: #{result ? 'SUCCESS' : 'FAILED'}"
232
+
233
+ if result
234
+ puts "🌿 [WORKSPACE] Switching to branch: #{current_branch}"
235
+ # Switch to specific branch
236
+ Dir.chdir(workspace_path) do
237
+ checkout_result = system("git checkout #{current_branch}")
238
+ puts "🌿 [WORKSPACE] Checkout result: #{checkout_result ? 'SUCCESS' : 'FAILED'}"
239
+ end
240
+ else
241
+ puts "🌿 [WORKSPACE] Full repo clone failed"
242
+ end
243
+ end
244
+ end
245
+
246
+ def backup_file(file_path)
247
+ backup_path = "#{file_path}.code_healer_backup"
248
+ FileUtils.cp(file_path, backup_path)
249
+ end
250
+
251
+ def apply_fix_to_file(file_path, new_code, class_name, method_name)
252
+ content = File.read(file_path)
253
+
254
+ # Find and replace the method
255
+ method_pattern = /def\s+#{Regexp.escape(method_name)}\s*\([^)]*\)(.*?)end/m
256
+ if content.match(method_pattern)
257
+ content.gsub!(method_pattern, new_code)
258
+ File.write(file_path, content)
259
+ puts "✅ Applied fix to #{File.basename(file_path)}##{method_name}"
260
+ else
261
+ puts "⚠️ Could not find method #{method_name} in #{File.basename(file_path)}"
262
+ end
263
+ end
264
+
265
+ def find_ruby_files
266
+ Dir.glob("**/*.rb")
267
+ end
268
+
269
+ def copy_fixed_files(workspace_path, repo_path)
270
+ # Copy all Ruby files from workspace to repo
271
+ Dir.glob(File.join(workspace_path, "**/*.rb")).each do |workspace_file|
272
+ relative_path = workspace_file.sub(workspace_path + "/", "")
273
+ repo_file = File.join(repo_path, relative_path)
274
+
275
+ if File.exist?(repo_file)
276
+ FileUtils.cp(workspace_file, repo_file)
277
+ puts "📁 Copied fixed file: #{relative_path}"
278
+ end
279
+ end
280
+ end
281
+
282
+ def workspace_expired?(workspace_path, hours)
283
+ return false unless hours && hours > 0
284
+
285
+ # Extract timestamp from workspace name
286
+ if workspace_path =~ /healing_(\d+)/
287
+ timestamp = $1.to_i
288
+ age_hours = (Time.now.to_i - timestamp) / 3600
289
+ age_hours > hours
290
+ else
291
+ false
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end
@@ -153,6 +153,17 @@ puts "🌿 Git Branch Configuration:"
153
153
  branch_prefix = ask_for_input("Enter branch prefix for healing branches (default: evolve):", default: "evolve")
154
154
  pr_target_branch = ask_for_input("Enter target branch for pull requests (default: main):", default: "main")
155
155
 
156
+ # Code Heal Directory Configuration
157
+ puts
158
+ puts "🏥 Code Heal Directory Configuration:"
159
+ puts "This directory will store isolated copies of your code for safe healing."
160
+ puts "CodeHealer will clone your current branch here before making fixes."
161
+ puts
162
+
163
+ code_heal_directory = ask_for_input("Enter code heal directory path (default: /tmp/code_healer_workspaces):", default: "/tmp/code_healer_workspaces")
164
+ auto_cleanup = ask_for_yes_no("Automatically clean up healing workspaces after use?", default: true)
165
+ cleanup_after_hours = ask_for_input("Clean up workspaces after how many hours? (default: 24):", default: "24")
166
+
156
167
  # Business Context
157
168
  puts
158
169
  puts "💼 Business Context Setup:"
@@ -297,6 +308,14 @@ config_content = <<~YAML
297
308
  max_concurrent_healing: 3
298
309
  healing_timeout: 300
299
310
  retry_attempts: 3
311
+
312
+ # Code Heal Directory Configuration
313
+ code_heal_directory:
314
+ path: "#{code_heal_directory}"
315
+ auto_cleanup: #{auto_cleanup}
316
+ cleanup_after_hours: #{cleanup_after_hours}
317
+ max_workspaces: 10
318
+ clone_strategy: "branch" # Options: branch, full_repo
300
319
  YAML
301
320
 
302
321
  create_file_with_content('config/code_healer.yml', config_content, dry_run: options[:dry_run])
@@ -366,14 +385,19 @@ else
366
385
  puts "4. Start your Rails server: rails s"
367
386
  puts
368
387
  puts "🔒 Security Notes:"
369
- puts " - .env file contains your actual API keys and is ignored by git"
370
- puts " - .env.example is safe to commit and shows the required format"
371
- puts " - Never commit .env files with real secrets to version control"
372
- puts
373
- puts "⚙️ Configuration:"
374
- puts " - code_healer.yml contains comprehensive settings with sensible defaults"
375
- puts " - Customize the configuration file as needed for your project"
376
- puts " - All features are pre-configured and ready to use"
388
+ puts " - .env file contains your actual API keys and is ignored by git"
389
+ puts " - .env.example is safe to commit and shows the required format"
390
+ puts " - Never commit .env files with real secrets to version control"
391
+ puts
392
+ puts "🏥 Code Heal Directory:"
393
+ puts " - Your code will be cloned to: #{code_heal_directory}"
394
+ puts " - This ensures safe, isolated healing without affecting your running server"
395
+ puts " - Workspaces are automatically cleaned up after #{cleanup_after_hours} hours"
396
+ puts
397
+ puts "⚙️ Configuration:"
398
+ puts " - code_healer.yml contains comprehensive settings with sensible defaults"
399
+ puts " - Customize the configuration file as needed for your project"
400
+ puts " - All features are pre-configured and ready to use"
377
401
  puts
378
402
  puts "🌍 Environment Variables:"
379
403
  puts " - Add 'gem \"dotenv-rails\"' to your Gemfile for automatic .env loading"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodeHealer
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
data/lib/code_healer.rb CHANGED
@@ -21,6 +21,7 @@ autoload :BusinessContextManager, "code_healer/business_context_manager"
21
21
  autoload :ClaudeCodeEvolutionHandler, "code_healer/claude_code_evolution_handler"
22
22
  autoload :SimpleHealer, "code_healer/simple_healer"
23
23
  autoload :HealingJob, "code_healer/healing_job"
24
+ autoload :HealingWorkspaceManager, "code_healer/healing_workspace_manager"
24
25
  autoload :PullRequestCreator, "code_healer/pull_request_creator"
25
26
  autoload :McpServer, "code_healer/mcp_server"
26
27
  autoload :McpTools, "code_healer/mcp_tools"
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.3
4
+ version: 0.1.5
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-08-14 00:00:00.000000000 Z
11
+ date: 2025-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -368,6 +368,7 @@ files:
368
368
  - lib/code_healer/evolution_job.rb
369
369
  - lib/code_healer/global_handler.rb
370
370
  - lib/code_healer/healing_job.rb
371
+ - lib/code_healer/healing_workspace_manager.rb
371
372
  - lib/code_healer/mcp.rb
372
373
  - lib/code_healer/mcp_prompts.rb
373
374
  - lib/code_healer/mcp_server.rb