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,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'logger'
|
4
|
+
|
5
|
+
module EnhanceSwarm
|
6
|
+
class TaskIntegration
|
7
|
+
def initialize
|
8
|
+
@tasks_available = check_swarm_tasks_availability
|
9
|
+
end
|
10
|
+
|
11
|
+
def swarm_tasks_available?
|
12
|
+
@tasks_available
|
13
|
+
end
|
14
|
+
|
15
|
+
def list_tasks
|
16
|
+
return [] unless @tasks_available
|
17
|
+
|
18
|
+
begin
|
19
|
+
output = `bundle exec swarm-tasks list --format=json 2>/dev/null`
|
20
|
+
return [] if output.empty?
|
21
|
+
|
22
|
+
JSON.parse(output)
|
23
|
+
rescue JSON::ParserError, StandardError => e
|
24
|
+
Logger.warn("Failed to parse swarm-tasks output: #{e.message}")
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_active_tasks
|
30
|
+
tasks = list_tasks
|
31
|
+
tasks.select { |task| task['status'] == 'active' || task['status'] == 'in_progress' }
|
32
|
+
end
|
33
|
+
|
34
|
+
def move_task(task_id, status)
|
35
|
+
return false unless @tasks_available
|
36
|
+
|
37
|
+
begin
|
38
|
+
result = `bundle exec swarm-tasks move #{task_id} #{status} 2>/dev/null`
|
39
|
+
$?.success?
|
40
|
+
rescue StandardError => e
|
41
|
+
Logger.error("Failed to move task #{task_id} to #{status}: #{e.message}")
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_task(title, description = nil, priority = 'medium')
|
47
|
+
return false unless @tasks_available
|
48
|
+
|
49
|
+
begin
|
50
|
+
cmd = "bundle exec swarm-tasks create \"#{title}\""
|
51
|
+
cmd += " --description=\"#{description}\"" if description
|
52
|
+
cmd += " --priority=#{priority}"
|
53
|
+
cmd += " 2>/dev/null"
|
54
|
+
|
55
|
+
result = `#{cmd}`
|
56
|
+
$?.success?
|
57
|
+
rescue StandardError => e
|
58
|
+
Logger.error("Failed to create task: #{e.message}")
|
59
|
+
false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_task_folders
|
64
|
+
return [] unless @tasks_available
|
65
|
+
|
66
|
+
begin
|
67
|
+
# Look for tasks directory structure
|
68
|
+
tasks_dir = File.join(Dir.pwd, 'tasks')
|
69
|
+
return [] unless Dir.exist?(tasks_dir)
|
70
|
+
|
71
|
+
folders = []
|
72
|
+
Dir.glob(File.join(tasks_dir, '*')).each do |path|
|
73
|
+
next unless File.directory?(path)
|
74
|
+
|
75
|
+
folder_name = File.basename(path)
|
76
|
+
task_files = Dir.glob(File.join(path, '*.md')).length
|
77
|
+
|
78
|
+
folders << {
|
79
|
+
name: folder_name,
|
80
|
+
path: path,
|
81
|
+
task_count: task_files,
|
82
|
+
status: folder_name # todo, in_progress, done, etc.
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
folders
|
87
|
+
rescue StandardError => e
|
88
|
+
Logger.warn("Failed to analyze task folders: #{e.message}")
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_kanban_data
|
94
|
+
{
|
95
|
+
swarm_tasks_available: @tasks_available,
|
96
|
+
tasks: list_tasks,
|
97
|
+
folders: get_task_folders,
|
98
|
+
active_tasks: get_active_tasks
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def setup_task_management
|
103
|
+
return false unless @tasks_available
|
104
|
+
|
105
|
+
begin
|
106
|
+
# Initialize swarm-tasks if not already done
|
107
|
+
unless Dir.exist?(File.join(Dir.pwd, 'tasks'))
|
108
|
+
Logger.info("Initializing swarm-tasks for project")
|
109
|
+
`bundle exec swarm-tasks init 2>/dev/null`
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create default task categories if they don't exist
|
113
|
+
default_folders = ['todo', 'in_progress', 'review', 'done']
|
114
|
+
tasks_dir = File.join(Dir.pwd, 'tasks')
|
115
|
+
|
116
|
+
default_folders.each do |folder|
|
117
|
+
folder_path = File.join(tasks_dir, folder)
|
118
|
+
unless Dir.exist?(folder_path)
|
119
|
+
FileUtils.mkdir_p(folder_path)
|
120
|
+
Logger.info("Created task folder: #{folder}")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
true
|
125
|
+
rescue StandardError => e
|
126
|
+
Logger.error("Failed to setup task management: #{e.message}")
|
127
|
+
false
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def check_swarm_tasks_availability
|
134
|
+
begin
|
135
|
+
# Check if swarm-tasks gem is available
|
136
|
+
require 'swarm_tasks'
|
137
|
+
|
138
|
+
# Check if swarm-tasks command is available
|
139
|
+
result = `bundle exec swarm-tasks --version 2>/dev/null`
|
140
|
+
$?.success?
|
141
|
+
rescue LoadError
|
142
|
+
Logger.warn("swarm-tasks gem not available - task management features limited")
|
143
|
+
false
|
144
|
+
rescue StandardError => e
|
145
|
+
Logger.warn("swarm-tasks not accessible: #{e.message}")
|
146
|
+
false
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require_relative 'command_executor'
|
5
|
+
|
6
|
+
module EnhanceSwarm
|
7
|
+
class TaskManager
|
8
|
+
def initialize
|
9
|
+
@config = EnhanceSwarm.configuration
|
10
|
+
end
|
11
|
+
|
12
|
+
def next_priority_task
|
13
|
+
# Try to use swarm-tasks if available
|
14
|
+
if swarm_tasks_available?
|
15
|
+
list_swarm_tasks('backlog').first
|
16
|
+
else
|
17
|
+
# Fallback to simple file-based tasks
|
18
|
+
find_next_file_task
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_task(task_id)
|
23
|
+
if swarm_tasks_available?
|
24
|
+
show_swarm_task(task_id)
|
25
|
+
else
|
26
|
+
find_file_task(task_id)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def move_task(task_id, state)
|
31
|
+
if swarm_tasks_available?
|
32
|
+
begin
|
33
|
+
CommandExecutor.execute(@config.task_command, 'move', task_id.to_s, state.to_s)
|
34
|
+
rescue CommandExecutor::CommandError => e
|
35
|
+
puts "Failed to move task: #{e.message}".colorize(:red)
|
36
|
+
false
|
37
|
+
end
|
38
|
+
else
|
39
|
+
puts 'Task management not configured. Please set up swarm-tasks.'.colorize(:yellow)
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def list_tasks(state = 'all')
|
45
|
+
if swarm_tasks_available?
|
46
|
+
list_swarm_tasks(state)
|
47
|
+
else
|
48
|
+
list_file_tasks(state)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def swarm_tasks_available?
|
55
|
+
CommandExecutor.command_available?(@config.task_command.split.first)
|
56
|
+
rescue CommandExecutor::CommandError
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
def list_swarm_tasks(state)
|
61
|
+
output = CommandExecutor.execute(@config.task_command, 'list', state, '--json')
|
62
|
+
return [] if output.empty?
|
63
|
+
|
64
|
+
tasks = JSON.parse(output)
|
65
|
+
tasks.map do |task|
|
66
|
+
{
|
67
|
+
id: task['id'],
|
68
|
+
title: task['title'] || task['content'],
|
69
|
+
description: task['description'] || task['content'],
|
70
|
+
state: task['state'] || task['status'],
|
71
|
+
priority: task['priority'],
|
72
|
+
effort: task['effort'],
|
73
|
+
tags: task['tags'] || []
|
74
|
+
}
|
75
|
+
end
|
76
|
+
rescue CommandExecutor::CommandError, JSON::ParserError
|
77
|
+
[]
|
78
|
+
end
|
79
|
+
|
80
|
+
def show_swarm_task(task_id)
|
81
|
+
output = CommandExecutor.execute(@config.task_command, 'show', task_id.to_s, '--json')
|
82
|
+
return nil if output.empty?
|
83
|
+
|
84
|
+
task = JSON.parse(output)
|
85
|
+
{
|
86
|
+
id: task['id'],
|
87
|
+
title: task['title'] || task['content'],
|
88
|
+
description: task['description'] || task['content'],
|
89
|
+
state: task['state'] || task['status'],
|
90
|
+
priority: task['priority'],
|
91
|
+
effort: task['effort'],
|
92
|
+
tags: task['tags'] || []
|
93
|
+
}
|
94
|
+
rescue CommandExecutor::CommandError, JSON::ParserError
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def find_next_file_task
|
99
|
+
# Simple file-based task system fallback
|
100
|
+
task_dir = File.join(EnhanceSwarm.root, 'tasks', 'backlog')
|
101
|
+
return nil unless Dir.exist?(task_dir)
|
102
|
+
|
103
|
+
task_files = Dir.glob(File.join(task_dir, '*.md')).sort
|
104
|
+
return nil if task_files.empty?
|
105
|
+
|
106
|
+
task_file = task_files.first
|
107
|
+
content = File.read(task_file)
|
108
|
+
|
109
|
+
{
|
110
|
+
id: File.basename(task_file, '.md'),
|
111
|
+
title: extract_title(content),
|
112
|
+
description: content,
|
113
|
+
state: 'backlog',
|
114
|
+
priority: 'medium',
|
115
|
+
effort: 4,
|
116
|
+
tags: []
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def find_file_task(task_id)
|
121
|
+
%w[backlog active completed].each do |state|
|
122
|
+
task_file = File.join(EnhanceSwarm.root, 'tasks', state, "#{task_id}.md")
|
123
|
+
next unless File.exist?(task_file)
|
124
|
+
|
125
|
+
content = File.read(task_file)
|
126
|
+
return {
|
127
|
+
id: task_id,
|
128
|
+
title: extract_title(content),
|
129
|
+
description: content,
|
130
|
+
state: state,
|
131
|
+
priority: 'medium',
|
132
|
+
effort: 4,
|
133
|
+
tags: []
|
134
|
+
}
|
135
|
+
end
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
|
139
|
+
def list_file_tasks(state)
|
140
|
+
tasks = []
|
141
|
+
|
142
|
+
states = state == 'all' ? %w[backlog active completed] : [state]
|
143
|
+
|
144
|
+
states.each do |s|
|
145
|
+
task_dir = File.join(EnhanceSwarm.root, 'tasks', s)
|
146
|
+
next unless Dir.exist?(task_dir)
|
147
|
+
|
148
|
+
Dir.glob(File.join(task_dir, '*.md')).each do |file|
|
149
|
+
content = File.read(file)
|
150
|
+
tasks << {
|
151
|
+
id: File.basename(file, '.md'),
|
152
|
+
title: extract_title(content),
|
153
|
+
description: content,
|
154
|
+
state: s,
|
155
|
+
priority: 'medium',
|
156
|
+
effort: 4,
|
157
|
+
tags: []
|
158
|
+
}
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
tasks
|
163
|
+
end
|
164
|
+
|
165
|
+
def extract_title(content)
|
166
|
+
# Try to extract first heading or first line
|
167
|
+
if (match = content.match(/^#\s+(.+)$/))
|
168
|
+
match[1]
|
169
|
+
else
|
170
|
+
content.lines.first&.strip || 'Untitled Task'
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|