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,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+
6
+ module EnhanceSwarm
7
+ class AgentReviewer
8
+ def self.review_all_work
9
+ results = {
10
+ timestamp: Time.now.iso8601,
11
+ worktrees: [],
12
+ completed_tasks: [],
13
+ active_tasks: [],
14
+ blocked_tasks: [],
15
+ summary: {}
16
+ }
17
+
18
+ begin
19
+ worktrees = list_swarm_worktrees
20
+ Logger.info("Found #{worktrees.count} swarm worktrees to review")
21
+
22
+ worktrees.each do |worktree|
23
+ review = review_worktree(worktree)
24
+ results[:worktrees] << review
25
+
26
+ categorize_tasks(review, results)
27
+ end
28
+
29
+ results[:summary] = generate_summary(results)
30
+ results
31
+ rescue StandardError => e
32
+ Logger.error("Failed to review agent work: #{e.message}")
33
+ results[:error] = e.message
34
+ results
35
+ end
36
+ end
37
+
38
+ def self.list_swarm_worktrees
39
+ output = CommandExecutor.execute('git', 'worktree', 'list', '--porcelain')
40
+ worktrees = []
41
+ current_worktree = {}
42
+
43
+ output.split("\n").each do |line|
44
+ case line
45
+ when /^worktree (.+)/
46
+ current_worktree[:path] = $1
47
+ when /^branch (.+)/
48
+ current_worktree[:branch] = $1
49
+ when /^HEAD (.+)/
50
+ current_worktree[:head] = $1
51
+ when ''
52
+ if current_worktree[:path]&.include?('swarm/')
53
+ worktrees << current_worktree.dup
54
+ end
55
+ current_worktree.clear
56
+ end
57
+ end
58
+
59
+ # Don't forget the last one
60
+ if current_worktree[:path]&.include?('swarm/')
61
+ worktrees << current_worktree
62
+ end
63
+
64
+ worktrees
65
+ end
66
+
67
+ def self.review_worktree(worktree)
68
+ review = {
69
+ path: worktree[:path],
70
+ branch: worktree[:branch],
71
+ last_activity: nil,
72
+ status: 'unknown',
73
+ files_changed: [],
74
+ commits: [],
75
+ task_progress: {},
76
+ issues: []
77
+ }
78
+
79
+ begin
80
+ Dir.chdir(worktree[:path]) do
81
+ # Get last activity
82
+ begin
83
+ last_commit = CommandExecutor.execute('git', 'log', '-1', '--format=%ci')
84
+ review[:last_activity] = Time.parse(last_commit.strip)
85
+ rescue
86
+ review[:last_activity] = File.mtime(worktree[:path])
87
+ end
88
+
89
+ # Check status
90
+ git_status = CommandExecutor.execute('git', 'status', '--porcelain')
91
+ review[:files_changed] = git_status.split("\n").map(&:strip).reject(&:empty?)
92
+
93
+ # Get recent commits
94
+ begin
95
+ commits_output = CommandExecutor.execute('git', 'log', '--oneline', '-5')
96
+ review[:commits] = commits_output.split("\n").map(&:strip).reject(&:empty?)
97
+ rescue
98
+ # No commits yet
99
+ end
100
+
101
+ # Determine status
102
+ review[:status] = determine_worktree_status(review)
103
+
104
+ # Check for issues
105
+ review[:issues] = detect_issues(worktree[:path], review)
106
+
107
+ # Look for task progress indicators
108
+ review[:task_progress] = extract_task_progress(worktree[:path])
109
+ end
110
+ rescue StandardError => e
111
+ review[:status] = 'error'
112
+ review[:issues] << "Failed to review: #{e.message}"
113
+ end
114
+
115
+ review
116
+ end
117
+
118
+ def self.determine_worktree_status(review)
119
+ return 'stale' if review[:last_activity] && review[:last_activity] < (Time.now - 3600) # 1 hour
120
+
121
+ if review[:files_changed].any?
122
+ 'active'
123
+ elsif review[:commits].any?
124
+ 'completed'
125
+ else
126
+ 'initialized'
127
+ end
128
+ end
129
+
130
+ def self.detect_issues(path, review)
131
+ issues = []
132
+
133
+ # Check for merge conflicts
134
+ if review[:files_changed].any? { |f| f.start_with?('UU ') }
135
+ issues << 'merge conflicts detected'
136
+ end
137
+
138
+ # Check for untracked important files
139
+ untracked = review[:files_changed].select { |f| f.start_with?('?? ') }
140
+ if untracked.any? { |f| f.include?('.rb') || f.include?('.js') || f.include?('.py') }
141
+ issues << 'untracked source files'
142
+ end
143
+
144
+ # Check disk space
145
+ begin
146
+ stat = File.statvfs(path)
147
+ free_gb = (stat.bavail * stat.frsize) / (1024 * 1024 * 1024)
148
+ issues << "low disk space (#{free_gb}GB)" if free_gb < 1
149
+ rescue
150
+ # Not all platforms support statvfs
151
+ end
152
+
153
+ issues
154
+ end
155
+
156
+ def self.extract_task_progress(path)
157
+ progress = {}
158
+
159
+ # Look for task files
160
+ task_files = Dir.glob(File.join(path, '.claude', 'tasks', '*.md'))
161
+ task_files.each do |file|
162
+ begin
163
+ content = File.read(file)
164
+ task_name = File.basename(file, '.md')
165
+
166
+ # Simple progress extraction
167
+ if content.include?('✅') || content.include?('completed')
168
+ progress[task_name] = 'completed'
169
+ elsif content.include?('🔄') || content.include?('in progress')
170
+ progress[task_name] = 'in_progress'
171
+ elsif content.include?('❌') || content.include?('blocked')
172
+ progress[task_name] = 'blocked'
173
+ else
174
+ progress[task_name] = 'pending'
175
+ end
176
+ rescue
177
+ # Ignore file read errors
178
+ end
179
+ end
180
+
181
+ progress
182
+ end
183
+
184
+ def self.categorize_tasks(review, results)
185
+ review[:task_progress].each do |task, status|
186
+ task_info = {
187
+ task: task,
188
+ worktree: review[:path],
189
+ branch: review[:branch],
190
+ status: status,
191
+ last_activity: review[:last_activity]
192
+ }
193
+
194
+ case status
195
+ when 'completed'
196
+ results[:completed_tasks] << task_info
197
+ when 'in_progress'
198
+ results[:active_tasks] << task_info
199
+ when 'blocked'
200
+ results[:blocked_tasks] << task_info
201
+ end
202
+ end
203
+ end
204
+
205
+ def self.generate_summary(results)
206
+ {
207
+ total_worktrees: results[:worktrees].count,
208
+ active_worktrees: results[:worktrees].count { |w| w[:status] == 'active' },
209
+ stale_worktrees: results[:worktrees].count { |w| w[:status] == 'stale' },
210
+ completed_tasks: results[:completed_tasks].count,
211
+ active_tasks: results[:active_tasks].count,
212
+ blocked_tasks: results[:blocked_tasks].count,
213
+ total_issues: results[:worktrees].sum { |w| w[:issues].count }
214
+ }
215
+ end
216
+
217
+ def self.print_review_report(results)
218
+ puts "=== Agent Work Review ==="
219
+ puts "Time: #{Time.parse(results[:timestamp]).strftime('%Y-%m-%d %H:%M:%S')}"
220
+ puts
221
+
222
+ summary = results[:summary]
223
+ puts "📊 Summary:"
224
+ puts " Total worktrees: #{summary[:total_worktrees]}"
225
+ puts " Active: #{summary[:active_worktrees]}".colorize(:green)
226
+ puts " Stale: #{summary[:stale_worktrees]}".colorize(:yellow)
227
+ puts
228
+
229
+ puts "📋 Tasks:"
230
+ puts " Completed: #{summary[:completed_tasks]}".colorize(:green)
231
+ puts " Active: #{summary[:active_tasks]}".colorize(:blue)
232
+ puts " Blocked: #{summary[:blocked_tasks]}".colorize(:red)
233
+ puts
234
+
235
+ if results[:completed_tasks].any?
236
+ puts "✅ Recently Completed:"
237
+ results[:completed_tasks].each do |task|
238
+ puts " #{task[:task]} (#{task[:worktree]})"
239
+ end
240
+ puts
241
+ end
242
+
243
+ if results[:active_tasks].any?
244
+ puts "🔄 Currently Active:"
245
+ results[:active_tasks].each do |task|
246
+ age = Time.now - task[:last_activity]
247
+ puts " #{task[:task]} (#{format_duration(age)} ago)"
248
+ end
249
+ puts
250
+ end
251
+
252
+ if results[:blocked_tasks].any?
253
+ puts "❌ Blocked Tasks:"
254
+ results[:blocked_tasks].each do |task|
255
+ puts " #{task[:task]} (#{task[:worktree]})".colorize(:red)
256
+ end
257
+ puts
258
+ end
259
+
260
+ # Show issues
261
+ issues = results[:worktrees].flat_map { |w| w[:issues] }
262
+ if issues.any?
263
+ puts "⚠️ Issues Found:"
264
+ issues.each { |issue| puts " - #{issue}".colorize(:yellow) }
265
+ puts
266
+ end
267
+
268
+ if summary[:stale_worktrees] > 0
269
+ puts "💡 Suggestion: Run 'enhance-swarm cleanup --all' to clean stale worktrees"
270
+ end
271
+ end
272
+
273
+ def self.format_duration(seconds)
274
+ if seconds < 60
275
+ "#{seconds.to_i}s"
276
+ elsif seconds < 3600
277
+ "#{(seconds / 60).to_i}m"
278
+ else
279
+ "#{(seconds / 3600).to_i}h"
280
+ end
281
+ end
282
+ end
283
+ end