ultra-dex 2.2.1 → 3.2.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 (87) hide show
  1. package/README.md +112 -151
  2. package/assets/agents/00-AGENT_INDEX.md +1 -1
  3. package/assets/code-patterns/clerk-middleware.ts +138 -0
  4. package/assets/code-patterns/prisma-schema.prisma +224 -0
  5. package/assets/code-patterns/rls-policies.sql +246 -0
  6. package/assets/code-patterns/server-actions.ts +191 -0
  7. package/assets/code-patterns/trpc-router.ts +258 -0
  8. package/assets/cursor-rules/13-ai-integration.mdc +155 -0
  9. package/assets/cursor-rules/14-server-components.mdc +81 -0
  10. package/assets/cursor-rules/15-server-actions.mdc +102 -0
  11. package/assets/cursor-rules/16-edge-middleware.mdc +105 -0
  12. package/assets/cursor-rules/17-streaming-ssr.mdc +138 -0
  13. package/assets/docs/LAUNCH-POSTS.md +1 -1
  14. package/assets/docs/QUICK-REFERENCE.md +9 -4
  15. package/assets/docs/VISION-V2.md +1 -1
  16. package/assets/hooks/pre-commit +98 -0
  17. package/assets/saas-plan/04-Imp-Template.md +1 -1
  18. package/bin/ultra-dex.js +132 -4
  19. package/lib/commands/advanced.js +471 -0
  20. package/lib/commands/agent-builder.js +226 -0
  21. package/lib/commands/agents.js +102 -42
  22. package/lib/commands/auto-implement.js +68 -0
  23. package/lib/commands/banner.js +43 -21
  24. package/lib/commands/build.js +78 -183
  25. package/lib/commands/ci-monitor.js +84 -0
  26. package/lib/commands/config.js +207 -0
  27. package/lib/commands/dashboard.js +770 -0
  28. package/lib/commands/diff.js +233 -0
  29. package/lib/commands/doctor.js +416 -0
  30. package/lib/commands/export.js +408 -0
  31. package/lib/commands/fix.js +96 -0
  32. package/lib/commands/generate.js +105 -78
  33. package/lib/commands/hooks.js +251 -76
  34. package/lib/commands/init.js +102 -54
  35. package/lib/commands/memory.js +80 -0
  36. package/lib/commands/plan.js +82 -0
  37. package/lib/commands/review.js +34 -5
  38. package/lib/commands/run.js +233 -0
  39. package/lib/commands/scaffold.js +151 -0
  40. package/lib/commands/serve.js +179 -146
  41. package/lib/commands/state.js +327 -0
  42. package/lib/commands/swarm.js +306 -0
  43. package/lib/commands/sync.js +82 -23
  44. package/lib/commands/team.js +275 -0
  45. package/lib/commands/upgrade.js +190 -0
  46. package/lib/commands/validate.js +34 -0
  47. package/lib/commands/verify.js +81 -0
  48. package/lib/commands/watch.js +79 -0
  49. package/lib/config/theme.js +47 -0
  50. package/lib/mcp/graph.js +92 -0
  51. package/lib/mcp/memory.js +95 -0
  52. package/lib/mcp/resources.js +152 -0
  53. package/lib/mcp/server.js +34 -0
  54. package/lib/mcp/tools.js +481 -0
  55. package/lib/mcp/websocket.js +117 -0
  56. package/lib/providers/index.js +49 -4
  57. package/lib/providers/ollama.js +136 -0
  58. package/lib/providers/router.js +63 -0
  59. package/lib/quality/scanner.js +128 -0
  60. package/lib/swarm/coordinator.js +97 -0
  61. package/lib/swarm/index.js +598 -0
  62. package/lib/swarm/protocol.js +677 -0
  63. package/lib/swarm/tiers.js +485 -0
  64. package/lib/templates/code/clerk-middleware.ts +138 -0
  65. package/lib/templates/code/prisma-schema.prisma +224 -0
  66. package/lib/templates/code/rls-policies.sql +246 -0
  67. package/lib/templates/code/server-actions.ts +191 -0
  68. package/lib/templates/code/trpc-router.ts +258 -0
  69. package/lib/templates/custom-agent.md +10 -0
  70. package/lib/themes/doomsday.js +229 -0
  71. package/lib/ui/index.js +5 -0
  72. package/lib/ui/interface.js +241 -0
  73. package/lib/ui/spinners.js +116 -0
  74. package/lib/ui/theme.js +183 -0
  75. package/lib/utils/agents.js +32 -0
  76. package/lib/utils/files.js +14 -0
  77. package/lib/utils/graph.js +108 -0
  78. package/lib/utils/help.js +64 -0
  79. package/lib/utils/messages.js +35 -0
  80. package/lib/utils/progress.js +24 -0
  81. package/lib/utils/prompts.js +47 -0
  82. package/lib/utils/spinners.js +46 -0
  83. package/lib/utils/status.js +31 -0
  84. package/lib/utils/tables.js +41 -0
  85. package/lib/utils/theme-state.js +9 -0
  86. package/lib/utils/version-display.js +32 -0
  87. package/package.json +31 -13
@@ -0,0 +1,327 @@
1
+ /**
2
+ * ultra-dex state management commands
3
+ * align, status, watch, pre-commit, state
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import { validateSafePath } from '../utils/validation.js';
10
+ import { buildGraph } from '../utils/graph.js';
11
+
12
+ // State management helpers
13
+ export async function loadState() {
14
+ try {
15
+ const content = await fs.readFile(path.resolve(process.cwd(), '.ultra/state.json'), 'utf8');
16
+ return JSON.parse(content);
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ export async function saveState(state) {
23
+ const ultraDir = path.resolve(process.cwd(), '.ultra');
24
+ const statePath = path.resolve(ultraDir, 'state.json');
25
+ try {
26
+ await fs.mkdir(ultraDir, { recursive: true });
27
+ await fs.writeFile(statePath, JSON.stringify(state, null, 2));
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ export async function computeState() {
35
+ const existing = await loadState();
36
+ if (existing && existing.project?.mode === 'ULTRA_MODE') {
37
+ existing.updatedAt = new Date().toISOString();
38
+ return existing;
39
+ }
40
+
41
+ const state = {
42
+ version: '3.2.0',
43
+ updatedAt: new Date().toISOString(),
44
+ project: { name: path.basename(process.cwd()), mode: 'ULTRA_MODE' },
45
+ files: {},
46
+ sections: { total: 34, completed: 0, list: [] },
47
+ score: 0
48
+ };
49
+
50
+ const coreFiles = ['CONTEXT.md', 'IMPLEMENTATION-PLAN.md', 'CHECKLIST.md', 'QUICK-START.md'];
51
+ for (const file of coreFiles) {
52
+ try {
53
+ const stat = await fs.stat(path.resolve(process.cwd(), file));
54
+ state.files[file] = { exists: true, size: stat.size, modified: stat.mtime.toISOString() };
55
+ } catch {
56
+ state.files[file] = { exists: false };
57
+ }
58
+ }
59
+
60
+ try {
61
+ const plan = await fs.readFile(path.resolve(process.cwd(), 'IMPLEMENTATION-PLAN.md'), 'utf8');
62
+ const sectionRegex = /^##\s+(\d+)\.\s+(.+)$/gm;
63
+ let match;
64
+ while ((match = sectionRegex.exec(plan)) !== null) {
65
+ state.sections.list.push({ number: parseInt(match[1]), title: match[2].trim() });
66
+ }
67
+ state.sections.completed = state.sections.list.length;
68
+ } catch { /* no plan */ }
69
+
70
+ const fileScore = Object.values(state.files).filter(f => f.exists).length / coreFiles.length * 40;
71
+ const sectionScore = Math.min(state.sections.completed / state.sections.total * 60, 60);
72
+ state.score = Math.round(fileScore + sectionScore);
73
+
74
+ return state;
75
+ }
76
+
77
+ export async function updateState() {
78
+ const state = await computeState();
79
+ await saveState(state);
80
+ return state;
81
+ }
82
+
83
+ export function registerAlignCommand(program) {
84
+ program
85
+ .command('align')
86
+ .description('Quick alignment score using Code Property Graph')
87
+ .option('--strict', 'Exit with error if score < 70')
88
+ .option('--json', 'Output as JSON')
89
+ .action(async (options) => {
90
+ const state = await computeState();
91
+
92
+ let graphScore = 0;
93
+ let graphStats = { nodes: 0, edges: 0 };
94
+
95
+ try {
96
+ const graph = await buildGraph();
97
+ graphStats = { nodes: graph.nodes.length, edges: graph.edges.length };
98
+
99
+ const nodesPoints = Math.min(graph.nodes.length * 2, 20);
100
+ const edgesPoints = Math.min(graph.edges.length * 2, 20);
101
+ graphScore = nodesPoints + edgesPoints;
102
+ } catch (e) {
103
+ // Graph failed
104
+ }
105
+
106
+ const totalScore = Math.min(Math.round((state.score * 0.6) + graphScore), 100);
107
+
108
+ if (options.json) {
109
+ console.log(JSON.stringify({
110
+ score: totalScore,
111
+ documentationScore: state.score,
112
+ graphScore,
113
+ graphStats,
114
+ files: Object.values(state.files).filter(f => f.exists).length,
115
+ sections: state.sections.completed
116
+ }));
117
+ } else {
118
+ const icon = totalScore >= 80 ? 'āœ…' : totalScore >= 50 ? 'āš ļø' : 'āŒ';
119
+ console.log(`${icon} Alignment: ${totalScore}/100`);
120
+ console.log(chalk.gray(` • Documentation: ${state.score}/100`));
121
+ console.log(chalk.gray(` • Code Graph: ${graphScore}/40 (Nodes: ${graphStats.nodes}, Edges: ${graphStats.edges})`));
122
+ }
123
+
124
+ if (options.strict && totalScore < 70) {
125
+ process.exit(1);
126
+ }
127
+ });
128
+ }
129
+
130
+ export function registerStatusCommand(program) {
131
+ program
132
+ .command('status')
133
+ .description('Show current project state')
134
+ .option('--refresh', 'Refresh state before showing')
135
+ .option('--json', 'Output raw JSON')
136
+ .action(async (options) => {
137
+ if (options.refresh) {
138
+ const state = await computeState();
139
+ await saveState(state);
140
+ }
141
+
142
+ let state = await loadState();
143
+ if (!state) {
144
+ console.log(chalk.yellow('\nā„¹ļø Initializing project state...\n'));
145
+ state = await computeState();
146
+ await saveState(state);
147
+ }
148
+
149
+ if (options.json) {
150
+ console.log(JSON.stringify(state, null, 2));
151
+ return;
152
+ }
153
+
154
+ console.log(chalk.bold('\nšŸ“Š Ultra-Dex Status\n'));
155
+ console.log(chalk.gray('─'.repeat(50)));
156
+
157
+ if (state.phases) {
158
+ console.log(chalk.cyan(` MODE: ${state.project?.mode || 'ULTRA_MODE'}`));
159
+ console.log(chalk.gray(` Version: ${state.project?.version || state.version}`));
160
+ console.log(chalk.gray('─'.repeat(50)));
161
+
162
+ console.log(chalk.bold('\nšŸš€ Implementation Phases:'));
163
+ state.phases.forEach(phase => {
164
+ const icon = phase.status === 'completed' ? 'āœ…' : phase.status === 'in_progress' ? 'šŸ”„' : 'ā³';
165
+ console.log(` ${icon} ${chalk.bold(phase.name)}`);
166
+ phase.steps.forEach(step => {
167
+ const stepIcon = step.status === 'completed' ? chalk.green('āœ“') : chalk.gray('-');
168
+ console.log(` ${stepIcon} ${step.task}`);
169
+ });
170
+ console.log('');
171
+ });
172
+
173
+ if (state.agents) {
174
+ console.log(chalk.bold('šŸ¤– Orchestration Agents:'));
175
+ state.agents.registry?.forEach(agent => {
176
+ const active = state.agents.active?.includes(agent) ? chalk.green('(Active)') : '';
177
+ console.log(` • @${agent} ${active}`);
178
+ });
179
+ }
180
+
181
+ } else {
182
+ const scoreColor = state.score >= 80 ? 'green' : state.score >= 50 ? 'yellow' : 'red';
183
+ console.log(chalk[scoreColor](` Score: ${state.score}/100`));
184
+ console.log(chalk.gray(` Updated: ${state.updatedAt}`));
185
+ console.log(chalk.gray('─'.repeat(50)));
186
+
187
+ console.log(chalk.bold('\nšŸ“ Documentation Files:'));
188
+ if (state.files) {
189
+ Object.entries(state.files).forEach(([name, info]) => {
190
+ const icon = info.exists ? chalk.green('āœ“') : chalk.red('āœ•');
191
+ const size = info.exists ? chalk.gray(` (${info.size} bytes)`) : '';
192
+ console.log(` ${icon} ${name}${size}`);
193
+ });
194
+ }
195
+
196
+ console.log(chalk.bold('\nšŸ“ Implementation Sections:'));
197
+ console.log(` ${state.sections.completed}/${state.sections.total} completed`);
198
+ if (state.sections.list?.length > 0) {
199
+ const recent = state.sections.list.slice(-3);
200
+ recent.forEach(s => console.log(chalk.gray(` ${s.number}. ${s.title}`)));
201
+ if (state.sections.list.length > 3) {
202
+ console.log(chalk.gray(` ... and ${state.sections.list.length - 3} more`));
203
+ }
204
+ }
205
+ }
206
+ console.log('');
207
+ });
208
+ }
209
+
210
+ export function registerWatchCommand(program) {
211
+ program
212
+ .command('watch-legacy')
213
+ .action(() => console.log("Use 'ultra-dex watch' instead."));
214
+ }
215
+
216
+ export function registerPreCommitCommand(program) {
217
+ program
218
+ .command('pre-commit')
219
+ .description('Pre-commit hook - verify standards before commit')
220
+ .option('--install', 'Install git pre-commit hook')
221
+ .option('--scan', 'Include deep code quality scan in hook')
222
+ .option('--ai', 'Run AI-powered quality review on staged changes')
223
+ .option('-d, --dir <directory>', 'Project directory', '.')
224
+ .action(async (options) => {
225
+ const dirValidation = validateSafePath(options.dir, 'Project directory');
226
+ if (dirValidation !== true) {
227
+ console.log(chalk.red(dirValidation));
228
+ process.exit(1);
229
+ }
230
+
231
+ const rootDir = path.resolve(options.dir);
232
+
233
+ if (options.install) {
234
+ const hookPath = path.resolve(rootDir, '.git/hooks/pre-commit');
235
+ const scanCmd = options.scan ? '\nnpx ultra-dex validate --scan' : '';
236
+ const aiCmd = options.ai ? '\nnpx ultra-dex pre-commit --ai' : '';
237
+ const hookScript = `#!/bin/sh
238
+ # Ultra-Dex pre-commit hook
239
+ npx ultra-dex align --strict${scanCmd}${aiCmd}
240
+ if [ $? -ne 0 ]; then
241
+ echo "āœ• Ultra-Dex quality gate failed."
242
+ echo " Run 'ultra-dex review' or 'ultra-dex validate --scan' for details."
243
+ exit 1
244
+ fi
245
+ `;
246
+ try {
247
+ await fs.mkdir(path.dirname(hookPath), { recursive: true });
248
+ await fs.writeFile(hookPath, hookScript, { mode: 0o755 });
249
+ console.log(chalk.green('āœ“ Pre-commit hook installed!'));
250
+ console.log(chalk.gray(' Commits will be blocked if alignment score < 70 or AI review fails.'));
251
+ } catch (e) {
252
+ console.log(chalk.red('āœ• Failed to install hook: ' + e.message));
253
+ }
254
+ return;
255
+ }
256
+
257
+ const state = await loadState();
258
+
259
+ if (options.ai) {
260
+ const spinner = (await import('ora')).default('šŸ¤– AI Quality Review: Analyzing staged changes...').start();
261
+ try {
262
+ const { execSync } = await import('child_process');
263
+ const staged = execSync('git diff --cached --name-only', { encoding: 'utf8' }).split('\n').filter(Boolean);
264
+ if (staged.length === 0) {
265
+ spinner.succeed('No staged changes to review.');
266
+ return;
267
+ }
268
+
269
+ const { createProvider, getDefaultProvider } = await import('../providers/index.js');
270
+ const { runAgentLoop } = await import('./run.js');
271
+
272
+ const provider = createProvider(getDefaultProvider());
273
+ const reviewResult = await runAgentLoop('reviewer', `Review these staged files for architectural violations (e.g. missing validation, security risks):\n${staged.join('\n')}`, provider, { state });
274
+
275
+ if (reviewResult.toLowerCase().includes('reject') || reviewResult.toLowerCase().includes('blocking violation')) {
276
+ spinner.fail('Quality Gate: REJECTED');
277
+ console.log(chalk.red('\nViolations found:'));
278
+ console.log(reviewResult);
279
+ process.exit(1);
280
+ }
281
+ spinner.succeed('Quality Gate: PASSED');
282
+ } catch (e) {
283
+ spinner.warn('Quality Gate skipped: ' + e.message);
284
+ }
285
+ }
286
+
287
+ if (state && state.score < 70) {
288
+ console.log(chalk.red(`āœ• BLOCKED: Alignment score ${state.score}/100 (required: 70)`));
289
+ console.log(chalk.yellow(' Run `ultra-dex review` for detailed analysis.'));
290
+ process.exit(1);
291
+ } else {
292
+ console.log(chalk.green(`āœ“ Alignment verified: ${state ? state.score : 'N/A'}/100`));
293
+ }
294
+ });
295
+ }
296
+
297
+ export function registerStateCommand(program) {
298
+ program
299
+ .command('state')
300
+ .description('Manage project state')
301
+ .option('--init', 'Initialize state directory')
302
+ .option('--refresh', 'Refresh state from documentation')
303
+ .action(async (options) => {
304
+ if (options.init || options.refresh) {
305
+ const state = await computeState();
306
+ await saveState(state);
307
+ console.log(chalk.green('āœ“ Project state updated'));
308
+ console.log(chalk.gray(` Score: ${state.score}/100`));
309
+ return;
310
+ }
311
+
312
+ const state = await loadState();
313
+ if (!state) {
314
+ console.log(chalk.yellow('No state file found. Run `ultra-dex state --init`'));
315
+ return;
316
+ }
317
+ console.log(JSON.stringify(state, null, 2));
318
+ });
319
+ }
320
+
321
+ export default {
322
+ registerAlignCommand,
323
+ registerStatusCommand,
324
+ registerWatchCommand,
325
+ registerPreCommitCommand,
326
+ registerStateCommand,
327
+ };
@@ -0,0 +1,306 @@
1
+ // cli/lib/commands/swarm.js
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { getProvider } from '../providers/index.js';
5
+ import { readFile, writeFile, mkdir } from 'fs/promises';
6
+ import { existsSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { glob } from 'glob';
9
+ import { projectGraph } from '../mcp/graph.js';
10
+ import { updateState, loadState, saveState } from './state.js';
11
+ import { agents } from '../utils/agents.js';
12
+ import { isDoomsdayMode } from '../utils/theme-state.js';
13
+ import { showSwarmAssemble as showDoomsdaySwarm } from '../themes/doomsday.js';
14
+
15
+ const AGENT_PIPELINE = [
16
+ { name: 'planner', description: 'Break down task into steps', tier: '1-planning' },
17
+ { name: 'cto', description: 'Define architecture', tier: '1-planning' },
18
+ { name: 'auth', description: 'Security & authentication review', tier: '3-security' },
19
+ { name: 'database', description: 'Design schema', tier: '2-implementation' },
20
+ { name: 'backend', description: 'Implement API', tier: '2-implementation' },
21
+ { name: 'frontend', description: 'Build UI', tier: '2-implementation' },
22
+ { name: 'testing', description: 'Write tests', tier: '4-quality' },
23
+ { name: 'reviewer', description: 'Code review', tier: '4-quality' }
24
+ ];
25
+
26
+ export function showSwarmAssemble(activeAgents) {
27
+ if (isDoomsdayMode()) {
28
+ return showDoomsdaySwarm(activeAgents);
29
+ }
30
+
31
+ console.log('');
32
+ console.log(chalk.hex('#8b5cf6').bold(' ⚔ AGENT PIPELINE INITIALIZED'));
33
+ console.log('');
34
+
35
+ activeAgents.forEach((agentInfo) => {
36
+ const agent = agents[agentInfo.name];
37
+ if (agent) {
38
+ console.log(` ${agent.emoji} ${chalk.hex('#6366f1').bold(agent.name)}`);
39
+ console.log(` ${chalk.dim('"' + agent.tagline + '"')}`);
40
+ console.log('');
41
+ }
42
+ });
43
+ }
44
+
45
+ async function runAgent(agent, task, context, previousOutput, provider) {
46
+ const agentPrompt = await loadAgentPrompt(agent.name);
47
+ const prompt = `
48
+ ${agentPrompt}
49
+
50
+ ## Context
51
+ ${context}
52
+
53
+ ## Previous Agent Output
54
+ ${previousOutput}
55
+
56
+ ## Task
57
+ ${task}
58
+
59
+ Provide your output for the next agent in the pipeline.
60
+ `;
61
+
62
+ // Standardize provider call
63
+ const response = provider.complete
64
+ ? await provider.complete(prompt)
65
+ : await provider.generate('', prompt);
66
+
67
+ return typeof response === 'string'
68
+ ? response
69
+ : (response.content || response.text || JSON.stringify(response));
70
+ }
71
+
72
+ async function ensureLogDirectory() {
73
+ const logDir = join(process.cwd(), '.ultra-dex', 'swarm-logs');
74
+ await mkdir(logDir, { recursive: true });
75
+ return logDir;
76
+ }
77
+
78
+ async function writeSwarmLog(logDir, task, results, stats) {
79
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
80
+ const logPath = join(logDir, `swarm-${timestamp}.json`);
81
+ const logData = {
82
+ task,
83
+ timestamp: new Date().toISOString(),
84
+ stats,
85
+ results
86
+ };
87
+ await writeFile(logPath, JSON.stringify(logData, null, 2));
88
+ return logPath;
89
+ }
90
+
91
+ export async function swarmCommand(task, options) {
92
+ console.log(chalk.cyan.bold('\nšŸ Ultra-Dex Swarm Mode\n'));
93
+ console.log(chalk.white(`Task: "${task}"\n`));
94
+
95
+ const startTime = Date.now();
96
+
97
+ if (options.dryRun) {
98
+ console.log(chalk.yellow('Dry run - showing pipeline:\n'));
99
+ AGENT_PIPELINE.forEach((agent, i) => {
100
+ console.log(` ${i + 1}. @${agent.name} - ${agent.description} [${agent.tier}]`);
101
+ });
102
+ if (options.parallel) {
103
+ console.log(chalk.blue('\nā„¹ļø Parallel execution enabled for implementation tier'));
104
+ }
105
+ return;
106
+ }
107
+
108
+ // Load context & Graph
109
+ const contextPath = join(process.cwd(), 'CONTEXT.md');
110
+ const planPath = join(process.cwd(), 'IMPLEMENTATION-PLAN.md');
111
+
112
+ let context = '';
113
+ if (existsSync(contextPath)) {
114
+ context += await readFile(contextPath, 'utf-8');
115
+ }
116
+ if (existsSync(planPath)) {
117
+ context += '\n\n' + await readFile(planPath, 'utf-8');
118
+ }
119
+
120
+ // Inject Code Graph into Context
121
+ const spinnerGraph = ora('🧠 Scanning Codebase Graph...').start();
122
+ try {
123
+ const graphSummary = await projectGraph.scan();
124
+ context += `\n\n## Codebase Graph Summary\n- Total Files: ${graphSummary.nodeCount}\n- Total Dependencies: ${graphSummary.edgeCount}\n- Files Analyzed: ${graphSummary.files.join(', ')}\n`;
125
+ spinnerGraph.succeed('Codebase Graph integrated into context.');
126
+ } catch (e) {
127
+ spinnerGraph.warn('Codebase Graph scan failed, proceeding with limited context.');
128
+ }
129
+
130
+ // Get AI provider
131
+ const provider = getProvider();
132
+ if (!provider) {
133
+ console.log(chalk.red('No AI provider configured. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY'));
134
+ return;
135
+ }
136
+
137
+ // Ensure log directory exists
138
+ const logDir = await ensureLogDirectory();
139
+
140
+ // Update State to indicate Swarm is running
141
+ const state = await loadState() || { project: { mode: 'ULTRA_MODE' }, agents: { active: [] } };
142
+ state.agents = state.agents || { active: [] };
143
+ state.updatedAt = new Date().toISOString();
144
+ await saveState(state);
145
+
146
+ // Run pipeline
147
+ let previousOutput = '';
148
+ const agentResults = [];
149
+ const agentTimings = {};
150
+
151
+ // Define execution tiers
152
+ const executionTiers = options.parallel
153
+ ? [
154
+ { name: '1-Planning', agents: AGENT_PIPELINE.filter(a => a.tier === '1-planning'), parallel: false },
155
+ { name: '2-Implementation', agents: AGENT_PIPELINE.filter(a => a.tier === '2-implementation'), parallel: true },
156
+ { name: '3-Security', agents: AGENT_PIPELINE.filter(a => a.tier === '3-security'), parallel: false },
157
+ { name: '4-Quality', agents: AGENT_PIPELINE.filter(a => a.tier === '4-quality'), parallel: false }
158
+ ]
159
+ : [{ name: 'All', agents: AGENT_PIPELINE, parallel: false }];
160
+
161
+ for (const tier of executionTiers) {
162
+ if (tier.agents.length === 0) continue;
163
+
164
+ if (options.parallel) {
165
+ console.log(chalk.blue.bold(`\nšŸ“¦ Tier: ${tier.name}`));
166
+ }
167
+
168
+ if (tier.parallel) {
169
+ // Parallel Execution
170
+ const tierStart = Date.now();
171
+ const promises = tier.agents.map(async (agent) => {
172
+ const agentStart = Date.now();
173
+ const spinner = ora(`Running @${agent.name}...`).start();
174
+
175
+ // Update state with active agent
176
+ const currentState = await loadState();
177
+ if (currentState) {
178
+ currentState.agents.active.push(agent.name);
179
+ await saveState(currentState);
180
+ }
181
+
182
+ try {
183
+ const result = await runAgent(agent, task, context, previousOutput, provider);
184
+ const duration = Date.now() - agentStart;
185
+ agentTimings[agent.name] = duration;
186
+ spinner.succeed(chalk.green(` @${agent.name} complete`) + chalk.gray(` (${duration}ms)`));
187
+
188
+ // Remove active agent from state
189
+ const stateDone = await loadState();
190
+ if (stateDone) {
191
+ stateDone.agents.active = stateDone.agents.active.filter(a => a !== agent.name);
192
+ await saveState(stateDone);
193
+ }
194
+
195
+ return { agent: agent.name, result, success: true };
196
+ } catch (error) {
197
+ const duration = Date.now() - agentStart;
198
+ agentTimings[agent.name] = duration;
199
+ spinner.fail(chalk.red(` @${agent.name} failed: ${error.message}`) + chalk.gray(` (${duration}ms)`));
200
+
201
+ const stateFail = await loadState();
202
+ if (stateFail) {
203
+ stateFail.agents.active = stateFail.agents.active.filter(a => a !== agent.name);
204
+ await saveState(stateFail);
205
+ }
206
+
207
+ return { agent: agent.name, error: error.message, success: false };
208
+ }
209
+ });
210
+
211
+ const results = await Promise.all(promises);
212
+ agentResults.push(...results);
213
+ previousOutput += '\n\n' + results.filter(r => r.success).map(r => r.result).join('\n\n');
214
+ console.log(chalk.gray(` Tier completed in ${Date.now() - tierStart}ms`));
215
+
216
+ } else {
217
+ // Serial Execution
218
+ for (const agent of tier.agents) {
219
+ const agentStart = Date.now();
220
+ const spinner = ora(`Running @${agent.name}...`).start();
221
+
222
+ // Update state
223
+ const currentState = await loadState();
224
+ if (currentState) {
225
+ currentState.agents.active.push(agent.name);
226
+ await saveState(currentState);
227
+ }
228
+
229
+ try {
230
+ const result = await runAgent(agent, task, context, previousOutput, provider);
231
+ const duration = Date.now() - agentStart;
232
+ agentTimings[agent.name] = duration;
233
+ previousOutput = result;
234
+ spinner.succeed(chalk.green(` @${agent.name} complete`) + chalk.gray(` (${duration}ms)`));
235
+ console.log(chalk.gray(` → ${result.slice(0, 100).replace(/\n/g, ' ')}...`));
236
+ agentResults.push({ agent: agent.name, result, success: true });
237
+
238
+ // Remove active agent
239
+ const stateDone = await loadState();
240
+ if (stateDone) {
241
+ stateDone.agents.active = stateDone.agents.active.filter(a => a !== agent.name);
242
+ await saveState(stateDone);
243
+ }
244
+
245
+ } catch (error) {
246
+ const duration = Date.now() - agentStart;
247
+ agentTimings[agent.name] = duration;
248
+ spinner.fail(chalk.red(` @${agent.name} failed: ${error.message}`) + chalk.gray(` (${duration}ms)`));
249
+ agentResults.push({ agent: agent.name, error: error.message, success: false });
250
+
251
+ const stateFail = await loadState();
252
+ if (stateFail) {
253
+ stateFail.agents.active = stateFail.agents.active.filter(a => a !== agent.name);
254
+ await saveState(stateFail);
255
+ }
256
+ break;
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ const totalDuration = Date.now() - startTime;
263
+ const successCount = agentResults.filter(r => r.success).length;
264
+ const failCount = agentResults.filter(r => !r.success).length;
265
+
266
+ // Final state update
267
+ await updateState();
268
+
269
+ // Write log
270
+ const stats = {
271
+ totalDuration,
272
+ agentTimings,
273
+ successCount,
274
+ failCount,
275
+ parallel: options.parallel || false
276
+ };
277
+ const logPath = await writeSwarmLog(logDir, task, agentResults, stats);
278
+
279
+ // Summary
280
+ console.log(chalk.cyan.bold('\nšŸ“Š Execution Stats:'));
281
+ console.log(chalk.white(` Total time: ${totalDuration}ms`));
282
+ console.log(chalk.green(` Succeeded: ${successCount}`) + chalk.red(` Failed: ${failCount}`));
283
+ Object.entries(agentTimings).forEach(([agent, time]) => {
284
+ console.log(chalk.gray(` • @${agent}: ${time}ms`));
285
+ });
286
+ console.log(chalk.gray(`\n Log saved: ${logPath}`));
287
+
288
+ console.log(chalk.green.bold('\nāœ… Swarm execution complete!\n'));
289
+ }
290
+
291
+ async function loadAgentPrompt(name) {
292
+ // Use glob to find agent file recursively
293
+ const files = await glob(`agents/**/${name}.md`, { ignore: 'node_modules/**' });
294
+
295
+ if (files.length > 0) {
296
+ return await readFile(files[0], 'utf-8');
297
+ }
298
+
299
+ // Fallback to direct check
300
+ const directPath = join(process.cwd(), 'agents', `${name}.md`);
301
+ if (existsSync(directPath)) {
302
+ return await readFile(directPath, 'utf-8');
303
+ }
304
+
305
+ return `You are the @${name} agent.`;
306
+ }