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.
Files changed (61) hide show
  1. package/README.md +84 -122
  2. package/assets/agents/0-orchestration/orchestrator.md +2 -2
  3. package/assets/agents/00-AGENT_INDEX.md +1 -1
  4. package/assets/docs/LAUNCH-POSTS.md +1 -1
  5. package/assets/docs/QUICK-REFERENCE.md +12 -7
  6. package/assets/docs/ROADMAP.md +5 -5
  7. package/assets/docs/VISION-V2.md +1 -1
  8. package/assets/docs/WORKFLOW-DIAGRAMS.md +1 -1
  9. package/assets/hooks/pre-commit +98 -0
  10. package/assets/saas-plan/04-Imp-Template.md +1 -1
  11. package/assets/templates/README.md +1 -1
  12. package/bin/ultra-dex.js +93 -2096
  13. package/lib/commands/advanced.js +471 -0
  14. package/lib/commands/agent-builder.js +226 -0
  15. package/lib/commands/agents.js +101 -47
  16. package/lib/commands/auto-implement.js +68 -0
  17. package/lib/commands/build.js +73 -187
  18. package/lib/commands/ci-monitor.js +84 -0
  19. package/lib/commands/config.js +207 -0
  20. package/lib/commands/dashboard.js +770 -0
  21. package/lib/commands/diff.js +233 -0
  22. package/lib/commands/doctor.js +397 -0
  23. package/lib/commands/export.js +408 -0
  24. package/lib/commands/fix.js +96 -0
  25. package/lib/commands/generate.js +96 -72
  26. package/lib/commands/hooks.js +251 -76
  27. package/lib/commands/init.js +56 -6
  28. package/lib/commands/memory.js +80 -0
  29. package/lib/commands/plan.js +82 -0
  30. package/lib/commands/review.js +34 -5
  31. package/lib/commands/run.js +233 -0
  32. package/lib/commands/serve.js +188 -40
  33. package/lib/commands/state.js +354 -0
  34. package/lib/commands/swarm.js +284 -0
  35. package/lib/commands/sync.js +94 -0
  36. package/lib/commands/team.js +275 -0
  37. package/lib/commands/upgrade.js +190 -0
  38. package/lib/commands/validate.js +34 -0
  39. package/lib/commands/verify.js +81 -0
  40. package/lib/commands/watch.js +79 -0
  41. package/lib/mcp/graph.js +92 -0
  42. package/lib/mcp/memory.js +95 -0
  43. package/lib/mcp/resources.js +152 -0
  44. package/lib/mcp/server.js +34 -0
  45. package/lib/mcp/tools.js +481 -0
  46. package/lib/mcp/websocket.js +117 -0
  47. package/lib/providers/index.js +49 -4
  48. package/lib/providers/ollama.js +136 -0
  49. package/lib/providers/router.js +63 -0
  50. package/lib/quality/scanner.js +128 -0
  51. package/lib/swarm/coordinator.js +97 -0
  52. package/lib/swarm/index.js +598 -0
  53. package/lib/swarm/protocol.js +677 -0
  54. package/lib/swarm/tiers.js +485 -0
  55. package/lib/templates/context.js +2 -2
  56. package/lib/templates/custom-agent.md +10 -0
  57. package/lib/utils/fallback.js +4 -2
  58. package/lib/utils/files.js +7 -34
  59. package/lib/utils/graph.js +108 -0
  60. package/lib/utils/sync.js +216 -0
  61. package/package.json +22 -13
@@ -1,56 +1,204 @@
1
1
  import chalk from 'chalk';
2
- import fs from 'fs/promises';
3
2
  import http from 'http';
4
- import { validateSafePath } from '../utils/validation.js';
5
-
6
- async function readFileSafe(filePath, label) {
7
- try {
8
- const content = await fs.readFile(filePath, 'utf-8');
9
- return { label, content };
10
- } catch {
11
- return { label, content: '' };
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('Serve Ultra-Dex context over HTTP (MCP-compatible)')
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
- const port = Number.parseInt(options.port, 10);
22
- if (Number.isNaN(port)) {
23
- console.log(chalk.red('Invalid port. Use a numeric value.'));
24
- process.exit(1);
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
- const server = http.createServer(async (req, res) => {
28
- if (!req.url || req.url === '/') {
29
- res.writeHead(200, { 'Content-Type': 'text/plain' });
30
- res.end('Ultra-Dex MCP Server\n');
31
- return;
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
- if (req.url === '/context') {
35
- const [context, plan, quickStart] = await Promise.all([
36
- readFileSafe('CONTEXT.md', 'CONTEXT.md'),
37
- readFileSafe('IMPLEMENTATION-PLAN.md', 'IMPLEMENTATION-PLAN.md'),
38
- readFileSafe('QUICK-START.md', 'QUICK-START.md'),
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
- res.writeHead(200, { 'Content-Type': 'application/json' });
42
- res.end(JSON.stringify({ files: [context, plan, quickStart] }));
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
- res.writeHead(404, { 'Content-Type': 'application/json' });
47
- res.end(JSON.stringify({ error: 'Not found' }));
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
- server.listen(port, () => {
51
- console.log(chalk.green(`\nāœ… Ultra-Dex MCP server running on http://localhost:${port}`));
52
- console.log(chalk.gray(' GET /context -> CONTEXT.md, IMPLEMENTATION-PLAN.md, QUICK-START.md'));
53
- console.log(chalk.gray(' GET / -> health check\n'));
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
+ };