ultra-dex 1.8.0 ā 2.2.1
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 +165 -140
- package/assets/agents/0-orchestration/orchestrator.md +2 -2
- package/assets/docs/QUICK-REFERENCE.md +3 -3
- package/assets/docs/ROADMAP.md +5 -5
- package/assets/docs/WORKFLOW-DIAGRAMS.md +1 -1
- package/assets/templates/README.md +1 -1
- package/bin/ultra-dex.js +27 -1893
- package/lib/commands/agents.js +151 -0
- package/lib/commands/audit.js +135 -0
- package/lib/commands/banner.js +21 -0
- package/lib/commands/build.js +214 -0
- package/lib/commands/examples.js +34 -0
- package/lib/commands/fetch.js +186 -0
- package/lib/commands/generate.js +217 -0
- package/lib/commands/hooks.js +105 -0
- package/lib/commands/init.js +335 -0
- package/lib/commands/placeholders.js +11 -0
- package/lib/commands/review.js +287 -0
- package/lib/commands/serve.js +173 -0
- package/lib/commands/suggest.js +126 -0
- package/lib/commands/sync.js +35 -0
- package/lib/commands/validate.js +140 -0
- package/lib/commands/workflows.js +185 -0
- package/lib/config/paths.js +9 -0
- package/lib/config/urls.js +16 -0
- package/lib/providers/base.js +82 -0
- package/lib/providers/claude.js +177 -0
- package/lib/providers/gemini.js +170 -0
- package/lib/providers/index.js +93 -0
- package/lib/providers/openai.js +163 -0
- package/lib/templates/context.js +26 -0
- package/lib/templates/embedded.js +141 -0
- package/lib/templates/prompts/generate-plan.js +147 -0
- package/lib/templates/prompts/review-code.js +57 -0
- package/lib/templates/prompts/section-prompts.js +275 -0
- package/lib/templates/prompts/system-prompt.md +58 -0
- package/lib/templates/quick-start.js +43 -0
- package/lib/utils/build-helpers.js +257 -0
- package/lib/utils/fallback.js +38 -0
- package/lib/utils/files.js +26 -0
- package/lib/utils/network.js +18 -0
- package/lib/utils/output.js +20 -0
- package/lib/utils/parser.js +155 -0
- package/lib/utils/prompt-builder.js +93 -0
- package/lib/utils/review-helpers.js +334 -0
- package/lib/utils/sync.js +216 -0
- package/lib/utils/validation.js +34 -0
- package/package.json +17 -3
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { GITHUB_RAW_BASE } from '../config/urls.js';
|
|
6
|
+
import { fetchWithRetry } from '../utils/network.js';
|
|
7
|
+
import { validateSafePath } from '../utils/validation.js';
|
|
8
|
+
|
|
9
|
+
async function downloadFile(url, destPath) {
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetchWithRetry(url);
|
|
12
|
+
const content = await response.text();
|
|
13
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
14
|
+
await fs.writeFile(destPath, content);
|
|
15
|
+
return true;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function registerFetchCommand(program) {
|
|
22
|
+
program
|
|
23
|
+
.command('fetch')
|
|
24
|
+
.description('Download all Ultra-Dex assets for offline use')
|
|
25
|
+
.option('-d, --dir <directory>', 'Target directory', '.ultra-dex')
|
|
26
|
+
.option('--agents', 'Fetch only agent prompts')
|
|
27
|
+
.option('--rules', 'Fetch only cursor rules')
|
|
28
|
+
.option('--docs', 'Fetch only documentation')
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
console.log(chalk.cyan('\nš¦ Ultra-Dex Asset Fetcher\n'));
|
|
31
|
+
|
|
32
|
+
const dirValidation = validateSafePath(options.dir, 'Target directory');
|
|
33
|
+
if (dirValidation !== true) {
|
|
34
|
+
console.log(chalk.red(dirValidation));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const targetDir = path.resolve(options.dir);
|
|
39
|
+
const fetchAll = !options.agents && !options.rules && !options.docs;
|
|
40
|
+
|
|
41
|
+
const spinner = ora('Preparing to fetch assets...').start();
|
|
42
|
+
|
|
43
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
let downloaded = 0;
|
|
46
|
+
let failed = 0;
|
|
47
|
+
|
|
48
|
+
if (fetchAll || options.rules) {
|
|
49
|
+
spinner.text = 'Fetching cursor rules...';
|
|
50
|
+
const rulesDir = path.join(targetDir, 'cursor-rules');
|
|
51
|
+
await fs.mkdir(rulesDir, { recursive: true });
|
|
52
|
+
|
|
53
|
+
const ruleFiles = [
|
|
54
|
+
'00-ultra-dex-core.mdc',
|
|
55
|
+
'01-database.mdc',
|
|
56
|
+
'02-api.mdc',
|
|
57
|
+
'03-auth.mdc',
|
|
58
|
+
'04-frontend.mdc',
|
|
59
|
+
'05-payments.mdc',
|
|
60
|
+
'06-testing.mdc',
|
|
61
|
+
'07-security.mdc',
|
|
62
|
+
'08-deployment.mdc',
|
|
63
|
+
'09-error-handling.mdc',
|
|
64
|
+
'10-performance.mdc',
|
|
65
|
+
'11-nextjs-v15.mdc',
|
|
66
|
+
'12-multi-tenancy.mdc',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
for (const file of ruleFiles) {
|
|
70
|
+
const url = `${GITHUB_RAW_BASE}/cursor-rules/${file}`;
|
|
71
|
+
const dest = path.join(rulesDir, file);
|
|
72
|
+
if (await downloadFile(url, dest)) {
|
|
73
|
+
downloaded++;
|
|
74
|
+
} else {
|
|
75
|
+
failed++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await downloadFile(`${GITHUB_RAW_BASE}/cursor-rules/load.sh`, path.join(rulesDir, 'load.sh'));
|
|
80
|
+
try {
|
|
81
|
+
await fs.chmod(path.join(rulesDir, 'load.sh'), '755');
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (fetchAll || options.agents) {
|
|
86
|
+
spinner.text = 'Fetching agent prompts...';
|
|
87
|
+
const agentsDir = path.join(targetDir, 'agents');
|
|
88
|
+
|
|
89
|
+
const agentPaths = [
|
|
90
|
+
'00-AGENT_INDEX.md',
|
|
91
|
+
'README.md',
|
|
92
|
+
'AGENT-INSTRUCTIONS.md',
|
|
93
|
+
'1-leadership/cto.md',
|
|
94
|
+
'1-leadership/planner.md',
|
|
95
|
+
'1-leadership/research.md',
|
|
96
|
+
'2-development/backend.md',
|
|
97
|
+
'2-development/frontend.md',
|
|
98
|
+
'2-development/database.md',
|
|
99
|
+
'3-security/security.md',
|
|
100
|
+
'4-devops/devops.md',
|
|
101
|
+
'5-quality/reviewer.md',
|
|
102
|
+
'5-quality/testing.md',
|
|
103
|
+
'5-quality/debugger.md',
|
|
104
|
+
'6-specialist/performance.md',
|
|
105
|
+
'6-specialist/refactoring.md',
|
|
106
|
+
'6-specialist/documentation.md',
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
for (const agentPath of agentPaths) {
|
|
110
|
+
const url = `${GITHUB_RAW_BASE}/agents/${agentPath}`;
|
|
111
|
+
const dest = path.join(agentsDir, agentPath);
|
|
112
|
+
if (await downloadFile(url, dest)) {
|
|
113
|
+
downloaded++;
|
|
114
|
+
} else {
|
|
115
|
+
failed++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (fetchAll || options.docs) {
|
|
121
|
+
spinner.text = 'Fetching documentation...';
|
|
122
|
+
const docsDir = path.join(targetDir, 'docs');
|
|
123
|
+
|
|
124
|
+
const docFiles = [
|
|
125
|
+
'VERIFICATION.md',
|
|
126
|
+
'BUILD-AUTH-30M.md',
|
|
127
|
+
'QUICK-REFERENCE.md',
|
|
128
|
+
'TROUBLESHOOTING.md',
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
for (const file of docFiles) {
|
|
132
|
+
const url = `${GITHUB_RAW_BASE}/docs/${file}`;
|
|
133
|
+
const dest = path.join(docsDir, file);
|
|
134
|
+
if (await downloadFile(url, dest)) {
|
|
135
|
+
downloaded++;
|
|
136
|
+
} else {
|
|
137
|
+
failed++;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const guidesDir = path.join(targetDir, 'guides');
|
|
142
|
+
const guideFiles = [
|
|
143
|
+
'PROJECT-ORCHESTRATION.md',
|
|
144
|
+
'ADVANCED-WORKFLOWS.md',
|
|
145
|
+
'DATABASE-DECISION-FRAMEWORK.md',
|
|
146
|
+
'ARCHITECTURE-PATTERNS.md',
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
for (const file of guideFiles) {
|
|
150
|
+
const url = `${GITHUB_RAW_BASE}/guides/${file}`;
|
|
151
|
+
const dest = path.join(guidesDir, file);
|
|
152
|
+
if (await downloadFile(url, dest)) {
|
|
153
|
+
downloaded++;
|
|
154
|
+
} else {
|
|
155
|
+
failed++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (failed === 0) {
|
|
161
|
+
spinner.succeed(chalk.green(`Downloaded ${downloaded} files to ${targetDir}`));
|
|
162
|
+
} else {
|
|
163
|
+
spinner.warn(chalk.yellow(`Downloaded ${downloaded} files, ${failed} failed`));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(chalk.bold('\nš Assets downloaded to:\n'));
|
|
167
|
+
if (fetchAll || options.rules) {
|
|
168
|
+
console.log(chalk.gray(` ${targetDir}/cursor-rules/ (12 .mdc files)`));
|
|
169
|
+
}
|
|
170
|
+
if (fetchAll || options.agents) {
|
|
171
|
+
console.log(chalk.gray(` ${targetDir}/agents/ (16 agent prompts)`));
|
|
172
|
+
}
|
|
173
|
+
if (fetchAll || options.docs) {
|
|
174
|
+
console.log(chalk.gray(` ${targetDir}/docs/ (documentation)`));
|
|
175
|
+
console.log(chalk.gray(` ${targetDir}/guides/ (guides)`));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(chalk.bold('\nš” Usage:\n'));
|
|
179
|
+
console.log(chalk.cyan(' # Copy cursor rules to project'));
|
|
180
|
+
console.log(chalk.gray(` cp -r ${targetDir}/cursor-rules .cursor/rules`));
|
|
181
|
+
console.log(chalk.cyan('\n # Copy agents to project'));
|
|
182
|
+
console.log(chalk.gray(` cp -r ${targetDir}/agents .agents`));
|
|
183
|
+
console.log(chalk.cyan('\n # Works offline now!'));
|
|
184
|
+
console.log(chalk.gray(' No GitHub access needed after fetch.\n'));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ultra-dex generate command
|
|
3
|
+
* Generates a full 34-section implementation plan from an idea using AI
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { createProvider, getDefaultProvider, checkConfiguredProviders } from '../providers/index.js';
|
|
12
|
+
import { SYSTEM_PROMPT, generateUserPrompt } from '../templates/prompts/generate-plan.js';
|
|
13
|
+
|
|
14
|
+
export function registerGenerateCommand(program) {
|
|
15
|
+
program
|
|
16
|
+
.command('generate [idea]')
|
|
17
|
+
.description('Generate a full implementation plan from an idea using AI')
|
|
18
|
+
.option('-p, --provider <provider>', 'AI provider (claude, openai, gemini)')
|
|
19
|
+
.option('-m, --model <model>', 'Specific model to use')
|
|
20
|
+
.option('-o, --output <directory>', 'Output directory', '.')
|
|
21
|
+
.option('-k, --key <apiKey>', 'API key (or use environment variable)')
|
|
22
|
+
.option('--stream', 'Stream output in real-time', true)
|
|
23
|
+
.option('--no-stream', 'Disable streaming')
|
|
24
|
+
.action(async (idea, options) => {
|
|
25
|
+
console.log(chalk.cyan('\nš Ultra-Dex Plan Generator\n'));
|
|
26
|
+
|
|
27
|
+
// Check configured providers
|
|
28
|
+
const configured = checkConfiguredProviders();
|
|
29
|
+
const hasProvider = configured.some(p => p.configured) || options.key;
|
|
30
|
+
|
|
31
|
+
if (!hasProvider) {
|
|
32
|
+
console.log(chalk.yellow('ā ļø No AI provider configured.\n'));
|
|
33
|
+
console.log(chalk.white('Set one of these environment variables:'));
|
|
34
|
+
configured.forEach(p => {
|
|
35
|
+
console.log(chalk.gray(` export ${p.envKey}=your-key-here`));
|
|
36
|
+
});
|
|
37
|
+
console.log(chalk.white('\nOr use --key option:'));
|
|
38
|
+
console.log(chalk.gray(' npx ultra-dex generate "your idea" --key sk-...\n'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Get idea if not provided
|
|
43
|
+
if (!idea) {
|
|
44
|
+
const answers = await inquirer.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: 'input',
|
|
47
|
+
name: 'idea',
|
|
48
|
+
message: 'Describe your SaaS idea:',
|
|
49
|
+
validate: input => input.trim().length > 10 || 'Please provide a more detailed description',
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
idea = answers.idea;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Select provider
|
|
56
|
+
const providerId = options.provider || getDefaultProvider();
|
|
57
|
+
if (!providerId) {
|
|
58
|
+
console.log(chalk.red('No provider available. Set an API key.'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(chalk.gray(`Using provider: ${providerId}`));
|
|
63
|
+
console.log(chalk.gray(`Idea: "${idea}"\n`));
|
|
64
|
+
|
|
65
|
+
// Create provider instance
|
|
66
|
+
let provider;
|
|
67
|
+
try {
|
|
68
|
+
provider = createProvider(providerId, {
|
|
69
|
+
apiKey: options.key,
|
|
70
|
+
model: options.model,
|
|
71
|
+
maxTokens: 16000, // Large output for full plan
|
|
72
|
+
});
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.log(chalk.red(`Error: ${err.message}`));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Generate the plan
|
|
79
|
+
const spinner = ora('Generating your implementation plan...').start();
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
let result;
|
|
84
|
+
let planContent = '';
|
|
85
|
+
|
|
86
|
+
if (options.stream) {
|
|
87
|
+
spinner.stop();
|
|
88
|
+
console.log(chalk.cyan('š Generating plan:\n'));
|
|
89
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
90
|
+
|
|
91
|
+
result = await provider.generateStream(
|
|
92
|
+
SYSTEM_PROMPT,
|
|
93
|
+
generateUserPrompt(idea),
|
|
94
|
+
(chunk) => {
|
|
95
|
+
process.stdout.write(chunk);
|
|
96
|
+
planContent += chunk;
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
console.log(chalk.gray('\n' + 'ā'.repeat(60)));
|
|
101
|
+
} else {
|
|
102
|
+
result = await provider.generate(SYSTEM_PROMPT, generateUserPrompt(idea));
|
|
103
|
+
planContent = result.content;
|
|
104
|
+
spinner.succeed('Plan generated!');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Calculate stats
|
|
108
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
109
|
+
const cost = provider.estimateCost(result.usage.inputTokens, result.usage.outputTokens);
|
|
110
|
+
|
|
111
|
+
// Save the plan
|
|
112
|
+
const outputDir = path.resolve(options.output);
|
|
113
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
114
|
+
|
|
115
|
+
const planPath = path.join(outputDir, 'IMPLEMENTATION-PLAN.md');
|
|
116
|
+
const contextPath = path.join(outputDir, 'CONTEXT.md');
|
|
117
|
+
const quickStartPath = path.join(outputDir, 'QUICK-START.md');
|
|
118
|
+
|
|
119
|
+
// Add header to plan
|
|
120
|
+
const header = `# Implementation Plan
|
|
121
|
+
|
|
122
|
+
> Generated by Ultra-Dex on ${new Date().toISOString().split('T')[0]}
|
|
123
|
+
> Idea: "${idea}"
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
`;
|
|
128
|
+
await fs.writeFile(planPath, header + planContent);
|
|
129
|
+
|
|
130
|
+
// Generate CONTEXT.md
|
|
131
|
+
const contextContent = `# Project Context
|
|
132
|
+
|
|
133
|
+
## Overview
|
|
134
|
+
${idea}
|
|
135
|
+
|
|
136
|
+
## Generated
|
|
137
|
+
- Date: ${new Date().toISOString().split('T')[0]}
|
|
138
|
+
- Provider: ${provider.getName()}
|
|
139
|
+
- Model: ${provider.model}
|
|
140
|
+
|
|
141
|
+
## Files
|
|
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
|
+
\`\`\`
|
|
152
|
+
`;
|
|
153
|
+
await fs.writeFile(contextPath, contextContent);
|
|
154
|
+
|
|
155
|
+
// Generate QUICK-START.md
|
|
156
|
+
const quickStartContent = `# Quick Start
|
|
157
|
+
|
|
158
|
+
## Your Idea
|
|
159
|
+
${idea}
|
|
160
|
+
|
|
161
|
+
## Next Steps
|
|
162
|
+
|
|
163
|
+
1. **Review the plan**: Open IMPLEMENTATION-PLAN.md
|
|
164
|
+
2. **Load into AI**: Copy CONTEXT.md to your AI tool
|
|
165
|
+
3. **Start building**: Use \`npx ultra-dex build\` or paste prompts
|
|
166
|
+
|
|
167
|
+
## Useful Commands
|
|
168
|
+
|
|
169
|
+
\`\`\`bash
|
|
170
|
+
npx ultra-dex build # Start AI-assisted build
|
|
171
|
+
npx ultra-dex review # Check code against plan
|
|
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
|
+
\`\`\`
|
|
186
|
+
`;
|
|
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
|
+
|
|
201
|
+
console.log(chalk.cyan('\nš Next steps:'));
|
|
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'));
|
|
205
|
+
|
|
206
|
+
} catch (err) {
|
|
207
|
+
spinner.fail('Generation failed');
|
|
208
|
+
console.log(chalk.red(`\nError: ${err.message}`));
|
|
209
|
+
|
|
210
|
+
if (err.message.includes('API')) {
|
|
211
|
+
console.log(chalk.yellow('\nTip: Check your API key and try again.'));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export default { registerGenerateCommand };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export function registerHooksCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('hooks')
|
|
8
|
+
.description('Set up git hooks for automated verification')
|
|
9
|
+
.option('--remove', 'Remove Ultra-Dex git hooks')
|
|
10
|
+
.action(async (options) => {
|
|
11
|
+
console.log(chalk.cyan('\nšŖ Ultra-Dex Git Hooks Setup\n'));
|
|
12
|
+
|
|
13
|
+
const gitDir = path.join(process.cwd(), '.git');
|
|
14
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
await fs.access(gitDir);
|
|
18
|
+
} catch {
|
|
19
|
+
console.log(chalk.red('ā Not a git repository. Run "git init" first.\n'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
24
|
+
|
|
25
|
+
const preCommitPath = path.join(hooksDir, 'pre-commit');
|
|
26
|
+
|
|
27
|
+
if (options.remove) {
|
|
28
|
+
try {
|
|
29
|
+
const content = await fs.readFile(preCommitPath, 'utf-8');
|
|
30
|
+
if (content.includes('ultra-dex')) {
|
|
31
|
+
await fs.unlink(preCommitPath);
|
|
32
|
+
console.log(chalk.green('ā
Ultra-Dex pre-commit hook removed.\n'));
|
|
33
|
+
} else {
|
|
34
|
+
console.log(chalk.yellow('ā ļø Pre-commit hook exists but is not from Ultra-Dex.\n'));
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
console.log(chalk.gray('No Ultra-Dex hooks found.\n'));
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const preCommitScript = `#!/bin/sh
|
|
43
|
+
# Ultra-Dex Pre-Commit Hook
|
|
44
|
+
# Validates project structure before allowing commits
|
|
45
|
+
# Remove with: npx ultra-dex hooks --remove
|
|
46
|
+
|
|
47
|
+
echo "š Ultra-Dex: Validating project structure..."
|
|
48
|
+
|
|
49
|
+
# Run validation
|
|
50
|
+
npx ultra-dex validate --dir . > /tmp/ultra-dex-validate.log 2>&1
|
|
51
|
+
RESULT=$?
|
|
52
|
+
|
|
53
|
+
if [ $RESULT -ne 0 ]; then
|
|
54
|
+
echo ""
|
|
55
|
+
echo "ā Ultra-Dex validation failed. Commit blocked."
|
|
56
|
+
echo ""
|
|
57
|
+
echo "Run 'npx ultra-dex validate' to see details."
|
|
58
|
+
echo "Fix issues or bypass with: git commit --no-verify"
|
|
59
|
+
echo ""
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Check for required files
|
|
64
|
+
if [ ! -f "QUICK-START.md" ]; then
|
|
65
|
+
echo "ā ļø Warning: QUICK-START.md not found"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if [ ! -f "IMPLEMENTATION-PLAN.md" ]; then
|
|
69
|
+
echo "ā ļø Warning: IMPLEMENTATION-PLAN.md not found"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
echo "ā
Ultra-Dex validation passed."
|
|
73
|
+
exit 0
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const existing = await fs.readFile(preCommitPath, 'utf-8');
|
|
78
|
+
if (existing.includes('ultra-dex')) {
|
|
79
|
+
console.log(chalk.yellow('ā ļø Ultra-Dex pre-commit hook already exists.\n'));
|
|
80
|
+
console.log(chalk.gray(' Use --remove to remove it first.\n'));
|
|
81
|
+
return;
|
|
82
|
+
} else {
|
|
83
|
+
const combined = existing + '\n\n' + preCommitScript;
|
|
84
|
+
await fs.writeFile(preCommitPath, combined);
|
|
85
|
+
await fs.chmod(preCommitPath, '755');
|
|
86
|
+
console.log(chalk.green('ā
Ultra-Dex hook appended to existing pre-commit.\n'));
|
|
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
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(chalk.bold('What this does:\n'));
|
|
95
|
+
console.log(chalk.gray(' ⢠Runs "ultra-dex validate" before each commit'));
|
|
96
|
+
console.log(chalk.gray(' ⢠Blocks commits if required files are missing'));
|
|
97
|
+
console.log(chalk.gray(' ⢠Warns about incomplete sections\n'));
|
|
98
|
+
|
|
99
|
+
console.log(chalk.bold('To bypass (not recommended):\n'));
|
|
100
|
+
console.log(chalk.cyan(' git commit --no-verify\n'));
|
|
101
|
+
|
|
102
|
+
console.log(chalk.bold('To remove:\n'));
|
|
103
|
+
console.log(chalk.cyan(' npx ultra-dex hooks --remove\n'));
|
|
104
|
+
});
|
|
105
|
+
}
|