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,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+ require_relative 'session_manager'
5
+ require_relative 'logger'
6
+
7
+ module EnhanceSwarm
8
+ class ProcessMonitor
9
+ def initialize
10
+ @session_manager = SessionManager.new
11
+ end
12
+
13
+ def status
14
+ session_status = @session_manager.session_status
15
+
16
+ unless session_status[:exists]
17
+ return {
18
+ session_exists: false,
19
+ active_agents: 0,
20
+ total_agents: 0,
21
+ message: 'No active session found'
22
+ }
23
+ end
24
+
25
+ # Check and update process statuses
26
+ active_agents = @session_manager.check_agent_processes
27
+ all_agents = @session_manager.get_all_agents
28
+
29
+ {
30
+ session_exists: true,
31
+ session_id: session_status[:session_id],
32
+ start_time: session_status[:start_time],
33
+ task_description: session_status[:task_description],
34
+ active_agents: active_agents.length,
35
+ total_agents: all_agents.length,
36
+ completed_agents: all_agents.count { |a| a[:status] == 'completed' },
37
+ failed_agents: all_agents.count { |a| a[:status] == 'failed' },
38
+ stopped_agents: all_agents.count { |a| a[:status] == 'stopped' },
39
+ agents: all_agents,
40
+ active_agent_details: active_agents
41
+ }
42
+ end
43
+
44
+ def display_status
45
+ status_data = status
46
+
47
+ unless status_data[:session_exists]
48
+ puts "📊 No active enhance-swarm session found".colorize(:yellow)
49
+ puts " Run 'enhance-swarm enhance' to start a new session"
50
+ return
51
+ end
52
+
53
+ puts "📊 EnhanceSwarm Session Status".colorize(:blue)
54
+ puts "=" * 50
55
+
56
+ # Session info
57
+ puts "\n🎯 Session: #{status_data[:session_id]}".colorize(:green)
58
+ puts " Started: #{format_time(status_data[:start_time])}"
59
+ puts " Task: #{status_data[:task_description] || 'No description'}" if status_data[:task_description]
60
+
61
+ # Agent summary
62
+ puts "\n📈 Agents Summary:".colorize(:blue)
63
+ puts " Total: #{status_data[:total_agents]}"
64
+ puts " Active: #{status_data[:active_agents]}".colorize(:green)
65
+ puts " Completed: #{status_data[:completed_agents]}".colorize(:green)
66
+ puts " Stopped: #{status_data[:stopped_agents]}".colorize(:yellow)
67
+ puts " Failed: #{status_data[:failed_agents]}".colorize(:red) if status_data[:failed_agents] > 0
68
+
69
+ # Active agents details
70
+ if status_data[:active_agents] > 0
71
+ puts "\n🤖 Active Agents:".colorize(:yellow)
72
+ status_data[:active_agent_details].each do |agent|
73
+ runtime = calculate_runtime(agent[:start_time])
74
+ puts " #{agent[:role].upcase.ljust(8)} PID: #{agent[:pid].to_s.ljust(6)} Runtime: #{runtime}".colorize(:yellow)
75
+ puts " #{' ' * 10} Worktree: #{agent[:worktree_path]}" if agent[:worktree_path]
76
+ end
77
+ end
78
+
79
+ # All agents table if requested or if there are completed/failed agents
80
+ if status_data[:total_agents] > status_data[:active_agents]
81
+ puts "\n📋 All Agents:".colorize(:blue)
82
+ display_agents_table(status_data[:agents])
83
+ end
84
+
85
+ # Git worktrees
86
+ display_worktree_status
87
+
88
+ puts ""
89
+ end
90
+
91
+ def display_agents_table(agents)
92
+ return if agents.empty?
93
+
94
+ # Table headers
95
+ printf " %-10s %-8s %-12s %-10s %s\n", "ROLE", "PID", "STATUS", "RUNTIME", "WORKTREE"
96
+ puts " " + "-" * 70
97
+
98
+ agents.each do |agent|
99
+ runtime = calculate_runtime(agent[:start_time], agent[:completion_time])
100
+ worktree = agent[:worktree_path] ? File.basename(agent[:worktree_path]) : 'none'
101
+
102
+ status_color = case agent[:status]
103
+ when 'running' then :green
104
+ when 'completed' then :blue
105
+ when 'failed' then :red
106
+ when 'stopped' then :yellow
107
+ else :white
108
+ end
109
+
110
+ role = agent[:role].upcase.ljust(10)
111
+ pid = agent[:pid].to_s.ljust(8)
112
+ status = agent[:status].ljust(12)
113
+
114
+ printf " %-10s %-8s ", role, pid
115
+ print status.colorize(status_color)
116
+ printf " %-10s %s\n", runtime, worktree
117
+ end
118
+ end
119
+
120
+ def display_worktree_status
121
+ begin
122
+ worktrees = get_git_worktrees
123
+ enhance_worktrees = worktrees.select { |wt| wt[:path].include?('.enhance_swarm/worktrees') }
124
+
125
+ if enhance_worktrees.any?
126
+ puts "\n🌳 Git Worktrees:".colorize(:blue)
127
+ enhance_worktrees.each do |worktree|
128
+ branch = worktree[:branch] || 'detached'
129
+ puts " #{File.basename(worktree[:path]).ljust(20)} #{branch}".colorize(:cyan)
130
+ end
131
+ end
132
+ rescue StandardError => e
133
+ Logger.warn("Could not get worktree status: #{e.message}")
134
+ end
135
+ end
136
+
137
+ def watch(interval: 5, timeout: nil)
138
+ start_time = Time.now
139
+ check_count = 0
140
+
141
+ puts "🔍 Watching enhance-swarm agents (Ctrl+C to stop)...".colorize(:yellow)
142
+ puts ""
143
+
144
+ begin
145
+ loop do
146
+ check_count += 1
147
+ elapsed = Time.now - start_time
148
+
149
+ # Check timeout
150
+ if timeout && elapsed > timeout
151
+ puts "\n⏱️ Watch timeout reached (#{timeout}s)".colorize(:blue)
152
+ break
153
+ end
154
+
155
+ # Clear screen and show status
156
+ system('clear') || system('cls')
157
+
158
+ puts "[Check #{check_count}] #{Time.now.strftime('%H:%M:%S')} (#{elapsed.round}s elapsed)".colorize(:gray)
159
+ display_status
160
+
161
+ # Check if all agents completed
162
+ status_data = status
163
+ if status_data[:session_exists] && status_data[:active_agents] == 0 && status_data[:total_agents] > 0
164
+ puts "✅ All agents completed!".colorize(:green)
165
+ break
166
+ end
167
+
168
+ sleep interval
169
+ end
170
+ rescue Interrupt
171
+ puts "\n\n👋 Watch stopped by user".colorize(:yellow)
172
+ end
173
+ end
174
+
175
+ def cleanup_completed_agents
176
+ all_agents = @session_manager.get_all_agents
177
+ completed_agents = all_agents.select { |a| a[:status] == 'completed' }
178
+
179
+ cleanup_count = 0
180
+ completed_agents.each do |agent|
181
+ if cleanup_agent_worktree(agent[:worktree_path])
182
+ @session_manager.remove_agent(agent[:pid])
183
+ cleanup_count += 1
184
+ end
185
+ end
186
+
187
+ if cleanup_count > 0
188
+ puts "🧹 Cleaned up #{cleanup_count} completed agents".colorize(:green)
189
+ end
190
+
191
+ cleanup_count
192
+ end
193
+
194
+ private
195
+
196
+ def format_time(time_string)
197
+ return 'unknown' unless time_string
198
+
199
+ begin
200
+ Time.parse(time_string).strftime('%Y-%m-%d %H:%M:%S')
201
+ rescue StandardError
202
+ time_string
203
+ end
204
+ end
205
+
206
+ def calculate_runtime(start_time, end_time = nil)
207
+ return 'unknown' unless start_time
208
+
209
+ begin
210
+ start = Time.parse(start_time)
211
+ finish = end_time ? Time.parse(end_time) : Time.now
212
+
213
+ duration = finish - start
214
+ format_duration(duration)
215
+ rescue StandardError
216
+ 'unknown'
217
+ end
218
+ end
219
+
220
+ def format_duration(seconds)
221
+ return '0s' if seconds <= 0
222
+
223
+ if seconds < 60
224
+ "#{seconds.round}s"
225
+ elsif seconds < 3600
226
+ minutes = (seconds / 60).round
227
+ "#{minutes}m"
228
+ else
229
+ hours = (seconds / 3600).round(1)
230
+ "#{hours}h"
231
+ end
232
+ end
233
+
234
+ def get_git_worktrees
235
+ output = `git worktree list 2>/dev/null`
236
+ return [] if output.empty?
237
+
238
+ output.lines.map do |line|
239
+ parts = line.strip.split
240
+ next if parts.empty?
241
+
242
+ {
243
+ path: parts[0],
244
+ commit: parts[1],
245
+ branch: parts[2]&.gsub(/[\[\]]/, '')
246
+ }
247
+ end.compact
248
+ rescue StandardError
249
+ []
250
+ end
251
+
252
+ def cleanup_agent_worktree(worktree_path)
253
+ return false unless worktree_path && Dir.exist?(worktree_path)
254
+
255
+ begin
256
+ # Remove git worktree
257
+ `git worktree remove --force "#{worktree_path}" 2>/dev/null`
258
+ Logger.info("Cleaned up worktree: #{worktree_path}")
259
+ true
260
+ rescue StandardError => e
261
+ Logger.warn("Failed to cleanup worktree #{worktree_path}: #{e.message}")
262
+ false
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+
5
+ module EnhanceSwarm
6
+ class ProgressTracker
7
+ attr_reader :current_step, :total_steps, :start_time, :estimated_tokens, :used_tokens
8
+
9
+ def initialize(total_steps: 100, estimated_tokens: nil)
10
+ @total_steps = total_steps
11
+ @current_step = 0
12
+ @start_time = Time.now
13
+ @estimated_tokens = estimated_tokens
14
+ @used_tokens = 0
15
+ @last_update = Time.now
16
+ @status_message = 'Initializing...'
17
+ @details = {}
18
+ @spinner_chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
19
+ @spinner_index = 0
20
+ end
21
+
22
+ def advance(steps = 1, message: nil, details: {})
23
+ @current_step = [@current_step + steps, @total_steps].min
24
+ @status_message = message if message
25
+ @details.merge!(details)
26
+ update_display
27
+ end
28
+
29
+ def set_progress(step, message: nil, details: {})
30
+ @current_step = [step, @total_steps].min
31
+ @status_message = message if message
32
+ @details.merge!(details)
33
+ update_display
34
+ end
35
+
36
+ def add_tokens(count)
37
+ @used_tokens += count
38
+ update_display
39
+ end
40
+
41
+ def update_status(message, details: {})
42
+ @status_message = message
43
+ @details.merge!(details)
44
+ update_display
45
+ end
46
+
47
+ def complete(message: 'Completed!')
48
+ @current_step = @total_steps
49
+ @status_message = message
50
+ update_display
51
+ puts # Final newline
52
+ end
53
+
54
+ def fail(message: 'Failed!')
55
+ @status_message = message
56
+ update_display
57
+ puts # Final newline
58
+ end
59
+
60
+ private
61
+
62
+ def update_display
63
+ return if Time.now - @last_update < 0.1 # Limit update frequency
64
+
65
+ @last_update = Time.now
66
+ @spinner_index = (@spinner_index + 1) % @spinner_chars.length
67
+
68
+ line = build_progress_line
69
+
70
+ # Clear line and print new content
71
+ print "\r\e[K#{line}"
72
+ $stdout.flush
73
+ end
74
+
75
+ def build_progress_line
76
+ spinner = @spinner_chars[@spinner_index]
77
+ percentage = (@current_step.to_f / @total_steps * 100).round(1)
78
+ elapsed = Time.now - @start_time
79
+
80
+ # Progress bar
81
+ bar_width = 20
82
+ filled = (percentage / 100 * bar_width).round
83
+ bar = '█' * filled + '░' * (bar_width - filled)
84
+
85
+ # Time estimates
86
+ time_info = build_time_info(elapsed, percentage)
87
+
88
+ # Token info
89
+ token_info = build_token_info
90
+
91
+ # Details
92
+ detail_info = build_detail_info
93
+
94
+ "#{spinner} [#{bar}] #{percentage}% #{@status_message} #{time_info}#{token_info}#{detail_info}"
95
+ end
96
+
97
+ def build_time_info(elapsed, percentage)
98
+ elapsed_str = format_duration(elapsed)
99
+
100
+ if percentage > 5 && percentage < 100
101
+ eta = (elapsed / percentage * 100) - elapsed
102
+ eta_str = format_duration(eta)
103
+ "(#{elapsed_str}/#{eta_str})"
104
+ else
105
+ "(#{elapsed_str})"
106
+ end
107
+ end
108
+
109
+ def build_token_info
110
+ return '' unless @estimated_tokens || @used_tokens > 0
111
+
112
+ if @estimated_tokens
113
+ token_percentage = (@used_tokens.to_f / @estimated_tokens * 100).round(1)
114
+ " [#{@used_tokens}/#{@estimated_tokens} tokens (#{token_percentage}%)]"
115
+ else
116
+ " [#{@used_tokens} tokens]"
117
+ end
118
+ end
119
+
120
+ def build_detail_info
121
+ return '' if @details.empty?
122
+
123
+ key_details = @details.slice(:agent, :worktree, :operation).compact
124
+ return '' if key_details.empty?
125
+
126
+ detail_str = key_details.map { |k, v| "#{k}: #{v}" }.join(', ')
127
+ " - #{detail_str}"
128
+ end
129
+
130
+ def format_duration(seconds)
131
+ if seconds < 60
132
+ "#{seconds.round}s"
133
+ elsif seconds < 3600
134
+ minutes = (seconds / 60).round
135
+ "#{minutes}m"
136
+ else
137
+ hours = (seconds / 3600).round(1)
138
+ "#{hours}h"
139
+ end
140
+ end
141
+
142
+ # Class methods for easy usage
143
+ def self.track(total_steps: 100, estimated_tokens: nil)
144
+ tracker = new(total_steps: total_steps, estimated_tokens: estimated_tokens)
145
+
146
+ begin
147
+ yield tracker
148
+ tracker.complete
149
+ rescue StandardError => e
150
+ tracker.fail(message: "Error: #{e.message}")
151
+ raise
152
+ end
153
+ end
154
+
155
+ # Agent progress tracking
156
+ def self.track_agent_operation(operation_name, estimated_steps: 10)
157
+ tracker = new(total_steps: estimated_steps)
158
+ tracker.update_status("#{operation_name}: Starting...")
159
+
160
+ begin
161
+ result = yield tracker
162
+ tracker.complete(message: "#{operation_name}: Completed!")
163
+ result
164
+ rescue StandardError => e
165
+ tracker.fail(message: "#{operation_name}: Failed - #{e.message}")
166
+ raise
167
+ end
168
+ end
169
+
170
+ # Multi-agent coordination
171
+ def self.track_multi_agent(agents)
172
+ total_steps = agents.count * 10 # Assume 10 steps per agent
173
+ tracker = new(total_steps: total_steps)
174
+
175
+ results = {}
176
+ agents.each_with_index do |agent_config, index|
177
+ agent_name = agent_config[:role] || "agent-#{index + 1}"
178
+
179
+ tracker.update_status("Spawning #{agent_name}...",
180
+ agent: agent_name,
181
+ operation: 'spawn')
182
+
183
+ begin
184
+ # This would be replaced with actual agent spawning
185
+ result = yield agent_config, tracker, index
186
+ results[agent_name] = result
187
+
188
+ tracker.advance(10,
189
+ message: "#{agent_name} completed",
190
+ agent: agent_name,
191
+ operation: 'completed')
192
+ rescue StandardError => e
193
+ tracker.fail(message: "#{agent_name} failed: #{e.message}")
194
+ results[agent_name] = { error: e.message }
195
+ end
196
+ end
197
+
198
+ tracker.complete(message: "All agents completed!")
199
+ results
200
+ end
201
+
202
+ # Token estimation helpers
203
+ def self.estimate_tokens_for_operation(operation_type)
204
+ estimates = {
205
+ 'spawn_agent' => 500,
206
+ 'code_review' => 1000,
207
+ 'implementation' => 2000,
208
+ 'testing' => 800,
209
+ 'documentation' => 600
210
+ }
211
+
212
+ estimates[operation_type] || 1000
213
+ end
214
+ end
215
+ end