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.
@@ -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
- puts "๐Ÿฅ [WORKSPACE] Starting workspace creation..."
10
- puts "๐Ÿฅ [WORKSPACE] Repo path: #{repo_path}"
11
- puts "๐Ÿฅ [WORKSPACE] Branch name: #{branch_name || 'current'}"
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
- # Create unique workspace directory
20
- workspace_id = "healing_#{Time.now.to_i}_#{SecureRandom.hex(4)}"
21
- workspace_path = File.join(base_path, workspace_id)
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
- puts "๐Ÿฅ [WORKSPACE] Workspace ID: #{workspace_id}"
24
- puts "๐Ÿฅ [WORKSPACE] Full workspace path: #{workspace_path}"
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
- # 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)
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
- puts "๐Ÿฅ [WORKSPACE] Using full repo cloning..."
41
- clone_full_repo(repo_path, workspace_path, branch_name)
49
+ PresentationLogger.detail("Creating new workspace")
50
+ create_persistent_workspace(repo_path, workspace_path, branch_name)
42
51
  end
43
52
 
44
- puts "๐Ÿฅ [WORKSPACE] Workspace creation completed successfully!"
45
- puts "๐Ÿฅ [WORKSPACE] Final workspace path: #{workspace_path}"
46
- puts "๐Ÿฅ [WORKSPACE] Workspace contents: #{Dir.entries(workspace_path).join(', ')}"
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
- puts "โŒ Failed to create healing workspace: #{e.message}"
50
- cleanup_workspace(workspace_path) if Dir.exist?(workspace_path)
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
- puts "๐Ÿงช Testing fixes in workspace: #{workspace_path}"
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
- return false unless syntax_check
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
- return false unless bundle_check
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
- puts "๐Ÿงช Test preparation: #{test_result ? 'โœ…' : 'โš ๏ธ'}"
135
+ PresentationLogger.detail("Test preparation: #{test_result ? 'passed' : 'skipped'}")
121
136
  end
122
137
  end
123
138
  end
124
139
 
125
- puts "โœ… Workspace validation passed"
140
+ PresentationLogger.success("Validation passed")
126
141
  true
127
142
  end
128
143
  rescue => e
129
- puts "โŒ Workspace validation failed: #{e.message}"
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
- puts "๐Ÿ”„ Creating healing branch and PR from isolated workspace"
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
- puts "๐ŸŒฟ [WORKSPACE] Working in isolated workspace: #{workspace_path}"
141
-
142
- # Debug Git configuration
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
- system("git add .")
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
- puts "๐Ÿ“ [WORKSPACE] Changes detected, committing to healing branch..."
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
- puts "๐Ÿš€ [WORKSPACE] Pushing healing branch from workspace..."
220
+ PresentationLogger.detail("Pushing healing branch")
169
221
  system("git push origin #{healing_branch}")
170
222
 
171
- puts "โœ… [WORKSPACE] Healing branch created and pushed: #{healing_branch}"
172
- puts "๐Ÿ”’ Main repository (#{repo_path}) remains completely untouched"
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
- # 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
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
- puts "โš ๏ธ [WORKSPACE] No changes detected in workspace"
188
- puts "๐Ÿ” This might indicate that:"
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
- puts "๐Ÿ—‘๏ธ [WORKSPACE] Deleted empty healing branch: #{healing_branch}"
247
+ PresentationLogger.detail("Deleted empty healing branch: #{healing_branch}")
196
248
  nil
197
249
  end
198
250
  end
199
251
  rescue => e
200
- puts "โŒ Failed to create healing branch from workspace: #{e.message}"
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
- puts "๐Ÿงน [WORKSPACE] Workspace cleanup completed"
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
- config = CodeHealer::ConfigManager.config
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 && repo_name
266
- puts "โŒ Missing GitHub token or repository configuration"
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
- # Parse repo name (owner/repo)
271
- owner, repo = repo_name.split('/')
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"