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,499 @@
1
+ // Kanban Board JavaScript for EnhanceSwarm
2
+
3
+ // Kanban state
4
+ window.KanbanBoard = {
5
+ columns: [
6
+ { id: 'todo', title: 'To Do', tasks: [] },
7
+ { id: 'in_progress', title: 'In Progress', tasks: [] },
8
+ { id: 'review', title: 'Review', tasks: [] },
9
+ { id: 'done', title: 'Done', tasks: [] }
10
+ ],
11
+ sortables: {}
12
+ };
13
+
14
+ // Initialize kanban board
15
+ function initializeKanban() {
16
+ console.log('Initializing Kanban board...');
17
+ }
18
+
19
+ // Load kanban data
20
+ async function loadKanbanData() {
21
+ try {
22
+ const taskData = await apiRequest('/api/tasks');
23
+
24
+ // Process task data
25
+ processTaskData(taskData);
26
+
27
+ // Render kanban board
28
+ renderKanbanBoard();
29
+
30
+ // Update stats
31
+ updateKanbanStats();
32
+
33
+ } catch (error) {
34
+ console.error('Failed to load kanban data:', error);
35
+ showKanbanError('Failed to load task data');
36
+ }
37
+ }
38
+
39
+ function processTaskData(taskData) {
40
+ // Reset columns
41
+ window.KanbanBoard.columns.forEach(column => {
42
+ column.tasks = [];
43
+ });
44
+
45
+ // Process swarm-tasks data
46
+ if (taskData.tasks && Array.isArray(taskData.tasks)) {
47
+ taskData.tasks.forEach(task => {
48
+ const column = findColumnByTaskStatus(task.status);
49
+ if (column) {
50
+ column.tasks.push(formatTask(task));
51
+ }
52
+ });
53
+ }
54
+
55
+ // Process folder-based tasks
56
+ if (taskData.folders && Array.isArray(taskData.folders)) {
57
+ taskData.folders.forEach(folder => {
58
+ const column = window.KanbanBoard.columns.find(col => col.id === folder.name);
59
+ if (column && folder.task_count > 0) {
60
+ // Add placeholder tasks for folder-based tasks
61
+ for (let i = 0; i < folder.task_count; i++) {
62
+ column.tasks.push({
63
+ id: `${folder.name}_${i}`,
64
+ title: `Task from ${folder.name}`,
65
+ description: `File-based task in ${folder.path}`,
66
+ priority: 'medium',
67
+ category: 'file-based',
68
+ agents: [],
69
+ created_at: new Date().toISOString()
70
+ });
71
+ }
72
+ }
73
+ });
74
+ }
75
+ }
76
+
77
+ function findColumnByTaskStatus(status) {
78
+ const statusMap = {
79
+ 'todo': 'todo',
80
+ 'pending': 'todo',
81
+ 'active': 'in_progress',
82
+ 'in_progress': 'in_progress',
83
+ 'working': 'in_progress',
84
+ 'review': 'review',
85
+ 'testing': 'review',
86
+ 'done': 'done',
87
+ 'completed': 'done',
88
+ 'finished': 'done'
89
+ };
90
+
91
+ const mappedStatus = statusMap[status] || 'todo';
92
+ return window.KanbanBoard.columns.find(col => col.id === mappedStatus);
93
+ }
94
+
95
+ function formatTask(task) {
96
+ return {
97
+ id: task.id || generateTaskId(),
98
+ title: task.title || task.name || 'Untitled Task',
99
+ description: task.description || task.content || '',
100
+ priority: task.priority || 'medium',
101
+ category: task.category || task.type || 'general',
102
+ agents: task.agents || task.recommended_agents || [],
103
+ created_at: task.created_at || task.start_time || new Date().toISOString(),
104
+ updated_at: task.updated_at || task.end_time,
105
+ status: task.status || 'todo'
106
+ };
107
+ }
108
+
109
+ function generateTaskId() {
110
+ return 'task_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
111
+ }
112
+
113
+ function renderKanbanBoard() {
114
+ const board = document.getElementById('kanban-board');
115
+ if (!board) return;
116
+
117
+ board.innerHTML = window.KanbanBoard.columns.map(column =>
118
+ renderKanbanColumn(column)
119
+ ).join('');
120
+
121
+ // Initialize drag and drop
122
+ initializeDragAndDrop();
123
+ }
124
+
125
+ function renderKanbanColumn(column) {
126
+ return `
127
+ <div class="kanban-column" data-column-id="${column.id}">
128
+ <div class="kanban-column-header">
129
+ <span class="kanban-column-title">${column.title}</span>
130
+ <span class="kanban-column-count">${column.tasks.length}</span>
131
+ </div>
132
+ <div class="kanban-column-body" id="column-${column.id}">
133
+ ${column.tasks.map(task => renderTaskCard(task)).join('')}
134
+ </div>
135
+ </div>
136
+ `;
137
+ }
138
+
139
+ function renderTaskCard(task) {
140
+ return `
141
+ <div class="task-card" data-task-id="${task.id}" onclick="openTaskDetails('${task.id}')">
142
+ <div class="task-title">${escapeHtml(task.title)}</div>
143
+ ${task.description ? `<div class="task-description">${escapeHtml(task.description.substring(0, 100))}${task.description.length > 100 ? '...' : ''}</div>` : ''}
144
+ <div class="task-meta">
145
+ <span class="task-priority ${task.priority}">${task.priority.toUpperCase()}</span>
146
+ <span class="task-date">${formatTaskDate(task.created_at)}</span>
147
+ </div>
148
+ ${task.agents && task.agents.length > 0 ? `
149
+ <div class="task-agents">
150
+ ${task.agents.slice(0, 3).map(agent => `<span class="task-agent">${agent}</span>`).join('')}
151
+ ${task.agents.length > 3 ? `<span class="task-agent-more">+${task.agents.length - 3}</span>` : ''}
152
+ </div>
153
+ ` : ''}
154
+ </div>
155
+ `;
156
+ }
157
+
158
+ function initializeDragAndDrop() {
159
+ // Destroy existing sortables
160
+ Object.values(window.KanbanBoard.sortables).forEach(sortable => {
161
+ if (sortable && typeof sortable.destroy === 'function') {
162
+ sortable.destroy();
163
+ }
164
+ });
165
+ window.KanbanBoard.sortables = {};
166
+
167
+ // Check if Sortable is available
168
+ if (typeof Sortable === 'undefined') {
169
+ console.warn('Sortable.js not loaded, drag and drop disabled');
170
+ return;
171
+ }
172
+
173
+ // Initialize sortable for each column
174
+ window.KanbanBoard.columns.forEach(column => {
175
+ const columnElement = document.getElementById(`column-${column.id}`);
176
+ if (columnElement) {
177
+ window.KanbanBoard.sortables[column.id] = Sortable.create(columnElement, {
178
+ group: 'kanban',
179
+ animation: 150,
180
+ ghostClass: 'task-card-ghost',
181
+ dragClass: 'task-card-drag',
182
+ onEnd: function(evt) {
183
+ handleTaskMove(evt);
184
+ }
185
+ });
186
+ }
187
+ });
188
+ }
189
+
190
+ function handleTaskMove(evt) {
191
+ const taskId = evt.item.dataset.taskId;
192
+ const newColumnId = evt.to.closest('.kanban-column').dataset.columnId;
193
+ const oldColumnId = evt.from.closest('.kanban-column').dataset.columnId;
194
+
195
+ if (newColumnId === oldColumnId) return;
196
+
197
+ console.log(`Moving task ${taskId} from ${oldColumnId} to ${newColumnId}`);
198
+
199
+ // Update local state
200
+ moveTaskBetweenColumns(taskId, oldColumnId, newColumnId);
201
+
202
+ // Update column counts
203
+ updateColumnCounts();
204
+
205
+ // Show success message
206
+ showNotification(`Task moved to ${getColumnTitle(newColumnId)}`, 'success');
207
+
208
+ // TODO: Send API request to update task status
209
+ // updateTaskStatus(taskId, newColumnId);
210
+ }
211
+
212
+ function moveTaskBetweenColumns(taskId, fromColumnId, toColumnId) {
213
+ const fromColumn = window.KanbanBoard.columns.find(col => col.id === fromColumnId);
214
+ const toColumn = window.KanbanBoard.columns.find(col => col.id === toColumnId);
215
+
216
+ if (!fromColumn || !toColumn) return;
217
+
218
+ const taskIndex = fromColumn.tasks.findIndex(task => task.id === taskId);
219
+ if (taskIndex === -1) return;
220
+
221
+ const task = fromColumn.tasks.splice(taskIndex, 1)[0];
222
+ task.status = toColumnId;
223
+ task.updated_at = new Date().toISOString();
224
+
225
+ toColumn.tasks.push(task);
226
+ }
227
+
228
+ function getColumnTitle(columnId) {
229
+ const column = window.KanbanBoard.columns.find(col => col.id === columnId);
230
+ return column ? column.title : columnId;
231
+ }
232
+
233
+ function updateColumnCounts() {
234
+ window.KanbanBoard.columns.forEach(column => {
235
+ const countElement = document.querySelector(`[data-column-id="${column.id}"] .kanban-column-count`);
236
+ if (countElement) {
237
+ countElement.textContent = column.tasks.length;
238
+ }
239
+ });
240
+ }
241
+
242
+ function updateKanbanStats() {
243
+ const totalTasks = window.KanbanBoard.columns.reduce((sum, col) => sum + col.tasks.length, 0);
244
+ const activeTasks = window.KanbanBoard.columns.find(col => col.id === 'in_progress')?.tasks.length || 0;
245
+ const completedTasks = window.KanbanBoard.columns.find(col => col.id === 'done')?.tasks.length || 0;
246
+
247
+ document.getElementById('total-tasks').textContent = totalTasks;
248
+ document.getElementById('active-tasks').textContent = activeTasks;
249
+ document.getElementById('completed-tasks').textContent = completedTasks;
250
+ }
251
+
252
+ // Task management functions
253
+ function createTask() {
254
+ openModal('create-task-modal');
255
+ }
256
+
257
+ function openTaskDetails(taskId) {
258
+ const task = findTaskById(taskId);
259
+ if (!task) return;
260
+
261
+ const modal = document.getElementById('task-details-modal');
262
+ const title = document.getElementById('task-details-title');
263
+ const body = document.getElementById('task-details-body');
264
+
265
+ title.textContent = task.title;
266
+ body.innerHTML = `
267
+ <div class="task-details">
268
+ <div class="detail-row">
269
+ <strong>Description:</strong>
270
+ <p>${escapeHtml(task.description) || 'No description provided'}</p>
271
+ </div>
272
+ <div class="detail-row">
273
+ <strong>Priority:</strong>
274
+ <span class="task-priority ${task.priority}">${task.priority.toUpperCase()}</span>
275
+ </div>
276
+ <div class="detail-row">
277
+ <strong>Category:</strong>
278
+ <span>${task.category}</span>
279
+ </div>
280
+ <div class="detail-row">
281
+ <strong>Status:</strong>
282
+ <span>${task.status}</span>
283
+ </div>
284
+ <div class="detail-row">
285
+ <strong>Created:</strong>
286
+ <span>${formatTaskDate(task.created_at)}</span>
287
+ </div>
288
+ ${task.updated_at ? `
289
+ <div class="detail-row">
290
+ <strong>Updated:</strong>
291
+ <span>${formatTaskDate(task.updated_at)}</span>
292
+ </div>
293
+ ` : ''}
294
+ ${task.agents && task.agents.length > 0 ? `
295
+ <div class="detail-row">
296
+ <strong>Recommended Agents:</strong>
297
+ <div class="agent-tags">
298
+ ${task.agents.map(agent => `<span class="agent-tag">${agent}</span>`).join('')}
299
+ </div>
300
+ </div>
301
+ ` : ''}
302
+ </div>
303
+ `;
304
+
305
+ openModal('task-details-modal');
306
+ }
307
+
308
+ function findTaskById(taskId) {
309
+ for (const column of window.KanbanBoard.columns) {
310
+ const task = column.tasks.find(t => t.id === taskId);
311
+ if (task) return task;
312
+ }
313
+ return null;
314
+ }
315
+
316
+ // Form handling for task creation
317
+ document.addEventListener('submit', function(event) {
318
+ if (event.target.id === 'create-task-form') {
319
+ event.preventDefault();
320
+ handleCreateTask(event.target);
321
+ }
322
+ });
323
+
324
+ function handleCreateTask(form) {
325
+ const formData = new FormData(form);
326
+ const agents = Array.from(form.querySelectorAll('input[name="agents"]:checked')).map(cb => cb.value);
327
+
328
+ const task = {
329
+ id: generateTaskId(),
330
+ title: formData.get('title'),
331
+ description: formData.get('description'),
332
+ priority: formData.get('priority'),
333
+ category: formData.get('category'),
334
+ agents: agents,
335
+ created_at: new Date().toISOString(),
336
+ status: 'todo'
337
+ };
338
+
339
+ // Add to todo column
340
+ const todoColumn = window.KanbanBoard.columns.find(col => col.id === 'todo');
341
+ if (todoColumn) {
342
+ todoColumn.tasks.push(task);
343
+ }
344
+
345
+ // Re-render the board
346
+ renderKanbanBoard();
347
+ updateKanbanStats();
348
+
349
+ // Close modal and reset form
350
+ closeModal('create-task-modal');
351
+ form.reset();
352
+
353
+ showNotification('Task created successfully', 'success');
354
+ }
355
+
356
+ // Utility functions
357
+ function refreshKanban() {
358
+ loadKanbanData();
359
+ }
360
+
361
+ function exportTasks() {
362
+ const allTasks = [];
363
+ window.KanbanBoard.columns.forEach(column => {
364
+ allTasks.push(...column.tasks.map(task => ({
365
+ ...task,
366
+ column: column.title
367
+ })));
368
+ });
369
+
370
+ const dataStr = JSON.stringify(allTasks, null, 2);
371
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
372
+
373
+ const link = document.createElement('a');
374
+ link.href = URL.createObjectURL(dataBlob);
375
+ link.download = 'enhance-swarm-tasks.json';
376
+ link.click();
377
+
378
+ showNotification('Tasks exported successfully', 'success');
379
+ }
380
+
381
+ function showKanbanError(message) {
382
+ const board = document.getElementById('kanban-board');
383
+ if (board) {
384
+ board.innerHTML = `
385
+ <div class="kanban-error">
386
+ <i class="fas fa-exclamation-triangle"></i>
387
+ <h3>Unable to load tasks</h3>
388
+ <p>${escapeHtml(message)}</p>
389
+ <button class="btn btn-primary" onclick="loadKanbanData()">
390
+ <i class="fas fa-retry"></i> Retry
391
+ </button>
392
+ </div>
393
+ `;
394
+ }
395
+ }
396
+
397
+ function formatTaskDate(dateString) {
398
+ if (!dateString) return 'Unknown';
399
+
400
+ try {
401
+ const date = new Date(dateString);
402
+ const now = new Date();
403
+ const diffMs = now - date;
404
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
405
+
406
+ if (diffDays === 0) return 'Today';
407
+ if (diffDays === 1) return 'Yesterday';
408
+ if (diffDays < 7) return `${diffDays} days ago`;
409
+
410
+ return date.toLocaleDateString();
411
+ } catch (error) {
412
+ return 'Unknown';
413
+ }
414
+ }
415
+
416
+ function escapeHtml(text) {
417
+ if (!text) return '';
418
+ const div = document.createElement('div');
419
+ div.textContent = text;
420
+ return div.innerHTML;
421
+ }
422
+
423
+ // Add custom styles for kanban
424
+ const kanbanStyles = `
425
+ .task-card-ghost {
426
+ opacity: 0.5;
427
+ }
428
+
429
+ .task-card-drag {
430
+ transform: rotate(5deg);
431
+ }
432
+
433
+ .task-agents {
434
+ margin-top: 0.5rem;
435
+ display: flex;
436
+ flex-wrap: wrap;
437
+ gap: 0.25rem;
438
+ }
439
+
440
+ .task-agent {
441
+ background: var(--primary-color);
442
+ color: white;
443
+ padding: 0.125rem 0.5rem;
444
+ border-radius: 12px;
445
+ font-size: 0.75rem;
446
+ }
447
+
448
+ .task-agent-more {
449
+ background: var(--text-secondary);
450
+ color: white;
451
+ padding: 0.125rem 0.5rem;
452
+ border-radius: 12px;
453
+ font-size: 0.75rem;
454
+ }
455
+
456
+ .kanban-error {
457
+ display: flex;
458
+ flex-direction: column;
459
+ align-items: center;
460
+ justify-content: center;
461
+ height: 400px;
462
+ text-align: center;
463
+ color: var(--text-secondary);
464
+ }
465
+
466
+ .kanban-error i {
467
+ font-size: 3rem;
468
+ margin-bottom: 1rem;
469
+ color: var(--warning-color);
470
+ }
471
+
472
+ .detail-row {
473
+ margin-bottom: 1rem;
474
+ }
475
+
476
+ .detail-row strong {
477
+ display: block;
478
+ margin-bottom: 0.5rem;
479
+ color: var(--text-primary);
480
+ }
481
+
482
+ .agent-tags {
483
+ display: flex;
484
+ flex-wrap: wrap;
485
+ gap: 0.5rem;
486
+ }
487
+
488
+ .agent-tag {
489
+ background: var(--light-bg);
490
+ padding: 0.25rem 0.75rem;
491
+ border-radius: 16px;
492
+ border: 1px solid var(--border-color);
493
+ font-size: 0.875rem;
494
+ }
495
+ `;
496
+
497
+ const kanbanStyleSheet = document.createElement('style');
498
+ kanbanStyleSheet.textContent = kanbanStyles;
499
+ document.head.appendChild(kanbanStyleSheet);