code_healer 0.1.22 โ 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -0
- data/lib/code_healer/business_context_manager.rb +36 -20
- data/lib/code_healer/claude_code_evolution_handler.rb +28 -31
- 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 +53 -31
- data/lib/code_healer/healing_workspace_manager.rb +413 -80
- data/lib/code_healer/presentation_logger.rb +114 -0
- data/lib/code_healer/setup.rb +3 -4
- data/lib/code_healer/version.rb +1 -1
- metadata +3 -2
@@ -1,53 +1,62 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'securerandom'
|
3
|
+
require_relative 'presentation_logger'
|
3
4
|
|
4
5
|
module CodeHealer
|
5
6
|
# Manages isolated healing workspaces for safe code evolution
|
6
7
|
class HealingWorkspaceManager
|
7
8
|
class << self
|
8
9
|
def create_healing_workspace(repo_path, branch_name = nil)
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
PresentationLogger.workspace_action("Preparing isolated workspace")
|
11
|
+
PresentationLogger.detail("Repo: #{repo_path}")
|
12
|
+
PresentationLogger.detail("Target branch: #{branch_name || 'default'}")
|
12
13
|
|
13
14
|
config = CodeHealer::ConfigManager.code_heal_directory_config
|
14
|
-
puts "๐ฅ [WORKSPACE] Raw config: #{config.inspect}"
|
15
|
-
|
16
15
|
base_path = config['path'] || config[:path] || '/tmp/code_healer_workspaces'
|
17
|
-
puts "๐ฅ [WORKSPACE] Base heal dir: #{base_path}"
|
18
16
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
17
|
+
# Use persistent workspace ID based on repo (if enabled)
|
18
|
+
if CodeHealer::ConfigManager.persistent_workspaces_enabled?
|
19
|
+
repo_name = extract_repo_name(repo_path)
|
20
|
+
workspace_id = "persistent_#{repo_name}"
|
21
|
+
workspace_path = File.join(base_path, workspace_id)
|
22
|
+
else
|
23
|
+
# Fallback to unique workspace for each healing session
|
24
|
+
workspace_id = "healing_#{Time.now.to_i}_#{SecureRandom.hex(4)}"
|
25
|
+
workspace_path = File.join(base_path, workspace_id)
|
26
|
+
end
|
22
27
|
|
23
|
-
|
24
|
-
|
28
|
+
if CodeHealer::ConfigManager.persistent_workspaces_enabled?
|
29
|
+
PresentationLogger.detail("Using persistent workspace: #{workspace_id}")
|
30
|
+
else
|
31
|
+
PresentationLogger.detail("Creating temporary workspace: #{workspace_id}")
|
32
|
+
end
|
25
33
|
|
26
34
|
begin
|
27
|
-
puts "๐ฅ [WORKSPACE] Creating base directory..."
|
28
35
|
# Ensure code heal directory exists
|
29
36
|
FileUtils.mkdir_p(base_path)
|
30
|
-
puts "๐ฅ [WORKSPACE] Base directory created/verified: #{base_path}"
|
31
37
|
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
# Check if workspace already exists
|
39
|
+
if Dir.exist?(workspace_path) && Dir.exist?(File.join(workspace_path, '.git'))
|
40
|
+
if CodeHealer::ConfigManager.persistent_workspaces_enabled?
|
41
|
+
PresentationLogger.detail("Reusing existing workspace, checking out target branch")
|
42
|
+
checkout_to_branch(workspace_path, branch_name, repo_path)
|
43
|
+
else
|
44
|
+
PresentationLogger.detail("Recreating workspace (persistent mode disabled)")
|
45
|
+
cleanup_workspace(workspace_path, true)
|
46
|
+
create_persistent_workspace(repo_path, workspace_path, branch_name)
|
47
|
+
end
|
39
48
|
else
|
40
|
-
|
41
|
-
|
49
|
+
PresentationLogger.detail("Creating new workspace")
|
50
|
+
create_persistent_workspace(repo_path, workspace_path, branch_name)
|
42
51
|
end
|
43
52
|
|
44
|
-
|
45
|
-
|
46
|
-
|
53
|
+
PresentationLogger.success("Workspace ready")
|
54
|
+
PresentationLogger.detail("Path: #{workspace_path}")
|
55
|
+
PresentationLogger.detail("Branch: #{get_current_branch(workspace_path)}")
|
47
56
|
workspace_path
|
48
57
|
rescue => e
|
49
|
-
|
50
|
-
|
58
|
+
PresentationLogger.error("Failed to create workspace: #{e.message}")
|
59
|
+
# Don't cleanup persistent workspace on error
|
51
60
|
raise e
|
52
61
|
end
|
53
62
|
end
|
@@ -97,51 +106,92 @@ module CodeHealer
|
|
97
106
|
def test_fixes_in_workspace(workspace_path)
|
98
107
|
config = CodeHealer::ConfigManager.code_heal_directory_config
|
99
108
|
|
100
|
-
|
109
|
+
PresentationLogger.step("Validating fixes")
|
101
110
|
|
102
111
|
begin
|
103
112
|
# Change to workspace directory
|
104
113
|
Dir.chdir(workspace_path) do
|
105
114
|
# Run basic syntax check
|
106
115
|
syntax_check = system("ruby -c #{find_ruby_files.join(' ')} 2>/dev/null")
|
107
|
-
|
116
|
+
unless syntax_check
|
117
|
+
PresentationLogger.error("Syntax validation failed")
|
118
|
+
return false
|
119
|
+
end
|
108
120
|
|
109
121
|
# Optionally skip heavy tests in demo mode
|
110
122
|
unless CodeHealer::ConfigManager.demo_skip_tests?
|
111
123
|
# Run tests if available
|
112
124
|
if File.exist?('Gemfile')
|
113
125
|
bundle_check = system("bundle check >/dev/null 2>&1")
|
114
|
-
|
126
|
+
unless bundle_check
|
127
|
+
PresentationLogger.error("Bundle check failed")
|
128
|
+
return false
|
129
|
+
end
|
115
130
|
|
116
131
|
# Run tests if RSpec is available
|
117
132
|
if File.exist?('spec') || File.exist?('test')
|
118
133
|
test_result = system("bundle exec rspec --dry-run >/dev/null 2>&1") ||
|
119
134
|
system("bundle exec rake test:prepare >/dev/null 2>&1")
|
120
|
-
|
135
|
+
PresentationLogger.detail("Test preparation: #{test_result ? 'passed' : 'skipped'}")
|
121
136
|
end
|
122
137
|
end
|
123
138
|
end
|
124
139
|
|
125
|
-
|
140
|
+
PresentationLogger.success("Validation passed")
|
126
141
|
true
|
127
142
|
end
|
128
143
|
rescue => e
|
129
|
-
|
144
|
+
PresentationLogger.error("Validation failed: #{e.message}")
|
130
145
|
false
|
131
146
|
end
|
132
147
|
end
|
133
148
|
|
149
|
+
def validate_workspace_for_commit(workspace_path)
|
150
|
+
puts "๐ [WORKSPACE] Validating workspace for commit..."
|
151
|
+
|
152
|
+
Dir.chdir(workspace_path) do
|
153
|
+
# Check for any temporary files that might have been added
|
154
|
+
staged_files = `git diff --cached --name-only`.strip.split("\n")
|
155
|
+
working_files = `git status --porcelain | grep '^ M\\|^M \\|^A ' | awk '{print $2}'`.strip.split("\n")
|
156
|
+
|
157
|
+
all_files = (staged_files + working_files).uniq.reject(&:empty?)
|
158
|
+
|
159
|
+
puts "๐ [WORKSPACE] Files to be committed: #{all_files.join(', ')}"
|
160
|
+
|
161
|
+
# Check for any temporary files
|
162
|
+
temp_files = all_files.select { |file| should_skip_file?(file) }
|
163
|
+
|
164
|
+
if temp_files.any?
|
165
|
+
puts "โ ๏ธ [WORKSPACE] WARNING: Temporary files detected in commit:"
|
166
|
+
temp_files.each { |file| puts " - #{file}" }
|
167
|
+
|
168
|
+
# Remove them from staging
|
169
|
+
temp_files.each do |file|
|
170
|
+
puts "๐๏ธ [WORKSPACE] Removing temporary file from staging: #{file}"
|
171
|
+
system("git reset HEAD '#{file}' 2>/dev/null || true")
|
172
|
+
end
|
173
|
+
|
174
|
+
puts "๐ [WORKSPACE] Temporary files removed from staging"
|
175
|
+
return false
|
176
|
+
end
|
177
|
+
|
178
|
+
puts "โ
[WORKSPACE] Workspace validation passed - no temporary files detected"
|
179
|
+
return true
|
180
|
+
end
|
181
|
+
rescue => e
|
182
|
+
puts "โ [WORKSPACE] Workspace validation failed: #{e.message}"
|
183
|
+
return false
|
184
|
+
end
|
185
|
+
|
134
186
|
def create_healing_branch(repo_path, workspace_path, branch_name)
|
135
|
-
|
187
|
+
PresentationLogger.git_action("Creating healing branch and PR")
|
136
188
|
|
137
189
|
begin
|
138
190
|
# All Git operations happen in the isolated workspace
|
139
191
|
Dir.chdir(workspace_path) do
|
140
|
-
|
141
|
-
|
142
|
-
#
|
143
|
-
puts "๐ฟ [WORKSPACE] Git remote origin: #{`git config --get remote.origin.url`.strip}"
|
144
|
-
puts "๐ฟ [WORKSPACE] Current branch: #{`git branch --show-current`.strip}"
|
192
|
+
PresentationLogger.detail("Working in isolated workspace")
|
193
|
+
PresentationLogger.detail("Remote: #{`git config --get remote.origin.url`.strip}")
|
194
|
+
PresentationLogger.detail("Current branch: #{`git branch --show-current`.strip}")
|
145
195
|
|
146
196
|
# Ensure we're on the target branch
|
147
197
|
system("git checkout #{branch_name}")
|
@@ -151,75 +201,80 @@ module CodeHealer
|
|
151
201
|
healing_branch = "code-healer-fix-#{Time.now.to_i}"
|
152
202
|
system("git checkout -b #{healing_branch}")
|
153
203
|
|
154
|
-
# Check Git status
|
155
|
-
puts "๐ [WORKSPACE] Git status in workspace:"
|
156
|
-
system("git status --porcelain")
|
157
|
-
|
158
204
|
# Add all changes (the fixes are already applied in the workspace)
|
159
|
-
|
205
|
+
add_only_relevant_files(workspace_path)
|
206
|
+
|
207
|
+
# Validate workspace before commit to ensure no temporary files
|
208
|
+
unless validate_workspace_for_commit(workspace_path)
|
209
|
+
PresentationLogger.detail("Retrying file addition after validation")
|
210
|
+
add_only_relevant_files(workspace_path)
|
211
|
+
end
|
160
212
|
|
161
213
|
# Check if there are changes to commit
|
162
214
|
if system("git diff --cached --quiet") == false
|
163
|
-
|
215
|
+
PresentationLogger.detail("Committing changes to healing branch")
|
164
216
|
commit_message = "Fix applied by CodeHealer: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
165
217
|
system("git commit -m '#{commit_message}'")
|
166
218
|
|
167
219
|
# Push healing branch from workspace
|
168
|
-
|
220
|
+
PresentationLogger.detail("Pushing healing branch")
|
169
221
|
system("git push origin #{healing_branch}")
|
170
222
|
|
171
|
-
|
172
|
-
|
173
|
-
puts "๐ All changes committed in isolated workspace"
|
223
|
+
PresentationLogger.success("Branch created and pushed: #{healing_branch}")
|
224
|
+
PresentationLogger.detail("Main repository remains untouched")
|
174
225
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
226
|
+
# Create pull request if auto-create is enabled
|
227
|
+
if should_create_pull_request?
|
228
|
+
PresentationLogger.detail("Creating pull request")
|
229
|
+
pr_url = create_pull_request(healing_branch, branch_name)
|
230
|
+
if pr_url
|
231
|
+
PresentationLogger.success("Pull request created: #{pr_url}")
|
232
|
+
else
|
233
|
+
PresentationLogger.warn("Failed to create pull request")
|
234
|
+
end
|
235
|
+
else
|
236
|
+
PresentationLogger.info("Review changes and create PR when ready")
|
237
|
+
end
|
184
238
|
|
185
239
|
healing_branch
|
186
240
|
else
|
187
|
-
|
188
|
-
|
189
|
-
puts " - The fixes were not applied to the workspace"
|
190
|
-
puts " - There was an issue with the healing process"
|
241
|
+
PresentationLogger.warn("No changes detected in workspace")
|
242
|
+
PresentationLogger.detail("This might indicate fixes were not applied or there was an issue")
|
191
243
|
|
192
244
|
# Delete the empty branch
|
193
245
|
system("git checkout #{branch_name}")
|
194
246
|
system("git branch -D #{healing_branch}")
|
195
|
-
|
247
|
+
PresentationLogger.detail("Deleted empty healing branch: #{healing_branch}")
|
196
248
|
nil
|
197
249
|
end
|
198
250
|
end
|
199
251
|
rescue => e
|
200
|
-
|
252
|
+
PresentationLogger.error("Failed to create healing branch: #{e.message}")
|
201
253
|
nil
|
202
254
|
end
|
203
255
|
end
|
204
256
|
|
205
|
-
def cleanup_workspace(workspace_path)
|
206
|
-
puts "๐งน [WORKSPACE] Starting workspace cleanup..."
|
207
|
-
puts "๐งน [WORKSPACE] Target: #{workspace_path}"
|
208
|
-
puts "๐งน [WORKSPACE] Exists: #{Dir.exist?(workspace_path)}"
|
209
|
-
|
257
|
+
def cleanup_workspace(workspace_path, force = false)
|
210
258
|
return unless Dir.exist?(workspace_path)
|
211
259
|
|
260
|
+
# Check if this is a persistent workspace
|
261
|
+
is_persistent = workspace_path.include?('persistent_')
|
262
|
+
|
263
|
+
if is_persistent && !force
|
264
|
+
PresentationLogger.detail("Skipping cleanup of persistent workspace")
|
265
|
+
return
|
266
|
+
end
|
267
|
+
|
268
|
+
PresentationLogger.detail("Cleaning up workspace")
|
269
|
+
|
212
270
|
# Remove .git directory first to avoid conflicts
|
213
271
|
git_dir = File.join(workspace_path, '.git')
|
214
272
|
if Dir.exist?(git_dir)
|
215
|
-
puts "๐งน [WORKSPACE] Removing .git directory to prevent conflicts..."
|
216
273
|
FileUtils.rm_rf(git_dir)
|
217
274
|
end
|
218
275
|
|
219
|
-
puts "๐งน [WORKSPACE] Removing workspace directory..."
|
220
276
|
FileUtils.rm_rf(workspace_path)
|
221
|
-
|
222
|
-
puts "๐งน [WORKSPACE] Directory still exists: #{Dir.exist?(workspace_path)}"
|
277
|
+
PresentationLogger.detail("Workspace cleanup completed")
|
223
278
|
end
|
224
279
|
|
225
280
|
def cleanup_expired_workspaces
|
@@ -258,17 +313,25 @@ module CodeHealer
|
|
258
313
|
begin
|
259
314
|
require 'octokit'
|
260
315
|
|
261
|
-
|
262
|
-
github_token = ENV['GITHUB_TOKEN']
|
263
|
-
repo_name = config['github_repo'] || config[:github_repo]
|
316
|
+
# Try to get GitHub token from environment
|
317
|
+
github_token = ENV['GITHUB_TOKEN'] || ENV['GITHUB_ACCESS_TOKEN']
|
264
318
|
|
265
|
-
unless github_token
|
266
|
-
puts "โ Missing GitHub token
|
319
|
+
unless github_token
|
320
|
+
puts "โ Missing GitHub token. Set GITHUB_TOKEN environment variable"
|
321
|
+
puts "๐ก You can create a token at: https://github.com/settings/tokens"
|
267
322
|
return nil
|
268
323
|
end
|
269
324
|
|
270
|
-
#
|
271
|
-
|
325
|
+
# Auto-detect repository from git remote
|
326
|
+
repo_name = detect_github_repository
|
327
|
+
|
328
|
+
unless repo_name
|
329
|
+
puts "โ Could not detect GitHub repository from git remote"
|
330
|
+
puts "๐ก Make sure your repository has a GitHub remote origin"
|
331
|
+
return nil
|
332
|
+
end
|
333
|
+
|
334
|
+
puts "๐ Creating PR for repository: #{repo_name}"
|
272
335
|
|
273
336
|
client = Octokit::Client.new(access_token: github_token)
|
274
337
|
|
@@ -283,13 +346,283 @@ module CodeHealer
|
|
283
346
|
"Generated at: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
284
347
|
)
|
285
348
|
|
349
|
+
puts "โ
Pull request created successfully: #{pr.html_url}"
|
286
350
|
pr.html_url
|
287
351
|
rescue => e
|
288
352
|
puts "โ Failed to create pull request: #{e.message}"
|
353
|
+
puts "๐ก Check your GitHub token and repository access"
|
289
354
|
nil
|
290
355
|
end
|
291
356
|
end
|
357
|
+
|
358
|
+
def detect_github_repository
|
359
|
+
# Try to detect from current git remote
|
360
|
+
Dir.chdir(Dir.pwd) do
|
361
|
+
remote_url = `git config --get remote.origin.url`.strip
|
362
|
+
|
363
|
+
if remote_url.include?('github.com')
|
364
|
+
# Extract owner/repo from GitHub URL
|
365
|
+
if remote_url.include?('git@github.com:')
|
366
|
+
# SSH format: git@github.com:owner/repo.git
|
367
|
+
repo_part = remote_url.gsub('git@github.com:', '').gsub('.git', '')
|
368
|
+
elsif remote_url.include?('https://github.com/')
|
369
|
+
# HTTPS format: https://github.com/owner/repo.git
|
370
|
+
repo_part = remote_url.gsub('https://github.com/', '').gsub('.git', '')
|
371
|
+
else
|
372
|
+
return nil
|
373
|
+
end
|
374
|
+
puts "๐ Detected GitHub repository: #{repo_part}"
|
375
|
+
return repo_part
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
nil
|
380
|
+
rescue
|
381
|
+
nil
|
382
|
+
end
|
292
383
|
|
384
|
+
def extract_repo_name(repo_path)
|
385
|
+
# Extract repo name from path or git remote
|
386
|
+
if File.exist?(File.join(repo_path, '.git'))
|
387
|
+
Dir.chdir(repo_path) do
|
388
|
+
remote_url = `git config --get remote.origin.url`.strip
|
389
|
+
if remote_url.include?('/')
|
390
|
+
remote_url.split('/').last.gsub('.git', '')
|
391
|
+
else
|
392
|
+
File.basename(repo_path)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
else
|
396
|
+
File.basename(repo_path)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def create_persistent_workspace(repo_path, workspace_path, branch_name)
|
401
|
+
puts "๐ฅ [WORKSPACE] Creating new persistent workspace..."
|
402
|
+
|
403
|
+
# Get the GitHub remote URL
|
404
|
+
Dir.chdir(repo_path) do
|
405
|
+
remote_url = `git config --get remote.origin.url`.strip
|
406
|
+
if remote_url.empty?
|
407
|
+
puts "โ [WORKSPACE] No remote origin found in #{repo_path}"
|
408
|
+
return false
|
409
|
+
end
|
410
|
+
|
411
|
+
puts "๐ฅ [WORKSPACE] Cloning from: #{remote_url}"
|
412
|
+
puts "๐ฅ [WORKSPACE] To workspace: #{workspace_path}"
|
413
|
+
|
414
|
+
# Clone the full repository for persistent use
|
415
|
+
result = system("git clone #{remote_url} #{workspace_path}")
|
416
|
+
if result
|
417
|
+
puts "๐ฅ [WORKSPACE] Repository cloned successfully"
|
418
|
+
# Now checkout to the target branch
|
419
|
+
checkout_to_branch(workspace_path, branch_name, repo_path)
|
420
|
+
else
|
421
|
+
puts "โ [WORKSPACE] Failed to clone repository"
|
422
|
+
return false
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def checkout_to_branch(workspace_path, branch_name, repo_path)
|
428
|
+
puts "๐ฅ [WORKSPACE] Checking out to target branch..."
|
429
|
+
|
430
|
+
# Determine target branch
|
431
|
+
target_branch = branch_name || CodeHealer::ConfigManager.pr_target_branch || get_default_branch(repo_path)
|
432
|
+
puts "๐ฅ [WORKSPACE] Target branch: #{target_branch}"
|
433
|
+
|
434
|
+
Dir.chdir(workspace_path) do
|
435
|
+
# Fetch latest changes
|
436
|
+
puts "๐ฅ [WORKSPACE] Fetching latest changes..."
|
437
|
+
system("git fetch origin")
|
438
|
+
|
439
|
+
# Check if branch exists locally
|
440
|
+
local_branch_exists = system("git show-ref --verify --quiet refs/heads/#{target_branch}")
|
441
|
+
|
442
|
+
if local_branch_exists
|
443
|
+
puts "๐ฅ [WORKSPACE] Checking out existing local branch: #{target_branch}"
|
444
|
+
system("git checkout #{target_branch}")
|
445
|
+
else
|
446
|
+
puts "๐ฅ [WORKSPACE] Checking out remote branch: #{target_branch}"
|
447
|
+
system("git checkout -b #{target_branch} origin/#{target_branch}")
|
448
|
+
end
|
449
|
+
|
450
|
+
# Pull latest changes
|
451
|
+
puts "๐ฅ [WORKSPACE] Pulling latest changes..."
|
452
|
+
system("git pull origin #{target_branch}")
|
453
|
+
|
454
|
+
# Ensure workspace is clean
|
455
|
+
puts "๐ฅ [WORKSPACE] Ensuring workspace is clean..."
|
456
|
+
system("git reset --hard HEAD")
|
457
|
+
system("git clean -fd")
|
458
|
+
|
459
|
+
# Remove any tracked temporary files that shouldn't be committed - AGGRESSIVE cleanup
|
460
|
+
puts "๐ฅ [WORKSPACE] Removing tracked temporary files..."
|
461
|
+
|
462
|
+
# Remove root level temporary directories
|
463
|
+
system("git rm -r --cached tmp/ 2>/dev/null || true")
|
464
|
+
system("git rm -r --cached log/ 2>/dev/null || true")
|
465
|
+
system("git rm -r --cached .bundle/ 2>/dev/null || true")
|
466
|
+
system("git rm -r --cached storage/ 2>/dev/null || true")
|
467
|
+
system("git rm -r --cached coverage/ 2>/dev/null || true")
|
468
|
+
system("git rm -r --cached .yardoc/ 2>/dev/null || true")
|
469
|
+
system("git rm -r --cached .rspec_status 2>/dev/null || true")
|
470
|
+
|
471
|
+
# Remove any nested tmp/ or log/ directories that might be tracked
|
472
|
+
system("find . -name 'tmp' -type d -exec git rm -r --cached {} + 2>/dev/null || true")
|
473
|
+
system("find . -name 'log' -type d -exec git rm -r --cached {} + 2>/dev/null || true")
|
474
|
+
|
475
|
+
# Remove any .tmp, .log, .cache files
|
476
|
+
system("find . -name '*.tmp' -exec git rm --cached {} + 2>/dev/null || true")
|
477
|
+
system("find . -name '*.log' -exec git rm --cached {} + 2>/dev/null || true")
|
478
|
+
system("find . -name '*.cache' -exec git rm --cached {} + 2>/dev/null || true")
|
479
|
+
|
480
|
+
puts "๐ฅ [WORKSPACE] Successfully checked out to: #{target_branch}"
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def get_default_branch(repo_path)
|
485
|
+
Dir.chdir(repo_path) do
|
486
|
+
# Try to get default branch from remote
|
487
|
+
default_branch = `git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null`.strip
|
488
|
+
if default_branch.include?('origin/')
|
489
|
+
default_branch.gsub('refs/remotes/origin/', '')
|
490
|
+
else
|
491
|
+
# Fallback to common defaults
|
492
|
+
['main', 'master'].find { |branch| system("git ls-remote --heads origin #{branch} >/dev/null 2>&1") }
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
def get_current_branch(workspace_path)
|
498
|
+
Dir.chdir(workspace_path) do
|
499
|
+
`git branch --show-current`.strip
|
500
|
+
end
|
501
|
+
rescue
|
502
|
+
'unknown'
|
503
|
+
end
|
504
|
+
|
505
|
+
def add_only_relevant_files(workspace_path)
|
506
|
+
puts "๐ [WORKSPACE] Adding only relevant files, respecting .gitignore..."
|
507
|
+
|
508
|
+
Dir.chdir(workspace_path) do
|
509
|
+
# First, ensure .gitignore is respected
|
510
|
+
if File.exist?('.gitignore')
|
511
|
+
puts "๐ [WORKSPACE] Using repository's .gitignore file"
|
512
|
+
else
|
513
|
+
puts "๐ [WORKSPACE] No .gitignore found, using default patterns"
|
514
|
+
end
|
515
|
+
|
516
|
+
# Get list of modified files
|
517
|
+
modified_files = `git status --porcelain | grep '^ M\\|^M \\|^A ' | awk '{print $2}'`.strip.split("\n")
|
518
|
+
|
519
|
+
if modified_files.empty?
|
520
|
+
puts "๐ [WORKSPACE] No modified files to add"
|
521
|
+
return
|
522
|
+
end
|
523
|
+
|
524
|
+
puts "๐ [WORKSPACE] Modified files: #{modified_files.join(', ')}"
|
525
|
+
|
526
|
+
# Add each modified file individually
|
527
|
+
modified_files.each do |file|
|
528
|
+
next if file.empty?
|
529
|
+
|
530
|
+
# Skip temporary and generated files
|
531
|
+
if should_skip_file?(file)
|
532
|
+
puts "๐ [WORKSPACE] Skipping temporary file: #{file}"
|
533
|
+
next
|
534
|
+
end
|
535
|
+
|
536
|
+
puts "๐ [WORKSPACE] Adding file: #{file}"
|
537
|
+
system("git add '#{file}'")
|
538
|
+
end
|
539
|
+
|
540
|
+
puts "๐ [WORKSPACE] File addition completed"
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
def should_skip_file?(file_path)
|
545
|
+
# Skip temporary and generated files - AGGRESSIVE filtering
|
546
|
+
skip_patterns = [
|
547
|
+
# Root level directories - NEVER commit these
|
548
|
+
/^tmp\//,
|
549
|
+
/^log\//,
|
550
|
+
/^\.git\//,
|
551
|
+
/^node_modules\//,
|
552
|
+
/^vendor\//,
|
553
|
+
/^public\/packs/,
|
554
|
+
/^public\/assets/,
|
555
|
+
/^\.bundle\//,
|
556
|
+
/^bootsnap/,
|
557
|
+
/^cache/,
|
558
|
+
/^storage\//,
|
559
|
+
/^coverage\//,
|
560
|
+
/^\.yardoc\//,
|
561
|
+
/^\.rspec_status/,
|
562
|
+
|
563
|
+
# Any tmp/ directory at any level
|
564
|
+
/tmp\//,
|
565
|
+
|
566
|
+
# Any log/ directory at any level
|
567
|
+
/log\//,
|
568
|
+
|
569
|
+
# Specific tmp subdirectories
|
570
|
+
/tmp\/cache\//,
|
571
|
+
/tmp\/bootsnap\//,
|
572
|
+
/tmp\/pids\//,
|
573
|
+
/tmp\/sockets\//,
|
574
|
+
/tmp\/sessions\//,
|
575
|
+
/tmp\/backup\//,
|
576
|
+
/tmp\/test\//,
|
577
|
+
|
578
|
+
# File extensions
|
579
|
+
/\.tmp$/,
|
580
|
+
/\.log$/,
|
581
|
+
/\.cache$/,
|
582
|
+
/\.swp$/,
|
583
|
+
/\.swo$/,
|
584
|
+
/\.bak$/,
|
585
|
+
/\.backup$/,
|
586
|
+
/~$/,
|
587
|
+
|
588
|
+
# Rails specific
|
589
|
+
/\.bundle\//,
|
590
|
+
/Gemfile\.lock\.backup/,
|
591
|
+
/config\/database\.yml\.backup/,
|
592
|
+
/config\/secrets\.yml\.backup/
|
593
|
+
]
|
594
|
+
|
595
|
+
# Additional check: if path contains 'tmp' or 'log' anywhere, skip it
|
596
|
+
if file_path.include?('tmp') || file_path.include?('log')
|
597
|
+
puts "๐ [WORKSPACE] Skipping file containing 'tmp' or 'log': #{file_path}"
|
598
|
+
return true
|
599
|
+
end
|
600
|
+
|
601
|
+
skip_patterns.any? { |pattern| file_path.match?(pattern) }
|
602
|
+
end
|
603
|
+
|
604
|
+
def reset_workspace_to_clean_state(workspace_path, branch_name = nil)
|
605
|
+
puts "๐ [WORKSPACE] Resetting workspace to clean state..."
|
606
|
+
|
607
|
+
Dir.chdir(workspace_path) do
|
608
|
+
# Stash any uncommitted changes
|
609
|
+
puts "๐ [WORKSPACE] Stashing any uncommitted changes..."
|
610
|
+
system("git stash")
|
611
|
+
|
612
|
+
# Reset to clean state
|
613
|
+
puts "๐ [WORKSPACE] Resetting to clean state..."
|
614
|
+
system("git reset --hard HEAD")
|
615
|
+
system("git clean -fd")
|
616
|
+
|
617
|
+
# Checkout to target branch if specified
|
618
|
+
if branch_name
|
619
|
+
checkout_to_branch(workspace_path, branch_name, nil)
|
620
|
+
end
|
621
|
+
|
622
|
+
puts "๐ [WORKSPACE] Workspace reset to clean state"
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
293
626
|
def clone_strategy
|
294
627
|
cfg = CodeHealer::ConfigManager.code_heal_directory_config
|
295
628
|
cfg['clone_strategy'] || cfg[:clone_strategy] || "branch"
|