ultra-dex 2.2.0 ā 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 -122
- package/assets/agents/0-orchestration/orchestrator.md +2 -2
- package/assets/agents/00-AGENT_INDEX.md +1 -1
- package/assets/docs/LAUNCH-POSTS.md +1 -1
- package/assets/docs/QUICK-REFERENCE.md +12 -7
- package/assets/docs/ROADMAP.md +5 -5
- package/assets/docs/VISION-V2.md +1 -1
- package/assets/docs/WORKFLOW-DIAGRAMS.md +1 -1
- package/assets/hooks/pre-commit +98 -0
- package/assets/saas-plan/04-Imp-Template.md +1 -1
- package/assets/templates/README.md +1 -1
- package/bin/ultra-dex.js +93 -2096
- package/lib/commands/advanced.js +471 -0
- package/lib/commands/agent-builder.js +226 -0
- package/lib/commands/agents.js +101 -47
- 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 +56 -6
- 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 +188 -40
- package/lib/commands/state.js +354 -0
- package/lib/commands/swarm.js +284 -0
- package/lib/commands/sync.js +94 -0
- 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/context.js +2 -2
- package/lib/templates/custom-agent.md +10 -0
- package/lib/utils/fallback.js +4 -2
- package/lib/utils/files.js +7 -34
- package/lib/utils/graph.js +108 -0
- package/lib/utils/sync.js +216 -0
- package/package.json +22 -13
package/lib/commands/generate.js
CHANGED
|
@@ -10,6 +10,9 @@ import fs from 'fs/promises';
|
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import { createProvider, getDefaultProvider, checkConfiguredProviders } from '../providers/index.js';
|
|
12
12
|
import { SYSTEM_PROMPT, generateUserPrompt } from '../templates/prompts/generate-plan.js';
|
|
13
|
+
import { validateSafePath } from '../utils/validation.js';
|
|
14
|
+
import { githubTreeUrl, githubWebUrl } from '../config/urls.js';
|
|
15
|
+
import { saveState } from './plan.js';
|
|
13
16
|
|
|
14
17
|
export function registerGenerateCommand(program) {
|
|
15
18
|
program
|
|
@@ -24,6 +27,12 @@ export function registerGenerateCommand(program) {
|
|
|
24
27
|
.action(async (idea, options) => {
|
|
25
28
|
console.log(chalk.cyan('\nš Ultra-Dex Plan Generator\n'));
|
|
26
29
|
|
|
30
|
+
const dirValidation = validateSafePath(options.output, 'Output directory');
|
|
31
|
+
if (dirValidation !== true) {
|
|
32
|
+
console.log(chalk.red(dirValidation));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
// Check configured providers
|
|
28
37
|
const configured = checkConfiguredProviders();
|
|
29
38
|
const hasProvider = configured.some(p => p.configured) || options.key;
|
|
@@ -119,99 +128,114 @@ export function registerGenerateCommand(program) {
|
|
|
119
128
|
// Add header to plan
|
|
120
129
|
const header = `# Implementation Plan
|
|
121
130
|
|
|
122
|
-
> Generated by Ultra-Dex
|
|
123
|
-
> Idea: "${idea}"
|
|
124
|
-
|
|
125
|
-
---
|
|
131
|
+
> Generated by Ultra-Dex AI Plan Generator
|
|
126
132
|
|
|
127
133
|
`;
|
|
128
|
-
|
|
134
|
+
if (!planContent.startsWith('#')) {
|
|
135
|
+
planContent = header + planContent;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await fs.writeFile(planPath, planContent);
|
|
139
|
+
|
|
140
|
+
// --- NEW: Generate state.json (ACTIVE SCALFOLDING) ---
|
|
141
|
+
const projectName = idea.split(' ').slice(0, 3).join('-').toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
142
|
+
|
|
143
|
+
const state = {
|
|
144
|
+
project: {
|
|
145
|
+
name: projectName,
|
|
146
|
+
version: '0.1.0',
|
|
147
|
+
mode: 'AI-First',
|
|
148
|
+
idea: idea
|
|
149
|
+
},
|
|
150
|
+
phases: [
|
|
151
|
+
{
|
|
152
|
+
id: '1',
|
|
153
|
+
name: 'Phase 1: Foundation',
|
|
154
|
+
status: 'in_progress',
|
|
155
|
+
steps: [
|
|
156
|
+
{ id: '1.1', task: 'Setup project boilerplate', status: 'pending' },
|
|
157
|
+
{ id: '1.2', task: 'Database schema design', status: 'pending' },
|
|
158
|
+
{ id: '1.3', task: 'Authentication implementation', status: 'pending' }
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: '2',
|
|
163
|
+
name: 'Phase 2: Core Features',
|
|
164
|
+
status: 'pending',
|
|
165
|
+
steps: [
|
|
166
|
+
{ id: '2.1', task: 'Implement primary feature loop', status: 'pending' },
|
|
167
|
+
{ id: '2.2', task: 'API endpoint development', status: 'pending' }
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
agents: {
|
|
172
|
+
active: ['planner', 'cto'],
|
|
173
|
+
registry: ['planner', 'cto', 'backend', 'frontend', 'database', 'testing', 'reviewer']
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
await saveState(state);
|
|
129
178
|
|
|
130
179
|
// Generate CONTEXT.md
|
|
131
180
|
const contextContent = `# Project Context
|
|
132
181
|
|
|
133
|
-
##
|
|
182
|
+
## Project Info
|
|
183
|
+
**Created:** ${new Date().toLocaleDateString()}
|
|
184
|
+
**Idea:** ${idea}
|
|
185
|
+
**Status:** Planning
|
|
186
|
+
|
|
187
|
+
## Summary
|
|
134
188
|
${idea}
|
|
135
189
|
|
|
136
|
-
##
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
- IMPLEMENTATION-PLAN.md - Full 34-section plan
|
|
143
|
-
- QUICK-START.md - Quick reference
|
|
144
|
-
- CONTEXT.md - This file
|
|
145
|
-
|
|
146
|
-
## Usage
|
|
147
|
-
Provide this context to any AI agent:
|
|
148
|
-
\`\`\`
|
|
149
|
-
Act as @Backend. Read CONTEXT.md and IMPLEMENTATION-PLAN.md first.
|
|
150
|
-
Task: [your task]
|
|
151
|
-
\`\`\`
|
|
190
|
+
## Current Focus
|
|
191
|
+
Review implementation plan and begin development.
|
|
192
|
+
|
|
193
|
+
## Ultra-Dex Resources
|
|
194
|
+
- Official Template: ${githubWebUrl()}
|
|
195
|
+
- Documentation: ${githubTreeUrl('docs')}
|
|
152
196
|
`;
|
|
197
|
+
|
|
153
198
|
await fs.writeFile(contextPath, contextContent);
|
|
154
199
|
|
|
155
200
|
// Generate QUICK-START.md
|
|
156
201
|
const quickStartContent = `# Quick Start
|
|
157
202
|
|
|
158
|
-
##
|
|
203
|
+
## Project Idea
|
|
159
204
|
${idea}
|
|
160
205
|
|
|
161
206
|
## Next Steps
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
npx ultra-dex serve # Serve context via HTTP
|
|
173
|
-
npx ultra-dex agents # List available agents
|
|
174
|
-
\`\`\`
|
|
175
|
-
|
|
176
|
-
## Agent Quick Start
|
|
177
|
-
|
|
178
|
-
\`\`\`
|
|
179
|
-
@Planner - Break down tasks
|
|
180
|
-
@Backend - API endpoints
|
|
181
|
-
@Frontend - UI components
|
|
182
|
-
@Database - Schema design
|
|
183
|
-
@Auth - Authentication
|
|
184
|
-
@Testing - Write tests
|
|
185
|
-
\`\`\`
|
|
207
|
+
1. Review IMPLEMENTATION-PLAN.md
|
|
208
|
+
2. Start with the first feature
|
|
209
|
+
3. Use Ultra-Dex agents for guidance
|
|
210
|
+
|
|
211
|
+
## AI Agents
|
|
212
|
+
- @Planner: Break down tasks
|
|
213
|
+
- @CTO: Architecture decisions
|
|
214
|
+
- @Backend: API logic
|
|
215
|
+
- @Frontend: UI components
|
|
216
|
+
- @Testing: QA and tests
|
|
186
217
|
`;
|
|
187
|
-
await fs.writeFile(quickStartPath, quickStartContent);
|
|
188
|
-
|
|
189
|
-
// Print summary
|
|
190
|
-
console.log(chalk.green('\nā
Plan generated successfully!\n'));
|
|
191
|
-
console.log(chalk.white('Files created:'));
|
|
192
|
-
console.log(chalk.gray(` š ${planPath}`));
|
|
193
|
-
console.log(chalk.gray(` š ${contextPath}`));
|
|
194
|
-
console.log(chalk.gray(` š ${quickStartPath}`));
|
|
195
|
-
|
|
196
|
-
console.log(chalk.white('\nStats:'));
|
|
197
|
-
console.log(chalk.gray(` ā±ļø Time: ${elapsed}s`));
|
|
198
|
-
console.log(chalk.gray(` š Tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out`));
|
|
199
|
-
console.log(chalk.gray(` š° Cost: ~$${cost.total.toFixed(4)}`));
|
|
200
218
|
|
|
201
|
-
|
|
202
|
-
console.log(chalk.white(' 1. Review IMPLEMENTATION-PLAN.md'));
|
|
203
|
-
console.log(chalk.white(' 2. Run: npx ultra-dex build'));
|
|
204
|
-
console.log(chalk.white(' 3. Start coding with AI agents\n'));
|
|
219
|
+
await fs.writeFile(quickStartPath, quickStartContent);
|
|
205
220
|
|
|
221
|
+
spinner.succeed('Plan generated successfully!');
|
|
222
|
+
|
|
223
|
+
console.log(chalk.green('\nā
Files created:'));
|
|
224
|
+
console.log(chalk.gray(` ${planPath}`));
|
|
225
|
+
console.log(chalk.gray(` ${contextPath}`));
|
|
226
|
+
console.log(chalk.gray(` ${quickStartPath}`));
|
|
227
|
+
console.log(chalk.gray(` .ultra/state.json (GOD MODE ACTIVE)`));
|
|
228
|
+
console.log(chalk.gray(`\nā±ļø Time: ${elapsed}s`));
|
|
229
|
+
console.log(chalk.gray(`š° Est. cost: ${cost}`));
|
|
230
|
+
|
|
231
|
+
console.log(chalk.bold('\nNext steps:'));
|
|
232
|
+
console.log(chalk.cyan(' 1. Review IMPLEMENTATION-PLAN.md'));
|
|
233
|
+
console.log(chalk.cyan(' 2. Run `ultra-dex dashboard` to visualize your progress'));
|
|
234
|
+
console.log(chalk.cyan(' 3. Run `ultra-dex build` to let Auto-Pilot take the first task'));
|
|
235
|
+
console.log(chalk.cyan(' 4. Use AI agents for specialized guidance\n'));
|
|
206
236
|
} catch (err) {
|
|
207
|
-
spinner.fail('
|
|
208
|
-
console.
|
|
209
|
-
|
|
210
|
-
if (err.message.includes('API')) {
|
|
211
|
-
console.log(chalk.yellow('\nTip: Check your API key and try again.'));
|
|
212
|
-
}
|
|
237
|
+
spinner.fail('Failed to generate plan');
|
|
238
|
+
console.error(chalk.red('Error:'), err.message);
|
|
213
239
|
}
|
|
214
240
|
});
|
|
215
241
|
}
|
|
216
|
-
|
|
217
|
-
export default { registerGenerateCommand };
|
package/lib/commands/hooks.js
CHANGED
|
@@ -1,105 +1,280 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
4
8
|
|
|
5
9
|
export function registerHooksCommand(program) {
|
|
6
|
-
program
|
|
10
|
+
const hooks = program
|
|
7
11
|
.command('hooks')
|
|
8
|
-
.description('
|
|
9
|
-
|
|
12
|
+
.description('Manage Ultra-Dex git hooks for automated verification');
|
|
13
|
+
|
|
14
|
+
// Install subcommand
|
|
15
|
+
hooks
|
|
16
|
+
.command('install')
|
|
17
|
+
.description('Install Ultra-Dex pre-commit hook to .git/hooks/')
|
|
18
|
+
.option('--force', 'Overwrite existing hooks')
|
|
19
|
+
.option('--min-score <score>', 'Minimum alignment score (default: 70)', '70')
|
|
10
20
|
.action(async (options) => {
|
|
11
|
-
console.log(chalk.cyan('\nšŖ Ultra-Dex Git Hooks
|
|
21
|
+
console.log(chalk.cyan('\nšŖ Ultra-Dex Git Hooks Installation\n'));
|
|
22
|
+
await installHook(options);
|
|
23
|
+
});
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
// Remove subcommand
|
|
26
|
+
hooks
|
|
27
|
+
.command('remove')
|
|
28
|
+
.alias('uninstall')
|
|
29
|
+
.description('Remove Ultra-Dex git hooks')
|
|
30
|
+
.action(async () => {
|
|
31
|
+
console.log(chalk.cyan('\nšŖ Ultra-Dex Git Hooks Removal\n'));
|
|
32
|
+
await removeHook();
|
|
33
|
+
});
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
// Status subcommand
|
|
36
|
+
hooks
|
|
37
|
+
.command('status')
|
|
38
|
+
.description('Check if Ultra-Dex hooks are installed')
|
|
39
|
+
.action(async () => {
|
|
40
|
+
console.log(chalk.cyan('\nšŖ Ultra-Dex Git Hooks Status\n'));
|
|
41
|
+
await checkHookStatus();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Default action (legacy support)
|
|
45
|
+
hooks
|
|
46
|
+
.option('--remove', 'Remove Ultra-Dex git hooks (deprecated: use "hooks remove")')
|
|
47
|
+
.action(async (options) => {
|
|
48
|
+
if (options.remove) {
|
|
49
|
+
console.log(chalk.cyan('\nšŖ Ultra-Dex Git Hooks Removal\n'));
|
|
50
|
+
await removeHook();
|
|
51
|
+
} else {
|
|
52
|
+
// Show help if no subcommand
|
|
53
|
+
hooks.outputHelp();
|
|
21
54
|
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
22
57
|
|
|
23
|
-
|
|
58
|
+
async function getGitHooksDir() {
|
|
59
|
+
const gitDir = path.join(process.cwd(), '.git');
|
|
60
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
24
61
|
|
|
25
|
-
|
|
62
|
+
try {
|
|
63
|
+
await fs.access(gitDir);
|
|
64
|
+
} catch {
|
|
65
|
+
console.log(chalk.red('ā Not a git repository. Run "git init" first.\n'));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
26
68
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
69
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
70
|
+
return hooksDir;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function getPreCommitHookPath() {
|
|
74
|
+
// Try to find the bundled hook first
|
|
75
|
+
const possiblePaths = [
|
|
76
|
+
path.join(__dirname, '..', '..', 'assets', 'hooks', 'pre-commit'),
|
|
77
|
+
path.join(__dirname, '..', '..', '..', 'assets', 'hooks', 'pre-commit'),
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const hookPath of possiblePaths) {
|
|
81
|
+
try {
|
|
82
|
+
await fs.access(hookPath);
|
|
83
|
+
return hookPath;
|
|
84
|
+
} catch {
|
|
85
|
+
// Continue to next path
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function installHook(options) {
|
|
93
|
+
const hooksDir = await getGitHooksDir();
|
|
94
|
+
const preCommitPath = path.join(hooksDir, 'pre-commit');
|
|
95
|
+
const minScore = parseInt(options.minScore, 10) || 70;
|
|
96
|
+
|
|
97
|
+
// Try to use bundled hook
|
|
98
|
+
const bundledHookPath = await getPreCommitHookPath();
|
|
99
|
+
let hookScript;
|
|
100
|
+
|
|
101
|
+
if (bundledHookPath) {
|
|
102
|
+
hookScript = await fs.readFile(bundledHookPath, 'utf-8');
|
|
103
|
+
// Update minimum score if specified
|
|
104
|
+
hookScript = hookScript.replace(/MIN_ALIGNMENT_SCORE=\d+/, `MIN_ALIGNMENT_SCORE=${minScore}`);
|
|
105
|
+
console.log(chalk.gray(` Using bundled hook from: ${bundledHookPath}`));
|
|
106
|
+
} else {
|
|
107
|
+
// Fallback to embedded script
|
|
108
|
+
hookScript = generatePreCommitScript(minScore);
|
|
109
|
+
console.log(chalk.gray(' Using embedded hook script'));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const existing = await fs.readFile(preCommitPath, 'utf-8');
|
|
114
|
+
if (existing.includes('ultra-dex') || existing.includes('Ultra-Dex')) {
|
|
115
|
+
if (options.force) {
|
|
116
|
+
await fs.writeFile(preCommitPath, hookScript);
|
|
117
|
+
await fs.chmod(preCommitPath, '755');
|
|
118
|
+
console.log(chalk.green('ā
Ultra-Dex pre-commit hook updated (--force).\n'));
|
|
119
|
+
} else {
|
|
120
|
+
console.log(chalk.yellow('ā ļø Ultra-Dex pre-commit hook already exists.\n'));
|
|
121
|
+
console.log(chalk.gray(' Use --force to overwrite, or "hooks remove" first.\n'));
|
|
39
122
|
return;
|
|
40
123
|
}
|
|
124
|
+
} else {
|
|
125
|
+
// Append to existing hook
|
|
126
|
+
const combined = existing + '\n\n' + hookScript;
|
|
127
|
+
await fs.writeFile(preCommitPath, combined);
|
|
128
|
+
await fs.chmod(preCommitPath, '755');
|
|
129
|
+
console.log(chalk.green('ā
Ultra-Dex hook appended to existing pre-commit.\n'));
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// No existing hook, create new one
|
|
133
|
+
await fs.writeFile(preCommitPath, hookScript);
|
|
134
|
+
await fs.chmod(preCommitPath, '755');
|
|
135
|
+
console.log(chalk.green('ā
Pre-commit hook installed.\n'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
printHookInfo(minScore);
|
|
139
|
+
}
|
|
41
140
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# Remove with: npx ultra-dex hooks --remove
|
|
141
|
+
async function removeHook() {
|
|
142
|
+
const hooksDir = await getGitHooksDir();
|
|
143
|
+
const preCommitPath = path.join(hooksDir, 'pre-commit');
|
|
46
144
|
|
|
47
|
-
|
|
145
|
+
try {
|
|
146
|
+
const content = await fs.readFile(preCommitPath, 'utf-8');
|
|
147
|
+
if (content.includes('ultra-dex') || content.includes('Ultra-Dex')) {
|
|
148
|
+
await fs.unlink(preCommitPath);
|
|
149
|
+
console.log(chalk.green('ā
Ultra-Dex pre-commit hook removed.\n'));
|
|
150
|
+
} else {
|
|
151
|
+
console.log(chalk.yellow('ā ļø Pre-commit hook exists but is not from Ultra-Dex.\n'));
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
console.log(chalk.gray('No Ultra-Dex hooks found.\n'));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
48
157
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
158
|
+
async function checkHookStatus() {
|
|
159
|
+
const hooksDir = await getGitHooksDir();
|
|
160
|
+
const preCommitPath = path.join(hooksDir, 'pre-commit');
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const content = await fs.readFile(preCommitPath, 'utf-8');
|
|
164
|
+
if (content.includes('ultra-dex') || content.includes('Ultra-Dex')) {
|
|
165
|
+
console.log(chalk.green('ā
Ultra-Dex pre-commit hook is installed.\n'));
|
|
166
|
+
|
|
167
|
+
// Extract min score if present
|
|
168
|
+
const scoreMatch = content.match(/MIN_ALIGNMENT_SCORE=(\d+)/);
|
|
169
|
+
if (scoreMatch) {
|
|
170
|
+
console.log(chalk.gray(` Minimum alignment score: ${scoreMatch[1]}%\n`));
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
console.log(chalk.yellow('ā ļø Pre-commit hook exists but is not from Ultra-Dex.\n'));
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
console.log(chalk.gray('ā No pre-commit hook installed.\n'));
|
|
177
|
+
console.log(chalk.cyan(' Install with: npx ultra-dex hooks install\n'));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function printHookInfo(minScore) {
|
|
182
|
+
console.log(chalk.bold('What this does:\n'));
|
|
183
|
+
console.log(chalk.gray(` ⢠Checks alignment score (minimum: ${minScore}%)`));
|
|
184
|
+
console.log(chalk.gray(' ⢠Runs "ultra-dex validate" before each commit'));
|
|
185
|
+
console.log(chalk.gray(' ⢠Blocks commits if validation fails'));
|
|
186
|
+
console.log(chalk.gray(' ⢠Warns about missing documentation\n'));
|
|
187
|
+
|
|
188
|
+
console.log(chalk.bold('To bypass (not recommended):\n'));
|
|
189
|
+
console.log(chalk.cyan(' git commit --no-verify\n'));
|
|
190
|
+
|
|
191
|
+
console.log(chalk.bold('To check status:\n'));
|
|
192
|
+
console.log(chalk.cyan(' npx ultra-dex hooks status\n'));
|
|
193
|
+
|
|
194
|
+
console.log(chalk.bold('To remove:\n'));
|
|
195
|
+
console.log(chalk.cyan(' npx ultra-dex hooks remove\n'));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function generatePreCommitScript(minScore = 70) {
|
|
199
|
+
return `#!/bin/sh
|
|
200
|
+
# Ultra-Dex Pre-Commit Hook v3.0
|
|
201
|
+
# Validates project alignment and structure before allowing commits
|
|
202
|
+
# Install with: npx ultra-dex hooks install
|
|
203
|
+
# Remove with: npx ultra-dex hooks remove
|
|
204
|
+
|
|
205
|
+
set -e
|
|
206
|
+
|
|
207
|
+
echo ""
|
|
208
|
+
echo "šÆ Ultra-Dex: Running pre-commit validation..."
|
|
209
|
+
echo ""
|
|
210
|
+
|
|
211
|
+
# Configuration
|
|
212
|
+
MIN_ALIGNMENT_SCORE=${minScore}
|
|
213
|
+
VALIDATION_LOG="/tmp/ultra-dex-validate.log"
|
|
214
|
+
ALIGN_LOG="/tmp/ultra-dex-align.log"
|
|
215
|
+
|
|
216
|
+
# Check if ultra-dex is available
|
|
217
|
+
if ! command -v ultra-dex &> /dev/null && ! npx ultra-dex --version &> /dev/null 2>&1; then
|
|
218
|
+
echo "ā ļø Ultra-Dex not found. Skipping validation."
|
|
219
|
+
echo " Install with: npm install -g ultra-dex"
|
|
220
|
+
exit 0
|
|
61
221
|
fi
|
|
62
222
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
223
|
+
# Run alignment check and capture score
|
|
224
|
+
echo "š Checking alignment score..."
|
|
225
|
+
npx ultra-dex align --dir . > "$ALIGN_LOG" 2>&1 || true
|
|
226
|
+
|
|
227
|
+
# Extract score from output
|
|
228
|
+
SCORE=$(grep -oE '[0-9]+%' "$ALIGN_LOG" | head -1 | tr -d '%' || echo "0")
|
|
229
|
+
|
|
230
|
+
if [ -z "$SCORE" ] || [ "$SCORE" = "0" ]; then
|
|
231
|
+
SCORE=$(grep -oE 'Score: [0-9]+' "$ALIGN_LOG" | grep -oE '[0-9]+' | head -1 || echo "85")
|
|
66
232
|
fi
|
|
67
233
|
|
|
68
|
-
|
|
69
|
-
|
|
234
|
+
echo " Current alignment score: \${SCORE}%"
|
|
235
|
+
|
|
236
|
+
# Check minimum score threshold
|
|
237
|
+
if [ "$SCORE" -lt "$MIN_ALIGNMENT_SCORE" ]; then
|
|
238
|
+
echo ""
|
|
239
|
+
echo "ā COMMIT BLOCKED: Alignment score (\$SCORE%) is below minimum ($MIN_ALIGNMENT_SCORE%)"
|
|
240
|
+
echo ""
|
|
241
|
+
echo "š To fix this:"
|
|
242
|
+
echo " 1. Run: npx ultra-dex validate"
|
|
243
|
+
echo " 2. Run: npx ultra-dex fix (to auto-fix issues)"
|
|
244
|
+
echo " 3. Review and commit again"
|
|
245
|
+
echo ""
|
|
246
|
+
echo "š To bypass (not recommended):"
|
|
247
|
+
echo " git commit --no-verify"
|
|
248
|
+
echo ""
|
|
249
|
+
exit 1
|
|
70
250
|
fi
|
|
71
251
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
252
|
+
# Run validation
|
|
253
|
+
echo "š Running validation checks..."
|
|
254
|
+
npx ultra-dex validate --dir . --scan > "$VALIDATION_LOG" 2>&1
|
|
255
|
+
VALIDATE_RESULT=$?
|
|
75
256
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
} catch {
|
|
89
|
-
await fs.writeFile(preCommitPath, preCommitScript);
|
|
90
|
-
await fs.chmod(preCommitPath, '755');
|
|
91
|
-
console.log(chalk.green('ā
Pre-commit hook installed.\n'));
|
|
92
|
-
}
|
|
257
|
+
if [ $VALIDATE_RESULT -ne 0 ]; then
|
|
258
|
+
echo ""
|
|
259
|
+
echo "ā COMMIT BLOCKED: Validation failed"
|
|
260
|
+
echo ""
|
|
261
|
+
cat "$VALIDATION_LOG"
|
|
262
|
+
echo ""
|
|
263
|
+
echo "š Run 'npx ultra-dex validate' for details"
|
|
264
|
+
echo "š To bypass: git commit --no-verify"
|
|
265
|
+
echo ""
|
|
266
|
+
exit 1
|
|
267
|
+
fi
|
|
93
268
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
269
|
+
# Success
|
|
270
|
+
echo ""
|
|
271
|
+
echo "ā
Ultra-Dex validation passed!"
|
|
272
|
+
echo " Score: \${SCORE}% (minimum: $MIN_ALIGNMENT_SCORE%)"
|
|
273
|
+
echo ""
|
|
98
274
|
|
|
99
|
-
|
|
100
|
-
|
|
275
|
+
# Cleanup
|
|
276
|
+
rm -f "$VALIDATION_LOG" "$ALIGN_LOG" 2>/dev/null || true
|
|
101
277
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
278
|
+
exit 0
|
|
279
|
+
`;
|
|
105
280
|
}
|
package/lib/commands/init.js
CHANGED
|
@@ -7,9 +7,16 @@ import path from 'path';
|
|
|
7
7
|
import { QUICK_START_TEMPLATE } from '../templates/quick-start.js';
|
|
8
8
|
import { CONTEXT_TEMPLATE } from '../templates/context.js';
|
|
9
9
|
import { validateProjectName, validateSafePath } from '../utils/validation.js';
|
|
10
|
-
import { ASSETS_ROOT, ROOT_FALLBACK } from '../config/paths.js';
|
|
10
|
+
import { ASSETS_ROOT, ROOT_FALLBACK, LIVE_TEMPLATES_ROOT } from '../config/paths.js';
|
|
11
11
|
import { githubBlobUrl, githubWebUrl } from '../config/urls.js';
|
|
12
12
|
import { copyWithFallback, listWithFallback, readWithFallback } from '../utils/fallback.js';
|
|
13
|
+
import { copyDirectory, pathExists } from '../utils/files.js';
|
|
14
|
+
|
|
15
|
+
const LIVE_STACKS = {
|
|
16
|
+
'next15-prisma-clerk': 'Next.js 15 + Prisma + Clerk',
|
|
17
|
+
'remix-supabase': 'Remix + Supabase',
|
|
18
|
+
'sveltekit-drizzle': 'SvelteKit + Drizzle',
|
|
19
|
+
};
|
|
13
20
|
|
|
14
21
|
export function registerInitCommand(program) {
|
|
15
22
|
program
|
|
@@ -18,6 +25,8 @@ export function registerInitCommand(program) {
|
|
|
18
25
|
.option('-n, --name <name>', 'Project name')
|
|
19
26
|
.option('-d, --dir <directory>', 'Output directory', '.')
|
|
20
27
|
.option('--preview', 'Preview files without creating them')
|
|
28
|
+
.option('--live', 'Generate a runnable scaffold')
|
|
29
|
+
.option('--stack <preset>', 'Preset: next15-prisma-clerk, remix-supabase, sveltekit-drizzle')
|
|
21
30
|
.action(async (options) => {
|
|
22
31
|
console.log(chalk.cyan(program.banner));
|
|
23
32
|
console.log(chalk.bold('\nWelcome to Ultra-Dex! Let\'s plan your SaaS.\n'));
|
|
@@ -39,6 +48,49 @@ export function registerInitCommand(program) {
|
|
|
39
48
|
process.exit(1);
|
|
40
49
|
}
|
|
41
50
|
|
|
51
|
+
if (options.live) {
|
|
52
|
+
const preset = options.stack || 'next15-prisma-clerk';
|
|
53
|
+
if (!LIVE_STACKS[preset]) {
|
|
54
|
+
console.log(chalk.red(`Unknown preset: ${preset}`));
|
|
55
|
+
console.log(chalk.gray(`Available presets: ${Object.keys(LIVE_STACKS).join(', ')}`));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const outputDir = path.resolve(options.dir);
|
|
60
|
+
if (await pathExists(outputDir, 'dir')) {
|
|
61
|
+
const existing = await fs.readdir(outputDir);
|
|
62
|
+
if (existing.length > 0) {
|
|
63
|
+
console.log(chalk.red('Target directory is not empty.'));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const liveSourcePath = path.join(LIVE_TEMPLATES_ROOT, preset);
|
|
69
|
+
const fallbackLivePath = path.join(ROOT_FALLBACK, 'cli', 'assets', 'live-templates', preset);
|
|
70
|
+
let sourcePath = liveSourcePath;
|
|
71
|
+
try {
|
|
72
|
+
await fs.access(liveSourcePath);
|
|
73
|
+
} catch {
|
|
74
|
+
sourcePath = fallbackLivePath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const spinner = ora(`Generating ${LIVE_STACKS[preset]} scaffold...`).start();
|
|
78
|
+
try {
|
|
79
|
+
await copyDirectory(sourcePath, outputDir);
|
|
80
|
+
spinner.succeed(chalk.green('Live scaffold created successfully!'));
|
|
81
|
+
console.log(chalk.gray(`\nPreset: ${preset}`));
|
|
82
|
+
console.log(chalk.gray(`Next steps:`));
|
|
83
|
+
console.log(chalk.cyan(` 1. cd ${outputDir}`));
|
|
84
|
+
console.log(chalk.cyan(' 2. npm install'));
|
|
85
|
+
console.log(chalk.cyan(' 3. npm run dev\n'));
|
|
86
|
+
} catch (error) {
|
|
87
|
+
spinner.fail(chalk.red('Failed to create live scaffold'));
|
|
88
|
+
console.error(`[init] ${error?.message ?? error}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
42
94
|
const answers = await inquirer.prompt([
|
|
43
95
|
{
|
|
44
96
|
type: 'input',
|
|
@@ -207,12 +259,10 @@ ${answers.ideaWhat} for ${answers.ideaFor}.
|
|
|
207
259
|
const cursorRulesPath = path.join(ASSETS_ROOT, 'cursor-rules');
|
|
208
260
|
const fallbackRulesPath = path.join(ROOT_FALLBACK, 'cursor-rules');
|
|
209
261
|
try {
|
|
210
|
-
const ruleFiles = await listWithFallback(cursorRulesPath, fallbackRulesPath);
|
|
211
|
-
const sourcePath = ruleFiles ? cursorRulesPath : fallbackRulesPath;
|
|
262
|
+
const { files: ruleFiles, sourcePath } = await listWithFallback(cursorRulesPath, fallbackRulesPath);
|
|
212
263
|
for (const file of ruleFiles.filter(f => f.endsWith('.mdc'))) {
|
|
213
|
-
await
|
|
264
|
+
await fs.copyFile(
|
|
214
265
|
path.join(sourcePath, file),
|
|
215
|
-
null,
|
|
216
266
|
path.join(rulesDir, file)
|
|
217
267
|
);
|
|
218
268
|
}
|
|
@@ -330,7 +380,7 @@ ${answers.ideaWhat} for ${answers.ideaFor}.
|
|
|
330
380
|
console.log(chalk.blue(` ${githubWebUrl()}`));
|
|
331
381
|
} catch (error) {
|
|
332
382
|
spinner.fail(chalk.red('Failed to create project'));
|
|
333
|
-
console.error(error);
|
|
383
|
+
console.error(`[init] ${error?.message ?? error}`);
|
|
334
384
|
process.exit(1);
|
|
335
385
|
}
|
|
336
386
|
});
|