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,460 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'json'
5
+ require 'fileutils'
6
+ require 'colorize'
7
+
8
+ module EnhanceSwarm
9
+ class AgentCommunicator
10
+ include Singleton
11
+
12
+ COMMUNICATION_DIR = '.enhance_swarm/communication'
13
+ MESSAGE_FILE_PATTERN = 'agent_*.json'
14
+ USER_RESPONSE_FILE = 'user_responses.json'
15
+ PROMPT_TIMEOUT = 120 # 2 minutes for user response
16
+
17
+ def initialize
18
+ @communication_dir = File.join(Dir.pwd, COMMUNICATION_DIR)
19
+ @user_responses = {}
20
+ @pending_messages = {}
21
+ @monitoring_active = false
22
+ @monitoring_thread = nil
23
+ ensure_communication_directory
24
+ load_existing_responses
25
+ end
26
+
27
+ # Agent sends a message/question to user
28
+ def agent_message(agent_id, message_type, content, options = {})
29
+ message = {
30
+ id: generate_message_id(agent_id),
31
+ agent_id: agent_id,
32
+ role: options[:role] || extract_role_from_id(agent_id),
33
+ type: message_type,
34
+ content: content,
35
+ timestamp: Time.now.iso8601,
36
+ priority: options[:priority] || :medium,
37
+ requires_response: options[:requires_response] || false,
38
+ timeout: options[:timeout] || PROMPT_TIMEOUT,
39
+ quick_actions: options[:quick_actions] || [],
40
+ context: options[:context] || {}
41
+ }
42
+
43
+ save_message(message)
44
+
45
+ if message[:requires_response]
46
+ @pending_messages[message[:id]] = message
47
+ prompt_user_for_response(message) if options[:immediate_prompt]
48
+ end
49
+
50
+ # Notify user through notification system
51
+ notify_user_of_message(message)
52
+
53
+ message[:id]
54
+ end
55
+
56
+ # Agent asks a quick question requiring user response
57
+ def agent_question(agent_id, question, quick_actions = [], options = {})
58
+ agent_message(
59
+ agent_id,
60
+ :question,
61
+ question,
62
+ {
63
+ requires_response: true,
64
+ quick_actions: quick_actions,
65
+ immediate_prompt: options[:immediate_prompt] || false,
66
+ priority: options[:priority] || :high,
67
+ **options
68
+ }
69
+ )
70
+ end
71
+
72
+ # Agent provides status update
73
+ def agent_status(agent_id, status, details = {})
74
+ agent_message(
75
+ agent_id,
76
+ :status,
77
+ status,
78
+ {
79
+ requires_response: false,
80
+ priority: :low,
81
+ context: details
82
+ }
83
+ )
84
+ end
85
+
86
+ # Agent reports progress
87
+ def agent_progress(agent_id, progress_message, percentage = nil, eta = nil)
88
+ agent_message(
89
+ agent_id,
90
+ :progress,
91
+ progress_message,
92
+ {
93
+ requires_response: false,
94
+ priority: :low,
95
+ context: {
96
+ percentage: percentage,
97
+ eta: eta&.iso8601
98
+ }
99
+ }
100
+ )
101
+ end
102
+
103
+ # Agent requests user decision
104
+ def agent_decision(agent_id, decision_prompt, options_list, default = nil)
105
+ agent_message(
106
+ agent_id,
107
+ :decision,
108
+ decision_prompt,
109
+ {
110
+ requires_response: true,
111
+ quick_actions: options_list,
112
+ immediate_prompt: true,
113
+ priority: :high,
114
+ context: { default: default }
115
+ }
116
+ )
117
+ end
118
+
119
+ # User responds to a pending message
120
+ def user_respond(message_id, response)
121
+ if @pending_messages[message_id]
122
+ @user_responses[message_id] = {
123
+ response: response,
124
+ timestamp: Time.now.iso8601
125
+ }
126
+
127
+ save_user_responses
128
+ @pending_messages.delete(message_id)
129
+
130
+ # Notify agent via file system
131
+ create_response_file(message_id, response)
132
+
133
+ puts "āœ… Response sent to agent".colorize(:green)
134
+ true
135
+ else
136
+ puts "āŒ Message ID not found or already responded".colorize(:red)
137
+ false
138
+ end
139
+ end
140
+
141
+ # Get pending messages for user
142
+ def pending_messages
143
+ @pending_messages.values.sort_by { |msg| msg[:timestamp] }
144
+ end
145
+
146
+ # Get recent messages (responded + pending)
147
+ def recent_messages(limit = 10)
148
+ all_messages = load_all_messages
149
+ all_messages.sort_by { |msg| msg[:timestamp] }.last(limit)
150
+ end
151
+
152
+ # Check for agent response to user input
153
+ def agent_get_response(message_id, timeout = PROMPT_TIMEOUT)
154
+ start_time = Time.now
155
+
156
+ while Time.now - start_time < timeout
157
+ response_file = File.join(@communication_dir, "response_#{message_id}.json")
158
+
159
+ if File.exist?(response_file)
160
+ response_data = JSON.parse(File.read(response_file))
161
+ File.delete(response_file) # Cleanup
162
+ return response_data['response']
163
+ end
164
+
165
+ sleep(1)
166
+ end
167
+
168
+ nil # Timeout
169
+ end
170
+
171
+ # Start monitoring for user responses (CLI integration)
172
+ def start_monitoring
173
+ return if @monitoring_active
174
+
175
+ @monitoring_active = true
176
+ @monitoring_thread = Thread.new do
177
+ monitor_for_pending_messages
178
+ end
179
+ end
180
+
181
+ def stop_monitoring
182
+ @monitoring_active = false
183
+ @monitoring_thread&.kill
184
+ @monitoring_thread = nil
185
+ end
186
+
187
+ # CLI: Show pending messages
188
+ def show_pending_messages
189
+ pending = pending_messages
190
+
191
+ if pending.empty?
192
+ puts "No pending messages from agents".colorize(:yellow)
193
+ return
194
+ end
195
+
196
+ puts "\nšŸ’¬ Pending Agent Messages:".colorize(:blue)
197
+ pending.each_with_index do |message, index|
198
+ show_message_summary(message, index + 1)
199
+ end
200
+
201
+ puts "\nUse 'enhance-swarm communicate --respond <id> <response>' to reply".colorize(:light_black)
202
+ end
203
+
204
+ # CLI: Interactive response mode
205
+ def interactive_response_mode
206
+ pending = pending_messages
207
+
208
+ if pending.empty?
209
+ puts "No pending messages".colorize(:yellow)
210
+ return
211
+ end
212
+
213
+ puts "\nšŸ’¬ Interactive Agent Communication".colorize(:blue)
214
+
215
+ pending.each_with_index do |message, index|
216
+ puts "\n#{'-' * 60}".colorize(:light_black)
217
+ show_message_detail(message, index + 1)
218
+
219
+ if message[:quick_actions].any?
220
+ puts "\nQuick actions:".colorize(:yellow)
221
+ message[:quick_actions].each_with_index do |action, i|
222
+ puts " #{i + 1}. #{action}"
223
+ end
224
+ puts " c. Custom response"
225
+ end
226
+
227
+ print "\nYour response: ".colorize(:blue)
228
+ response = $stdin.gets&.chomp
229
+
230
+ next if response.nil? || response.empty?
231
+
232
+ # Handle quick action selection
233
+ if message[:quick_actions].any? && response.match?(/^\d+$/)
234
+ action_index = response.to_i - 1
235
+ if action_index >= 0 && action_index < message[:quick_actions].length
236
+ response = message[:quick_actions][action_index]
237
+ end
238
+ end
239
+
240
+ user_respond(message[:id], response)
241
+ end
242
+ end
243
+
244
+ # Clean up old messages
245
+ def cleanup_old_messages(days_old = 7)
246
+ cutoff = Time.now - (days_old * 24 * 60 * 60)
247
+
248
+ Dir.glob(File.join(@communication_dir, MESSAGE_FILE_PATTERN)).each do |file|
249
+ begin
250
+ message = JSON.parse(File.read(file))
251
+ message_time = Time.parse(message['timestamp'])
252
+
253
+ if message_time < cutoff
254
+ File.delete(file)
255
+ end
256
+ rescue
257
+ # Delete malformed files
258
+ File.delete(file)
259
+ end
260
+ end
261
+ end
262
+
263
+ private
264
+
265
+ def ensure_communication_directory
266
+ FileUtils.mkdir_p(@communication_dir) unless Dir.exist?(@communication_dir)
267
+ end
268
+
269
+ def generate_message_id(agent_id)
270
+ "#{agent_id}_#{Time.now.to_i}_#{rand(1000)}"
271
+ end
272
+
273
+ def extract_role_from_id(agent_id)
274
+ agent_id.split('-').first
275
+ end
276
+
277
+ def save_message(message)
278
+ filename = "agent_#{message[:id]}.json"
279
+ filepath = File.join(@communication_dir, filename)
280
+
281
+ File.write(filepath, JSON.pretty_generate(message))
282
+ end
283
+
284
+ def load_all_messages
285
+ messages = []
286
+
287
+ Dir.glob(File.join(@communication_dir, MESSAGE_FILE_PATTERN)).each do |file|
288
+ begin
289
+ message = JSON.parse(File.read(file), symbolize_names: true)
290
+ messages << message
291
+ rescue
292
+ # Skip malformed files
293
+ end
294
+ end
295
+
296
+ messages
297
+ end
298
+
299
+ def load_existing_responses
300
+ response_file = File.join(@communication_dir, USER_RESPONSE_FILE)
301
+
302
+ if File.exist?(response_file)
303
+ @user_responses = JSON.parse(File.read(response_file))
304
+ end
305
+ rescue
306
+ @user_responses = {}
307
+ end
308
+
309
+ def save_user_responses
310
+ response_file = File.join(@communication_dir, USER_RESPONSE_FILE)
311
+ File.write(response_file, JSON.pretty_generate(@user_responses))
312
+ end
313
+
314
+ def create_response_file(message_id, response)
315
+ response_file = File.join(@communication_dir, "response_#{message_id}.json")
316
+ File.write(response_file, JSON.pretty_generate({
317
+ message_id: message_id,
318
+ response: response,
319
+ timestamp: Time.now.iso8601
320
+ }))
321
+ end
322
+
323
+ def notify_user_of_message(message)
324
+ return unless defined?(NotificationManager)
325
+
326
+ notification_content = case message[:type]
327
+ when :question
328
+ "ā“ #{message[:role].capitalize} agent has a question"
329
+ when :decision
330
+ "šŸ¤” #{message[:role].capitalize} agent needs a decision"
331
+ when :status
332
+ "šŸ“ #{message[:role].capitalize}: #{message[:content]}"
333
+ when :progress
334
+ "šŸ“Š #{message[:role].capitalize}: #{message[:content]}"
335
+ else
336
+ "šŸ’¬ Message from #{message[:role]} agent"
337
+ end
338
+
339
+ priority = message[:requires_response] ? :high : :low
340
+
341
+ NotificationManager.instance.notify(
342
+ :agent_communication,
343
+ notification_content,
344
+ {
345
+ agent_id: message[:agent_id],
346
+ message_id: message[:id],
347
+ requires_response: message[:requires_response],
348
+ type: message[:type]
349
+ }
350
+ )
351
+ end
352
+
353
+ def prompt_user_for_response(message)
354
+ puts "\nšŸ’¬ Agent Message [#{message[:agent_id]}]:".colorize(:blue)
355
+ puts "#{message[:content]}"
356
+
357
+ if message[:quick_actions].any?
358
+ puts "\nQuick actions:".colorize(:yellow)
359
+ message[:quick_actions].each_with_index do |action, i|
360
+ puts " #{i + 1}. #{action}"
361
+ end
362
+ puts " Or provide custom response:"
363
+ end
364
+
365
+ puts "Use 'enhance-swarm communicate --respond #{message[:id]} <response>' to reply".colorize(:light_black)
366
+ end
367
+
368
+ def monitor_for_pending_messages
369
+ while @monitoring_active
370
+ # Check for messages that need immediate user attention
371
+ @pending_messages.values.each do |message|
372
+ age = Time.now - Time.parse(message[:timestamp])
373
+
374
+ # Prompt if message is getting old and high priority
375
+ if age > 60 && message[:priority] == :high && !message[:notified]
376
+ puts "\nāš ļø Urgent: Agent #{message[:agent_id]} waiting for response!".colorize(:red)
377
+ puts "Message: #{message[:content]}"
378
+ puts "Use 'enhance-swarm communicate --list' to see all pending messages".colorize(:light_black)
379
+
380
+ message[:notified] = true
381
+ end
382
+ end
383
+
384
+ sleep(30) # Check every 30 seconds
385
+ end
386
+ rescue StandardError => e
387
+ Logger.error("Communication monitoring error: #{e.message}")
388
+ end
389
+
390
+ def show_message_summary(message, index)
391
+ age = time_ago_in_words(Time.parse(message[:timestamp]))
392
+ priority_color = case message[:priority]
393
+ when :high then :yellow
394
+ when :critical then :red
395
+ else :white
396
+ end
397
+
398
+ puts "#{index}. [#{message[:id]}] #{message[:type].upcase} from #{message[:role]} (#{age} ago)".colorize(priority_color)
399
+ puts " #{message[:content][0..80]}#{message[:content].length > 80 ? '...' : ''}"
400
+ end
401
+
402
+ def show_message_detail(message, index)
403
+ age = time_ago_in_words(Time.parse(message[:timestamp]))
404
+
405
+ puts "#{index}. Message from #{message[:role]} agent [#{message[:agent_id]}]".colorize(:blue)
406
+ puts " Type: #{message[:type]}".colorize(:light_black)
407
+ puts " Priority: #{message[:priority]}".colorize(:light_black)
408
+ puts " Sent: #{age} ago".colorize(:light_black)
409
+ puts "\n#{message[:content]}"
410
+
411
+ if message[:context] && message[:context].any?
412
+ puts "\nContext:".colorize(:light_black)
413
+ message[:context].each do |key, value|
414
+ puts " #{key}: #{value}"
415
+ end
416
+ end
417
+ end
418
+
419
+ def time_ago_in_words(time)
420
+ seconds = Time.now - time
421
+
422
+ if seconds < 60
423
+ "#{seconds.round}s"
424
+ elsif seconds < 3600
425
+ "#{(seconds / 60).round}m"
426
+ elsif seconds < 86400
427
+ "#{(seconds / 3600).round}h"
428
+ else
429
+ "#{(seconds / 86400).round}d"
430
+ end
431
+ end
432
+
433
+ # Class methods for singleton access
434
+ class << self
435
+ def instance
436
+ @instance ||= new
437
+ end
438
+
439
+ def agent_message(*args)
440
+ instance.agent_message(*args)
441
+ end
442
+
443
+ def agent_question(*args)
444
+ instance.agent_question(*args)
445
+ end
446
+
447
+ def agent_status(*args)
448
+ instance.agent_status(*args)
449
+ end
450
+
451
+ def agent_progress(*args)
452
+ instance.agent_progress(*args)
453
+ end
454
+
455
+ def agent_decision(*args)
456
+ instance.agent_decision(*args)
457
+ end
458
+ end
459
+ end
460
+ end