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/serve.js
CHANGED
|
@@ -1,56 +1,204 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
2
|
import http from 'http';
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { loadState, generateMarkdown } from './plan.js';
|
|
6
|
+
import { startMcpServer } from '../mcp/server.js';
|
|
7
|
+
import { projectGraph } from '../mcp/graph.js';
|
|
8
|
+
import { UltraDexSocket } from '../mcp/websocket.js';
|
|
9
|
+
import { swarmCommand } from './swarm.js';
|
|
10
|
+
import { glob } from 'glob';
|
|
11
|
+
import { execSync, spawn } from 'child_process';
|
|
14
12
|
|
|
15
13
|
export function registerServeCommand(program) {
|
|
16
14
|
program
|
|
17
15
|
.command('serve')
|
|
18
|
-
.description('
|
|
16
|
+
.description('Start the Ultra-Dex Active Kernel (MCP + Dashboard + API)')
|
|
19
17
|
.option('-p, --port <port>', 'Port to listen on', '3001')
|
|
18
|
+
.option('--stdio', 'Run in Stdio mode (MCP Standard Only)', false)
|
|
20
19
|
.action(async (options) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
if (options.stdio) {
|
|
21
|
+
// Run only MCP Stdio server
|
|
22
|
+
try {
|
|
23
|
+
await startMcpServer();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error("Failed to start MCP Server:", error);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
// Run full Unified Kernel (HTTP + WebSocket + Dashboard + MCP over HTTP)
|
|
30
|
+
await startUnifiedKernel(options.port);
|
|
25
31
|
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
async function getGitInfo() {
|
|
36
|
+
try {
|
|
37
|
+
const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
38
|
+
const lastCommit = execSync('git log -1 --format="%h %s" 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
39
|
+
const status = execSync('git status --porcelain 2>/dev/null', { encoding: 'utf8' });
|
|
40
|
+
const changedFiles = status.split('\n').filter(l => l.trim()).length;
|
|
41
|
+
return { branch, lastCommit, changedFiles };
|
|
42
|
+
} catch {
|
|
43
|
+
return { branch: 'unknown', lastCommit: 'N/A', changedFiles: 0 };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Re-using dashboard HTML generation logic (modularized)
|
|
48
|
+
async function getDashboardHTML() {
|
|
49
|
+
const { generateDashboardHTML } = await import('./dashboard.js');
|
|
50
|
+
const state = await loadState();
|
|
51
|
+
const gitInfo = await getGitInfo();
|
|
52
|
+
await projectGraph.scan();
|
|
53
|
+
const summary = projectGraph.getSummary();
|
|
54
|
+
return generateDashboardHTML(state, gitInfo, { nodes: summary.nodeCount, edges: summary.edgeCount });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function startUnifiedKernel(portStr) {
|
|
58
|
+
const port = Number.parseInt(portStr, 10);
|
|
59
|
+
|
|
60
|
+
console.log(chalk.bold.cyan('\nš Ultra-Dex Active Kernel Starting (GOD MODE)...\n'));
|
|
61
|
+
|
|
62
|
+
// Initialize Graph
|
|
63
|
+
console.log(chalk.gray('š§ Initializing Neural Link (Code Graph)...'));
|
|
64
|
+
try {
|
|
65
|
+
await projectGraph.scan();
|
|
66
|
+
console.log(chalk.green(`ā
Graph loaded: ${projectGraph.nodes.size} nodes`));
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.log(chalk.yellow(`ā ļø Graph init failed: ${e.message}`));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const server = http.createServer(async (req, res) => {
|
|
72
|
+
// CORS headers for local tools
|
|
73
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
74
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
75
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
76
|
+
|
|
77
|
+
if (req.method === 'OPTIONS') {
|
|
78
|
+
res.writeHead(204);
|
|
79
|
+
res.end();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
84
|
+
const pathname = url.pathname;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Dashboard UI
|
|
88
|
+
if (pathname === '/' || pathname === '/dashboard') {
|
|
89
|
+
const html = await getDashboardHTML();
|
|
90
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
91
|
+
res.end(html);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
33
94
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
95
|
+
// Endpoint: /api/info
|
|
96
|
+
if (pathname === '/api/info') {
|
|
97
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
98
|
+
res.end(JSON.stringify({
|
|
99
|
+
name: 'Ultra-Dex Active Kernel',
|
|
100
|
+
version: '2.4.1',
|
|
101
|
+
status: 'online',
|
|
102
|
+
endpoints: ['/api/state', '/api/plan', '/api/context', '/api/graph', '/api/swarm']
|
|
103
|
+
}, null, 2));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Endpoint: /api/graph
|
|
108
|
+
if (pathname === '/api/graph' || pathname === '/graph') {
|
|
109
|
+
const summary = projectGraph.getSummary();
|
|
110
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
111
|
+
res.end(JSON.stringify(summary, null, 2));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Endpoint: /api/state
|
|
116
|
+
if (pathname === '/api/state' || pathname === '/state') {
|
|
117
|
+
const state = await loadState();
|
|
118
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
119
|
+
res.end(JSON.stringify(state, null, 2));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
40
122
|
|
|
41
|
-
|
|
42
|
-
|
|
123
|
+
// Endpoint: /api/swarm (Execute Swarm)
|
|
124
|
+
if ((pathname === '/api/swarm' || pathname === '/swarm') && req.method === 'POST') {
|
|
125
|
+
let body = '';
|
|
126
|
+
req.on('data', chunk => body += chunk);
|
|
127
|
+
req.on('end', async () => {
|
|
128
|
+
try {
|
|
129
|
+
const { task, feature, parallel } = JSON.parse(body);
|
|
130
|
+
const objective = task || feature;
|
|
131
|
+
if (!objective) throw new Error('Task/Feature objective is required');
|
|
132
|
+
|
|
133
|
+
// Run swarm
|
|
134
|
+
swarmCommand(objective, { parallel, dryRun: false }).catch(err => console.error(err));
|
|
135
|
+
|
|
136
|
+
res.writeHead(202, { 'Content-Type': 'application/json' });
|
|
137
|
+
res.end(JSON.stringify({ status: 'accepted', message: 'Swarm started' }));
|
|
138
|
+
} catch (e) {
|
|
139
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
140
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Endpoint: /api/plan
|
|
147
|
+
if (pathname === '/api/plan' || pathname === '/plan') {
|
|
148
|
+
const state = await loadState();
|
|
149
|
+
const markdown = generateMarkdown(state);
|
|
150
|
+
res.writeHead(200, { 'Content-Type': 'text/markdown' });
|
|
151
|
+
res.end(markdown);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// SSE Events for Dashboard
|
|
156
|
+
if (pathname === '/events') {
|
|
157
|
+
res.writeHead(200, {
|
|
158
|
+
'Content-Type': 'text/event-stream',
|
|
159
|
+
'Cache-Control': 'no-cache',
|
|
160
|
+
'Connection': 'keep-alive'
|
|
161
|
+
});
|
|
162
|
+
res.write(`data: ${JSON.stringify({ type: 'log', message: 'Connected to Active Kernel' })}\n\n`);
|
|
163
|
+
// We'd need to manage clients here if we wanted to push updates
|
|
43
164
|
return;
|
|
44
|
-
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
168
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
169
|
+
|
|
170
|
+
} catch (error) {
|
|
171
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
172
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const wss = new UltraDexSocket(server);
|
|
45
177
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
178
|
+
server.listen(port, () => {
|
|
179
|
+
console.log(chalk.green(`ā
Unified Kernel active at http://localhost:${port}`));
|
|
180
|
+
console.log(chalk.gray(` ⢠Dashboard: http://localhost:${port}/`));
|
|
181
|
+
console.log(chalk.gray(` ⢠MCP API: http://localhost:${port}/api/info`));
|
|
182
|
+
|
|
183
|
+
console.log(chalk.bold.magenta('\nš AI Tool Integration:'));
|
|
184
|
+
console.log(chalk.white(' Cursor IDE: '));
|
|
185
|
+
console.log(chalk.cyan(` URL: http://localhost:${port}/api/info`));
|
|
186
|
+
console.log(chalk.white(' Claude Desktop:'));
|
|
187
|
+
console.log(chalk.cyan(` Run "ultra-dex config --mcp" to register.`));
|
|
49
188
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
189
|
+
// Auto-Pilot
|
|
190
|
+
fs.watch(process.cwd(), { recursive: true }, async (eventType, filename) => {
|
|
191
|
+
if (!filename || filename.includes('node_modules') || filename.includes('.git') || filename.includes('IMPLEMENTATION-PLAN.md')) return;
|
|
192
|
+
|
|
193
|
+
console.log(chalk.gray(`\nš Change in ${filename}. Synchronizing...`));
|
|
194
|
+
try {
|
|
195
|
+
const state = await loadState();
|
|
196
|
+
if (state) {
|
|
197
|
+
const markdown = generateMarkdown(state);
|
|
198
|
+
await fs.writeFile(path.resolve(process.cwd(), 'IMPLEMENTATION-PLAN.md'), markdown);
|
|
199
|
+
wss.sendStateUpdate(state);
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {}
|
|
55
202
|
});
|
|
56
|
-
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ultra-dex state management commands
|
|
3
|
+
* align, status, watch, pre-commit, state
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import { watch as fsWatch } from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { validateSafePath } from '../utils/validation.js';
|
|
12
|
+
import { buildGraph } from '../utils/graph.js';
|
|
13
|
+
|
|
14
|
+
// State management helpers
|
|
15
|
+
export async function loadState() {
|
|
16
|
+
try {
|
|
17
|
+
const content = await fs.readFile(path.resolve(process.cwd(), '.ultra/state.json'), 'utf8');
|
|
18
|
+
return JSON.parse(content);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function saveState(state) {
|
|
25
|
+
const ultraDir = path.resolve(process.cwd(), '.ultra');
|
|
26
|
+
const statePath = path.resolve(ultraDir, 'state.json');
|
|
27
|
+
try {
|
|
28
|
+
await fs.mkdir(ultraDir, { recursive: true });
|
|
29
|
+
await fs.writeFile(statePath, JSON.stringify(state, null, 2));
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function computeState() {
|
|
37
|
+
// Try to load existing state first to check schema
|
|
38
|
+
const existing = await loadState();
|
|
39
|
+
if (existing && existing.project?.mode === 'GOD_MODE') {
|
|
40
|
+
// In God Mode, we update the timestamp
|
|
41
|
+
existing.updatedAt = new Date().toISOString();
|
|
42
|
+
return existing;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Legacy computation logic
|
|
46
|
+
const state = {
|
|
47
|
+
version: '2.4.0',
|
|
48
|
+
updatedAt: new Date().toISOString(),
|
|
49
|
+
project: { name: path.basename(process.cwd()) },
|
|
50
|
+
files: {},
|
|
51
|
+
sections: { total: 34, completed: 0, list: [] },
|
|
52
|
+
score: 0
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const coreFiles = ['CONTEXT.md', 'IMPLEMENTATION-PLAN.md', 'CHECKLIST.md', 'QUICK-START.md'];
|
|
56
|
+
for (const file of coreFiles) {
|
|
57
|
+
try {
|
|
58
|
+
const stat = await fs.stat(path.resolve(process.cwd(), file));
|
|
59
|
+
state.files[file] = { exists: true, size: stat.size, modified: stat.mtime.toISOString() };
|
|
60
|
+
} catch {
|
|
61
|
+
state.files[file] = { exists: false };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const plan = await fs.readFile(path.resolve(process.cwd(), 'IMPLEMENTATION-PLAN.md'), 'utf8');
|
|
67
|
+
const sectionRegex = /^##\s+(\d+)\.\s+(.+)$/gm;
|
|
68
|
+
let match;
|
|
69
|
+
while ((match = sectionRegex.exec(plan)) !== null) {
|
|
70
|
+
state.sections.list.push({ number: parseInt(match[1]), title: match[2].trim() });
|
|
71
|
+
}
|
|
72
|
+
state.sections.completed = state.sections.list.length;
|
|
73
|
+
} catch { /* no plan */ }
|
|
74
|
+
|
|
75
|
+
const fileScore = Object.values(state.files).filter(f => f.exists).length / coreFiles.length * 40;
|
|
76
|
+
const sectionScore = state.sections.completed / state.sections.total * 60;
|
|
77
|
+
state.score = Math.round(fileScore + sectionScore);
|
|
78
|
+
|
|
79
|
+
return state;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function updateState() {
|
|
83
|
+
const state = await computeState();
|
|
84
|
+
await saveState(state);
|
|
85
|
+
return state;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function registerAlignCommand(program) {
|
|
89
|
+
program
|
|
90
|
+
.command('align')
|
|
91
|
+
.description('Quick alignment score using Code Property Graph')
|
|
92
|
+
.option('--strict', 'Exit with error if score < 70')
|
|
93
|
+
.option('--json', 'Output as JSON')
|
|
94
|
+
.action(async (options) => {
|
|
95
|
+
// 1. Compute Base State
|
|
96
|
+
const state = await computeState();
|
|
97
|
+
|
|
98
|
+
// 2. Compute Graph Score (God Mode)
|
|
99
|
+
let graphScore = 0;
|
|
100
|
+
let graphStats = { nodes: 0, edges: 0 };
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const graph = await buildGraph();
|
|
104
|
+
graphStats = { nodes: graph.nodes.length, edges: graph.edges.length };
|
|
105
|
+
|
|
106
|
+
// Simple heuristic: A healthy project has nodes and edges
|
|
107
|
+
// 10+ nodes = 20 points
|
|
108
|
+
// 10+ edges = 20 points
|
|
109
|
+
const nodesPoints = Math.min(graph.nodes.length * 2, 20);
|
|
110
|
+
const edgesPoints = Math.min(graph.edges.length * 2, 20);
|
|
111
|
+
graphScore = nodesPoints + edgesPoints;
|
|
112
|
+
} catch (e) {
|
|
113
|
+
// Graph failed
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 3. Combine Scores
|
|
117
|
+
// Legacy score (files/plan) is 60% weight, Graph is 40%
|
|
118
|
+
const totalScore = Math.min(Math.round((state.score * 0.6) + graphScore), 100);
|
|
119
|
+
|
|
120
|
+
if (options.json) {
|
|
121
|
+
console.log(JSON.stringify({
|
|
122
|
+
score: totalScore,
|
|
123
|
+
legacyScore: state.score,
|
|
124
|
+
graphScore,
|
|
125
|
+
graphStats,
|
|
126
|
+
files: Object.values(state.files).filter(f => f.exists).length,
|
|
127
|
+
sections: state.sections.completed
|
|
128
|
+
}));
|
|
129
|
+
} else {
|
|
130
|
+
const icon = totalScore >= 80 ? 'ā
' : totalScore >= 50 ? 'ā ļø' : 'ā';
|
|
131
|
+
console.log(`${icon} Alignment: ${totalScore}/100`);
|
|
132
|
+
console.log(chalk.gray(` ⢠Plan/Docs: ${state.score}/100`));
|
|
133
|
+
console.log(chalk.gray(` ⢠Code Graph: ${graphScore}/40 (Nodes: ${graphStats.nodes}, Edges: ${graphStats.edges})`));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (options.strict && totalScore < 70) {
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function registerStatusCommand(program) {
|
|
143
|
+
program
|
|
144
|
+
.command('status')
|
|
145
|
+
.description('Show current project state')
|
|
146
|
+
.option('--refresh', 'Refresh state before showing')
|
|
147
|
+
.option('--json', 'Output raw JSON')
|
|
148
|
+
.action(async (options) => {
|
|
149
|
+
if (options.refresh) {
|
|
150
|
+
const state = await computeState();
|
|
151
|
+
await saveState(state);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let state = await loadState();
|
|
155
|
+
if (!state) {
|
|
156
|
+
console.log(chalk.yellow('\nā ļø No .ultra/state.json found. Generating...\n'));
|
|
157
|
+
state = await computeState();
|
|
158
|
+
await saveState(state);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (options.json) {
|
|
162
|
+
console.log(JSON.stringify(state, null, 2));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(chalk.bold('\nš Ultra-Dex Status\n'));
|
|
167
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
168
|
+
|
|
169
|
+
if (state.project?.mode === 'GOD_MODE') {
|
|
170
|
+
// Render God Mode Status
|
|
171
|
+
console.log(chalk.cyan(` MODE: ${state.project.mode}`));
|
|
172
|
+
console.log(chalk.gray(` Version: ${state.project.version}`));
|
|
173
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
174
|
+
|
|
175
|
+
console.log(chalk.bold('\nš Phases:'));
|
|
176
|
+
state.phases.forEach(phase => {
|
|
177
|
+
const icon = phase.status === 'completed' ? 'ā
' : phase.status === 'in_progress' ? 'š' : 'ā³';
|
|
178
|
+
console.log(` ${icon} ${chalk.bold(phase.name)}`);
|
|
179
|
+
phase.steps.forEach(step => {
|
|
180
|
+
const stepIcon = step.status === 'completed' ? chalk.green('ā') : chalk.gray('-');
|
|
181
|
+
console.log(` ${stepIcon} ${step.task}`);
|
|
182
|
+
});
|
|
183
|
+
console.log('');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
console.log(chalk.bold('š¤ Agents:'));
|
|
187
|
+
state.agents.registry.forEach(agent => {
|
|
188
|
+
const active = state.agents.active.includes(agent) ? chalk.green('(Active)') : '';
|
|
189
|
+
console.log(` ⢠@${agent} ${active}`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
} else {
|
|
193
|
+
// Render Legacy Status
|
|
194
|
+
const scoreColor = state.score >= 80 ? 'green' : state.score >= 50 ? 'yellow' : 'red';
|
|
195
|
+
console.log(chalk[scoreColor](` Score: ${state.score}/100`));
|
|
196
|
+
console.log(chalk.gray(` Updated: ${state.updatedAt}`));
|
|
197
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
198
|
+
|
|
199
|
+
console.log(chalk.bold('\nš Files:'));
|
|
200
|
+
if (state.files) {
|
|
201
|
+
Object.entries(state.files).forEach(([name, info]) => {
|
|
202
|
+
const icon = info.exists ? chalk.green('ā') : chalk.red('ā');
|
|
203
|
+
const size = info.exists ? chalk.gray(` (${info.size} bytes)`) : '';
|
|
204
|
+
console.log(` ${icon} ${name}${size}`);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log(chalk.bold('\nš Sections:'));
|
|
209
|
+
console.log(` ${state.sections.completed}/${state.sections.total} documented`);
|
|
210
|
+
if (state.sections.list.length > 0) {
|
|
211
|
+
const recent = state.sections.list.slice(-3);
|
|
212
|
+
recent.forEach(s => console.log(chalk.gray(` ${s.number}. ${s.title}`)));
|
|
213
|
+
if (state.sections.list.length > 3) {
|
|
214
|
+
console.log(chalk.gray(` ... and ${state.sections.list.length - 3} more`));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
console.log('');
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function registerWatchCommand(program) {
|
|
223
|
+
// This is now handled by watch.js, but keeping here for legacy imports if any.
|
|
224
|
+
// In God Mode, watch.js replaces this.
|
|
225
|
+
// The bin/ultra-dex.js uses watch.js, so this might be dead code or overwritten.
|
|
226
|
+
// I will leave it as is or update it to be safe.
|
|
227
|
+
program
|
|
228
|
+
.command('watch-legacy') // Rename to avoid conflict if both registered
|
|
229
|
+
.action(() => console.log("Use 'ultra-dex watch' instead."));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function registerPreCommitCommand(program) {
|
|
233
|
+
program
|
|
234
|
+
.command('pre-commit')
|
|
235
|
+
.description('Pre-commit hook - verify before commit')
|
|
236
|
+
.option('--install', 'Install git pre-commit hook')
|
|
237
|
+
.option('--scan', 'Include deep code quality scan in hook')
|
|
238
|
+
.option('--ai', 'Run AI-powered quality review on staged changes')
|
|
239
|
+
.option('-d, --dir <directory>', 'Project directory', '.')
|
|
240
|
+
.action(async (options) => {
|
|
241
|
+
const dirValidation = validateSafePath(options.dir, 'Project directory');
|
|
242
|
+
if (dirValidation !== true) {
|
|
243
|
+
console.log(chalk.red(dirValidation));
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const rootDir = path.resolve(options.dir);
|
|
248
|
+
|
|
249
|
+
if (options.install) {
|
|
250
|
+
const hookPath = path.resolve(rootDir, '.git/hooks/pre-commit');
|
|
251
|
+
const scanCmd = options.scan ? '\nnpx ultra-dex validate --scan' : '';
|
|
252
|
+
const aiCmd = options.ai ? '\nnpx ultra-dex pre-commit --ai' : '';
|
|
253
|
+
const hookScript = `#!/bin/sh
|
|
254
|
+
# Ultra-Dex pre-commit hook
|
|
255
|
+
npx ultra-dex align --strict${scanCmd}${aiCmd}
|
|
256
|
+
if [ $? -ne 0 ]; then
|
|
257
|
+
echo "ā Ultra-Dex quality gate failed."
|
|
258
|
+
echo " Run 'ultra-dex review' or 'ultra-dex validate --scan' for details."
|
|
259
|
+
exit 1
|
|
260
|
+
fi
|
|
261
|
+
`;
|
|
262
|
+
try {
|
|
263
|
+
await fs.mkdir(path.dirname(hookPath), { recursive: true });
|
|
264
|
+
await fs.writeFile(hookPath, hookScript, { mode: 0o755 });
|
|
265
|
+
console.log(chalk.green('ā
Pre-commit hook installed!'));
|
|
266
|
+
console.log(chalk.gray(' Commits will be blocked if alignment score < 70 or AI review fails.'));
|
|
267
|
+
} catch (e) {
|
|
268
|
+
console.log(chalk.red('ā Failed to install hook: ' + e.message));
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const state = await loadState();
|
|
274
|
+
|
|
275
|
+
// AI Quality Gate (God Mode)
|
|
276
|
+
if (options.ai) {
|
|
277
|
+
const spinner = (await import('ora')).default('š¤ AI Quality Gate: Reviewing staged changes...').start();
|
|
278
|
+
try {
|
|
279
|
+
const { execSync } = await import('child_process');
|
|
280
|
+
// Get staged changes
|
|
281
|
+
const staged = execSync('git diff --cached --name-only', { encoding: 'utf8' }).split('\n').filter(Boolean);
|
|
282
|
+
if (staged.length === 0) {
|
|
283
|
+
spinner.succeed('No staged changes to review.');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const { createProvider, getDefaultProvider } = await import('../providers/index.js');
|
|
288
|
+
const { runAgentLoop } = await import('./run.js');
|
|
289
|
+
|
|
290
|
+
const provider = createProvider(getDefaultProvider());
|
|
291
|
+
const reviewResult = await runAgentLoop('reviewer', `Review these staged files for architectural violations (e.g. missing validation, security risks):\n${staged.join('\n')}`, provider, { state });
|
|
292
|
+
|
|
293
|
+
if (reviewResult.toLowerCase().includes('reject') || reviewResult.toLowerCase().includes('blocking violation')) {
|
|
294
|
+
spinner.fail('AI Quality Gate: REJECTED');
|
|
295
|
+
console.log(chalk.red('\nviolations found:'));
|
|
296
|
+
console.log(reviewResult);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
spinner.succeed('AI Quality Gate: PASSED');
|
|
300
|
+
} catch (e) {
|
|
301
|
+
spinner.warn('AI Quality Gate skipped: ' + e.message);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (state && state.project?.mode === 'GOD_MODE') {
|
|
306
|
+
console.log(chalk.green(`ā
Alignment OK: GOD MODE ACTIVE`));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (state && state.score < 70) {
|
|
311
|
+
console.log(chalk.red(`ā BLOCKED: Alignment score ${state.score}/100 (required: 70)`));
|
|
312
|
+
console.log(chalk.yellow(' Run `ultra-dex review` for detailed analysis.'));
|
|
313
|
+
process.exit(1);
|
|
314
|
+
} else {
|
|
315
|
+
console.log(chalk.green(`ā
Alignment OK: ${state ? state.score : 'N/A'}/100`));
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function registerStateCommand(program) {
|
|
321
|
+
program
|
|
322
|
+
.command('state')
|
|
323
|
+
.description('Manage .ultra/state.json')
|
|
324
|
+
.option('--init', 'Initialize .ultra directory')
|
|
325
|
+
.option('--refresh', 'Refresh state from files')
|
|
326
|
+
.action(async (options) => {
|
|
327
|
+
if (options.init || options.refresh) {
|
|
328
|
+
const state = await computeState();
|
|
329
|
+
await saveState(state);
|
|
330
|
+
console.log(chalk.green('ā
State updated'));
|
|
331
|
+
if (state.project?.mode === 'GOD_MODE') {
|
|
332
|
+
console.log(chalk.gray(` Mode: GOD_MODE`));
|
|
333
|
+
} else {
|
|
334
|
+
console.log(chalk.gray(` Score: ${state.score}/100`));
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const state = await loadState();
|
|
340
|
+
if (!state) {
|
|
341
|
+
console.log(chalk.yellow('No .ultra/state.json found. Run `ultra-dex state --init`'));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
console.log(JSON.stringify(state, null, 2));
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export default {
|
|
349
|
+
registerAlignCommand,
|
|
350
|
+
registerStatusCommand,
|
|
351
|
+
registerWatchCommand,
|
|
352
|
+
registerPreCommitCommand,
|
|
353
|
+
registerStateCommand,
|
|
354
|
+
};
|