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
package/lib/commands/sync.js
CHANGED
|
@@ -1,35 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ultra-dex sync command
|
|
3
|
+
* Synchronizes project state and graph across devices
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import chalk from 'chalk';
|
|
7
|
+
import fs from 'fs/promises';
|
|
2
8
|
import path from 'path';
|
|
9
|
+
import { loadState, saveState } from './state.js';
|
|
10
|
+
import { buildGraph } from '../utils/graph.js';
|
|
3
11
|
import { snapshotContext } from '../utils/sync.js';
|
|
12
|
+
import { validateSafePath } from '../utils/validation.js';
|
|
4
13
|
|
|
5
14
|
export function registerSyncCommand(program) {
|
|
6
15
|
program
|
|
7
16
|
.command('sync')
|
|
8
|
-
.description('
|
|
9
|
-
.option('-d, --dir <directory>', 'Project directory', '.')
|
|
17
|
+
.description('Synchronize project state and graph (God Mode Sync)')
|
|
18
|
+
.option('-d, --dir <directory>', 'Project directory to sync', '.')
|
|
19
|
+
.option('--push', 'Push local state to sync target')
|
|
20
|
+
.option('--pull', 'Pull state from sync target')
|
|
21
|
+
.option('--target <path>', 'Sync target (local folder or s3-like)', '.ultra/sync')
|
|
10
22
|
.action(async (options) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (result.missingContext) {
|
|
17
|
-
console.log(chalk.red('❌ CONTEXT.md not found. Run `ultra-dex init` first.'));
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (result.updated) {
|
|
22
|
-
console.log(chalk.green('✅ CONTEXT.md updated with latest snapshot.'));
|
|
23
|
-
} else {
|
|
24
|
-
console.log(chalk.yellow('⚠️ CONTEXT.md already up to date.'));
|
|
25
|
-
}
|
|
26
|
-
console.log(chalk.gray(`Files scanned: ${result.summary.fileCount}`));
|
|
27
|
-
console.log(chalk.gray(`Stack guess: ${result.summary.stack}`));
|
|
28
|
-
console.log(chalk.gray(`Changes since last sync: +${result.diff.added} / -${result.diff.removed}\n`));
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.log(chalk.red('❌ Sync failed.'));
|
|
31
|
-
console.error(error);
|
|
23
|
+
console.log(chalk.cyan('\n🔄 Ultra-Dex State Sync\n'));
|
|
24
|
+
|
|
25
|
+
const dirValidation = validateSafePath(options.dir, 'Project directory');
|
|
26
|
+
if (dirValidation !== true) {
|
|
27
|
+
console.log(chalk.red(dirValidation));
|
|
32
28
|
process.exit(1);
|
|
33
29
|
}
|
|
30
|
+
|
|
31
|
+
const projectDir = path.resolve(options.dir);
|
|
32
|
+
|
|
33
|
+
// 1. Snapshot Context (Updates CONTEXT.md)
|
|
34
|
+
const syncResult = await snapshotContext(projectDir);
|
|
35
|
+
console.log(chalk.green(` ✅ Context Snapshot Complete (${syncResult.summary.fileCount} Files scanned)`));
|
|
36
|
+
if (syncResult.updated) {
|
|
37
|
+
console.log(chalk.gray(' CONTEXT.md updated with latest project structure.'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const syncTarget = path.resolve(projectDir, options.target);
|
|
41
|
+
await fs.mkdir(syncTarget, { recursive: true });
|
|
42
|
+
|
|
43
|
+
if (options.push) {
|
|
44
|
+
await handlePush(projectDir, syncTarget);
|
|
45
|
+
} else if (options.pull) {
|
|
46
|
+
await handlePull(projectDir, syncTarget);
|
|
47
|
+
} else {
|
|
48
|
+
// Default: Bidirectional Sync (Simplified for Phase 2.1)
|
|
49
|
+
console.log(chalk.yellow('\nDefaulting to PUSH local state to target.'));
|
|
50
|
+
await handlePush(projectDir, syncTarget);
|
|
51
|
+
}
|
|
34
52
|
});
|
|
35
53
|
}
|
|
54
|
+
|
|
55
|
+
async function handlePush(projectDir, target) {
|
|
56
|
+
const spinner = (await import('ora')).default('Pushing state to sync target...').start();
|
|
57
|
+
try {
|
|
58
|
+
// Note: loadState/saveState might need to be aware of projectDir if they use relative paths
|
|
59
|
+
// For now they use process.cwd() which might be wrong if --dir is used.
|
|
60
|
+
// However, let's keep it simple as most commands assume CWD = project root unless --dir is used.
|
|
61
|
+
|
|
62
|
+
const state = await loadState();
|
|
63
|
+
if (!state) throw new Error('No local state found');
|
|
64
|
+
|
|
65
|
+
const graph = await buildGraph();
|
|
66
|
+
|
|
67
|
+
const bundle = {
|
|
68
|
+
state,
|
|
69
|
+
graph,
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
machine: process.env.USER || 'unknown'
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
await fs.writeFile(path.join(target, 'sync-bundle.json'), JSON.stringify(bundle, null, 2));
|
|
75
|
+
spinner.succeed(chalk.green(`State pushed to ${target}`));
|
|
76
|
+
} catch (e) {
|
|
77
|
+
spinner.fail(chalk.red(`Push failed: ${e.message}`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function handlePull(projectDir, target) {
|
|
82
|
+
const spinner = (await import('ora')).default('Pulling state from sync target...').start();
|
|
83
|
+
try {
|
|
84
|
+
const bundleContent = await fs.readFile(path.join(target, 'sync-bundle.json'), 'utf8');
|
|
85
|
+
const bundle = JSON.parse(bundleContent);
|
|
86
|
+
|
|
87
|
+
await saveState(bundle.state);
|
|
88
|
+
spinner.succeed(chalk.green('Local state updated from sync bundle.'));
|
|
89
|
+
console.log(chalk.gray(` Bundle Timestamp: ${bundle.timestamp}`));
|
|
90
|
+
console.log(chalk.gray(` Source Machine: ${bundle.machine}`));
|
|
91
|
+
} catch (e) {
|
|
92
|
+
spinner.fail(chalk.red(`Pull failed: ${e.message}`));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
|
|
7
|
+
const TEAM_DIR = '.ultra-dex';
|
|
8
|
+
const TEAM_FILE = 'team.json';
|
|
9
|
+
const TEAM_PATH = path.resolve(process.cwd(), TEAM_DIR, TEAM_FILE);
|
|
10
|
+
const VALID_ROLES = ['admin', 'member', 'viewer'];
|
|
11
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
12
|
+
|
|
13
|
+
function normalizeEmail(email) {
|
|
14
|
+
return email.trim().toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isValidEmail(email) {
|
|
18
|
+
return EMAIL_REGEX.test(email);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function loadTeamConfig() {
|
|
22
|
+
try {
|
|
23
|
+
const content = await fs.readFile(TEAM_PATH, 'utf8');
|
|
24
|
+
return JSON.parse(content);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.code === 'ENOENT') {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function saveTeamConfig(config) {
|
|
34
|
+
const dir = path.resolve(process.cwd(), TEAM_DIR);
|
|
35
|
+
await fs.mkdir(dir, { recursive: true });
|
|
36
|
+
await fs.writeFile(TEAM_PATH, JSON.stringify(config, null, 2));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function requireTeamConfig(team) {
|
|
40
|
+
if (!team) {
|
|
41
|
+
console.log(chalk.red('\n❌ Team not initialized. Run "ultra-dex team init" first.\n'));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatTable(rows) {
|
|
47
|
+
const headers = ['Email', 'Role', 'Added'];
|
|
48
|
+
const widths = headers.map((header, index) => {
|
|
49
|
+
const maxRow = rows.reduce((max, row) => Math.max(max, row[index].length), header.length);
|
|
50
|
+
return Math.max(header.length, maxRow);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const headerLine = headers
|
|
54
|
+
.map((header, index) => header.padEnd(widths[index]))
|
|
55
|
+
.join(' | ');
|
|
56
|
+
const divider = widths.map((width) => '-'.repeat(width)).join('-|-');
|
|
57
|
+
|
|
58
|
+
const body = rows
|
|
59
|
+
.map((row) => row.map((cell, index) => cell.padEnd(widths[index])).join(' | '))
|
|
60
|
+
.join('\n');
|
|
61
|
+
|
|
62
|
+
return `${headerLine}\n${divider}\n${body}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildInitCommand() {
|
|
66
|
+
const command = new Command('init');
|
|
67
|
+
command
|
|
68
|
+
.description('Initialize team configuration')
|
|
69
|
+
.action(async () => {
|
|
70
|
+
const existing = await loadTeamConfig();
|
|
71
|
+
if (existing) {
|
|
72
|
+
const { overwrite } = await inquirer.prompt([
|
|
73
|
+
{
|
|
74
|
+
type: 'confirm',
|
|
75
|
+
name: 'overwrite',
|
|
76
|
+
message: 'Team config already exists. Overwrite it?',
|
|
77
|
+
default: false,
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
if (!overwrite) {
|
|
81
|
+
console.log(chalk.yellow('\nCanceled.\n'));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const answers = await inquirer.prompt([
|
|
87
|
+
{
|
|
88
|
+
type: 'input',
|
|
89
|
+
name: 'name',
|
|
90
|
+
message: 'Team name:',
|
|
91
|
+
validate: (input) => (input.trim().length > 0 ? true : 'Team name is required'),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'input',
|
|
95
|
+
name: 'description',
|
|
96
|
+
message: 'Team description:',
|
|
97
|
+
default: '',
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
const config = {
|
|
102
|
+
name: answers.name.trim(),
|
|
103
|
+
description: answers.description.trim(),
|
|
104
|
+
members: [],
|
|
105
|
+
createdAt: new Date().toISOString(),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
await saveTeamConfig(config);
|
|
109
|
+
console.log(chalk.green('\n✅ Team config created at .ultra-dex/team.json\n'));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return command;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildAddCommand() {
|
|
116
|
+
const command = new Command('add');
|
|
117
|
+
command
|
|
118
|
+
.description('Add a team member')
|
|
119
|
+
.argument('<email>', 'Member email')
|
|
120
|
+
.option('-r, --role <role>', 'Role (admin|member|viewer)', 'member')
|
|
121
|
+
.action(async (email, options) => {
|
|
122
|
+
const normalizedEmail = normalizeEmail(email);
|
|
123
|
+
if (!isValidEmail(normalizedEmail)) {
|
|
124
|
+
console.log(chalk.red('\n❌ Invalid email format.\n'));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const role = options.role?.toLowerCase() || 'member';
|
|
129
|
+
if (!VALID_ROLES.includes(role)) {
|
|
130
|
+
console.log(chalk.red(`\n❌ Invalid role. Use one of: ${VALID_ROLES.join(', ')}.\n`));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const team = await loadTeamConfig();
|
|
135
|
+
requireTeamConfig(team);
|
|
136
|
+
|
|
137
|
+
const exists = team.members.some((member) => normalizeEmail(member.email) === normalizedEmail);
|
|
138
|
+
if (exists) {
|
|
139
|
+
console.log(chalk.yellow(`\n⚠️ ${normalizedEmail} is already on the team.\n`));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
team.members.push({
|
|
144
|
+
email: normalizedEmail,
|
|
145
|
+
role,
|
|
146
|
+
addedAt: new Date().toISOString(),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await saveTeamConfig(team);
|
|
150
|
+
console.log(chalk.green(`\n✅ Added ${normalizedEmail} as ${role}.\n`));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return command;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildListCommand() {
|
|
157
|
+
const command = new Command('list');
|
|
158
|
+
command
|
|
159
|
+
.description('List team members')
|
|
160
|
+
.action(async () => {
|
|
161
|
+
const team = await loadTeamConfig();
|
|
162
|
+
requireTeamConfig(team);
|
|
163
|
+
|
|
164
|
+
if (!team.members.length) {
|
|
165
|
+
console.log(chalk.yellow('\nNo team members yet.\n'));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const rows = team.members.map((member) => [
|
|
170
|
+
member.email,
|
|
171
|
+
member.role,
|
|
172
|
+
member.addedAt,
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
console.log('\n' + chalk.bold('Team Members'));
|
|
176
|
+
console.log(formatTable(rows));
|
|
177
|
+
console.log('');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return command;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function buildRemoveCommand() {
|
|
184
|
+
const command = new Command('remove');
|
|
185
|
+
command
|
|
186
|
+
.description('Remove a team member')
|
|
187
|
+
.argument('<email>', 'Member email')
|
|
188
|
+
.action(async (email) => {
|
|
189
|
+
const normalizedEmail = normalizeEmail(email);
|
|
190
|
+
if (!isValidEmail(normalizedEmail)) {
|
|
191
|
+
console.log(chalk.red('\n❌ Invalid email format.\n'));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const team = await loadTeamConfig();
|
|
196
|
+
requireTeamConfig(team);
|
|
197
|
+
|
|
198
|
+
const index = team.members.findIndex((member) => normalizeEmail(member.email) === normalizedEmail);
|
|
199
|
+
if (index === -1) {
|
|
200
|
+
console.log(chalk.red(`\n❌ ${normalizedEmail} not found in team.\n`));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const { confirm } = await inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
type: 'confirm',
|
|
207
|
+
name: 'confirm',
|
|
208
|
+
message: `Remove ${normalizedEmail} from the team?`,
|
|
209
|
+
default: false,
|
|
210
|
+
},
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
if (!confirm) {
|
|
214
|
+
console.log(chalk.yellow('\nCanceled.\n'));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
team.members.splice(index, 1);
|
|
219
|
+
await saveTeamConfig(team);
|
|
220
|
+
console.log(chalk.green(`\n✅ Removed ${normalizedEmail}.\n`));
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return command;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function buildConfigCommand() {
|
|
227
|
+
const command = new Command('config');
|
|
228
|
+
command
|
|
229
|
+
.description('Show or update team settings')
|
|
230
|
+
.argument('[key]', 'Setting key (name, description)')
|
|
231
|
+
.argument('[value]', 'Setting value')
|
|
232
|
+
.action(async (key, value) => {
|
|
233
|
+
const team = await loadTeamConfig();
|
|
234
|
+
requireTeamConfig(team);
|
|
235
|
+
|
|
236
|
+
const validKeys = ['name', 'description'];
|
|
237
|
+
if (!key) {
|
|
238
|
+
console.log(chalk.bold('\nTeam Settings\n'));
|
|
239
|
+
console.log(chalk.gray(`Name: ${team.name || '-'}`));
|
|
240
|
+
console.log(chalk.gray(`Description: ${team.description || '-'}`));
|
|
241
|
+
console.log(chalk.gray(`Created: ${team.createdAt || '-'}`));
|
|
242
|
+
console.log(chalk.gray(`Members: ${team.members.length}`));
|
|
243
|
+
console.log('');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!validKeys.includes(key)) {
|
|
248
|
+
console.log(chalk.red(`\n❌ Invalid key. Use: ${validKeys.join(', ')}.\n`));
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (value === undefined) {
|
|
253
|
+
console.log(chalk.gray(`\n${key}: ${team[key] || '-'}\n`));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
team[key] = value.trim();
|
|
258
|
+
await saveTeamConfig(team);
|
|
259
|
+
console.log(chalk.green(`\n✅ Updated ${key}.\n`));
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return command;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function registerTeamCommand(program) {
|
|
266
|
+
const team = program
|
|
267
|
+
.command('team')
|
|
268
|
+
.description('Team collaboration');
|
|
269
|
+
|
|
270
|
+
team.addCommand(buildInitCommand());
|
|
271
|
+
team.addCommand(buildAddCommand());
|
|
272
|
+
team.addCommand(buildListCommand());
|
|
273
|
+
team.addCommand(buildRemoveCommand());
|
|
274
|
+
team.addCommand(buildConfigCommand());
|
|
275
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// cli/lib/commands/upgrade.js
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { readFileSync, existsSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
export async function upgradeCommand(options) {
|
|
12
|
+
console.log(chalk.cyan.bold('\n⬆️ Ultra-Dex Upgrade Check\n'));
|
|
13
|
+
|
|
14
|
+
// Get local version from package.json
|
|
15
|
+
const localVersion = getLocalVersion();
|
|
16
|
+
|
|
17
|
+
const spinner = ora('Checking npm registry...').start();
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Query npm registry for latest version
|
|
21
|
+
const latestVersion = await getLatestVersion();
|
|
22
|
+
spinner.succeed('Registry check complete');
|
|
23
|
+
|
|
24
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
25
|
+
console.log(` ${chalk.gray('Installed:')} ${chalk.white(localVersion)}`);
|
|
26
|
+
console.log(` ${chalk.gray('Latest:')} ${chalk.cyan(latestVersion)}`);
|
|
27
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
28
|
+
|
|
29
|
+
const comparison = compareVersions(localVersion, latestVersion);
|
|
30
|
+
|
|
31
|
+
if (comparison < 0) {
|
|
32
|
+
// Update available
|
|
33
|
+
console.log(chalk.yellow.bold('\n 📦 Update available!\n'));
|
|
34
|
+
|
|
35
|
+
// Show version diff summary
|
|
36
|
+
const [localMajor, localMinor] = localVersion.split('.').map(Number);
|
|
37
|
+
const [latestMajor, latestMinor] = latestVersion.split('.').map(Number);
|
|
38
|
+
|
|
39
|
+
if (latestMajor > localMajor) {
|
|
40
|
+
console.log(chalk.red(' ⚠️ Major version update - may contain breaking changes'));
|
|
41
|
+
} else if (latestMinor > localMinor) {
|
|
42
|
+
console.log(chalk.yellow(' ✨ Minor version update - new features available'));
|
|
43
|
+
} else {
|
|
44
|
+
console.log(chalk.green(' 🔧 Patch update - bug fixes and improvements'));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Try to fetch changelog
|
|
48
|
+
if (!options.check) {
|
|
49
|
+
const changelogSpinner = ora('Fetching changelog...').start();
|
|
50
|
+
try {
|
|
51
|
+
const changelog = await fetchChangelog(localVersion, latestVersion);
|
|
52
|
+
if (changelog) {
|
|
53
|
+
changelogSpinner.succeed('Changelog retrieved');
|
|
54
|
+
console.log(chalk.bold('\n 📋 What\'s New:\n'));
|
|
55
|
+
console.log(chalk.gray(indent(changelog, 4)));
|
|
56
|
+
} else {
|
|
57
|
+
changelogSpinner.info('No changelog available');
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
changelogSpinner.info('Could not fetch changelog');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Show install instructions or run update
|
|
65
|
+
console.log('');
|
|
66
|
+
if (options.install) {
|
|
67
|
+
const installSpinner = ora('Installing update...').start();
|
|
68
|
+
try {
|
|
69
|
+
execSync('npm update -g ultra-dex', { encoding: 'utf-8', stdio: 'pipe' });
|
|
70
|
+
installSpinner.succeed(chalk.green(`Updated to v${latestVersion}`));
|
|
71
|
+
console.log(chalk.gray('\n Run `ultra-dex --version` to verify.\n'));
|
|
72
|
+
} catch (e) {
|
|
73
|
+
installSpinner.fail('Installation failed');
|
|
74
|
+
console.log(chalk.red(` ${e.message}`));
|
|
75
|
+
console.log(chalk.gray('\n Try running manually:'));
|
|
76
|
+
console.log(chalk.white(' npm install -g ultra-dex@latest\n'));
|
|
77
|
+
}
|
|
78
|
+
} else if (!options.check) {
|
|
79
|
+
console.log(chalk.gray(' To upgrade, run:'));
|
|
80
|
+
console.log(chalk.white(' npm install -g ultra-dex@latest'));
|
|
81
|
+
console.log(chalk.gray(' Or use:'));
|
|
82
|
+
console.log(chalk.white(' ultra-dex upgrade --install\n'));
|
|
83
|
+
} else {
|
|
84
|
+
console.log(chalk.gray(' Check complete. Use --install to update.\n'));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} else if (comparison === 0) {
|
|
88
|
+
// Up to date
|
|
89
|
+
console.log(chalk.green.bold('\n ✅ You are running the latest version!\n'));
|
|
90
|
+
} else {
|
|
91
|
+
// Local is newer (dev/beta)
|
|
92
|
+
console.log(chalk.blue.bold('\n 🔬 You are running a development/pre-release version\n'));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
} catch (e) {
|
|
96
|
+
spinner.warn('Could not reach npm registry');
|
|
97
|
+
console.log(chalk.gray(` Current version: ${localVersion}`));
|
|
98
|
+
console.log(chalk.yellow(`\n ${e.message}`));
|
|
99
|
+
console.log(chalk.gray('\n Check your network connection and try again.\n'));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getLocalVersion() {
|
|
104
|
+
try {
|
|
105
|
+
// Try to get from the CLI's package.json
|
|
106
|
+
const pkgPath = join(__dirname, '..', '..', 'package.json');
|
|
107
|
+
if (existsSync(pkgPath)) {
|
|
108
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
109
|
+
return pkg.version;
|
|
110
|
+
}
|
|
111
|
+
} catch { /* fall through */ }
|
|
112
|
+
|
|
113
|
+
// Fallback to hardcoded version
|
|
114
|
+
return '3.0.0';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function getLatestVersion() {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
try {
|
|
120
|
+
const output = execSync('npm view ultra-dex version 2>/dev/null', {
|
|
121
|
+
encoding: 'utf-8',
|
|
122
|
+
timeout: 10000
|
|
123
|
+
}).trim();
|
|
124
|
+
|
|
125
|
+
if (output && /^\d+\.\d+\.\d+/.test(output)) {
|
|
126
|
+
resolve(output);
|
|
127
|
+
} else {
|
|
128
|
+
reject(new Error('Invalid version format from registry'));
|
|
129
|
+
}
|
|
130
|
+
} catch (e) {
|
|
131
|
+
// Package might not be published yet
|
|
132
|
+
reject(new Error('Package not found in npm registry (may not be published yet)'));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function compareVersions(v1, v2) {
|
|
138
|
+
const parts1 = v1.split('.').map(Number);
|
|
139
|
+
const parts2 = v2.split('.').map(Number);
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < 3; i++) {
|
|
142
|
+
const a = parts1[i] || 0;
|
|
143
|
+
const b = parts2[i] || 0;
|
|
144
|
+
if (a < b) return -1;
|
|
145
|
+
if (a > b) return 1;
|
|
146
|
+
}
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function fetchChangelog(fromVersion, toVersion) {
|
|
151
|
+
try {
|
|
152
|
+
// Try to fetch from GitHub releases API
|
|
153
|
+
const output = execSync(
|
|
154
|
+
`curl -s "https://api.github.com/repos/Srujan0798/Ultra-Dex/releases" | head -c 5000`,
|
|
155
|
+
{ encoding: 'utf-8', timeout: 10000 }
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const releases = JSON.parse(output);
|
|
159
|
+
if (!Array.isArray(releases) || releases.length === 0) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Get release notes between versions
|
|
164
|
+
const changelog = [];
|
|
165
|
+
for (const release of releases.slice(0, 5)) {
|
|
166
|
+
const releaseVersion = release.tag_name?.replace(/^v/, '') || '';
|
|
167
|
+
if (compareVersions(releaseVersion, fromVersion) > 0 &&
|
|
168
|
+
compareVersions(releaseVersion, toVersion) <= 0) {
|
|
169
|
+
changelog.push(`v${releaseVersion}:`);
|
|
170
|
+
// Extract first few bullet points from body
|
|
171
|
+
const body = release.body || '';
|
|
172
|
+
const lines = body.split('\n')
|
|
173
|
+
.filter(line => line.trim().startsWith('-') || line.trim().startsWith('*'))
|
|
174
|
+
.slice(0, 5)
|
|
175
|
+
.map(line => ' ' + line.trim());
|
|
176
|
+
changelog.push(...lines);
|
|
177
|
+
changelog.push('');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return changelog.length > 0 ? changelog.join('\n') : null;
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function indent(text, spaces) {
|
|
188
|
+
const prefix = ' '.repeat(spaces);
|
|
189
|
+
return text.split('\n').map(line => prefix + line).join('\n');
|
|
190
|
+
}
|
package/lib/commands/validate.js
CHANGED
|
@@ -2,12 +2,14 @@ import chalk from 'chalk';
|
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { validateSafePath } from '../utils/validation.js';
|
|
5
|
+
import { runQualityScan } from '../quality/scanner.js';
|
|
5
6
|
|
|
6
7
|
export function registerValidateCommand(program) {
|
|
7
8
|
program
|
|
8
9
|
.command('validate')
|
|
9
10
|
.description('Validate project structure against Ultra-Dex standards')
|
|
10
11
|
.option('-d, --dir <directory>', 'Project directory to validate', '.')
|
|
12
|
+
.option('--scan', 'Run deep code quality scan')
|
|
11
13
|
.action(async (options) => {
|
|
12
14
|
console.log(chalk.cyan('\n✅ Ultra-Dex Structure Validator\n'));
|
|
13
15
|
|
|
@@ -114,6 +116,34 @@ export function registerValidateCommand(program) {
|
|
|
114
116
|
console.log(chalk.gray(' ⊘ Could not validate IMPLEMENTATION-PLAN.md content'));
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
// Deep Code Scan
|
|
120
|
+
if (options.scan) {
|
|
121
|
+
console.log(chalk.bold('\nRunning Deep Code Scan (Active State Tracking)...\n'));
|
|
122
|
+
const scanResults = await runQualityScan(projectDir);
|
|
123
|
+
|
|
124
|
+
if (scanResults.failed > 0) {
|
|
125
|
+
failed += scanResults.failed;
|
|
126
|
+
console.log(chalk.red(` ❌ Code Scan Failed: ${scanResults.failed} critical issues found.`));
|
|
127
|
+
} else {
|
|
128
|
+
passed++;
|
|
129
|
+
console.log(chalk.green(` ✅ Code Scan Passed (${scanResults.filesScanned} files scanned).`));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (scanResults.warnings > 0) {
|
|
133
|
+
console.log(chalk.yellow(` ⚠️ ${scanResults.warnings} code warnings found.`));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (scanResults.details.length > 0) {
|
|
137
|
+
console.log(chalk.gray('\n Scan Details:'));
|
|
138
|
+
scanResults.details.forEach(issue => {
|
|
139
|
+
const icon = issue.severity === 'error' || issue.severity === 'critical' ? '❌' : '⚠️';
|
|
140
|
+
console.log(` ${icon} [${issue.ruleName}] ${issue.file}: ${issue.message}`);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.gray('\nℹ️ Run with --scan to enable Deep Code Quality Scan.'));
|
|
145
|
+
}
|
|
146
|
+
|
|
117
147
|
console.log('\n' + chalk.bold('─'.repeat(50)));
|
|
118
148
|
console.log(chalk.bold('\nValidation Summary:\n'));
|
|
119
149
|
console.log(chalk.green(` ✅ Passed: ${passed}`));
|
|
@@ -126,6 +156,10 @@ export function registerValidateCommand(program) {
|
|
|
126
156
|
} else {
|
|
127
157
|
console.log(chalk.bold.yellow('\n⚠️ VALIDATION INCOMPLETE\n'));
|
|
128
158
|
console.log(chalk.gray('Fix required files to meet Ultra-Dex standards.'));
|
|
159
|
+
if (options.scan && failed > 0) {
|
|
160
|
+
console.log(chalk.red('Code quality gates failed. Commit rejected (if in pre-commit).'));
|
|
161
|
+
}
|
|
162
|
+
process.exit(1);
|
|
129
163
|
}
|
|
130
164
|
|
|
131
165
|
if (warnings.length > 0) {
|