enhance_swarm 1.0.0

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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.enhance_swarm/agent_scripts/frontend_agent.md +39 -0
  3. data/.enhance_swarm/user_patterns.json +37 -0
  4. data/CHANGELOG.md +184 -0
  5. data/LICENSE +21 -0
  6. data/PRODUCTION_TEST_LOG.md +502 -0
  7. data/README.md +905 -0
  8. data/Rakefile +28 -0
  9. data/USAGE_EXAMPLES.md +477 -0
  10. data/examples/enhance_workflow.md +346 -0
  11. data/examples/rails_project.md +253 -0
  12. data/exe/enhance-swarm +30 -0
  13. data/lib/enhance_swarm/additional_commands.rb +299 -0
  14. data/lib/enhance_swarm/agent_communicator.rb +460 -0
  15. data/lib/enhance_swarm/agent_reviewer.rb +283 -0
  16. data/lib/enhance_swarm/agent_spawner.rb +462 -0
  17. data/lib/enhance_swarm/cleanup_manager.rb +245 -0
  18. data/lib/enhance_swarm/cli.rb +1592 -0
  19. data/lib/enhance_swarm/command_executor.rb +78 -0
  20. data/lib/enhance_swarm/configuration.rb +324 -0
  21. data/lib/enhance_swarm/control_agent.rb +307 -0
  22. data/lib/enhance_swarm/dependency_validator.rb +195 -0
  23. data/lib/enhance_swarm/error_recovery.rb +785 -0
  24. data/lib/enhance_swarm/generator.rb +194 -0
  25. data/lib/enhance_swarm/interrupt_handler.rb +512 -0
  26. data/lib/enhance_swarm/logger.rb +106 -0
  27. data/lib/enhance_swarm/mcp_integration.rb +85 -0
  28. data/lib/enhance_swarm/monitor.rb +28 -0
  29. data/lib/enhance_swarm/notification_manager.rb +444 -0
  30. data/lib/enhance_swarm/orchestrator.rb +313 -0
  31. data/lib/enhance_swarm/output_streamer.rb +281 -0
  32. data/lib/enhance_swarm/process_monitor.rb +266 -0
  33. data/lib/enhance_swarm/progress_tracker.rb +215 -0
  34. data/lib/enhance_swarm/project_analyzer.rb +612 -0
  35. data/lib/enhance_swarm/resource_manager.rb +177 -0
  36. data/lib/enhance_swarm/retry_handler.rb +40 -0
  37. data/lib/enhance_swarm/session_manager.rb +247 -0
  38. data/lib/enhance_swarm/signal_handler.rb +95 -0
  39. data/lib/enhance_swarm/smart_defaults.rb +708 -0
  40. data/lib/enhance_swarm/task_integration.rb +150 -0
  41. data/lib/enhance_swarm/task_manager.rb +174 -0
  42. data/lib/enhance_swarm/version.rb +5 -0
  43. data/lib/enhance_swarm/visual_dashboard.rb +555 -0
  44. data/lib/enhance_swarm/web_ui.rb +211 -0
  45. data/lib/enhance_swarm.rb +69 -0
  46. data/setup.sh +86 -0
  47. data/sig/enhance_swarm.rbs +4 -0
  48. data/templates/claude/CLAUDE.md +160 -0
  49. data/templates/claude/MCP.md +117 -0
  50. data/templates/claude/PERSONAS.md +114 -0
  51. data/templates/claude/RULES.md +221 -0
  52. data/test_builtin_functionality.rb +121 -0
  53. data/test_core_components.rb +156 -0
  54. data/test_real_claude_integration.rb +285 -0
  55. data/test_security.rb +150 -0
  56. data/test_smart_defaults.rb +155 -0
  57. data/test_task_integration.rb +173 -0
  58. data/test_web_ui.rb +245 -0
  59. data/web/assets/css/main.css +645 -0
  60. data/web/assets/js/kanban.js +499 -0
  61. data/web/assets/js/main.js +525 -0
  62. data/web/templates/dashboard.html.erb +226 -0
  63. data/web/templates/kanban.html.erb +193 -0
  64. metadata +293 -0
@@ -0,0 +1,462 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'tempfile'
5
+ require_relative 'command_executor'
6
+ require_relative 'session_manager'
7
+ require_relative 'logger'
8
+ require_relative 'resource_manager'
9
+
10
+ module EnhanceSwarm
11
+ class AgentSpawner
12
+ def initialize
13
+ @config = EnhanceSwarm.configuration
14
+ @session_manager = SessionManager.new
15
+ @resource_manager = ResourceManager.new
16
+ end
17
+
18
+ def spawn_agent(role:, task:, worktree: true)
19
+ Logger.info("Spawning #{role} agent for task: #{task}")
20
+
21
+ # Check resource limits before spawning
22
+ resource_check = @resource_manager.can_spawn_agent?
23
+ unless resource_check[:allowed]
24
+ Logger.error("Cannot spawn agent - resource limits exceeded:")
25
+ resource_check[:reasons].each { |reason| Logger.error(" - #{reason}") }
26
+ return false
27
+ end
28
+
29
+ begin
30
+ # Create worktree if requested
31
+ worktree_path = nil
32
+ if worktree
33
+ worktree_path = create_agent_worktree(role)
34
+ return false unless worktree_path
35
+ end
36
+
37
+ # Generate agent prompt
38
+ prompt = build_agent_prompt(task, role, worktree_path)
39
+
40
+ # Spawn the agent process
41
+ pid = spawn_claude_process(prompt, role, worktree_path)
42
+ return false unless pid
43
+
44
+ # Register agent in session
45
+ success = @session_manager.add_agent(role, pid, worktree_path, task)
46
+
47
+ if success
48
+ Logger.info("Successfully spawned #{role} agent (PID: #{pid})")
49
+ { pid: pid, worktree_path: worktree_path, role: role }
50
+ else
51
+ Logger.error("Failed to register agent in session")
52
+ cleanup_failed_spawn(pid, worktree_path)
53
+ false
54
+ end
55
+
56
+ rescue StandardError => e
57
+ Logger.error("Failed to spawn #{role} agent: #{e.message}")
58
+ cleanup_failed_spawn(nil, worktree_path)
59
+ false
60
+ end
61
+ end
62
+
63
+ def spawn_multiple_agents(agents)
64
+ results = []
65
+
66
+ agents.each_with_index do |agent_config, index|
67
+ # Add jitter to prevent resource contention
68
+ sleep(2 + rand(0..2)) if index > 0
69
+
70
+ result = spawn_agent(
71
+ role: agent_config[:role],
72
+ task: agent_config[:task],
73
+ worktree: agent_config.fetch(:worktree, true)
74
+ )
75
+
76
+ results << result if result
77
+ end
78
+
79
+ results
80
+ end
81
+
82
+ def get_running_agents
83
+ @session_manager.check_agent_processes
84
+ end
85
+
86
+ def stop_agent(pid)
87
+ begin
88
+ Process.kill('TERM', pid.to_i)
89
+ @session_manager.update_agent_status(pid, 'stopped', Time.now.iso8601)
90
+ Logger.info("Stopped agent with PID: #{pid}")
91
+ true
92
+ rescue Errno::ESRCH
93
+ # Process already stopped
94
+ @session_manager.update_agent_status(pid, 'stopped', Time.now.iso8601)
95
+ true
96
+ rescue StandardError => e
97
+ Logger.error("Failed to stop agent (PID: #{pid}): #{e.message}")
98
+ false
99
+ end
100
+ end
101
+
102
+ def stop_all_agents
103
+ active_agents = @session_manager.get_active_agents
104
+ stopped_count = 0
105
+
106
+ active_agents.each do |agent|
107
+ if stop_agent(agent[:pid])
108
+ stopped_count += 1
109
+ end
110
+ end
111
+
112
+ Logger.info("Stopped #{stopped_count}/#{active_agents.length} agents")
113
+ stopped_count
114
+ end
115
+
116
+ def claude_cli_available?
117
+ @claude_cli_available ||= begin
118
+ result = `claude --version 2>/dev/null`
119
+ $?.success? && result.strip.length > 0
120
+ rescue StandardError
121
+ false
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def create_agent_worktree(role)
128
+ timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
129
+ worktree_name = "#{role}-#{timestamp}"
130
+ worktree_path = File.join('.enhance_swarm', 'worktrees', worktree_name)
131
+
132
+ begin
133
+ # Ensure worktrees directory exists
134
+ worktrees_dir = File.dirname(worktree_path)
135
+ FileUtils.mkdir_p(worktrees_dir) unless Dir.exist?(worktrees_dir)
136
+
137
+ # Check if we have any commits (required for git worktree)
138
+ ensure_initial_commit
139
+
140
+ # Create git worktree
141
+ CommandExecutor.execute('git', 'worktree', 'add', worktree_path)
142
+
143
+ Logger.info("Created worktree for #{role} agent: #{worktree_path}")
144
+ File.expand_path(worktree_path)
145
+
146
+ rescue CommandExecutor::CommandError => e
147
+ Logger.error("Failed to create worktree for #{role}: #{e.message}")
148
+
149
+ # If the error is about no commits, try to create one
150
+ if e.message.include?('does not have any commits yet')
151
+ Logger.info("No initial commit found, creating one...")
152
+ if create_initial_commit
153
+ retry
154
+ end
155
+ end
156
+
157
+ nil
158
+ end
159
+ end
160
+
161
+ def ensure_initial_commit
162
+ # Check if we have any commits
163
+ result = CommandExecutor.execute('git', 'log', '--oneline', '-1')
164
+ true
165
+ rescue CommandExecutor::CommandError
166
+ # No commits exist, create initial commit
167
+ create_initial_commit
168
+ end
169
+
170
+ def create_initial_commit
171
+ begin
172
+ # Add all files to git
173
+ CommandExecutor.execute('git', 'add', '.')
174
+
175
+ # Create initial commit
176
+ CommandExecutor.execute('git', 'commit', '-m', 'Initial commit - EnhanceSwarm setup')
177
+
178
+ Logger.info("Created initial git commit for EnhanceSwarm")
179
+ true
180
+ rescue CommandExecutor::CommandError => e
181
+ Logger.error("Failed to create initial commit: #{e.message}")
182
+ false
183
+ end
184
+ end
185
+
186
+ def spawn_claude_process(prompt, role, worktree_path)
187
+ begin
188
+ # Check if Claude CLI is available
189
+ unless claude_cli_available?
190
+ Logger.error("Claude CLI not available - falling back to simulation mode")
191
+ return spawn_simulated_process(role, worktree_path)
192
+ end
193
+
194
+ # Prepare the enhanced prompt for the agent
195
+ enhanced_prompt = build_enhanced_agent_prompt(prompt, role, worktree_path)
196
+
197
+ # Create working directory for the agent
198
+ agent_dir = worktree_path || Dir.pwd
199
+
200
+ # Prepare environment
201
+ env = build_agent_environment(role, agent_dir)
202
+
203
+ # Create a temporary script to handle the Claude interaction
204
+ script_file = create_agent_script(enhanced_prompt, role, agent_dir)
205
+
206
+ # Ensure logs directory exists
207
+ FileUtils.mkdir_p(File.join('.enhance_swarm', 'logs'))
208
+
209
+ # Spawn the Claude process
210
+ pid = Process.spawn(
211
+ env,
212
+ '/bin/bash', script_file,
213
+ chdir: agent_dir,
214
+ out: File.join('.enhance_swarm', 'logs', "#{role}_output.log"),
215
+ err: File.join('.enhance_swarm', 'logs', "#{role}_error.log")
216
+ )
217
+
218
+ # Don't wait for the process - let it run independently
219
+ Process.detach(pid)
220
+
221
+ Logger.info("Spawned Claude agent process: #{role} (PID: #{pid})")
222
+ pid
223
+
224
+ rescue StandardError => e
225
+ Logger.error("Failed to spawn Claude process for #{role}: #{e.message}")
226
+
227
+ # Enhanced debugging information
228
+ if ENV['ENHANCE_SWARM_DEBUG'] == 'true'
229
+ Logger.error("Debug info - Error class: #{e.class}")
230
+ Logger.error("Debug info - Backtrace: #{e.backtrace.first(3).join(', ')}")
231
+ Logger.error("Debug info - Working directory: #{agent_dir}")
232
+ Logger.error("Debug info - Claude CLI available: #{claude_cli_available?}")
233
+ end
234
+
235
+ # Fall back to simulation mode
236
+ spawn_simulated_process(role, worktree_path)
237
+ end
238
+ end
239
+
240
+ def build_enhanced_agent_prompt(base_prompt, role, worktree_path)
241
+ config = EnhanceSwarm.configuration
242
+
243
+ <<~PROMPT
244
+ You are a specialized #{role.upcase} agent working as part of an EnhanceSwarm multi-agent team.
245
+
246
+ ## Your Role: #{role.capitalize}
247
+ #{get_role_description(role)}
248
+
249
+ ## Working Context:
250
+ - Project: #{config.project_name}
251
+ - Technology Stack: #{config.technology_stack}
252
+ - Working Directory: #{worktree_path || Dir.pwd}
253
+ - Code Standards: #{config.code_standards.join(', ')}
254
+
255
+ ## Your Task:
256
+ #{base_prompt}
257
+
258
+ ## Important Instructions:
259
+ 1. Stay focused on your role as a #{role} specialist
260
+ 2. Follow the project's code standards and conventions
261
+ 3. Work autonomously but consider integration with other agents
262
+ 4. Create high-quality, production-ready code
263
+ 5. Include comprehensive tests where appropriate
264
+ 6. Document your changes and decisions
265
+ 7. If you encounter permission issues, provide detailed implementation plans instead
266
+ 8. Always output what you would implement, even if file operations fail
267
+
268
+ ## Available Tools:
269
+ You have access to all Claude Code tools for file editing, terminal commands, and project analysis.
270
+ Note: If file write operations fail due to permissions, focus on providing comprehensive
271
+ implementation details and code that could be manually applied.
272
+
273
+ Begin working on your assigned task now.
274
+ PROMPT
275
+ end
276
+
277
+ def get_role_description(role)
278
+ case role.to_s.downcase
279
+ when 'backend'
280
+ 'You specialize in server-side logic, APIs, database design, models, and business logic implementation.'
281
+ when 'frontend'
282
+ 'You specialize in user interfaces, client-side code, styling, user experience, and presentation layer.'
283
+ when 'qa'
284
+ 'You specialize in testing, quality assurance, test automation, edge case analysis, and validation.'
285
+ when 'ux'
286
+ 'You specialize in user experience design, interaction flows, accessibility, and user-centric improvements.'
287
+ when 'general'
288
+ 'You are a general-purpose agent capable of handling various development tasks across the full stack.'
289
+ else
290
+ "You are a #{role} specialist agent focusing on your area of expertise."
291
+ end
292
+ end
293
+
294
+ def create_agent_script(prompt, role, working_dir)
295
+ # Create a temporary script file that will run Claude
296
+ script_file = Tempfile.new(['agent_script', '.sh'])
297
+
298
+ begin
299
+ script_content = <<~SCRIPT
300
+ #!/bin/bash
301
+ set -e
302
+
303
+ # Agent script for #{role} agent
304
+ echo "Starting #{role} agent in #{working_dir}"
305
+
306
+ # Change to working directory
307
+ cd "#{working_dir}"
308
+
309
+ # Create a unique temporary prompt file using PID and timestamp
310
+ TIMESTAMP=$(date +%s)
311
+ PROMPT_FILE="/tmp/claude_prompt_#{role}_$${TIMESTAMP}_$$.md"
312
+
313
+ # Ensure unique filename by adding counter if needed
314
+ COUNTER=0
315
+ while [[ -e "$PROMPT_FILE" ]]; do
316
+ COUNTER=$((COUNTER + 1))
317
+ PROMPT_FILE="/tmp/claude_prompt_#{role}_$${TIMESTAMP}_$$_${COUNTER}.md"
318
+ done
319
+
320
+ cat > "$PROMPT_FILE" << 'EOF'
321
+ #{prompt}
322
+ EOF
323
+
324
+ # Run Claude with the prompt
325
+ echo "Executing Claude for #{role} agent..."
326
+ claude --print < "$PROMPT_FILE"
327
+
328
+ # Cleanup
329
+ rm -f "$PROMPT_FILE"
330
+
331
+ echo "#{role} agent completed successfully"
332
+ SCRIPT
333
+
334
+ script_file.write(script_content)
335
+ script_file.flush
336
+ script_file.close
337
+
338
+ # Make the script executable
339
+ File.chmod(0755, script_file.path)
340
+
341
+ script_file.path
342
+ rescue StandardError => e
343
+ Logger.error("Failed to create agent script: #{e.message}")
344
+ script_file.close if script_file && !script_file.closed?
345
+ raise e
346
+ end
347
+ end
348
+
349
+ def spawn_simulated_process(role, worktree_path)
350
+ # Fallback simulation when Claude CLI is not available
351
+ Logger.warn("Using simulation mode for #{role} agent")
352
+
353
+ # Create a simple background process that simulates agent work
354
+ pid = Process.spawn(
355
+ '/bin/bash', '-c',
356
+ "sleep 30 && echo 'Simulated #{role} agent completed' > /dev/null",
357
+ chdir: worktree_path || Dir.pwd
358
+ )
359
+
360
+ Process.detach(pid)
361
+ Logger.info("Spawned simulated agent: #{role} (PID: #{pid})")
362
+ pid
363
+ end
364
+
365
+ def build_agent_environment(role, worktree_path)
366
+ env = ENV.to_h
367
+ env['ENHANCE_SWARM_ROLE'] = role
368
+ env['ENHANCE_SWARM_WORKTREE'] = worktree_path if worktree_path
369
+ env['ENHANCE_SWARM_SESSION'] = @session_manager.read_session&.dig(:session_id)
370
+ env
371
+ end
372
+
373
+ def build_agent_prompt(task, role, worktree_path)
374
+ # Sanitize inputs
375
+ safe_task = sanitize_task_description(task)
376
+ safe_role = sanitize_role(role)
377
+ safe_test_command = sanitize_command(@config.test_command)
378
+
379
+ timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
380
+ task_words = safe_task.split.first(5).join(' ')
381
+ working_dir = worktree_path || Dir.pwd
382
+
383
+ base_prompt = <<~PROMPT
384
+ AUTONOMOUS EXECUTION REQUIRED - #{safe_role.upcase} SPECIALIST
385
+
386
+ TASK: #{safe_task}
387
+
388
+ WORKING DIRECTORY: #{working_dir}
389
+
390
+ CRITICAL INSTRUCTIONS:
391
+ 1. You have FULL PERMISSION to read, write, edit files and run commands
392
+ 2. Work in the directory: #{working_dir}
393
+ 3. DO NOT wait for any permissions - proceed immediately
394
+ 4. Complete the task fully and thoroughly
395
+ 5. Test your implementation using: #{safe_test_command}
396
+ 6. When complete:
397
+ - Run: git add -A
398
+ - Run: git commit -m '#{safe_role}: #{task_words}...'
399
+ - Create completion marker: echo "completed" > .enhance_swarm/completed/#{safe_role}_completed.txt
400
+ 7. Document what was implemented in your final message
401
+
402
+ PROJECT CONTEXT:
403
+ - Technology stack: #{Array(@config.technology_stack).join(', ')}
404
+ - Test command: #{safe_test_command}
405
+ - Project type: #{@config.project_name}
406
+
407
+ Remember: You are autonomous. Make all decisions needed to complete this task successfully.
408
+ PROMPT
409
+
410
+ # Add role-specific instructions
411
+ case safe_role
412
+ when 'ux'
413
+ base_prompt += "\n\nFOCUS: UI/UX design, templates, user experience, styling, and accessibility."
414
+ when 'backend'
415
+ base_prompt += "\n\nFOCUS: Models, services, APIs, business logic, database operations, and security."
416
+ when 'frontend'
417
+ base_prompt += "\n\nFOCUS: Controllers, views, JavaScript, forms, user interactions, and integration."
418
+ when 'qa'
419
+ base_prompt += "\n\nFOCUS: Comprehensive testing, edge cases, quality assurance, and validation."
420
+ end
421
+
422
+ base_prompt
423
+ end
424
+
425
+ def cleanup_failed_spawn(pid, worktree_path)
426
+ # Clean up process if it was started
427
+ if pid
428
+ begin
429
+ Process.kill('KILL', pid.to_i)
430
+ rescue StandardError
431
+ # Process may not exist, ignore
432
+ end
433
+ end
434
+
435
+ # Clean up worktree if it was created
436
+ if worktree_path && Dir.exist?(worktree_path)
437
+ begin
438
+ CommandExecutor.execute('git', 'worktree', 'remove', '--force', worktree_path)
439
+ rescue StandardError => e
440
+ Logger.warn("Failed to cleanup worktree #{worktree_path}: #{e.message}")
441
+ end
442
+ end
443
+ end
444
+
445
+ def sanitize_task_description(task)
446
+ # Remove potentially dangerous characters while preserving readability
447
+ task.to_s.gsub(/[`$\\;|&><]/, '').strip
448
+ end
449
+
450
+ def sanitize_role(role)
451
+ # Only allow known safe roles
452
+ allowed_roles = %w[ux backend frontend qa general]
453
+ role = role.to_s.downcase.strip
454
+ allowed_roles.include?(role) ? role : 'general'
455
+ end
456
+
457
+ def sanitize_command(command)
458
+ # Basic command sanitization - remove shell metacharacters
459
+ command.to_s.gsub(/[;&|`$\\]/, '').strip
460
+ end
461
+ end
462
+ end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require_relative 'command_executor'
5
+ require_relative 'logger'
6
+
7
+ module EnhanceSwarm
8
+ class CleanupManager
9
+ CLEANUP_TIMEOUT = 30 # seconds
10
+
11
+ def self.cleanup_failed_operation(operation_id, details = {})
12
+ Logger.info("Starting cleanup for failed operation: #{operation_id}")
13
+
14
+ cleanup_tasks = []
15
+
16
+ # Cleanup git worktrees
17
+ if details[:worktree_path]
18
+ cleanup_tasks << -> { cleanup_worktree(details[:worktree_path]) }
19
+ end
20
+
21
+ # Cleanup git branches
22
+ if details[:branch_name]
23
+ cleanup_tasks << -> { cleanup_branch(details[:branch_name]) }
24
+ end
25
+
26
+ # Cleanup temporary files
27
+ if details[:temp_files]
28
+ cleanup_tasks << -> { cleanup_temp_files(details[:temp_files]) }
29
+ end
30
+
31
+ # Kill hanging processes
32
+ if details[:process_pid]
33
+ cleanup_tasks << -> { cleanup_process(details[:process_pid]) }
34
+ end
35
+
36
+ # Execute cleanup tasks with timeout protection
37
+ cleanup_results = execute_cleanup_tasks(cleanup_tasks)
38
+
39
+ Logger.log_operation("cleanup_#{operation_id}", 'completed', cleanup_results)
40
+ cleanup_results
41
+ end
42
+
43
+ def self.cleanup_all_swarm_resources
44
+ Logger.info("Starting comprehensive swarm resource cleanup")
45
+
46
+ results = {
47
+ worktrees: cleanup_swarm_worktrees,
48
+ branches: cleanup_swarm_branches,
49
+ processes: cleanup_swarm_processes,
50
+ temp_files: cleanup_swarm_temp_files
51
+ }
52
+
53
+ Logger.log_operation('cleanup_all', 'completed', results)
54
+ results
55
+ end
56
+
57
+ private
58
+
59
+ def self.execute_cleanup_tasks(tasks)
60
+ results = []
61
+
62
+ tasks.each_with_index do |task, index|
63
+ begin
64
+ Timeout.timeout(CLEANUP_TIMEOUT) do
65
+ result = task.call
66
+ results << { task: index, status: 'success', result: result }
67
+ end
68
+ rescue Timeout::Error
69
+ Logger.warn("Cleanup task #{index} timed out after #{CLEANUP_TIMEOUT}s")
70
+ results << { task: index, status: 'timeout', error: 'Cleanup timed out' }
71
+ rescue StandardError => e
72
+ Logger.error("Cleanup task #{index} failed: #{e.message}")
73
+ results << { task: index, status: 'failed', error: e.message }
74
+ end
75
+ end
76
+
77
+ results
78
+ end
79
+
80
+ def self.cleanup_worktree(worktree_path)
81
+ return { status: 'skipped', reason: 'Path not provided' } unless worktree_path
82
+
83
+ begin
84
+ if Dir.exist?(worktree_path)
85
+ # Force remove worktree
86
+ CommandExecutor.execute('git', 'worktree', 'remove', '--force', worktree_path)
87
+ Logger.info("Removed worktree: #{worktree_path}")
88
+ { status: 'success', path: worktree_path }
89
+ else
90
+ { status: 'not_found', path: worktree_path }
91
+ end
92
+ rescue CommandExecutor::CommandError => e
93
+ Logger.error("Failed to cleanup worktree #{worktree_path}: #{e.message}")
94
+ # Try manual cleanup if git command fails
95
+ FileUtils.rm_rf(worktree_path) if Dir.exist?(worktree_path)
96
+ { status: 'manual_cleanup', path: worktree_path, error: e.message }
97
+ end
98
+ end
99
+
100
+ def self.cleanup_branch(branch_name)
101
+ return { status: 'skipped', reason: 'Branch not provided' } unless branch_name
102
+
103
+ begin
104
+ # Check if branch exists
105
+ CommandExecutor.execute('git', 'rev-parse', '--verify', branch_name)
106
+
107
+ # Force delete branch
108
+ CommandExecutor.execute('git', 'branch', '-D', branch_name)
109
+ Logger.info("Deleted branch: #{branch_name}")
110
+ { status: 'success', branch: branch_name }
111
+ rescue CommandExecutor::CommandError => e
112
+ if e.message.include?('does not exist')
113
+ { status: 'not_found', branch: branch_name }
114
+ else
115
+ Logger.error("Failed to cleanup branch #{branch_name}: #{e.message}")
116
+ { status: 'failed', branch: branch_name, error: e.message }
117
+ end
118
+ end
119
+ end
120
+
121
+ def self.cleanup_temp_files(file_patterns)
122
+ return { status: 'skipped', reason: 'No patterns provided' } unless file_patterns
123
+
124
+ cleaned_files = []
125
+ file_patterns.each do |pattern|
126
+ Dir.glob(pattern).each do |file|
127
+ begin
128
+ FileUtils.rm_f(file)
129
+ cleaned_files << file
130
+ rescue StandardError => e
131
+ Logger.error("Failed to remove temp file #{file}: #{e.message}")
132
+ end
133
+ end
134
+ end
135
+
136
+ { status: 'success', files_removed: cleaned_files.size, files: cleaned_files }
137
+ end
138
+
139
+ def self.cleanup_process(pid)
140
+ return { status: 'skipped', reason: 'No PID provided' } unless pid
141
+
142
+ begin
143
+ # Check if process exists
144
+ Process.kill(0, pid.to_i)
145
+
146
+ # Try graceful termination first
147
+ Process.kill('TERM', pid.to_i)
148
+ sleep(2)
149
+
150
+ # Force kill if still running
151
+ begin
152
+ Process.kill(0, pid.to_i)
153
+ Process.kill('KILL', pid.to_i)
154
+ Logger.info("Force killed process: #{pid}")
155
+ { status: 'force_killed', pid: pid }
156
+ rescue Errno::ESRCH
157
+ Logger.info("Process terminated gracefully: #{pid}")
158
+ { status: 'terminated', pid: pid }
159
+ end
160
+ rescue Errno::ESRCH
161
+ { status: 'not_found', pid: pid }
162
+ rescue Errno::EPERM
163
+ Logger.error("Permission denied killing process: #{pid}")
164
+ { status: 'permission_denied', pid: pid }
165
+ end
166
+ end
167
+
168
+ def self.cleanup_swarm_worktrees
169
+ begin
170
+ output = CommandExecutor.execute('git', 'worktree', 'list', '--porcelain')
171
+ worktrees = parse_worktree_list(output)
172
+
173
+ swarm_worktrees = worktrees.select { |wt| wt[:branch]&.start_with?('swarm/') }
174
+
175
+ results = swarm_worktrees.map do |worktree|
176
+ cleanup_worktree(worktree[:path])
177
+ end
178
+
179
+ { count: results.size, results: results }
180
+ rescue CommandExecutor::CommandError => e
181
+ Logger.error("Failed to list worktrees for cleanup: #{e.message}")
182
+ { count: 0, error: e.message }
183
+ end
184
+ end
185
+
186
+ def self.cleanup_swarm_branches
187
+ begin
188
+ output = CommandExecutor.execute('git', 'branch', '-a')
189
+ branches = output.lines.map(&:strip).reject(&:empty?)
190
+
191
+ swarm_branches = branches.select { |b| b.include?('swarm/') }
192
+ .map { |b| b.gsub(/^\*?\s*/, '').gsub(/^remotes\/origin\//, '') }
193
+ .uniq
194
+
195
+ results = swarm_branches.map do |branch|
196
+ cleanup_branch(branch)
197
+ end
198
+
199
+ { count: results.size, results: results }
200
+ rescue CommandExecutor::CommandError => e
201
+ Logger.error("Failed to list branches for cleanup: #{e.message}")
202
+ { count: 0, error: e.message }
203
+ end
204
+ end
205
+
206
+ def self.cleanup_swarm_processes
207
+ # This is OS-specific and should be implemented based on requirements
208
+ # For now, just return a placeholder
209
+ { count: 0, message: 'Process cleanup not implemented for this platform' }
210
+ end
211
+
212
+ def self.cleanup_swarm_temp_files
213
+ patterns = [
214
+ '/tmp/enhance_swarm_*',
215
+ '*.enhance_swarm.tmp',
216
+ '.enhance_swarm.lock'
217
+ ]
218
+
219
+ cleanup_temp_files(patterns)
220
+ end
221
+
222
+ def self.parse_worktree_list(output)
223
+ worktrees = []
224
+ current_worktree = {}
225
+
226
+ output.lines.each do |line|
227
+ line = line.strip
228
+ next if line.empty?
229
+
230
+ case line
231
+ when /^worktree (.+)$/
232
+ worktrees << current_worktree unless current_worktree.empty?
233
+ current_worktree = { path: $1 }
234
+ when /^branch (.+)$/
235
+ current_worktree[:branch] = $1.gsub(/^refs\/heads\//, '')
236
+ when /^HEAD (.+)$/
237
+ current_worktree[:head] = $1
238
+ end
239
+ end
240
+
241
+ worktrees << current_worktree unless current_worktree.empty?
242
+ worktrees
243
+ end
244
+ end
245
+ end