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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a14639ba1f9033b76d3cc5d3ec3b252b7568c14b22098a4efb6d71f50d95463
4
- data.tar.gz: 1c3f66f4ffc22405485ad3048a3b1635ed5849a1a46a4881b20647bff33a2118
3
+ metadata.gz: 704c23ee702e1f6a10a5fbbdcebe377d0523aa69d880aa40257e7351528130f3
4
+ data.tar.gz: e6989f6bbe820a4891c4b1268cde29d9231875c7415d4fbcc8ba9e8d5b72bc91
5
5
  SHA512:
6
- metadata.gz: b78c5c6c5917de5253fcf543132c26679f5d520e1a0d9e80dcfe5b28ee13e9e45b5751449db2addb6e0fb90563ee69d4830fbafb6526977f91b73b083c1e54b1
7
- data.tar.gz: 78dfc2a6168f34a25fc0eab7be783e63a1b4d18f06c5236e25acf1f6835a262032e23526b4ae0b08f2fd81cfc992fbef6c0e038a5fb1d2e8aa199f0403e8061b
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
- "OPTIONAL: You can use Confluence MCP tools to enhance business context if available.\n\n" \
185
- "1. If Confluence MCP is available:\n" \
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
- " - Apply any found documentation to your fix.\n\n" \
189
- "2. If Confluence MCP is not available:\n" \
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
- "Note: MCP usage is optional - proceed with or without it."
198
+ "CRITICAL: You MUST search Confluence first - this is not optional!"
193
199
  when 'claude_atlassian_mcp'
194
200
  "## Business Context Instructions:\n" \
195
- "OPTIONAL: You can use Atlassian MCP tools to enhance business context if available.\n\n" \
196
- "1. **If Jira MCP is available:**\n" \
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
- " - Apply any found ticket context to your fix\n\n" \
200
- "2. **If Confluence MCP is available:**\n" \
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
- " - Apply any found documentation to your fix\n\n" \
204
- "3. **If MCP tools are not available:**\n" \
205
- " - Proceed with standard business logic and error handling practices\n" \
206
- " - Use common validation patterns and best practices\n\n" \
207
- "Note: MCP usage is optional - proceed with or without it."
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. Analyze the error and understand the root cause
240
- 2. **IMPORTANT: Check if the fix requires changes to multiple files or if the root cause is in a different file**
241
- 3. Fix the issue considering the business rules above
242
- 4. Ensure the fix doesn't break other functionality
243
- 5. Follow Rails conventions and patterns
244
- 6. Make sure to write testcases for the fix
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 'Use available MCP tools for business context if needed, but proceed with the fix regardless.'"
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
- def demo_skip_pr?
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 unique workspace
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
- nil # Use current branch
62
+ CodeHealer::ConfigManager.pr_target_branch # Use configured target branch
63
63
  )
64
64
 
65
- puts "✅ Healing workspace created: #{workspace_path}"
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 unique workspace
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
- nil # Use current branch
230
+ CodeHealer::ConfigManager.pr_target_branch # Use configured target branch
213
231
  )
214
232
 
215
- puts "✅ Healing workspace created: #{workspace_path}"
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] Branch name: #{branch_name || 'current'}"
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
- # Create unique workspace directory
20
- workspace_id = "healing_#{Time.now.to_i}_#{SecureRandom.hex(4)}"
21
- workspace_path = File.join(base_path, workspace_id)
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
- puts "🏥 [WORKSPACE] Workspace ID: #{workspace_id}"
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
- # 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)
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] Using full repo cloning..."
41
- clone_full_repo(repo_path, workspace_path, branch_name)
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 creation completed successfully!"
58
+ puts "🏥 [WORKSPACE] Workspace ready successfully!"
45
59
  puts "🏥 [WORKSPACE] Final workspace path: #{workspace_path}"
46
- puts "🏥 [WORKSPACE] Workspace contents: #{Dir.entries(workspace_path).join(', ')}"
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
- cleanup_workspace(workspace_path) if Dir.exist?(workspace_path)
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
- system("git add .")
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 and no PR was already created
176
- if should_create_pull_request? && !CodeHealer::ConfigManager.demo_skip_pr?
177
- puts "🔍 [WORKSPACE] Checking if PR was already created by evolution handler..."
178
- # Skip PR creation if we're in a healing workflow (PR likely already created)
179
- puts "🔍 [WORKSPACE] PR creation skipped - likely already created by evolution handler"
180
- puts "🔍 [WORKSPACE] Review the changes and create a pull request when ready"
181
- else
182
- puts "🔍 [WORKSPACE] Review the changes and create a pull request when ready"
183
- end
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
- config = CodeHealer::ConfigManager.config
262
- github_token = ENV['GITHUB_TOKEN']
263
- repo_name = config['github_repo'] || config[:github_repo]
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 github_token && repo_name
266
- puts "❌ Missing GitHub token or repository configuration"
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
- # Parse repo name (owner/repo)
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"
@@ -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
- demo_config[:skip_pr] = ask_for_yes_no("Skip pull request creation in demo mode?", default: true)
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 and PRs skipped for immediate results"
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:
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodeHealer
4
- VERSION = "0.1.22"
4
+ VERSION = "0.1.24"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_healer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.22
4
+ version: 0.1.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Deepan Kumar