ultra-dex 2.2.1 → 3.1.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 +84 -128
- package/assets/agents/00-AGENT_INDEX.md +1 -1
- 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 +95 -4
- package/lib/commands/advanced.js +471 -0
- package/lib/commands/agent-builder.js +226 -0
- package/lib/commands/agents.js +99 -42
- package/lib/commands/auto-implement.js +68 -0
- package/lib/commands/build.js +73 -187
- 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 +397 -0
- package/lib/commands/export.js +408 -0
- package/lib/commands/fix.js +96 -0
- package/lib/commands/generate.js +96 -72
- package/lib/commands/hooks.js +251 -76
- package/lib/commands/init.js +53 -1
- 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/serve.js +177 -146
- package/lib/commands/state.js +354 -0
- package/lib/commands/swarm.js +284 -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/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/custom-agent.md +10 -0
- package/lib/utils/files.js +14 -0
- package/lib/utils/graph.js +108 -0
- package/package.json +22 -13
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ultra-dex diff & export commands
|
|
3
|
+
* Compare plan vs code, export to various formats
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
|
|
12
|
+
async function readFileSafe(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
return await fs.readFile(filePath, 'utf8');
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function getProjectStructure(dir, depth = 3) {
|
|
21
|
+
const structure = [];
|
|
22
|
+
|
|
23
|
+
async function scan(currentDir, currentDepth, prefix = '') {
|
|
24
|
+
if (currentDepth <= 0) return;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
28
|
+
const filtered = entries.filter(e =>
|
|
29
|
+
!['node_modules', '.git', '.next', 'dist', 'build', '.ultra-dex', '.ultra'].includes(e.name) &&
|
|
30
|
+
!e.name.startsWith('.')
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
for (const entry of filtered) {
|
|
34
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
35
|
+
const relativePath = path.relative(dir, fullPath);
|
|
36
|
+
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
structure.push({ type: 'dir', path: relativePath });
|
|
39
|
+
await scan(fullPath, currentDepth - 1, prefix + ' ');
|
|
40
|
+
} else {
|
|
41
|
+
structure.push({ type: 'file', path: relativePath });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch { /* permission denied or other error */ }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await scan(dir, depth);
|
|
48
|
+
return structure;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extractPlanExpectations(planContent) {
|
|
52
|
+
const expectations = {
|
|
53
|
+
database: [],
|
|
54
|
+
api: [],
|
|
55
|
+
frontend: [],
|
|
56
|
+
auth: [],
|
|
57
|
+
testing: [],
|
|
58
|
+
other: []
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (!planContent) return expectations;
|
|
62
|
+
|
|
63
|
+
// Extract database expectations (look for schema, tables, models)
|
|
64
|
+
const dbPatterns = /(?:table|schema|model|entity|database|prisma)[\s:]+[`"]?(\w+)[`"]?/gi;
|
|
65
|
+
let match;
|
|
66
|
+
while ((match = dbPatterns.exec(planContent)) !== null) {
|
|
67
|
+
if (!expectations.database.includes(match[1].toLowerCase())) {
|
|
68
|
+
expectations.database.push(match[1].toLowerCase());
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Extract API endpoints
|
|
73
|
+
const apiPatterns = /(?:endpoint|route|api|POST|GET|PUT|DELETE|PATCH)[\s:]+[`"]?([\/\w-]+)[`"]?/gi;
|
|
74
|
+
while ((match = apiPatterns.exec(planContent)) !== null) {
|
|
75
|
+
if (!expectations.api.includes(match[1])) {
|
|
76
|
+
expectations.api.push(match[1]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Extract frontend components
|
|
81
|
+
const componentPatterns = /(?:component|page|view|screen)[\s:]+[`"]?(\w+)[`"]?/gi;
|
|
82
|
+
while ((match = componentPatterns.exec(planContent)) !== null) {
|
|
83
|
+
if (!expectations.frontend.includes(match[1])) {
|
|
84
|
+
expectations.frontend.push(match[1]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Extract auth features
|
|
89
|
+
const authPatterns = /(?:auth|login|signup|session|jwt|oauth|password)/gi;
|
|
90
|
+
while ((match = authPatterns.exec(planContent)) !== null) {
|
|
91
|
+
if (!expectations.auth.includes(match[0].toLowerCase())) {
|
|
92
|
+
expectations.auth.push(match[0].toLowerCase());
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Extract testing expectations
|
|
97
|
+
const testPatterns = /(?:test|spec|coverage|jest|vitest|cypress)/gi;
|
|
98
|
+
while ((match = testPatterns.exec(planContent)) !== null) {
|
|
99
|
+
if (!expectations.testing.includes(match[0].toLowerCase())) {
|
|
100
|
+
expectations.testing.push(match[0].toLowerCase());
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return expectations;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function checkCodeImplementation(structure, expectations) {
|
|
108
|
+
const findings = {
|
|
109
|
+
implemented: [],
|
|
110
|
+
missing: [],
|
|
111
|
+
extra: []
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const fileNames = structure.filter(s => s.type === 'file').map(s => s.path.toLowerCase());
|
|
115
|
+
const dirNames = structure.filter(s => s.type === 'dir').map(s => s.path.toLowerCase());
|
|
116
|
+
|
|
117
|
+
// Check database
|
|
118
|
+
const hasSchema = fileNames.some(f => f.includes('schema') || f.includes('prisma'));
|
|
119
|
+
if (expectations.database.length > 0 && hasSchema) {
|
|
120
|
+
findings.implemented.push('Database schema exists');
|
|
121
|
+
} else if (expectations.database.length > 0) {
|
|
122
|
+
findings.missing.push('Database schema (expected models: ' + expectations.database.slice(0, 3).join(', ') + ')');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check API
|
|
126
|
+
const hasApi = dirNames.some(d => d.includes('api') || d.includes('routes'));
|
|
127
|
+
if (expectations.api.length > 0 && hasApi) {
|
|
128
|
+
findings.implemented.push('API routes directory exists');
|
|
129
|
+
} else if (expectations.api.length > 0) {
|
|
130
|
+
findings.missing.push('API routes directory');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check frontend
|
|
134
|
+
const hasFrontend = dirNames.some(d => d.includes('components') || d.includes('pages') || d.includes('app'));
|
|
135
|
+
if (expectations.frontend.length > 0 && hasFrontend) {
|
|
136
|
+
findings.implemented.push('Frontend components exist');
|
|
137
|
+
} else if (expectations.frontend.length > 0) {
|
|
138
|
+
findings.missing.push('Frontend components');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check auth
|
|
142
|
+
const hasAuth = fileNames.some(f => f.includes('auth') || f.includes('login') || f.includes('session'));
|
|
143
|
+
if (expectations.auth.length > 0 && hasAuth) {
|
|
144
|
+
findings.implemented.push('Authentication implemented');
|
|
145
|
+
} else if (expectations.auth.length > 0) {
|
|
146
|
+
findings.missing.push('Authentication (expected: ' + expectations.auth.slice(0, 3).join(', ') + ')');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check testing
|
|
150
|
+
const hasTests = fileNames.some(f => f.includes('.test.') || f.includes('.spec.')) ||
|
|
151
|
+
dirNames.some(d => d.includes('test') || d.includes('__tests__'));
|
|
152
|
+
if (expectations.testing.length > 0 && hasTests) {
|
|
153
|
+
findings.implemented.push('Tests exist');
|
|
154
|
+
} else if (expectations.testing.length > 0) {
|
|
155
|
+
findings.missing.push('Tests');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return findings;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function registerDiffCommand(program) {
|
|
162
|
+
program
|
|
163
|
+
.command('diff')
|
|
164
|
+
.description('Compare implementation plan vs actual code')
|
|
165
|
+
.option('-d, --dir <directory>', 'Directory to analyze', '.')
|
|
166
|
+
.option('--json', 'Output as JSON')
|
|
167
|
+
.action(async (options) => {
|
|
168
|
+
console.log(chalk.cyan('\n🔍 Ultra-Dex Diff: Plan vs Code\n'));
|
|
169
|
+
|
|
170
|
+
const spinner = ora('Analyzing project...').start();
|
|
171
|
+
|
|
172
|
+
// Load plan
|
|
173
|
+
const plan = await readFileSafe(path.join(options.dir, 'IMPLEMENTATION-PLAN.md'));
|
|
174
|
+
if (!plan) {
|
|
175
|
+
spinner.fail('No IMPLEMENTATION-PLAN.md found');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Get project structure
|
|
180
|
+
const structure = await getProjectStructure(path.resolve(options.dir));
|
|
181
|
+
|
|
182
|
+
// Extract expectations and check implementation
|
|
183
|
+
const expectations = extractPlanExpectations(plan);
|
|
184
|
+
const findings = checkCodeImplementation(structure, expectations);
|
|
185
|
+
|
|
186
|
+
spinner.succeed('Analysis complete');
|
|
187
|
+
|
|
188
|
+
// Calculate alignment
|
|
189
|
+
const totalExpected = Object.values(expectations).flat().length;
|
|
190
|
+
const implementedCount = findings.implemented.length;
|
|
191
|
+
const missingCount = findings.missing.length;
|
|
192
|
+
const alignmentScore = totalExpected > 0
|
|
193
|
+
? Math.round((implementedCount / (implementedCount + missingCount)) * 100)
|
|
194
|
+
: 0;
|
|
195
|
+
|
|
196
|
+
if (options.json) {
|
|
197
|
+
console.log(JSON.stringify({
|
|
198
|
+
score: alignmentScore,
|
|
199
|
+
expectations,
|
|
200
|
+
findings,
|
|
201
|
+
structure: structure.slice(0, 50)
|
|
202
|
+
}, null, 2));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Display results
|
|
207
|
+
console.log(chalk.bold('\n📊 Alignment Analysis\n'));
|
|
208
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
209
|
+
|
|
210
|
+
const scoreColor = alignmentScore >= 80 ? 'green' : alignmentScore >= 50 ? 'yellow' : 'red';
|
|
211
|
+
console.log(chalk[scoreColor](` Code-to-Plan Alignment: ${alignmentScore}%`));
|
|
212
|
+
|
|
213
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
214
|
+
|
|
215
|
+
if (findings.implemented.length > 0) {
|
|
216
|
+
console.log(chalk.green('\n✅ Implemented:'));
|
|
217
|
+
findings.implemented.forEach(item => console.log(chalk.gray(` • ${item}`)));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (findings.missing.length > 0) {
|
|
221
|
+
console.log(chalk.red('\n❌ Missing:'));
|
|
222
|
+
findings.missing.forEach(item => console.log(chalk.yellow(` • ${item}`)));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log(chalk.bold('\n📋 Plan Expectations Found:\n'));
|
|
226
|
+
Object.entries(expectations).forEach(([category, items]) => {
|
|
227
|
+
if (items.length > 0) {
|
|
228
|
+
console.log(chalk.cyan(` ${category}: `) + chalk.gray(items.slice(0, 5).join(', ') + (items.length > 5 ? '...' : '')));
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
console.log(chalk.gray('\n─'.repeat(50)));
|
|
233
|
+
console.log(chalk.cyan('\n💡 Run `ultra-dex run planner` to get tasks for missing items.\n'));
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function registerExportCommand(program) {
|
|
238
|
+
program
|
|
239
|
+
.command('export')
|
|
240
|
+
.description('Export project to various formats')
|
|
241
|
+
.option('-f, --format <format>', 'Export format: json, markdown, html', 'json')
|
|
242
|
+
.option('-o, --output <file>', 'Output file path')
|
|
243
|
+
.option('--include-code', 'Include code snippets in export')
|
|
244
|
+
.action(async (options) => {
|
|
245
|
+
console.log(chalk.cyan('\n📦 Ultra-Dex Export\n'));
|
|
246
|
+
|
|
247
|
+
const spinner = ora('Gathering project data...').start();
|
|
248
|
+
|
|
249
|
+
// Gather all project data
|
|
250
|
+
const data = {
|
|
251
|
+
exportedAt: new Date().toISOString(),
|
|
252
|
+
project: { name: path.basename(process.cwd()) },
|
|
253
|
+
files: {},
|
|
254
|
+
state: null,
|
|
255
|
+
git: null
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Load all markdown files
|
|
259
|
+
const mdFiles = ['CONTEXT.md', 'IMPLEMENTATION-PLAN.md', 'CHECKLIST.md', 'QUICK-START.md'];
|
|
260
|
+
for (const file of mdFiles) {
|
|
261
|
+
const content = await readFileSafe(file);
|
|
262
|
+
if (content) {
|
|
263
|
+
data.files[file] = content;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Load state
|
|
268
|
+
const stateContent = await readFileSafe('.ultra/state.json');
|
|
269
|
+
if (stateContent) {
|
|
270
|
+
try {
|
|
271
|
+
data.state = JSON.parse(stateContent);
|
|
272
|
+
} catch { /* invalid json */ }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Get git info
|
|
276
|
+
try {
|
|
277
|
+
data.git = {
|
|
278
|
+
branch: execSync('git branch --show-current', { encoding: 'utf8' }).trim(),
|
|
279
|
+
remoteUrl: execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf8' }).trim(),
|
|
280
|
+
lastCommit: execSync('git log -1 --format="%H %s"', { encoding: 'utf8' }).trim()
|
|
281
|
+
};
|
|
282
|
+
} catch { /* not a git repo */ }
|
|
283
|
+
|
|
284
|
+
spinner.succeed('Data gathered');
|
|
285
|
+
|
|
286
|
+
let output;
|
|
287
|
+
let extension;
|
|
288
|
+
|
|
289
|
+
switch (options.format) {
|
|
290
|
+
case 'json':
|
|
291
|
+
output = JSON.stringify(data, null, 2);
|
|
292
|
+
extension = '.json';
|
|
293
|
+
break;
|
|
294
|
+
|
|
295
|
+
case 'markdown':
|
|
296
|
+
output = `# ${data.project.name} - Ultra-Dex Export
|
|
297
|
+
|
|
298
|
+
Exported: ${new Date(data.exportedAt).toLocaleString()}
|
|
299
|
+
|
|
300
|
+
${data.git ? `## Git Info
|
|
301
|
+
- Branch: ${data.git.branch}
|
|
302
|
+
- Last Commit: ${data.git.lastCommit}
|
|
303
|
+
` : ''}
|
|
304
|
+
|
|
305
|
+
${data.state ? `## Project State
|
|
306
|
+
- Score: ${data.state.score}/100
|
|
307
|
+
- Sections: ${data.state.sections?.completed || 0}/34
|
|
308
|
+
` : ''}
|
|
309
|
+
|
|
310
|
+
${Object.entries(data.files).map(([name, content]) => `
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## ${name}
|
|
314
|
+
|
|
315
|
+
${content}
|
|
316
|
+
`).join('\n')}
|
|
317
|
+
`;
|
|
318
|
+
extension = '.md';
|
|
319
|
+
break;
|
|
320
|
+
|
|
321
|
+
case 'html':
|
|
322
|
+
output = `<!DOCTYPE html>
|
|
323
|
+
<html>
|
|
324
|
+
<head>
|
|
325
|
+
<title>${data.project.name} - Ultra-Dex Export</title>
|
|
326
|
+
<style>
|
|
327
|
+
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; }
|
|
328
|
+
h1 { color: #06b6d4; }
|
|
329
|
+
pre { background: #1e1e1e; color: #d4d4d4; padding: 1rem; overflow-x: auto; border-radius: 0.5rem; }
|
|
330
|
+
.meta { color: #666; font-size: 0.875rem; }
|
|
331
|
+
.section { margin: 2rem 0; padding: 1rem; border: 1px solid #e5e5e5; border-radius: 0.5rem; }
|
|
332
|
+
</style>
|
|
333
|
+
</head>
|
|
334
|
+
<body>
|
|
335
|
+
<h1>🚀 ${data.project.name}</h1>
|
|
336
|
+
<p class="meta">Exported: ${new Date(data.exportedAt).toLocaleString()}</p>
|
|
337
|
+
|
|
338
|
+
${data.state ? `
|
|
339
|
+
<div class="section">
|
|
340
|
+
<h2>📊 Project State</h2>
|
|
341
|
+
<p>Score: <strong>${data.state.score}/100</strong></p>
|
|
342
|
+
<p>Sections: ${data.state.sections?.completed || 0}/34</p>
|
|
343
|
+
</div>
|
|
344
|
+
` : ''}
|
|
345
|
+
|
|
346
|
+
${Object.entries(data.files).map(([name, content]) => `
|
|
347
|
+
<div class="section">
|
|
348
|
+
<h2>📄 ${name}</h2>
|
|
349
|
+
<pre>${content.replace(/</g, '<').replace(/>/g, '>')}</pre>
|
|
350
|
+
</div>
|
|
351
|
+
`).join('\n')}
|
|
352
|
+
</body>
|
|
353
|
+
</html>`;
|
|
354
|
+
extension = '.html';
|
|
355
|
+
break;
|
|
356
|
+
|
|
357
|
+
default:
|
|
358
|
+
console.log(chalk.red(`Unknown format: ${options.format}`));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (options.output) {
|
|
363
|
+
await fs.writeFile(options.output, output);
|
|
364
|
+
console.log(chalk.green(`✅ Exported to: ${options.output}`));
|
|
365
|
+
} else {
|
|
366
|
+
const defaultPath = `ultra-dex-export${extension}`;
|
|
367
|
+
await fs.writeFile(defaultPath, output);
|
|
368
|
+
console.log(chalk.green(`✅ Exported to: ${defaultPath}`));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
console.log(chalk.gray(` Format: ${options.format}`));
|
|
372
|
+
console.log(chalk.gray(` Size: ${(output.length / 1024).toFixed(1)}KB\n`));
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function registerCheckCommand(program) {
|
|
377
|
+
program
|
|
378
|
+
.command('check')
|
|
379
|
+
.description('Repository health and alignment check (God Mode)')
|
|
380
|
+
.action(async () => {
|
|
381
|
+
console.log(chalk.cyan('\n🩺 Ultra-Dex Repository Check\n'));
|
|
382
|
+
|
|
383
|
+
const { buildGraph } = await import('../utils/graph.js');
|
|
384
|
+
const { loadState } = await import('./state.js');
|
|
385
|
+
|
|
386
|
+
// 1. Check Graph
|
|
387
|
+
const graphSpinner = (await import('ora')).default('Checking Code Property Graph...').start();
|
|
388
|
+
try {
|
|
389
|
+
const graph = await buildGraph();
|
|
390
|
+
graphSpinner.succeed(chalk.green(`CPG Healthy: ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
|
|
391
|
+
} catch (e) {
|
|
392
|
+
graphSpinner.fail(chalk.red(`CPG Corrupt: ${e.message}`));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 2. Check State
|
|
396
|
+
const stateSpinner = (await import('ora')).default('Checking Project State...').start();
|
|
397
|
+
const state = await loadState();
|
|
398
|
+
if (state) {
|
|
399
|
+
stateSpinner.succeed(chalk.green('Project state loaded'));
|
|
400
|
+
} else {
|
|
401
|
+
stateSpinner.warn(chalk.yellow('No .ultra/state.json found. System is stateless.'));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 3. Check Core Files
|
|
405
|
+
const files = ['CONTEXT.md', 'IMPLEMENTATION-PLAN.md', 'QUICK-START.md'];
|
|
406
|
+
console.log(chalk.bold('\nCore Documents:'));
|
|
407
|
+
for (const file of files) {
|
|
408
|
+
try {
|
|
409
|
+
await fs.access(file);
|
|
410
|
+
console.log(chalk.green(` ✅ ${file}`));
|
|
411
|
+
} catch {
|
|
412
|
+
console.log(chalk.red(` ❌ ${file} (Required)`));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
console.log(chalk.cyan('\n💡 Run "ultra-dex audit" for a detailed scoring report.\n'));
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function registerUpgradeCommand(program) {
|
|
421
|
+
program
|
|
422
|
+
.command('upgrade')
|
|
423
|
+
.description('Check for and install Ultra-Dex updates')
|
|
424
|
+
.option('--check', 'Only check for updates, don\'t install')
|
|
425
|
+
.action(async (options) => {
|
|
426
|
+
console.log(chalk.cyan('\n🔄 Ultra-Dex Upgrade\n'));
|
|
427
|
+
|
|
428
|
+
const spinner = ora('Checking for updates...').start();
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
// Get current version
|
|
432
|
+
const currentVersion = '2.4.0';
|
|
433
|
+
|
|
434
|
+
// Check npm for latest version
|
|
435
|
+
const latestInfo = execSync('npm view ultra-dex version 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
436
|
+
const latestVersion = latestInfo || currentVersion;
|
|
437
|
+
|
|
438
|
+
spinner.succeed('Version check complete');
|
|
439
|
+
|
|
440
|
+
console.log(chalk.gray(` Current: v${currentVersion}`));
|
|
441
|
+
console.log(chalk.gray(` Latest: v${latestVersion}`));
|
|
442
|
+
|
|
443
|
+
if (currentVersion === latestVersion) {
|
|
444
|
+
console.log(chalk.green('\n✅ You\'re on the latest version!\n'));
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (options.check) {
|
|
449
|
+
console.log(chalk.yellow(`\n⬆️ Update available: v${currentVersion} → v${latestVersion}`));
|
|
450
|
+
console.log(chalk.gray(' Run `ultra-dex upgrade` to install.\n'));
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Install update
|
|
455
|
+
const installSpinner = ora('Installing update...').start();
|
|
456
|
+
execSync('npm install -g ultra-dex@latest', { encoding: 'utf8' });
|
|
457
|
+
installSpinner.succeed('Update installed!');
|
|
458
|
+
|
|
459
|
+
console.log(chalk.green(`\n✅ Upgraded to v${latestVersion}!\n`));
|
|
460
|
+
console.log(chalk.gray(' Run `ultra-dex --version` to verify.\n'));
|
|
461
|
+
|
|
462
|
+
} catch (err) {
|
|
463
|
+
spinner.fail('Upgrade check failed');
|
|
464
|
+
console.log(chalk.yellow('\n⚠️ Could not check for updates.'));
|
|
465
|
+
console.log(chalk.gray(' You may not be connected to npm, or ultra-dex is not published yet.'));
|
|
466
|
+
console.log(chalk.gray(` Current version: v3.0.0\n`));
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export default { registerDiffCommand, registerExportCommand, registerUpgradeCommand };
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { AGENTS, findBuiltInAgent, listCustomAgents, readCustomAgent, getCustomAgentPath, readAgentPrompt } from './agents.js';
|
|
7
|
+
import { validateProjectName } from '../utils/validation.js';
|
|
8
|
+
|
|
9
|
+
const CUSTOM_AGENTS_DIR = path.join(process.cwd(), '.ultra-dex', 'custom-agents');
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const TEMPLATE_PATH = path.join(__dirname, '../templates/custom-agent.md');
|
|
13
|
+
|
|
14
|
+
const TIERS = [
|
|
15
|
+
{ name: '0 - Orchestration', value: '0' },
|
|
16
|
+
{ name: '1 - Leadership', value: '1' },
|
|
17
|
+
{ name: '2 - Development', value: '2' },
|
|
18
|
+
{ name: '3 - Security', value: '3' },
|
|
19
|
+
{ name: '4 - DevOps', value: '4' },
|
|
20
|
+
{ name: '5 - Quality', value: '5' },
|
|
21
|
+
{ name: '6 - Specialist', value: '6' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function validateKebabCase(name) {
|
|
25
|
+
const result = validateProjectName(name);
|
|
26
|
+
if (result !== true) return result;
|
|
27
|
+
if (name !== name.toLowerCase()) {
|
|
28
|
+
return 'Agent name must be kebab-case (lowercase letters, numbers, dashes)';
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readTemplate() {
|
|
34
|
+
return fs.readFile(TEMPLATE_PATH, 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderTemplate(template, data) {
|
|
38
|
+
return template
|
|
39
|
+
.replace(/{{name}}/g, data.name)
|
|
40
|
+
.replace(/{{tier}}/g, data.tier)
|
|
41
|
+
.replace(/{{role}}/g, data.role)
|
|
42
|
+
.replace(/{{expertise}}/g, data.expertise)
|
|
43
|
+
.replace(/{{prompt}}/g, data.prompt);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function ensureCustomDir() {
|
|
47
|
+
await fs.mkdir(CUSTOM_AGENTS_DIR, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function listBuiltinAgents() {
|
|
51
|
+
return AGENTS.map(agent => ({
|
|
52
|
+
name: agent.name,
|
|
53
|
+
description: agent.description,
|
|
54
|
+
tier: agent.tier,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function registerAgentBuilderCommand(program) {
|
|
59
|
+
const agentCommand = program
|
|
60
|
+
.command('agent')
|
|
61
|
+
.description('Manage AI agents');
|
|
62
|
+
|
|
63
|
+
agentCommand
|
|
64
|
+
.command('create <name>')
|
|
65
|
+
.description('Create a custom agent with an interactive wizard')
|
|
66
|
+
.action(async (name) => {
|
|
67
|
+
const validation = validateKebabCase(name);
|
|
68
|
+
if (validation !== true) {
|
|
69
|
+
console.log(chalk.red(`\n❌ ${validation}\n`));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (findBuiltInAgent(name)) {
|
|
74
|
+
console.log(chalk.red(`\n❌ "${name}" conflicts with a built-in agent.\n`));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await ensureCustomDir();
|
|
79
|
+
const existing = await getCustomAgentPath(name);
|
|
80
|
+
if (existing) {
|
|
81
|
+
console.log(chalk.red(`\n❌ Custom agent "${name}" already exists.\n`));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const answers = await inquirer.prompt([
|
|
86
|
+
{
|
|
87
|
+
type: 'input',
|
|
88
|
+
name: 'role',
|
|
89
|
+
message: 'Role description (1 sentence):',
|
|
90
|
+
validate: input => input.trim().length > 0 || 'Role description is required',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'list',
|
|
94
|
+
name: 'tier',
|
|
95
|
+
message: 'Select tier:',
|
|
96
|
+
choices: TIERS,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
type: 'input',
|
|
100
|
+
name: 'expertise',
|
|
101
|
+
message: 'Expertise areas (comma-separated):',
|
|
102
|
+
validate: input => input.trim().length > 0 || 'Expertise is required',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'editor',
|
|
106
|
+
name: 'prompt',
|
|
107
|
+
message: 'Base system prompt:',
|
|
108
|
+
validate: input => input.trim().length > 0 || 'System prompt is required',
|
|
109
|
+
},
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const template = await readTemplate();
|
|
113
|
+
const content = renderTemplate(template, {
|
|
114
|
+
name,
|
|
115
|
+
tier: answers.tier,
|
|
116
|
+
role: answers.role.trim(),
|
|
117
|
+
expertise: answers.expertise.trim(),
|
|
118
|
+
prompt: answers.prompt.trim(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const outputPath = path.join(CUSTOM_AGENTS_DIR, `${name}.md`);
|
|
122
|
+
await fs.writeFile(outputPath, content);
|
|
123
|
+
console.log(chalk.green(`\n✅ Custom agent created: ${outputPath}\n`));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
agentCommand
|
|
127
|
+
.command('list')
|
|
128
|
+
.description('List built-in and custom agents')
|
|
129
|
+
.option('--all', 'Show built-in and custom agents')
|
|
130
|
+
.option('--custom', 'Show custom agents only')
|
|
131
|
+
.option('--builtin', 'Show built-in agents only')
|
|
132
|
+
.action(async (options) => {
|
|
133
|
+
const showCustom = options.custom || options.all || (!options.custom && !options.builtin);
|
|
134
|
+
const showBuiltin = options.builtin || options.all || (!options.custom && !options.builtin);
|
|
135
|
+
|
|
136
|
+
if (showBuiltin) {
|
|
137
|
+
console.log(chalk.bold('\n🤖 Built-in Agents\n'));
|
|
138
|
+
const builtinAgents = await listBuiltinAgents();
|
|
139
|
+
let currentTier = '';
|
|
140
|
+
builtinAgents.forEach((agent) => {
|
|
141
|
+
if (agent.tier !== currentTier) {
|
|
142
|
+
currentTier = agent.tier;
|
|
143
|
+
console.log(chalk.bold(` ${currentTier} Tier:`));
|
|
144
|
+
}
|
|
145
|
+
console.log(chalk.cyan(` ${agent.name}`) + chalk.gray(` - ${agent.description}`));
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (showCustom) {
|
|
150
|
+
const customAgents = await listCustomAgents();
|
|
151
|
+
console.log(chalk.bold('\n✨ Custom Agents\n'));
|
|
152
|
+
if (customAgents.length === 0) {
|
|
153
|
+
console.log(chalk.gray(' (none found)'));
|
|
154
|
+
} else {
|
|
155
|
+
customAgents.forEach((name) => console.log(chalk.cyan(` ${name}`)));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
console.log('');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
agentCommand
|
|
162
|
+
.command('show <name>')
|
|
163
|
+
.description('Show a specific agent prompt')
|
|
164
|
+
.action(async (name) => {
|
|
165
|
+
const agent = findBuiltInAgent(name);
|
|
166
|
+
if (agent) {
|
|
167
|
+
try {
|
|
168
|
+
const content = await readAgentPrompt(agent);
|
|
169
|
+
console.log(chalk.bold(`\n🤖 ${agent.name.toUpperCase()} Agent\n`));
|
|
170
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
171
|
+
console.log(content);
|
|
172
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
173
|
+
console.log(chalk.bold('\n📋 Copy the above prompt and paste into your AI tool.\n'));
|
|
174
|
+
} catch {
|
|
175
|
+
console.log(chalk.bold(`\n🤖 ${agent.name.toUpperCase()} Agent\n`));
|
|
176
|
+
console.log(chalk.gray('View full prompt on GitHub:'));
|
|
177
|
+
console.log(chalk.blue(` https://github.com/Srujan0798/Ultra-Dex/blob/main/agents/${agent.file}\n`));
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const content = await readCustomAgent(name);
|
|
184
|
+
console.log(chalk.bold(`\n✨ ${name.toUpperCase()} Custom Agent\n`));
|
|
185
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
186
|
+
console.log(content);
|
|
187
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
188
|
+
} catch {
|
|
189
|
+
console.log(chalk.red(`\n❌ Agent "${name}" not found.\n`));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
agentCommand
|
|
195
|
+
.command('delete <name>')
|
|
196
|
+
.description('Delete a custom agent')
|
|
197
|
+
.action(async (name) => {
|
|
198
|
+
if (findBuiltInAgent(name)) {
|
|
199
|
+
console.log(chalk.red(`\n❌ Cannot delete built-in agent "${name}".\n`));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const filePath = await getCustomAgentPath(name);
|
|
204
|
+
if (!filePath) {
|
|
205
|
+
console.log(chalk.red(`\n❌ Custom agent "${name}" not found.\n`));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const { confirmDelete } = await inquirer.prompt([
|
|
210
|
+
{
|
|
211
|
+
type: 'confirm',
|
|
212
|
+
name: 'confirmDelete',
|
|
213
|
+
message: `Delete custom agent "${name}"?`,
|
|
214
|
+
default: false,
|
|
215
|
+
},
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
if (!confirmDelete) {
|
|
219
|
+
console.log(chalk.gray('\nAborted.\n'));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await fs.unlink(filePath);
|
|
224
|
+
console.log(chalk.green(`\n✅ Deleted custom agent "${name}".\n`));
|
|
225
|
+
});
|
|
226
|
+
}
|