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.
- package/README.md +112 -151
- package/assets/agents/00-AGENT_INDEX.md +1 -1
- package/assets/code-patterns/clerk-middleware.ts +138 -0
- package/assets/code-patterns/prisma-schema.prisma +224 -0
- package/assets/code-patterns/rls-policies.sql +246 -0
- package/assets/code-patterns/server-actions.ts +191 -0
- package/assets/code-patterns/trpc-router.ts +258 -0
- package/assets/cursor-rules/13-ai-integration.mdc +155 -0
- package/assets/cursor-rules/14-server-components.mdc +81 -0
- package/assets/cursor-rules/15-server-actions.mdc +102 -0
- package/assets/cursor-rules/16-edge-middleware.mdc +105 -0
- package/assets/cursor-rules/17-streaming-ssr.mdc +138 -0
- package/assets/docs/LAUNCH-POSTS.md +1 -1
- package/assets/docs/QUICK-REFERENCE.md +9 -4
- package/assets/docs/VISION-V2.md +1 -1
- package/assets/hooks/pre-commit +98 -0
- package/assets/saas-plan/04-Imp-Template.md +1 -1
- package/bin/ultra-dex.js +132 -4
- package/lib/commands/advanced.js +471 -0
- package/lib/commands/agent-builder.js +226 -0
- package/lib/commands/agents.js +102 -42
- package/lib/commands/auto-implement.js +68 -0
- package/lib/commands/banner.js +43 -21
- package/lib/commands/build.js +78 -183
- package/lib/commands/ci-monitor.js +84 -0
- package/lib/commands/config.js +207 -0
- package/lib/commands/dashboard.js +770 -0
- package/lib/commands/diff.js +233 -0
- package/lib/commands/doctor.js +416 -0
- package/lib/commands/export.js +408 -0
- package/lib/commands/fix.js +96 -0
- package/lib/commands/generate.js +105 -78
- package/lib/commands/hooks.js +251 -76
- package/lib/commands/init.js +102 -54
- package/lib/commands/memory.js +80 -0
- package/lib/commands/plan.js +82 -0
- package/lib/commands/review.js +34 -5
- package/lib/commands/run.js +233 -0
- package/lib/commands/scaffold.js +151 -0
- package/lib/commands/serve.js +179 -146
- package/lib/commands/state.js +327 -0
- package/lib/commands/swarm.js +306 -0
- package/lib/commands/sync.js +82 -23
- package/lib/commands/team.js +275 -0
- package/lib/commands/upgrade.js +190 -0
- package/lib/commands/validate.js +34 -0
- package/lib/commands/verify.js +81 -0
- package/lib/commands/watch.js +79 -0
- package/lib/config/theme.js +47 -0
- package/lib/mcp/graph.js +92 -0
- package/lib/mcp/memory.js +95 -0
- package/lib/mcp/resources.js +152 -0
- package/lib/mcp/server.js +34 -0
- package/lib/mcp/tools.js +481 -0
- package/lib/mcp/websocket.js +117 -0
- package/lib/providers/index.js +49 -4
- package/lib/providers/ollama.js +136 -0
- package/lib/providers/router.js +63 -0
- package/lib/quality/scanner.js +128 -0
- package/lib/swarm/coordinator.js +97 -0
- package/lib/swarm/index.js +598 -0
- package/lib/swarm/protocol.js +677 -0
- package/lib/swarm/tiers.js +485 -0
- package/lib/templates/code/clerk-middleware.ts +138 -0
- package/lib/templates/code/prisma-schema.prisma +224 -0
- package/lib/templates/code/rls-policies.sql +246 -0
- package/lib/templates/code/server-actions.ts +191 -0
- package/lib/templates/code/trpc-router.ts +258 -0
- package/lib/templates/custom-agent.md +10 -0
- package/lib/themes/doomsday.js +229 -0
- package/lib/ui/index.js +5 -0
- package/lib/ui/interface.js +241 -0
- package/lib/ui/spinners.js +116 -0
- package/lib/ui/theme.js +183 -0
- package/lib/utils/agents.js +32 -0
- package/lib/utils/files.js +14 -0
- package/lib/utils/graph.js +108 -0
- package/lib/utils/help.js +64 -0
- package/lib/utils/messages.js +35 -0
- package/lib/utils/progress.js +24 -0
- package/lib/utils/prompts.js +47 -0
- package/lib/utils/spinners.js +46 -0
- package/lib/utils/status.js +31 -0
- package/lib/utils/tables.js +41 -0
- package/lib/utils/theme-state.js +9 -0
- package/lib/utils/version-display.js +32 -0
- 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
|
+
}
|