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.
- checksums.yaml +7 -0
- data/.enhance_swarm/agent_scripts/frontend_agent.md +39 -0
- data/.enhance_swarm/user_patterns.json +37 -0
- data/CHANGELOG.md +184 -0
- data/LICENSE +21 -0
- data/PRODUCTION_TEST_LOG.md +502 -0
- data/README.md +905 -0
- data/Rakefile +28 -0
- data/USAGE_EXAMPLES.md +477 -0
- data/examples/enhance_workflow.md +346 -0
- data/examples/rails_project.md +253 -0
- data/exe/enhance-swarm +30 -0
- data/lib/enhance_swarm/additional_commands.rb +299 -0
- data/lib/enhance_swarm/agent_communicator.rb +460 -0
- data/lib/enhance_swarm/agent_reviewer.rb +283 -0
- data/lib/enhance_swarm/agent_spawner.rb +462 -0
- data/lib/enhance_swarm/cleanup_manager.rb +245 -0
- data/lib/enhance_swarm/cli.rb +1592 -0
- data/lib/enhance_swarm/command_executor.rb +78 -0
- data/lib/enhance_swarm/configuration.rb +324 -0
- data/lib/enhance_swarm/control_agent.rb +307 -0
- data/lib/enhance_swarm/dependency_validator.rb +195 -0
- data/lib/enhance_swarm/error_recovery.rb +785 -0
- data/lib/enhance_swarm/generator.rb +194 -0
- data/lib/enhance_swarm/interrupt_handler.rb +512 -0
- data/lib/enhance_swarm/logger.rb +106 -0
- data/lib/enhance_swarm/mcp_integration.rb +85 -0
- data/lib/enhance_swarm/monitor.rb +28 -0
- data/lib/enhance_swarm/notification_manager.rb +444 -0
- data/lib/enhance_swarm/orchestrator.rb +313 -0
- data/lib/enhance_swarm/output_streamer.rb +281 -0
- data/lib/enhance_swarm/process_monitor.rb +266 -0
- data/lib/enhance_swarm/progress_tracker.rb +215 -0
- data/lib/enhance_swarm/project_analyzer.rb +612 -0
- data/lib/enhance_swarm/resource_manager.rb +177 -0
- data/lib/enhance_swarm/retry_handler.rb +40 -0
- data/lib/enhance_swarm/session_manager.rb +247 -0
- data/lib/enhance_swarm/signal_handler.rb +95 -0
- data/lib/enhance_swarm/smart_defaults.rb +708 -0
- data/lib/enhance_swarm/task_integration.rb +150 -0
- data/lib/enhance_swarm/task_manager.rb +174 -0
- data/lib/enhance_swarm/version.rb +5 -0
- data/lib/enhance_swarm/visual_dashboard.rb +555 -0
- data/lib/enhance_swarm/web_ui.rb +211 -0
- data/lib/enhance_swarm.rb +69 -0
- data/setup.sh +86 -0
- data/sig/enhance_swarm.rbs +4 -0
- data/templates/claude/CLAUDE.md +160 -0
- data/templates/claude/MCP.md +117 -0
- data/templates/claude/PERSONAS.md +114 -0
- data/templates/claude/RULES.md +221 -0
- data/test_builtin_functionality.rb +121 -0
- data/test_core_components.rb +156 -0
- data/test_real_claude_integration.rb +285 -0
- data/test_security.rb +150 -0
- data/test_smart_defaults.rb +155 -0
- data/test_task_integration.rb +173 -0
- data/test_web_ui.rb +245 -0
- data/web/assets/css/main.css +645 -0
- data/web/assets/js/kanban.js +499 -0
- data/web/assets/js/main.js +525 -0
- data/web/templates/dashboard.html.erb +226 -0
- data/web/templates/kanban.html.erb +193 -0
- 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
|