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,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
|