ultra-dex 1.8.0 → 2.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 +162 -143
- package/bin/ultra-dex.js +1005 -777
- package/lib/commands/agents.js +154 -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 +337 -0
- package/lib/commands/placeholders.js +11 -0
- package/lib/commands/review.js +287 -0
- package/lib/commands/serve.js +56 -0
- package/lib/commands/suggest.js +126 -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 +36 -0
- package/lib/utils/files.js +67 -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/validation.js +34 -0
- package/package.json +17 -3
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Command Utilities
|
|
3
|
+
* Helpers for code-to-plan alignment checking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { extractSection, SECTION_TITLES } from './build-helpers.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Scan directory for code files
|
|
12
|
+
* @param {string} dir - Directory to scan
|
|
13
|
+
* @param {string[]} extensions - File extensions to include
|
|
14
|
+
* @returns {Promise<string[]>} List of file paths
|
|
15
|
+
*/
|
|
16
|
+
export async function scanCodeFiles(dir, extensions = ['.ts', '.tsx', '.js', '.jsx', '.py', '.go']) {
|
|
17
|
+
const files = [];
|
|
18
|
+
|
|
19
|
+
async function scan(currentDir) {
|
|
20
|
+
try {
|
|
21
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
22
|
+
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
25
|
+
|
|
26
|
+
// Skip common non-code directories
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
if (!['node_modules', '.git', '.next', 'dist', 'build', '__pycache__', '.venv'].includes(entry.name)) {
|
|
29
|
+
await scan(fullPath);
|
|
30
|
+
}
|
|
31
|
+
} else if (entry.isFile()) {
|
|
32
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
33
|
+
if (extensions.includes(ext)) {
|
|
34
|
+
files.push(fullPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// Directory not accessible
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await scan(dir);
|
|
44
|
+
return files;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extract code structure from files
|
|
49
|
+
* @param {string[]} files - List of file paths
|
|
50
|
+
* @returns {Promise<Object>} Code structure analysis
|
|
51
|
+
*/
|
|
52
|
+
export async function analyzeCodeStructure(files) {
|
|
53
|
+
const structure = {
|
|
54
|
+
components: [],
|
|
55
|
+
apiRoutes: [],
|
|
56
|
+
models: [],
|
|
57
|
+
services: [],
|
|
58
|
+
tests: [],
|
|
59
|
+
configs: [],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
const relativePath = file;
|
|
64
|
+
const filename = path.basename(file);
|
|
65
|
+
const dir = path.dirname(file);
|
|
66
|
+
|
|
67
|
+
// Categorize files
|
|
68
|
+
if (relativePath.includes('/components/') || relativePath.includes('/Components/')) {
|
|
69
|
+
structure.components.push(relativePath);
|
|
70
|
+
} else if (relativePath.includes('/api/') || relativePath.includes('/routes/')) {
|
|
71
|
+
structure.apiRoutes.push(relativePath);
|
|
72
|
+
} else if (relativePath.includes('/models/') || relativePath.includes('/schema/') || filename.includes('schema')) {
|
|
73
|
+
structure.models.push(relativePath);
|
|
74
|
+
} else if (relativePath.includes('/services/') || relativePath.includes('/lib/')) {
|
|
75
|
+
structure.services.push(relativePath);
|
|
76
|
+
} else if (relativePath.includes('/test') || relativePath.includes('.test.') || relativePath.includes('.spec.')) {
|
|
77
|
+
structure.tests.push(relativePath);
|
|
78
|
+
} else if (filename.includes('config') || filename.includes('.env')) {
|
|
79
|
+
structure.configs.push(relativePath);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return structure;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check alignment between code and plan section
|
|
88
|
+
* @param {string} planContent - Full plan content
|
|
89
|
+
* @param {Object} codeStructure - Analyzed code structure
|
|
90
|
+
* @param {number} sectionNum - Section to check
|
|
91
|
+
* @returns {Object} Alignment result
|
|
92
|
+
*/
|
|
93
|
+
export function checkSectionAlignment(planContent, codeStructure, sectionNum) {
|
|
94
|
+
const section = extractSection(planContent, sectionNum);
|
|
95
|
+
if (!section) {
|
|
96
|
+
return { section: sectionNum, score: 0, status: 'missing', issues: ['Section not found in plan'] };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const result = {
|
|
100
|
+
section: sectionNum,
|
|
101
|
+
title: SECTION_TITLES[sectionNum] || `Section ${sectionNum}`,
|
|
102
|
+
score: 0,
|
|
103
|
+
status: 'unknown',
|
|
104
|
+
issues: [],
|
|
105
|
+
suggestions: [],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Section-specific checks
|
|
109
|
+
switch (sectionNum) {
|
|
110
|
+
case 10: // Data Model
|
|
111
|
+
result.score = checkDataModelAlignment(section, codeStructure);
|
|
112
|
+
break;
|
|
113
|
+
case 11: // API Blueprint
|
|
114
|
+
result.score = checkApiAlignment(section, codeStructure);
|
|
115
|
+
break;
|
|
116
|
+
case 13: // Authentication
|
|
117
|
+
result.score = checkAuthAlignment(section, codeStructure);
|
|
118
|
+
break;
|
|
119
|
+
case 20: // Testing Strategy
|
|
120
|
+
result.score = checkTestingAlignment(section, codeStructure);
|
|
121
|
+
break;
|
|
122
|
+
default:
|
|
123
|
+
result.score = 50; // Default partial score for unchecked sections
|
|
124
|
+
result.status = 'not-verified';
|
|
125
|
+
result.issues.push('Automated verification not available for this section');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Set status based on score
|
|
129
|
+
if (result.score >= 90) {
|
|
130
|
+
result.status = 'complete';
|
|
131
|
+
} else if (result.score >= 70) {
|
|
132
|
+
result.status = 'partial';
|
|
133
|
+
} else if (result.score >= 30) {
|
|
134
|
+
result.status = 'incomplete';
|
|
135
|
+
} else {
|
|
136
|
+
result.status = 'missing';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check data model alignment (Section 10)
|
|
144
|
+
*/
|
|
145
|
+
function checkDataModelAlignment(section, codeStructure) {
|
|
146
|
+
let score = 0;
|
|
147
|
+
|
|
148
|
+
// Check if schema/model files exist
|
|
149
|
+
if (codeStructure.models.length > 0) {
|
|
150
|
+
score += 50;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check for Prisma schema
|
|
154
|
+
const hasPrisma = codeStructure.models.some(f => f.includes('prisma') || f.includes('schema.prisma'));
|
|
155
|
+
if (hasPrisma) {
|
|
156
|
+
score += 30;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check for migrations
|
|
160
|
+
const hasMigrations = codeStructure.models.some(f => f.includes('migration'));
|
|
161
|
+
if (hasMigrations) {
|
|
162
|
+
score += 20;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return Math.min(score, 100);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check API alignment (Section 11)
|
|
170
|
+
*/
|
|
171
|
+
function checkApiAlignment(section, codeStructure) {
|
|
172
|
+
let score = 0;
|
|
173
|
+
|
|
174
|
+
// Check if API routes exist
|
|
175
|
+
if (codeStructure.apiRoutes.length > 0) {
|
|
176
|
+
score += 40;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Count endpoints mentioned in plan vs implemented
|
|
180
|
+
const planEndpoints = (section.match(/(?:GET|POST|PUT|DELETE|PATCH)\s+\/[^\s]+/gi) || []).length;
|
|
181
|
+
const implementedRoutes = codeStructure.apiRoutes.length;
|
|
182
|
+
|
|
183
|
+
if (planEndpoints > 0) {
|
|
184
|
+
const coverage = Math.min(implementedRoutes / planEndpoints, 1);
|
|
185
|
+
score += Math.round(coverage * 60);
|
|
186
|
+
} else {
|
|
187
|
+
score += 30; // No specific endpoints in plan
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return Math.min(score, 100);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Check authentication alignment (Section 13)
|
|
195
|
+
*/
|
|
196
|
+
function checkAuthAlignment(section, codeStructure) {
|
|
197
|
+
let score = 0;
|
|
198
|
+
|
|
199
|
+
// Check for auth-related files
|
|
200
|
+
const authFiles = [...codeStructure.apiRoutes, ...codeStructure.services].filter(f =>
|
|
201
|
+
f.toLowerCase().includes('auth') ||
|
|
202
|
+
f.toLowerCase().includes('login') ||
|
|
203
|
+
f.toLowerCase().includes('session')
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if (authFiles.length > 0) {
|
|
207
|
+
score += 50;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check for NextAuth config
|
|
211
|
+
const hasNextAuth = codeStructure.configs.some(f => f.includes('auth'));
|
|
212
|
+
if (hasNextAuth) {
|
|
213
|
+
score += 30;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Check for middleware
|
|
217
|
+
const hasMiddleware = codeStructure.services.some(f => f.includes('middleware'));
|
|
218
|
+
if (hasMiddleware) {
|
|
219
|
+
score += 20;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return Math.min(score, 100);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Check testing alignment (Section 20)
|
|
227
|
+
*/
|
|
228
|
+
function checkTestingAlignment(section, codeStructure) {
|
|
229
|
+
let score = 0;
|
|
230
|
+
|
|
231
|
+
// Check if test files exist
|
|
232
|
+
if (codeStructure.tests.length > 0) {
|
|
233
|
+
score += 40;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Calculate test coverage ratio (tests vs code files)
|
|
237
|
+
const codeFiles = codeStructure.components.length + codeStructure.apiRoutes.length + codeStructure.services.length;
|
|
238
|
+
if (codeFiles > 0) {
|
|
239
|
+
const testRatio = codeStructure.tests.length / codeFiles;
|
|
240
|
+
score += Math.min(Math.round(testRatio * 100), 60);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return Math.min(score, 100);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Generate alignment report
|
|
248
|
+
* @param {Object[]} results - Section alignment results
|
|
249
|
+
* @returns {Object} Full report
|
|
250
|
+
*/
|
|
251
|
+
export function generateAlignmentReport(results) {
|
|
252
|
+
const totalScore = results.reduce((sum, r) => sum + r.score, 0);
|
|
253
|
+
const avgScore = Math.round(totalScore / results.length);
|
|
254
|
+
|
|
255
|
+
const complete = results.filter(r => r.status === 'complete').length;
|
|
256
|
+
const partial = results.filter(r => r.status === 'partial').length;
|
|
257
|
+
const incomplete = results.filter(r => r.status === 'incomplete').length;
|
|
258
|
+
const missing = results.filter(r => r.status === 'missing').length;
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
overallScore: avgScore,
|
|
262
|
+
grade: getGrade(avgScore),
|
|
263
|
+
summary: {
|
|
264
|
+
complete,
|
|
265
|
+
partial,
|
|
266
|
+
incomplete,
|
|
267
|
+
missing,
|
|
268
|
+
total: results.length,
|
|
269
|
+
},
|
|
270
|
+
sections: results,
|
|
271
|
+
topIssues: results
|
|
272
|
+
.filter(r => r.score < 70)
|
|
273
|
+
.sort((a, b) => a.score - b.score)
|
|
274
|
+
.slice(0, 5)
|
|
275
|
+
.map(r => ({
|
|
276
|
+
section: r.section,
|
|
277
|
+
title: r.title,
|
|
278
|
+
score: r.score,
|
|
279
|
+
issues: r.issues,
|
|
280
|
+
})),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get letter grade from score
|
|
286
|
+
*/
|
|
287
|
+
function getGrade(score) {
|
|
288
|
+
if (score >= 90) return 'A';
|
|
289
|
+
if (score >= 80) return 'B';
|
|
290
|
+
if (score >= 70) return 'C';
|
|
291
|
+
if (score >= 60) return 'D';
|
|
292
|
+
return 'F';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Format report for CLI output
|
|
297
|
+
* @param {Object} report - Alignment report
|
|
298
|
+
* @returns {string} Formatted output
|
|
299
|
+
*/
|
|
300
|
+
export function formatReportForCLI(report) {
|
|
301
|
+
let output = '';
|
|
302
|
+
|
|
303
|
+
output += `\n╔════════════════════════════════════════╗\n`;
|
|
304
|
+
output += `║ Ultra-Dex Alignment Report ║\n`;
|
|
305
|
+
output += `╚════════════════════════════════════════╝\n\n`;
|
|
306
|
+
|
|
307
|
+
output += `Overall Score: ${report.overallScore}% (Grade: ${report.grade})\n\n`;
|
|
308
|
+
|
|
309
|
+
output += `Summary:\n`;
|
|
310
|
+
output += ` ✅ Complete: ${report.summary.complete} sections\n`;
|
|
311
|
+
output += ` ⚠️ Partial: ${report.summary.partial} sections\n`;
|
|
312
|
+
output += ` ❌ Incomplete: ${report.summary.incomplete} sections\n`;
|
|
313
|
+
output += ` ⬜ Missing: ${report.summary.missing} sections\n\n`;
|
|
314
|
+
|
|
315
|
+
if (report.topIssues.length > 0) {
|
|
316
|
+
output += `Top Issues:\n`;
|
|
317
|
+
for (const issue of report.topIssues) {
|
|
318
|
+
output += ` • Section ${issue.section} (${issue.title}): ${issue.score}%\n`;
|
|
319
|
+
for (const i of issue.issues) {
|
|
320
|
+
output += ` - ${i}\n`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return output;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export default {
|
|
329
|
+
scanCodeFiles,
|
|
330
|
+
analyzeCodeStructure,
|
|
331
|
+
checkSectionAlignment,
|
|
332
|
+
generateAlignmentReport,
|
|
333
|
+
formatReportForCLI,
|
|
334
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const PROJECT_NAME_REGEX = /^[a-z0-9-]+$/i;
|
|
2
|
+
|
|
3
|
+
export function validateProjectName(name) {
|
|
4
|
+
if (!name || !name.trim()) {
|
|
5
|
+
return 'Project name is required';
|
|
6
|
+
}
|
|
7
|
+
const trimmed = name.trim();
|
|
8
|
+
if (!PROJECT_NAME_REGEX.test(trimmed)) {
|
|
9
|
+
return 'Project name must use only letters, numbers, and dashes';
|
|
10
|
+
}
|
|
11
|
+
if (trimmed.includes('..') || trimmed.includes('/') || trimmed.includes('\\')) {
|
|
12
|
+
return 'Project name cannot include path separators';
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function validateSafePath(input, label = 'Path') {
|
|
18
|
+
if (!input || !input.trim()) {
|
|
19
|
+
return `${label} is required`;
|
|
20
|
+
}
|
|
21
|
+
const trimmed = input.trim();
|
|
22
|
+
if (trimmed.includes('..')) {
|
|
23
|
+
return `${label} cannot include ".."`;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function assertValidPath(input, label) {
|
|
29
|
+
const result = validateSafePath(input, label);
|
|
30
|
+
if (result !== true) {
|
|
31
|
+
throw new Error(result);
|
|
32
|
+
}
|
|
33
|
+
return input;
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra-dex",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "CLI for Ultra-Dex SaaS Implementation Framework with
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "CLI for Ultra-Dex SaaS Implementation Framework with AI-Powered Plan Generation, Build Mode, and Code Review",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"saas",
|
|
7
7
|
"template",
|
|
8
8
|
"implementation",
|
|
9
9
|
"planning",
|
|
10
10
|
"startup",
|
|
11
|
-
"framework"
|
|
11
|
+
"framework",
|
|
12
|
+
"ai",
|
|
13
|
+
"claude",
|
|
14
|
+
"openai",
|
|
15
|
+
"gemini",
|
|
16
|
+
"orchestration",
|
|
17
|
+
"mcp"
|
|
12
18
|
],
|
|
13
19
|
"author": "Srujan Sai Karna",
|
|
14
20
|
"license": "MIT",
|
|
@@ -22,8 +28,10 @@
|
|
|
22
28
|
},
|
|
23
29
|
"files": [
|
|
24
30
|
"bin",
|
|
31
|
+
"lib",
|
|
25
32
|
"assets"
|
|
26
33
|
],
|
|
34
|
+
"type": "module",
|
|
27
35
|
"engines": {
|
|
28
36
|
"node": ">=18"
|
|
29
37
|
},
|
|
@@ -32,5 +40,11 @@
|
|
|
32
40
|
"commander": "^11.1.0",
|
|
33
41
|
"inquirer": "^9.2.12",
|
|
34
42
|
"ora": "^8.0.1"
|
|
43
|
+
},
|
|
44
|
+
"optionalDependencies": {
|
|
45
|
+
"@anthropic-ai/sdk": "^0.30.0",
|
|
46
|
+
"openai": "^4.70.0",
|
|
47
|
+
"@google/generative-ai": "^0.21.0",
|
|
48
|
+
"dotenv": "^16.4.5"
|
|
35
49
|
}
|
|
36
50
|
}
|