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
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// cli/lib/commands/export.js
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
5
|
+
import { join, basename, dirname, extname } from 'path';
|
|
6
|
+
|
|
7
|
+
export async function exportCommand(options) {
|
|
8
|
+
const format = options.format || 'json';
|
|
9
|
+
const outputPath = options.output || `ultra-dex-export.${format === 'md' ? 'md' : format}`;
|
|
10
|
+
|
|
11
|
+
console.log(chalk.cyan.bold(`\nš¦ Ultra-Dex Export\n`));
|
|
12
|
+
|
|
13
|
+
const spinner = ora('Collecting project data...').start();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const context = loadContext(options.includeAgents);
|
|
17
|
+
spinner.succeed(`Collected ${Object.keys(context.files).length} files`);
|
|
18
|
+
|
|
19
|
+
const formatSpinner = ora(`Generating ${format.toUpperCase()} output...`).start();
|
|
20
|
+
|
|
21
|
+
let output;
|
|
22
|
+
switch (format) {
|
|
23
|
+
case 'json':
|
|
24
|
+
output = generateJSON(context);
|
|
25
|
+
break;
|
|
26
|
+
case 'html':
|
|
27
|
+
output = generateHTML(context);
|
|
28
|
+
break;
|
|
29
|
+
case 'markdown':
|
|
30
|
+
case 'md':
|
|
31
|
+
output = generateMarkdown(context);
|
|
32
|
+
break;
|
|
33
|
+
case 'pdf':
|
|
34
|
+
formatSpinner.warn('PDF export requires external tools');
|
|
35
|
+
console.log(chalk.gray(' Generating HTML instead. Convert with: '));
|
|
36
|
+
console.log(chalk.gray(' wkhtmltopdf ultra-dex-export.html ultra-dex-export.pdf'));
|
|
37
|
+
output = generateHTML(context, { forPdf: true });
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
formatSpinner.fail(`Unknown format: ${format}`);
|
|
41
|
+
console.log(chalk.yellow(' Supported formats: json, html, markdown (md), pdf'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
writeFileSync(outputPath, output);
|
|
46
|
+
formatSpinner.succeed(`Generated ${format.toUpperCase()} output`);
|
|
47
|
+
|
|
48
|
+
console.log(chalk.green(`\nā
Exported to ${chalk.bold(outputPath)}`));
|
|
49
|
+
console.log(chalk.gray(` Size: ${(output.length / 1024).toFixed(1)} KB`));
|
|
50
|
+
|
|
51
|
+
if (options.includeAgents && context.agents.length > 0) {
|
|
52
|
+
console.log(chalk.gray(` Agents bundled: ${context.agents.length}`));
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
spinner.fail('Export failed');
|
|
56
|
+
console.log(chalk.red(` ${error.message}`));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function loadContext(includeAgents = false) {
|
|
61
|
+
const coreFiles = ['CONTEXT.md', 'IMPLEMENTATION-PLAN.md', 'QUICK-START.md', 'CHECKLIST.md'];
|
|
62
|
+
const context = {
|
|
63
|
+
exportedAt: new Date().toISOString(),
|
|
64
|
+
version: '3.0.0',
|
|
65
|
+
project: basename(process.cwd()),
|
|
66
|
+
files: {},
|
|
67
|
+
state: null,
|
|
68
|
+
agents: []
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Load core documentation files
|
|
72
|
+
coreFiles.forEach(file => {
|
|
73
|
+
const filePath = join(process.cwd(), file);
|
|
74
|
+
if (existsSync(filePath)) {
|
|
75
|
+
context.files[file] = readFileSync(filePath, 'utf-8');
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Load state.json if exists
|
|
80
|
+
const statePath = join(process.cwd(), '.ultra', 'state.json');
|
|
81
|
+
if (existsSync(statePath)) {
|
|
82
|
+
try {
|
|
83
|
+
context.state = JSON.parse(readFileSync(statePath, 'utf-8'));
|
|
84
|
+
} catch { /* invalid state */ }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Load agent prompts if requested
|
|
88
|
+
if (includeAgents) {
|
|
89
|
+
context.agents = loadAgentPrompts();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return context;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function loadAgentPrompts() {
|
|
96
|
+
const agents = [];
|
|
97
|
+
const agentsDir = join(process.cwd(), 'agents');
|
|
98
|
+
|
|
99
|
+
if (!existsSync(agentsDir)) return agents;
|
|
100
|
+
|
|
101
|
+
const walkDir = (dir, category = '') => {
|
|
102
|
+
try {
|
|
103
|
+
const items = readdirSync(dir);
|
|
104
|
+
for (const item of items) {
|
|
105
|
+
const itemPath = join(dir, item);
|
|
106
|
+
const stat = statSync(itemPath);
|
|
107
|
+
|
|
108
|
+
if (stat.isDirectory()) {
|
|
109
|
+
walkDir(itemPath, item);
|
|
110
|
+
} else if (item.endsWith('.md') && !item.startsWith('README') && !item.startsWith('00-')) {
|
|
111
|
+
try {
|
|
112
|
+
const content = readFileSync(itemPath, 'utf-8');
|
|
113
|
+
agents.push({
|
|
114
|
+
name: basename(item, '.md'),
|
|
115
|
+
category: category || 'root',
|
|
116
|
+
path: itemPath.replace(process.cwd(), '.'),
|
|
117
|
+
content
|
|
118
|
+
});
|
|
119
|
+
} catch { /* skip unreadable */ }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch { /* skip inaccessible dirs */ }
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
walkDir(agentsDir);
|
|
126
|
+
return agents;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function generateJSON(context) {
|
|
130
|
+
return JSON.stringify(context, null, 2);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function generateHTML(context, options = {}) {
|
|
134
|
+
const { forPdf = false } = options;
|
|
135
|
+
|
|
136
|
+
const styles = `
|
|
137
|
+
:root {
|
|
138
|
+
--bg-primary: #0d1117;
|
|
139
|
+
--bg-secondary: #161b22;
|
|
140
|
+
--bg-tertiary: #21262d;
|
|
141
|
+
--text-primary: #c9d1d9;
|
|
142
|
+
--text-secondary: #8b949e;
|
|
143
|
+
--accent: #58a6ff;
|
|
144
|
+
--accent-green: #3fb950;
|
|
145
|
+
--accent-yellow: #d29922;
|
|
146
|
+
--border: #30363d;
|
|
147
|
+
}
|
|
148
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
149
|
+
body {
|
|
150
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
151
|
+
background: var(--bg-primary);
|
|
152
|
+
color: var(--text-primary);
|
|
153
|
+
line-height: 1.6;
|
|
154
|
+
padding: 0;
|
|
155
|
+
}
|
|
156
|
+
.container { max-width: 1000px; margin: 0 auto; padding: 40px 20px; }
|
|
157
|
+
header {
|
|
158
|
+
text-align: center;
|
|
159
|
+
padding: 40px 0;
|
|
160
|
+
border-bottom: 1px solid var(--border);
|
|
161
|
+
margin-bottom: 40px;
|
|
162
|
+
}
|
|
163
|
+
header h1 {
|
|
164
|
+
font-size: 2.5rem;
|
|
165
|
+
color: var(--accent);
|
|
166
|
+
margin-bottom: 10px;
|
|
167
|
+
}
|
|
168
|
+
header .meta { color: var(--text-secondary); font-size: 0.9rem; }
|
|
169
|
+
nav {
|
|
170
|
+
background: var(--bg-secondary);
|
|
171
|
+
border: 1px solid var(--border);
|
|
172
|
+
border-radius: 6px;
|
|
173
|
+
padding: 20px;
|
|
174
|
+
margin-bottom: 30px;
|
|
175
|
+
}
|
|
176
|
+
nav h3 { color: var(--accent); margin-bottom: 15px; }
|
|
177
|
+
nav ul { list-style: none; display: flex; flex-wrap: wrap; gap: 10px; }
|
|
178
|
+
nav a {
|
|
179
|
+
color: var(--text-primary);
|
|
180
|
+
text-decoration: none;
|
|
181
|
+
padding: 6px 12px;
|
|
182
|
+
background: var(--bg-tertiary);
|
|
183
|
+
border-radius: 4px;
|
|
184
|
+
font-size: 0.9rem;
|
|
185
|
+
}
|
|
186
|
+
nav a:hover { background: var(--accent); color: var(--bg-primary); }
|
|
187
|
+
section {
|
|
188
|
+
background: var(--bg-secondary);
|
|
189
|
+
border: 1px solid var(--border);
|
|
190
|
+
border-radius: 6px;
|
|
191
|
+
margin-bottom: 30px;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
}
|
|
194
|
+
section h2 {
|
|
195
|
+
background: var(--bg-tertiary);
|
|
196
|
+
padding: 15px 20px;
|
|
197
|
+
font-size: 1.2rem;
|
|
198
|
+
border-bottom: 1px solid var(--border);
|
|
199
|
+
color: var(--accent-green);
|
|
200
|
+
}
|
|
201
|
+
section .content {
|
|
202
|
+
padding: 20px;
|
|
203
|
+
white-space: pre-wrap;
|
|
204
|
+
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
|
205
|
+
font-size: 0.85rem;
|
|
206
|
+
max-height: ${forPdf ? 'none' : '600px'};
|
|
207
|
+
overflow-y: auto;
|
|
208
|
+
}
|
|
209
|
+
.state-summary {
|
|
210
|
+
display: grid;
|
|
211
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
212
|
+
gap: 15px;
|
|
213
|
+
padding: 20px;
|
|
214
|
+
}
|
|
215
|
+
.state-card {
|
|
216
|
+
background: var(--bg-tertiary);
|
|
217
|
+
padding: 15px;
|
|
218
|
+
border-radius: 6px;
|
|
219
|
+
}
|
|
220
|
+
.state-card label {
|
|
221
|
+
color: var(--text-secondary);
|
|
222
|
+
font-size: 0.8rem;
|
|
223
|
+
text-transform: uppercase;
|
|
224
|
+
}
|
|
225
|
+
.state-card .value {
|
|
226
|
+
font-size: 1.5rem;
|
|
227
|
+
color: var(--accent);
|
|
228
|
+
margin-top: 5px;
|
|
229
|
+
}
|
|
230
|
+
.agents-grid {
|
|
231
|
+
display: grid;
|
|
232
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
233
|
+
gap: 15px;
|
|
234
|
+
padding: 20px;
|
|
235
|
+
}
|
|
236
|
+
.agent-card {
|
|
237
|
+
background: var(--bg-tertiary);
|
|
238
|
+
border-radius: 6px;
|
|
239
|
+
overflow: hidden;
|
|
240
|
+
}
|
|
241
|
+
.agent-card h4 {
|
|
242
|
+
padding: 10px 15px;
|
|
243
|
+
background: var(--bg-primary);
|
|
244
|
+
color: var(--accent-yellow);
|
|
245
|
+
font-size: 0.9rem;
|
|
246
|
+
}
|
|
247
|
+
.agent-card .preview {
|
|
248
|
+
padding: 15px;
|
|
249
|
+
font-family: monospace;
|
|
250
|
+
font-size: 0.75rem;
|
|
251
|
+
color: var(--text-secondary);
|
|
252
|
+
max-height: 150px;
|
|
253
|
+
overflow: hidden;
|
|
254
|
+
}
|
|
255
|
+
footer {
|
|
256
|
+
text-align: center;
|
|
257
|
+
padding: 30px;
|
|
258
|
+
color: var(--text-secondary);
|
|
259
|
+
font-size: 0.85rem;
|
|
260
|
+
border-top: 1px solid var(--border);
|
|
261
|
+
}
|
|
262
|
+
@media print {
|
|
263
|
+
body { background: white; color: black; }
|
|
264
|
+
section .content { max-height: none; }
|
|
265
|
+
}
|
|
266
|
+
`;
|
|
267
|
+
|
|
268
|
+
const escapeHtml = (str) => str
|
|
269
|
+
.replace(/&/g, '&')
|
|
270
|
+
.replace(/</g, '<')
|
|
271
|
+
.replace(/>/g, '>');
|
|
272
|
+
|
|
273
|
+
const fileEntries = Object.entries(context.files);
|
|
274
|
+
const toc = fileEntries.map(([file]) =>
|
|
275
|
+
`<li><a href="#${file.replace(/\./g, '-')}">${file}</a></li>`
|
|
276
|
+
).join('');
|
|
277
|
+
|
|
278
|
+
const fileSections = fileEntries.map(([file, content]) => `
|
|
279
|
+
<section id="${file.replace(/\./g, '-')}">
|
|
280
|
+
<h2>š ${file}</h2>
|
|
281
|
+
<div class="content">${escapeHtml(content)}</div>
|
|
282
|
+
</section>
|
|
283
|
+
`).join('');
|
|
284
|
+
|
|
285
|
+
const stateSection = context.state ? `
|
|
286
|
+
<section>
|
|
287
|
+
<h2>š Project State</h2>
|
|
288
|
+
<div class="state-summary">
|
|
289
|
+
<div class="state-card">
|
|
290
|
+
<label>Score</label>
|
|
291
|
+
<div class="value">${context.state.score || 'N/A'}/100</div>
|
|
292
|
+
</div>
|
|
293
|
+
<div class="state-card">
|
|
294
|
+
<label>Mode</label>
|
|
295
|
+
<div class="value">${context.state.project?.mode || 'Standard'}</div>
|
|
296
|
+
</div>
|
|
297
|
+
<div class="state-card">
|
|
298
|
+
<label>Updated</label>
|
|
299
|
+
<div class="value" style="font-size: 0.9rem;">${context.state.updatedAt?.split('T')[0] || 'N/A'}</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</section>
|
|
303
|
+
` : '';
|
|
304
|
+
|
|
305
|
+
const agentsSection = context.agents.length > 0 ? `
|
|
306
|
+
<section>
|
|
307
|
+
<h2>š¤ Bundled Agents (${context.agents.length})</h2>
|
|
308
|
+
<div class="agents-grid">
|
|
309
|
+
${context.agents.map(agent => `
|
|
310
|
+
<div class="agent-card">
|
|
311
|
+
<h4>@${agent.name} <span style="color: var(--text-secondary); font-weight: normal;">(${agent.category})</span></h4>
|
|
312
|
+
<div class="preview">${escapeHtml(agent.content.substring(0, 300))}...</div>
|
|
313
|
+
</div>
|
|
314
|
+
`).join('')}
|
|
315
|
+
</div>
|
|
316
|
+
</section>
|
|
317
|
+
` : '';
|
|
318
|
+
|
|
319
|
+
return `<!DOCTYPE html>
|
|
320
|
+
<html lang="en">
|
|
321
|
+
<head>
|
|
322
|
+
<meta charset="UTF-8">
|
|
323
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
324
|
+
<title>Ultra-Dex Export - ${context.project}</title>
|
|
325
|
+
<style>${styles}</style>
|
|
326
|
+
</head>
|
|
327
|
+
<body>
|
|
328
|
+
<div class="container">
|
|
329
|
+
<header>
|
|
330
|
+
<h1>ā” Ultra-Dex Export</h1>
|
|
331
|
+
<div class="meta">
|
|
332
|
+
Project: <strong>${context.project}</strong> |
|
|
333
|
+
Exported: ${new Date(context.exportedAt).toLocaleString()} |
|
|
334
|
+
Version: ${context.version}
|
|
335
|
+
</div>
|
|
336
|
+
</header>
|
|
337
|
+
|
|
338
|
+
<nav>
|
|
339
|
+
<h3>š Contents</h3>
|
|
340
|
+
<ul>${toc}</ul>
|
|
341
|
+
</nav>
|
|
342
|
+
|
|
343
|
+
${stateSection}
|
|
344
|
+
${fileSections}
|
|
345
|
+
${agentsSection}
|
|
346
|
+
|
|
347
|
+
<footer>
|
|
348
|
+
Generated by Ultra-Dex v${context.version} |
|
|
349
|
+
<a href="https://github.com/Srujan0798/Ultra-Dex" style="color: var(--accent);">GitHub</a>
|
|
350
|
+
</footer>
|
|
351
|
+
</div>
|
|
352
|
+
</body>
|
|
353
|
+
</html>`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function generateMarkdown(context) {
|
|
357
|
+
const lines = [
|
|
358
|
+
`# Ultra-Dex Export`,
|
|
359
|
+
``,
|
|
360
|
+
`> **Project:** ${context.project}`,
|
|
361
|
+
`> **Exported:** ${new Date(context.exportedAt).toLocaleString()}`,
|
|
362
|
+
`> **Version:** ${context.version}`,
|
|
363
|
+
``,
|
|
364
|
+
`---`,
|
|
365
|
+
``
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
// Table of contents
|
|
369
|
+
lines.push(`## Table of Contents`, ``);
|
|
370
|
+
Object.keys(context.files).forEach((file, i) => {
|
|
371
|
+
lines.push(`${i + 1}. [${file}](#${file.toLowerCase().replace(/\./g, '')})`);
|
|
372
|
+
});
|
|
373
|
+
if (context.state) lines.push(`${Object.keys(context.files).length + 1}. [Project State](#project-state)`);
|
|
374
|
+
if (context.agents.length > 0) lines.push(`${Object.keys(context.files).length + 2}. [Agents](#agents)`);
|
|
375
|
+
lines.push(``, `---`, ``);
|
|
376
|
+
|
|
377
|
+
// File contents
|
|
378
|
+
Object.entries(context.files).forEach(([file, content]) => {
|
|
379
|
+
lines.push(`## ${file}`, ``);
|
|
380
|
+
lines.push('```markdown');
|
|
381
|
+
lines.push(content);
|
|
382
|
+
lines.push('```', ``);
|
|
383
|
+
lines.push(`---`, ``);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// State
|
|
387
|
+
if (context.state) {
|
|
388
|
+
lines.push(`## Project State`, ``);
|
|
389
|
+
lines.push('```json');
|
|
390
|
+
lines.push(JSON.stringify(context.state, null, 2));
|
|
391
|
+
lines.push('```', ``);
|
|
392
|
+
lines.push(`---`, ``);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Agents
|
|
396
|
+
if (context.agents.length > 0) {
|
|
397
|
+
lines.push(`## Agents`, ``);
|
|
398
|
+
lines.push(`Bundled ${context.agents.length} agent prompts:`, ``);
|
|
399
|
+
context.agents.forEach(agent => {
|
|
400
|
+
lines.push(`### @${agent.name} (${agent.category})`, ``);
|
|
401
|
+
lines.push('```markdown');
|
|
402
|
+
lines.push(agent.content.substring(0, 500) + (agent.content.length > 500 ? '\n...(truncated)' : ''));
|
|
403
|
+
lines.push('```', ``);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return lines.join('\n');
|
|
408
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ultra-dex fix command
|
|
3
|
+
* Self-Healing: Scans code and applies AI fixes automatically
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { runQualityScan } from '../quality/scanner.js';
|
|
10
|
+
import { createProvider, getDefaultProvider, checkConfiguredProviders } from '../providers/index.js';
|
|
11
|
+
|
|
12
|
+
export function registerFixCommand(program) {
|
|
13
|
+
program
|
|
14
|
+
.command('fix')
|
|
15
|
+
.description('Self-Healing: Scan and fix code quality issues')
|
|
16
|
+
.option('-p, --provider <provider>', 'AI provider')
|
|
17
|
+
.option('-k, --key <apiKey>', 'API key')
|
|
18
|
+
.option('--dry-run', 'Show fixes without applying')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
console.log(chalk.cyan('\nš Ultra-Dex Self-Healing\n'));
|
|
21
|
+
|
|
22
|
+
// Check for API key
|
|
23
|
+
const configured = checkConfiguredProviders();
|
|
24
|
+
const hasProvider = configured.some(p => p.configured) || options.key;
|
|
25
|
+
|
|
26
|
+
if (!hasProvider) {
|
|
27
|
+
console.log(chalk.yellow('ā ļø No AI provider configured.'));
|
|
28
|
+
console.log(chalk.white('Self-healing requires an AI provider.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(chalk.gray('Scanning project...'));
|
|
33
|
+
const results = await runQualityScan(process.cwd());
|
|
34
|
+
|
|
35
|
+
if (results.failed === 0 && results.warnings === 0) {
|
|
36
|
+
console.log(chalk.green('ā
No issues found. System healthy.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(chalk.yellow(`Found ${results.failed} errors and ${results.warnings} warnings.`));
|
|
41
|
+
|
|
42
|
+
const providerId = options.provider || getDefaultProvider();
|
|
43
|
+
const provider = createProvider(providerId, { apiKey: options.key, maxTokens: 4000 });
|
|
44
|
+
|
|
45
|
+
// Group by file
|
|
46
|
+
const issuesByFile = {};
|
|
47
|
+
results.details.forEach(issue => {
|
|
48
|
+
if (!issuesByFile[issue.file]) issuesByFile[issue.file] = [];
|
|
49
|
+
issuesByFile[issue.file].push(issue);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
for (const [file, issues] of Object.entries(issuesByFile)) {
|
|
53
|
+
console.log(chalk.bold(`\nFixing ${file}...`));
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const filePath = path.resolve(process.cwd(), file);
|
|
57
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
58
|
+
|
|
59
|
+
const prompt = `You are an expert code fixer. Fix the following issues in the code file.
|
|
60
|
+
|
|
61
|
+
ISSUES TO FIX:
|
|
62
|
+
${issues.map(i => `- [${i.severity}] ${i.message}`).join('\n')}
|
|
63
|
+
|
|
64
|
+
FILE CONTENT:
|
|
65
|
+
\`\`\`
|
|
66
|
+
${content}
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
Return ONLY the full corrected file content. Do not include markdown code blocks or explanations. Just the code.`;
|
|
70
|
+
|
|
71
|
+
if (options.dryRun) {
|
|
72
|
+
console.log(chalk.gray('Dry run: Skipping AI generation.'));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = await provider.generate('You are a code fixing bot. Output only code.', prompt);
|
|
77
|
+
let fixedCode = result.content.trim();
|
|
78
|
+
|
|
79
|
+
// clean up markdown code blocks if AI added them
|
|
80
|
+
if (fixedCode.startsWith('```')) {
|
|
81
|
+
fixedCode = fixedCode.replace(/^```[a-z]*\n/, '').replace(/\n```$/, '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await fs.writeFile(filePath, fixedCode);
|
|
85
|
+
console.log(chalk.green(`ā Fixed ${issues.length} issues in ${file}`));
|
|
86
|
+
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.log(chalk.red(`ā Failed to fix ${file}: ${err.message}`));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(chalk.green('\n⨠Self-healing complete.'));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default { registerFixCommand };
|