code_healer 0.1.22 → 0.1.24
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 +36 -0
- data/lib/code_healer/business_context_manager.rb +36 -20
- data/lib/code_healer/claude_code_evolution_handler.rb +2 -2
- data/lib/code_healer/config_manager.rb +10 -7
- data/lib/code_healer/evolution_job.rb +3 -3
- data/lib/code_healer/healing_job.rb +21 -3
- data/lib/code_healer/healing_workspace_manager.rb +384 -36
- data/lib/code_healer/setup.rb +3 -4
- data/lib/code_healer/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 704c23ee702e1f6a10a5fbbdcebe377d0523aa69d880aa40257e7351528130f3
|
4
|
+
data.tar.gz: e6989f6bbe820a4891c4b1268cde29d9231875c7415d4fbcc8ba9e8d5b72bc91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e383c0afbc418195f3de44b0763bca3d18014d198d4cf0d204cf65be5b4c0a763a25ae86faef7c90eba11cb694bf76f5ed24c1322ae3ebeab293fe00509c1d25
|
7
|
+
data.tar.gz: 694a3cad9243ece93065c849f5dba28e3316d9a678dc8b299d5b675d7125a96441a886b946f24956b0ec54052a3f6bb2e00b3cd0815b941da35094f26c7935ea
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,42 @@ 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.24] - 2025-08-31
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- **Aggressive File Filtering**: Implemented comprehensive filtering to prevent `tmp/`, `log/`, and other temporary files from being committed
|
12
|
+
- **Pre-Commit Validation**: Added workspace validation before commit to ensure no temporary files slip through
|
13
|
+
- **Mandatory MCP Usage**: Enhanced business context prompts to force Claude to use Atlassian MCP tools for Confluence/Jira integration
|
14
|
+
- **Missing Method Fix**: Added `extract_file_path_from_error` method to resolve undefined method errors
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
- **File Filtering**: Enhanced `should_skip_file?` method with aggressive patterns for temporary files at any level
|
18
|
+
- **Git Cleanup**: Comprehensive cleanup of tracked temporary files using `git rm --cached` and `find` commands
|
19
|
+
- **Business Context**: Changed from "optional" to "mandatory" MCP tool usage in prompts
|
20
|
+
- **Setup Configuration**: Fixed `setup.rb` to include `mcp__atlassian` permission in command template
|
21
|
+
|
22
|
+
### Fixed
|
23
|
+
- **Temporary File Commits**: Prevents `tmp/`, `log/`, `storage/`, `coverage/` directories from being committed
|
24
|
+
- **MCP Tool Access**: Fixed missing `mcp__atlassian` permission in setup script
|
25
|
+
- **Syntax Error**: Removed stray `y` character that caused Ruby syntax errors
|
26
|
+
- **Method Missing**: Added missing `extract_file_path_from_error` method to `HealingJob` class
|
27
|
+
|
28
|
+
## [0.1.23] - 2025-08-27
|
29
|
+
|
30
|
+
### Added
|
31
|
+
- **Persistent Isolated Workspaces**: Implemented persistent workspaces that reuse the same isolated environment instead of cloning each time
|
32
|
+
- **Smart Branch Checkout**: Workspaces now checkout to the configured target branch instead of cloning the current working branch
|
33
|
+
- **Workspace Reset**: Added ability to reset workspace to clean state without deleting the entire workspace
|
34
|
+
- **Configuration Control**: Added `persistent_workspaces` and `sticky_workspace` configuration options
|
35
|
+
|
36
|
+
### Changed
|
37
|
+
- **Workspace Strategy**: Changed from cloning new workspaces each time to reusing persistent workspaces with branch checkout
|
38
|
+
- **Performance**: Significantly faster healing operations by avoiding repeated repository cloning
|
39
|
+
- **Branch Targeting**: Workspaces now target the configured `pr_target_branch` instead of the current working branch
|
40
|
+
|
41
|
+
### Fixed
|
42
|
+
- **Branch Consistency**: Ensures fixes are always applied to the correct target branch regardless of current working branch
|
43
|
+
|
8
44
|
## [0.1.22] - 2025-08-27
|
9
45
|
|
10
46
|
### Fixed
|
@@ -181,30 +181,43 @@ module CodeHealer
|
|
181
181
|
business_context = case CodeHealer::ConfigManager.business_context_strategy
|
182
182
|
when 'confluence_only'
|
183
183
|
"## Business Context Instructions:\n" \
|
184
|
-
"
|
185
|
-
"1.
|
184
|
+
"MANDATORY: You MUST use Confluence MCP tools to search for business context before fixing any code.\n\n" \
|
185
|
+
"1. **REQUIRED - Search Confluence for business context:**\n" \
|
186
186
|
" - Search for PRDs/specs related to '#{class_name}' / '#{method_name}'.\n" \
|
187
187
|
" - Query terms: '#{class_name}', '#{method_name}', 'business rules', 'validation patterns'.\n" \
|
188
|
-
" -
|
189
|
-
"
|
188
|
+
" - Search for domain-specific business logic and requirements.\n" \
|
189
|
+
" - Look for validation rules and business constraints.\n\n" \
|
190
|
+
"2. **After finding business context:**\n" \
|
191
|
+
" - Apply ALL found business rules to your fix.\n" \
|
192
|
+
" - Ensure the fix follows the documented business requirements.\n" \
|
193
|
+
" - Validate that your solution aligns with business logic.\n\n" \
|
194
|
+
"3. **If no documentation is found:**\n" \
|
195
|
+
" - Note this explicitly in your response.\n" \
|
190
196
|
" - Proceed with standard business logic and error handling practices.\n" \
|
191
197
|
" - Use common validation patterns and best practices.\n\n" \
|
192
|
-
"
|
198
|
+
"CRITICAL: You MUST search Confluence first - this is not optional!"
|
193
199
|
when 'claude_atlassian_mcp'
|
194
200
|
"## Business Context Instructions:\n" \
|
195
|
-
"
|
196
|
-
"1. **
|
201
|
+
"MANDATORY: You MUST use Atlassian MCP tools to search for business context before fixing any code.\n\n" \
|
202
|
+
"1. **REQUIRED - Search Jira for business context:**\n" \
|
197
203
|
" - Search for tickets related to '#{class_name}' or '#{method_name}'\n" \
|
198
204
|
" - Look for bug reports, requirements, or business rules\n" \
|
199
|
-
" -
|
200
|
-
"
|
205
|
+
" - Search for domain-specific business logic and constraints\n" \
|
206
|
+
" - Apply ALL found ticket context to your fix\n\n" \
|
207
|
+
"2. **REQUIRED - Search Confluence for business context:**\n" \
|
201
208
|
" - Search for PRDs, technical specs, or business process docs\n" \
|
202
209
|
" - Look for domain-specific business rules related to '#{class_name}'\n" \
|
203
|
-
" -
|
204
|
-
"
|
205
|
-
"
|
206
|
-
" -
|
207
|
-
"
|
210
|
+
" - Search for validation patterns and business requirements\n" \
|
211
|
+
" - Apply ALL found documentation to your fix\n\n" \
|
212
|
+
"3. **After finding business context:**\n" \
|
213
|
+
" - Ensure your fix follows ALL documented business requirements\n" \
|
214
|
+
" - Validate that your solution aligns with business logic\n" \
|
215
|
+
" - Apply business rules and constraints to your solution\n\n" \
|
216
|
+
"4. **If no documentation is found:**\n" \
|
217
|
+
" - Note this explicitly in your response.\n" \
|
218
|
+
" - Proceed with standard business logic and error handling practices.\n" \
|
219
|
+
" - Use common validation patterns and best practices.\n\n" \
|
220
|
+
"CRITICAL: You MUST search both Jira and Confluence first - this is not optional!"
|
208
221
|
when 'jira_mcp'
|
209
222
|
# Use Jira MCP context
|
210
223
|
get_jira_business_context(class_name)
|
@@ -236,12 +249,15 @@ module CodeHealer
|
|
236
249
|
|
237
250
|
## Instructions:
|
238
251
|
Please:
|
239
|
-
1.
|
240
|
-
2. **
|
241
|
-
3.
|
242
|
-
4.
|
243
|
-
5.
|
244
|
-
6.
|
252
|
+
1. **CRITICAL: First, use Atlassian MCP tools to search for business context about '#{class_name}' and '#{method_name}'**
|
253
|
+
2. **REQUIRED: Search Confluence for business rules, PRDs, and validation patterns**
|
254
|
+
3. **REQUIRED: Search Jira for related tickets and business requirements**
|
255
|
+
4. Analyze the error and understand the root cause
|
256
|
+
5. **IMPORTANT: Check if the fix requires changes to multiple files or if the root cause is in a different file**
|
257
|
+
6. Fix the issue considering the business rules found in MCP tools
|
258
|
+
7. Ensure the fix doesn't break other functionality
|
259
|
+
8. Follow Rails conventions and patterns
|
260
|
+
9. Make sure to write testcases for the fix
|
245
261
|
|
246
262
|
## IMPORTANT: Full Codebase Access & Multi-File Fixes
|
247
263
|
You have permission to edit ANY files in the codebase. Please:
|
@@ -112,7 +112,7 @@ module CodeHealer
|
|
112
112
|
escaped_prompt = prompt.gsub("'", "'\"'\"'")
|
113
113
|
|
114
114
|
# Build command template for MCP tools access
|
115
|
-
command_template = config['command_template'] || "claude --print '{prompt}' --output-format text"
|
115
|
+
command_template = config['command_template'] || "claude --print '{prompt}' --output-format text --allowedTools Edit,mcp__atlassian"
|
116
116
|
|
117
117
|
# Replace placeholder
|
118
118
|
command = command_template.gsub('{prompt}', escaped_prompt)
|
@@ -131,7 +131,7 @@ module CodeHealer
|
|
131
131
|
end
|
132
132
|
|
133
133
|
# Add business context instructions
|
134
|
-
command += " --append-system-prompt '
|
134
|
+
command += " --append-system-prompt 'CRITICAL: Before fixing any code, you MUST use the Atlassian MCP tools to search Confluence for business context. Search for documentation about the class and method you are fixing. Only proceed with the fix after reviewing the business requirements from Confluence. If no documentation is found, note this in your response.'"
|
135
135
|
|
136
136
|
# Return command
|
137
137
|
command
|
@@ -176,9 +176,7 @@ module CodeHealer
|
|
176
176
|
demo_mode? && demo_settings['skip_tests'] != false
|
177
177
|
end
|
178
178
|
|
179
|
-
|
180
|
-
demo_mode? && demo_settings['skip_pr'] != false
|
181
|
-
end
|
179
|
+
|
182
180
|
|
183
181
|
def git_settings
|
184
182
|
config['git'] || {}
|
@@ -197,6 +195,10 @@ module CodeHealer
|
|
197
195
|
cfg = code_heal_directory_config
|
198
196
|
cfg['sticky_workspace'] == true || cfg[:sticky_workspace] == true
|
199
197
|
end
|
198
|
+
|
199
|
+
def persistent_workspaces_enabled?
|
200
|
+
code_heal_directory_config['persistent_workspaces'] != false
|
201
|
+
end
|
200
202
|
|
201
203
|
def auto_cleanup_workspaces?
|
202
204
|
code_heal_directory_config['auto_cleanup'] != false
|
@@ -346,7 +348,7 @@ module CodeHealer
|
|
346
348
|
'timeout' => 300,
|
347
349
|
'max_file_changes' => 10,
|
348
350
|
'include_tests' => true,
|
349
|
-
'command_template' => "claude --print '{prompt}' --output-format text --permission-mode acceptEdits --allowedTools Edit",
|
351
|
+
'command_template' => "claude --print '{prompt}' --output-format text --permission-mode acceptEdits --allowedTools Edit,mcp__atlassian",
|
350
352
|
'business_context_sources' => [
|
351
353
|
'config/business_rules.yml',
|
352
354
|
'docs/business_logic.md',
|
@@ -365,8 +367,7 @@ module CodeHealer
|
|
365
367
|
},
|
366
368
|
'demo' => {
|
367
369
|
'enabled' => false,
|
368
|
-
'skip_tests' => true
|
369
|
-
'skip_pr' => true
|
370
|
+
'skip_tests' => true
|
370
371
|
},
|
371
372
|
'git' => {
|
372
373
|
'auto_commit' => true,
|
@@ -389,7 +390,9 @@ module CodeHealer
|
|
389
390
|
'auto_cleanup' => true,
|
390
391
|
'cleanup_after_hours' => 24,
|
391
392
|
'max_workspaces' => 10,
|
392
|
-
'clone_strategy' => 'branch'
|
393
|
+
'clone_strategy' => 'branch',
|
394
|
+
'persistent_workspaces' => true,
|
395
|
+
'sticky_workspace' => false
|
393
396
|
}
|
394
397
|
}
|
395
398
|
end
|
@@ -56,13 +56,13 @@ class EvolutionJob
|
|
56
56
|
def create_healing_workspace(class_name, method_name)
|
57
57
|
puts "🏥 Creating isolated healing workspace for #{class_name}##{method_name}"
|
58
58
|
|
59
|
-
# Create
|
59
|
+
# Create persistent workspace and checkout to target branch
|
60
60
|
workspace_path = CodeHealer::HealingWorkspaceManager.create_healing_workspace(
|
61
61
|
Rails.root.to_s,
|
62
|
-
|
62
|
+
CodeHealer::ConfigManager.pr_target_branch # Use configured target branch
|
63
63
|
)
|
64
64
|
|
65
|
-
puts "✅ Healing workspace
|
65
|
+
puts "✅ Healing workspace ready: #{workspace_path}"
|
66
66
|
workspace_path
|
67
67
|
end
|
68
68
|
|
@@ -172,6 +172,24 @@ module CodeHealer
|
|
172
172
|
puts "🔍 [HEALING_JOB] MCP tools check complete"
|
173
173
|
end
|
174
174
|
|
175
|
+
def extract_file_path_from_error(error)
|
176
|
+
return nil unless error&.backtrace
|
177
|
+
|
178
|
+
# Look for the first line that contains a file path
|
179
|
+
error.backtrace.each do |line|
|
180
|
+
if line.match?(/^(.+\.rb):\d+:in/)
|
181
|
+
file_path = $1
|
182
|
+
# Convert to absolute path if it's relative
|
183
|
+
if file_path.start_with?('./') || !file_path.start_with?('/')
|
184
|
+
file_path = File.expand_path(file_path)
|
185
|
+
end
|
186
|
+
return file_path
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
nil
|
191
|
+
end
|
192
|
+
|
175
193
|
def parse_args(args)
|
176
194
|
# Formats supported:
|
177
195
|
# 1) [error_class, error_message, class_name, method_name, evolution_method, backtrace]
|
@@ -206,13 +224,13 @@ module CodeHealer
|
|
206
224
|
def create_healing_workspace(class_name, method_name)
|
207
225
|
puts "🏥 Creating isolated healing workspace for #{class_name}##{method_name}"
|
208
226
|
|
209
|
-
# Create
|
227
|
+
# Create persistent workspace and checkout to target branch
|
210
228
|
workspace_path = CodeHealer::HealingWorkspaceManager.create_healing_workspace(
|
211
229
|
Rails.root.to_s,
|
212
|
-
|
230
|
+
CodeHealer::ConfigManager.pr_target_branch # Use configured target branch
|
213
231
|
)
|
214
232
|
|
215
|
-
puts "✅ Healing workspace
|
233
|
+
puts "✅ Healing workspace ready: #{workspace_path}"
|
216
234
|
workspace_path
|
217
235
|
end
|
218
236
|
|
@@ -8,7 +8,7 @@ module CodeHealer
|
|
8
8
|
def create_healing_workspace(repo_path, branch_name = nil)
|
9
9
|
puts "🏥 [WORKSPACE] Starting workspace creation..."
|
10
10
|
puts "🏥 [WORKSPACE] Repo path: #{repo_path}"
|
11
|
-
puts "🏥 [WORKSPACE]
|
11
|
+
puts "🏥 [WORKSPACE] Target branch: #{branch_name || 'default'}"
|
12
12
|
|
13
13
|
config = CodeHealer::ConfigManager.code_heal_directory_config
|
14
14
|
puts "🏥 [WORKSPACE] Raw config: #{config.inspect}"
|
@@ -16,11 +16,22 @@ module CodeHealer
|
|
16
16
|
base_path = config['path'] || config[:path] || '/tmp/code_healer_workspaces'
|
17
17
|
puts "🏥 [WORKSPACE] Base heal dir: #{base_path}"
|
18
18
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
19
|
+
# Use persistent workspace ID based on repo (if enabled)
|
20
|
+
if CodeHealer::ConfigManager.persistent_workspaces_enabled?
|
21
|
+
repo_name = extract_repo_name(repo_path)
|
22
|
+
workspace_id = "persistent_#{repo_name}"
|
23
|
+
workspace_path = File.join(base_path, workspace_id)
|
24
|
+
else
|
25
|
+
# Fallback to unique workspace for each healing session
|
26
|
+
workspace_id = "healing_#{Time.now.to_i}_#{SecureRandom.hex(4)}"
|
27
|
+
workspace_path = File.join(base_path, workspace_id)
|
28
|
+
end
|
22
29
|
|
23
|
-
|
30
|
+
if CodeHealer::ConfigManager.persistent_workspaces_enabled?
|
31
|
+
puts "🏥 [WORKSPACE] Persistent workspace ID: #{workspace_id}"
|
32
|
+
else
|
33
|
+
puts "🏥 [WORKSPACE] Temporary workspace ID: #{workspace_id}"
|
34
|
+
end
|
24
35
|
puts "🏥 [WORKSPACE] Full workspace path: #{workspace_path}"
|
25
36
|
|
26
37
|
begin
|
@@ -29,25 +40,28 @@ module CodeHealer
|
|
29
40
|
FileUtils.mkdir_p(base_path)
|
30
41
|
puts "🏥 [WORKSPACE] Base directory created/verified: #{base_path}"
|
31
42
|
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
43
|
+
# Check if workspace already exists
|
44
|
+
if Dir.exist?(workspace_path) && Dir.exist?(File.join(workspace_path, '.git'))
|
45
|
+
if CodeHealer::ConfigManager.persistent_workspaces_enabled?
|
46
|
+
puts "🏥 [WORKSPACE] Persistent workspace exists, checking out to target branch..."
|
47
|
+
checkout_to_branch(workspace_path, branch_name, repo_path)
|
48
|
+
else
|
49
|
+
puts "🏥 [WORKSPACE] Workspace exists but persistent mode disabled, creating new one..."
|
50
|
+
cleanup_workspace(workspace_path, true)
|
51
|
+
create_persistent_workspace(repo_path, workspace_path, branch_name)
|
52
|
+
end
|
39
53
|
else
|
40
|
-
puts "🏥 [WORKSPACE]
|
41
|
-
|
54
|
+
puts "🏥 [WORKSPACE] Creating new workspace..."
|
55
|
+
create_persistent_workspace(repo_path, workspace_path, branch_name)
|
42
56
|
end
|
43
57
|
|
44
|
-
puts "🏥 [WORKSPACE] Workspace
|
58
|
+
puts "🏥 [WORKSPACE] Workspace ready successfully!"
|
45
59
|
puts "🏥 [WORKSPACE] Final workspace path: #{workspace_path}"
|
46
|
-
puts "🏥 [WORKSPACE]
|
60
|
+
puts "🏥 [WORKSPACE] Current branch: #{get_current_branch(workspace_path)}"
|
47
61
|
workspace_path
|
48
62
|
rescue => e
|
49
|
-
puts "❌ Failed to create healing workspace: #{e.message}"
|
50
|
-
|
63
|
+
puts "❌ Failed to create/prepare healing workspace: #{e.message}"
|
64
|
+
# Don't cleanup persistent workspace on error
|
51
65
|
raise e
|
52
66
|
end
|
53
67
|
end
|
@@ -131,6 +145,43 @@ module CodeHealer
|
|
131
145
|
end
|
132
146
|
end
|
133
147
|
|
148
|
+
def validate_workspace_for_commit(workspace_path)
|
149
|
+
puts "🔍 [WORKSPACE] Validating workspace for commit..."
|
150
|
+
|
151
|
+
Dir.chdir(workspace_path) do
|
152
|
+
# Check for any temporary files that might have been added
|
153
|
+
staged_files = `git diff --cached --name-only`.strip.split("\n")
|
154
|
+
working_files = `git status --porcelain | grep '^ M\\|^M \\|^A ' | awk '{print $2}'`.strip.split("\n")
|
155
|
+
|
156
|
+
all_files = (staged_files + working_files).uniq.reject(&:empty?)
|
157
|
+
|
158
|
+
puts "🔍 [WORKSPACE] Files to be committed: #{all_files.join(', ')}"
|
159
|
+
|
160
|
+
# Check for any temporary files
|
161
|
+
temp_files = all_files.select { |file| should_skip_file?(file) }
|
162
|
+
|
163
|
+
if temp_files.any?
|
164
|
+
puts "⚠️ [WORKSPACE] WARNING: Temporary files detected in commit:"
|
165
|
+
temp_files.each { |file| puts " - #{file}" }
|
166
|
+
|
167
|
+
# Remove them from staging
|
168
|
+
temp_files.each do |file|
|
169
|
+
puts "🗑️ [WORKSPACE] Removing temporary file from staging: #{file}"
|
170
|
+
system("git reset HEAD '#{file}' 2>/dev/null || true")
|
171
|
+
end
|
172
|
+
|
173
|
+
puts "🔍 [WORKSPACE] Temporary files removed from staging"
|
174
|
+
return false
|
175
|
+
end
|
176
|
+
|
177
|
+
puts "✅ [WORKSPACE] Workspace validation passed - no temporary files detected"
|
178
|
+
return true
|
179
|
+
end
|
180
|
+
rescue => e
|
181
|
+
puts "❌ [WORKSPACE] Workspace validation failed: #{e.message}"
|
182
|
+
return false
|
183
|
+
end
|
184
|
+
|
134
185
|
def create_healing_branch(repo_path, workspace_path, branch_name)
|
135
186
|
puts "🔄 Creating healing branch and PR from isolated workspace"
|
136
187
|
|
@@ -156,7 +207,13 @@ module CodeHealer
|
|
156
207
|
system("git status --porcelain")
|
157
208
|
|
158
209
|
# Add all changes (the fixes are already applied in the workspace)
|
159
|
-
|
210
|
+
add_only_relevant_files(workspace_path)
|
211
|
+
|
212
|
+
# Validate workspace before commit to ensure no temporary files
|
213
|
+
unless validate_workspace_for_commit(workspace_path)
|
214
|
+
puts "⚠️ [WORKSPACE] Workspace validation failed, retrying file addition..."
|
215
|
+
add_only_relevant_files(workspace_path)
|
216
|
+
end
|
160
217
|
|
161
218
|
# Check if there are changes to commit
|
162
219
|
if system("git diff --cached --quiet") == false
|
@@ -172,15 +229,18 @@ module CodeHealer
|
|
172
229
|
puts "🔒 Main repository (#{repo_path}) remains completely untouched"
|
173
230
|
puts "📝 All changes committed in isolated workspace"
|
174
231
|
|
175
|
-
# Create pull request if auto-create is enabled
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
232
|
+
# Create pull request if auto-create is enabled
|
233
|
+
if should_create_pull_request?
|
234
|
+
puts "🔍 [WORKSPACE] Auto-creating pull request..."
|
235
|
+
pr_url = create_pull_request(healing_branch, branch_name)
|
236
|
+
if pr_url
|
237
|
+
puts "✅ [WORKSPACE] Pull request created: #{pr_url}"
|
238
|
+
else
|
239
|
+
puts "⚠️ [WORKSPACE] Failed to create pull request"
|
240
|
+
end
|
241
|
+
else
|
242
|
+
puts "🔍 [WORKSPACE] Review the changes and create a pull request when ready"
|
243
|
+
end
|
184
244
|
|
185
245
|
healing_branch
|
186
246
|
else
|
@@ -202,13 +262,23 @@ module CodeHealer
|
|
202
262
|
end
|
203
263
|
end
|
204
264
|
|
205
|
-
def cleanup_workspace(workspace_path)
|
265
|
+
def cleanup_workspace(workspace_path, force = false)
|
206
266
|
puts "🧹 [WORKSPACE] Starting workspace cleanup..."
|
207
267
|
puts "🧹 [WORKSPACE] Target: #{workspace_path}"
|
268
|
+
puts "🧹 [WORKSPACE] Force cleanup: #{force}"
|
208
269
|
puts "🧹 [WORKSPACE] Exists: #{Dir.exist?(workspace_path)}"
|
209
270
|
|
210
271
|
return unless Dir.exist?(workspace_path)
|
211
272
|
|
273
|
+
# Check if this is a persistent workspace
|
274
|
+
is_persistent = workspace_path.include?('persistent_')
|
275
|
+
|
276
|
+
if is_persistent && !force
|
277
|
+
puts "🧹 [WORKSPACE] This is a persistent workspace - skipping cleanup"
|
278
|
+
puts "🧹 [WORKSPACE] Use force=true to override"
|
279
|
+
return
|
280
|
+
end
|
281
|
+
|
212
282
|
# Remove .git directory first to avoid conflicts
|
213
283
|
git_dir = File.join(workspace_path, '.git')
|
214
284
|
if Dir.exist?(git_dir)
|
@@ -258,17 +328,25 @@ module CodeHealer
|
|
258
328
|
begin
|
259
329
|
require 'octokit'
|
260
330
|
|
261
|
-
|
262
|
-
github_token = ENV['GITHUB_TOKEN']
|
263
|
-
|
331
|
+
# Try to get GitHub token from environment
|
332
|
+
github_token = ENV['GITHUB_TOKEN'] || ENV['GITHUB_ACCESS_TOKEN']
|
333
|
+
|
334
|
+
unless github_token
|
335
|
+
puts "❌ Missing GitHub token. Set GITHUB_TOKEN environment variable"
|
336
|
+
puts "💡 You can create a token at: https://github.com/settings/tokens"
|
337
|
+
return nil
|
338
|
+
end
|
339
|
+
|
340
|
+
# Auto-detect repository from git remote
|
341
|
+
repo_name = detect_github_repository
|
264
342
|
|
265
|
-
unless
|
266
|
-
puts "❌
|
343
|
+
unless repo_name
|
344
|
+
puts "❌ Could not detect GitHub repository from git remote"
|
345
|
+
puts "💡 Make sure your repository has a GitHub remote origin"
|
267
346
|
return nil
|
268
347
|
end
|
269
348
|
|
270
|
-
|
271
|
-
owner, repo = repo_name.split('/')
|
349
|
+
puts "🔗 Creating PR for repository: #{repo_name}"
|
272
350
|
|
273
351
|
client = Octokit::Client.new(access_token: github_token)
|
274
352
|
|
@@ -283,13 +361,283 @@ module CodeHealer
|
|
283
361
|
"Generated at: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
284
362
|
)
|
285
363
|
|
364
|
+
puts "✅ Pull request created successfully: #{pr.html_url}"
|
286
365
|
pr.html_url
|
287
366
|
rescue => e
|
288
367
|
puts "❌ Failed to create pull request: #{e.message}"
|
368
|
+
puts "💡 Check your GitHub token and repository access"
|
289
369
|
nil
|
290
370
|
end
|
291
371
|
end
|
372
|
+
|
373
|
+
def detect_github_repository
|
374
|
+
# Try to detect from current git remote
|
375
|
+
Dir.chdir(Dir.pwd) do
|
376
|
+
remote_url = `git config --get remote.origin.url`.strip
|
377
|
+
|
378
|
+
if remote_url.include?('github.com')
|
379
|
+
# Extract owner/repo from GitHub URL
|
380
|
+
if remote_url.include?('git@github.com:')
|
381
|
+
# SSH format: git@github.com:owner/repo.git
|
382
|
+
repo_part = remote_url.gsub('git@github.com:', '').gsub('.git', '')
|
383
|
+
elsif remote_url.include?('https://github.com/')
|
384
|
+
# HTTPS format: https://github.com/owner/repo.git
|
385
|
+
repo_part = remote_url.gsub('https://github.com/', '').gsub('.git', '')
|
386
|
+
else
|
387
|
+
return nil
|
388
|
+
end
|
389
|
+
puts "🔍 Detected GitHub repository: #{repo_part}"
|
390
|
+
return repo_part
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
nil
|
395
|
+
rescue
|
396
|
+
nil
|
397
|
+
end
|
292
398
|
|
399
|
+
def extract_repo_name(repo_path)
|
400
|
+
# Extract repo name from path or git remote
|
401
|
+
if File.exist?(File.join(repo_path, '.git'))
|
402
|
+
Dir.chdir(repo_path) do
|
403
|
+
remote_url = `git config --get remote.origin.url`.strip
|
404
|
+
if remote_url.include?('/')
|
405
|
+
remote_url.split('/').last.gsub('.git', '')
|
406
|
+
else
|
407
|
+
File.basename(repo_path)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
else
|
411
|
+
File.basename(repo_path)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def create_persistent_workspace(repo_path, workspace_path, branch_name)
|
416
|
+
puts "🏥 [WORKSPACE] Creating new persistent workspace..."
|
417
|
+
|
418
|
+
# Get the GitHub remote URL
|
419
|
+
Dir.chdir(repo_path) do
|
420
|
+
remote_url = `git config --get remote.origin.url`.strip
|
421
|
+
if remote_url.empty?
|
422
|
+
puts "❌ [WORKSPACE] No remote origin found in #{repo_path}"
|
423
|
+
return false
|
424
|
+
end
|
425
|
+
|
426
|
+
puts "🏥 [WORKSPACE] Cloning from: #{remote_url}"
|
427
|
+
puts "🏥 [WORKSPACE] To workspace: #{workspace_path}"
|
428
|
+
|
429
|
+
# Clone the full repository for persistent use
|
430
|
+
result = system("git clone #{remote_url} #{workspace_path}")
|
431
|
+
if result
|
432
|
+
puts "🏥 [WORKSPACE] Repository cloned successfully"
|
433
|
+
# Now checkout to the target branch
|
434
|
+
checkout_to_branch(workspace_path, branch_name, repo_path)
|
435
|
+
else
|
436
|
+
puts "❌ [WORKSPACE] Failed to clone repository"
|
437
|
+
return false
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def checkout_to_branch(workspace_path, branch_name, repo_path)
|
443
|
+
puts "🏥 [WORKSPACE] Checking out to target branch..."
|
444
|
+
|
445
|
+
# Determine target branch
|
446
|
+
target_branch = branch_name || CodeHealer::ConfigManager.pr_target_branch || get_default_branch(repo_path)
|
447
|
+
puts "🏥 [WORKSPACE] Target branch: #{target_branch}"
|
448
|
+
|
449
|
+
Dir.chdir(workspace_path) do
|
450
|
+
# Fetch latest changes
|
451
|
+
puts "🏥 [WORKSPACE] Fetching latest changes..."
|
452
|
+
system("git fetch origin")
|
453
|
+
|
454
|
+
# Check if branch exists locally
|
455
|
+
local_branch_exists = system("git show-ref --verify --quiet refs/heads/#{target_branch}")
|
456
|
+
|
457
|
+
if local_branch_exists
|
458
|
+
puts "🏥 [WORKSPACE] Checking out existing local branch: #{target_branch}"
|
459
|
+
system("git checkout #{target_branch}")
|
460
|
+
else
|
461
|
+
puts "🏥 [WORKSPACE] Checking out remote branch: #{target_branch}"
|
462
|
+
system("git checkout -b #{target_branch} origin/#{target_branch}")
|
463
|
+
end
|
464
|
+
|
465
|
+
# Pull latest changes
|
466
|
+
puts "🏥 [WORKSPACE] Pulling latest changes..."
|
467
|
+
system("git pull origin #{target_branch}")
|
468
|
+
|
469
|
+
# Ensure workspace is clean
|
470
|
+
puts "🏥 [WORKSPACE] Ensuring workspace is clean..."
|
471
|
+
system("git reset --hard HEAD")
|
472
|
+
system("git clean -fd")
|
473
|
+
|
474
|
+
# Remove any tracked temporary files that shouldn't be committed - AGGRESSIVE cleanup
|
475
|
+
puts "🏥 [WORKSPACE] Removing tracked temporary files..."
|
476
|
+
|
477
|
+
# Remove root level temporary directories
|
478
|
+
system("git rm -r --cached tmp/ 2>/dev/null || true")
|
479
|
+
system("git rm -r --cached log/ 2>/dev/null || true")
|
480
|
+
system("git rm -r --cached .bundle/ 2>/dev/null || true")
|
481
|
+
system("git rm -r --cached storage/ 2>/dev/null || true")
|
482
|
+
system("git rm -r --cached coverage/ 2>/dev/null || true")
|
483
|
+
system("git rm -r --cached .yardoc/ 2>/dev/null || true")
|
484
|
+
system("git rm -r --cached .rspec_status 2>/dev/null || true")
|
485
|
+
|
486
|
+
# Remove any nested tmp/ or log/ directories that might be tracked
|
487
|
+
system("find . -name 'tmp' -type d -exec git rm -r --cached {} + 2>/dev/null || true")
|
488
|
+
system("find . -name 'log' -type d -exec git rm -r --cached {} + 2>/dev/null || true")
|
489
|
+
|
490
|
+
# Remove any .tmp, .log, .cache files
|
491
|
+
system("find . -name '*.tmp' -exec git rm --cached {} + 2>/dev/null || true")
|
492
|
+
system("find . -name '*.log' -exec git rm --cached {} + 2>/dev/null || true")
|
493
|
+
system("find . -name '*.cache' -exec git rm --cached {} + 2>/dev/null || true")
|
494
|
+
|
495
|
+
puts "🏥 [WORKSPACE] Successfully checked out to: #{target_branch}"
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def get_default_branch(repo_path)
|
500
|
+
Dir.chdir(repo_path) do
|
501
|
+
# Try to get default branch from remote
|
502
|
+
default_branch = `git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null`.strip
|
503
|
+
if default_branch.include?('origin/')
|
504
|
+
default_branch.gsub('refs/remotes/origin/', '')
|
505
|
+
else
|
506
|
+
# Fallback to common defaults
|
507
|
+
['main', 'master'].find { |branch| system("git ls-remote --heads origin #{branch} >/dev/null 2>&1") }
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def get_current_branch(workspace_path)
|
513
|
+
Dir.chdir(workspace_path) do
|
514
|
+
`git branch --show-current`.strip
|
515
|
+
end
|
516
|
+
rescue
|
517
|
+
'unknown'
|
518
|
+
end
|
519
|
+
|
520
|
+
def add_only_relevant_files(workspace_path)
|
521
|
+
puts "📁 [WORKSPACE] Adding only relevant files, respecting .gitignore..."
|
522
|
+
|
523
|
+
Dir.chdir(workspace_path) do
|
524
|
+
# First, ensure .gitignore is respected
|
525
|
+
if File.exist?('.gitignore')
|
526
|
+
puts "📁 [WORKSPACE] Using repository's .gitignore file"
|
527
|
+
else
|
528
|
+
puts "📁 [WORKSPACE] No .gitignore found, using default patterns"
|
529
|
+
end
|
530
|
+
|
531
|
+
# Get list of modified files
|
532
|
+
modified_files = `git status --porcelain | grep '^ M\\|^M \\|^A ' | awk '{print $2}'`.strip.split("\n")
|
533
|
+
|
534
|
+
if modified_files.empty?
|
535
|
+
puts "📁 [WORKSPACE] No modified files to add"
|
536
|
+
return
|
537
|
+
end
|
538
|
+
|
539
|
+
puts "📁 [WORKSPACE] Modified files: #{modified_files.join(', ')}"
|
540
|
+
|
541
|
+
# Add each modified file individually
|
542
|
+
modified_files.each do |file|
|
543
|
+
next if file.empty?
|
544
|
+
|
545
|
+
# Skip temporary and generated files
|
546
|
+
if should_skip_file?(file)
|
547
|
+
puts "📁 [WORKSPACE] Skipping temporary file: #{file}"
|
548
|
+
next
|
549
|
+
end
|
550
|
+
|
551
|
+
puts "📁 [WORKSPACE] Adding file: #{file}"
|
552
|
+
system("git add '#{file}'")
|
553
|
+
end
|
554
|
+
|
555
|
+
puts "📁 [WORKSPACE] File addition completed"
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
def should_skip_file?(file_path)
|
560
|
+
# Skip temporary and generated files - AGGRESSIVE filtering
|
561
|
+
skip_patterns = [
|
562
|
+
# Root level directories - NEVER commit these
|
563
|
+
/^tmp\//,
|
564
|
+
/^log\//,
|
565
|
+
/^\.git\//,
|
566
|
+
/^node_modules\//,
|
567
|
+
/^vendor\//,
|
568
|
+
/^public\/packs/,
|
569
|
+
/^public\/assets/,
|
570
|
+
/^\.bundle\//,
|
571
|
+
/^bootsnap/,
|
572
|
+
/^cache/,
|
573
|
+
/^storage\//,
|
574
|
+
/^coverage\//,
|
575
|
+
/^\.yardoc\//,
|
576
|
+
/^\.rspec_status/,
|
577
|
+
|
578
|
+
# Any tmp/ directory at any level
|
579
|
+
/tmp\//,
|
580
|
+
|
581
|
+
# Any log/ directory at any level
|
582
|
+
/log\//,
|
583
|
+
|
584
|
+
# Specific tmp subdirectories
|
585
|
+
/tmp\/cache\//,
|
586
|
+
/tmp\/bootsnap\//,
|
587
|
+
/tmp\/pids\//,
|
588
|
+
/tmp\/sockets\//,
|
589
|
+
/tmp\/sessions\//,
|
590
|
+
/tmp\/backup\//,
|
591
|
+
/tmp\/test\//,
|
592
|
+
|
593
|
+
# File extensions
|
594
|
+
/\.tmp$/,
|
595
|
+
/\.log$/,
|
596
|
+
/\.cache$/,
|
597
|
+
/\.swp$/,
|
598
|
+
/\.swo$/,
|
599
|
+
/\.bak$/,
|
600
|
+
/\.backup$/,
|
601
|
+
/~$/,
|
602
|
+
|
603
|
+
# Rails specific
|
604
|
+
/\.bundle\//,
|
605
|
+
/Gemfile\.lock\.backup/,
|
606
|
+
/config\/database\.yml\.backup/,
|
607
|
+
/config\/secrets\.yml\.backup/
|
608
|
+
]
|
609
|
+
|
610
|
+
# Additional check: if path contains 'tmp' or 'log' anywhere, skip it
|
611
|
+
if file_path.include?('tmp') || file_path.include?('log')
|
612
|
+
puts "📁 [WORKSPACE] Skipping file containing 'tmp' or 'log': #{file_path}"
|
613
|
+
return true
|
614
|
+
end
|
615
|
+
|
616
|
+
skip_patterns.any? { |pattern| file_path.match?(pattern) }
|
617
|
+
end
|
618
|
+
|
619
|
+
def reset_workspace_to_clean_state(workspace_path, branch_name = nil)
|
620
|
+
puts "🔄 [WORKSPACE] Resetting workspace to clean state..."
|
621
|
+
|
622
|
+
Dir.chdir(workspace_path) do
|
623
|
+
# Stash any uncommitted changes
|
624
|
+
puts "🔄 [WORKSPACE] Stashing any uncommitted changes..."
|
625
|
+
system("git stash")
|
626
|
+
|
627
|
+
# Reset to clean state
|
628
|
+
puts "🔄 [WORKSPACE] Resetting to clean state..."
|
629
|
+
system("git reset --hard HEAD")
|
630
|
+
system("git clean -fd")
|
631
|
+
|
632
|
+
# Checkout to target branch if specified
|
633
|
+
if branch_name
|
634
|
+
checkout_to_branch(workspace_path, branch_name, nil)
|
635
|
+
end
|
636
|
+
|
637
|
+
puts "🔄 [WORKSPACE] Workspace reset to clean state"
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
293
641
|
def clone_strategy
|
294
642
|
cfg = CodeHealer::ConfigManager.code_heal_directory_config
|
295
643
|
cfg['clone_strategy'] || cfg[:clone_strategy] || "branch"
|
data/lib/code_healer/setup.rb
CHANGED
@@ -428,7 +428,7 @@ enable_demo_mode = ask_for_yes_no("Enable demo mode for fast demonstrations?", d
|
|
428
428
|
demo_config = {}
|
429
429
|
if enable_demo_mode
|
430
430
|
demo_config[:skip_tests] = ask_for_yes_no("Skip test generation in demo mode?", default: true)
|
431
|
-
|
431
|
+
|
432
432
|
|
433
433
|
puts
|
434
434
|
puts "🚀 Demo mode will significantly speed up healing operations!"
|
@@ -440,7 +440,7 @@ if enable_demo_mode
|
|
440
440
|
puts " - Timeout reduced to 60 seconds for quick responses"
|
441
441
|
puts " - Sticky workspace enabled for faster context loading"
|
442
442
|
puts " - Claude session persistence for better performance"
|
443
|
-
puts " - Tests
|
443
|
+
puts " - Tests skipped for immediate results (PRs still created)"
|
444
444
|
end
|
445
445
|
|
446
446
|
# Create configuration files
|
@@ -523,7 +523,7 @@ create_file_with_content('.env', env_content, dry_run: options[:dry_run])
|
|
523
523
|
- ".git/"
|
524
524
|
- "node_modules/"
|
525
525
|
- "vendor/"
|
526
|
-
command_template: "claude --print '{prompt}' --output-format text --permission-mode acceptEdits --allowedTools Edit"
|
526
|
+
command_template: "claude --print '{prompt}' --output-format text --permission-mode acceptEdits --allowedTools Edit,mcp__atlassian"
|
527
527
|
business_context_sources:
|
528
528
|
- "config/business_rules.yml"
|
529
529
|
- "docs/business_logic.md"
|
@@ -617,7 +617,6 @@ create_file_with_content('.env', env_content, dry_run: options[:dry_run])
|
|
617
617
|
demo:
|
618
618
|
enabled: #{enable_demo_mode}
|
619
619
|
skip_tests: #{demo_config[:skip_tests] || false}
|
620
|
-
skip_pr: #{demo_config[:skip_pr] || false}
|
621
620
|
|
622
621
|
# Performance Configuration
|
623
622
|
performance:
|
data/lib/code_healer/version.rb
CHANGED