@vibecheckai/cli 3.5.5 → 3.6.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.
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Enterprise Doctor Output - V5 "Mission Control" Format
3
+ * Features:
4
+ * - Dynamic "DOCTOR" ASCII Art
5
+ * - Health Check Status
6
+ * - System Diagnostics
7
+ * - Pixel-perfect 80-char alignment
8
+ */
9
+
10
+ const path = require('path');
11
+
12
+ // ANSI Color Helpers
13
+ const ESC = '\x1b';
14
+ const chalk = {
15
+ reset: `${ESC}[0m`,
16
+ bold: `${ESC}[1m`,
17
+ dim: `${ESC}[2m`,
18
+ red: `${ESC}[31m`,
19
+ green: `${ESC}[32m`,
20
+ yellow: `${ESC}[33m`,
21
+ cyan: `${ESC}[36m`,
22
+ magenta: `${ESC}[35m`,
23
+ white: `${ESC}[37m`,
24
+ gray: `${ESC}[90m`,
25
+ };
26
+
27
+ // ═══════════════════════════════════════════════════════════════════════════════
28
+ // CONFIGURATION
29
+ // ═══════════════════════════════════════════════════════════════════════════════
30
+
31
+ const WIDTH = 80;
32
+
33
+ const BOX = {
34
+ topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
35
+ horizontal: '═', vertical: '║',
36
+ teeRight: '╠', teeLeft: '╣', teeTop: '╤', teeBottom: '╧',
37
+ cross: '╪',
38
+ lightH: '─', lightV: '│',
39
+ lightTeeLeft: '├', lightTeeRight: '┤', lightCross: '┼'
40
+ };
41
+
42
+ // EXTERNAL HEADER
43
+ const LOGO_VIBECHECK = `
44
+ ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗
45
+ ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝
46
+ ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝
47
+ ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗
48
+ ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗
49
+ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝
50
+ `;
51
+
52
+ // HERO: DOCTOR (Cyan/Green)
53
+ const LOGO_DOCTOR = `
54
+ ██████╗ ██████╗ ██████╗████████╗ ██████╗ ██████╗
55
+ ██╔══██╗██╔═══██╗██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗
56
+ ██║ ██║██║ ██║██║ ██║ ██║ ██║██████╔╝
57
+ ██║ ██║██║ ██║██║ ██║ ██║ ██║██╔══██╗
58
+ ██████╔╝╚██████╔╝╚██████╗ ██║ ╚██████╔╝██║ ██║
59
+ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
60
+ `;
61
+
62
+ // ═══════════════════════════════════════════════════════════════════════════════
63
+ // UTILITIES
64
+ // ═══════════════════════════════════════════════════════════════════════════════
65
+
66
+ function stripAnsi(str) {
67
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
68
+ }
69
+
70
+ function padCenter(str, width) {
71
+ const visibleLen = stripAnsi(str).length;
72
+ const padding = Math.max(0, width - visibleLen);
73
+ const left = Math.floor(padding / 2);
74
+ const right = padding - left;
75
+ return ' '.repeat(left) + str + ' '.repeat(right);
76
+ }
77
+
78
+ function padRight(str, len) {
79
+ const visibleLen = stripAnsi(str).length;
80
+ const truncated = visibleLen > len ? str.substring(0, len - 3) + '...' : str;
81
+ return truncated + ' '.repeat(Math.max(0, len - visibleLen));
82
+ }
83
+
84
+ // ═══════════════════════════════════════════════════════════════════════════════
85
+ // MAIN FORMATTER
86
+ // ═══════════════════════════════════════════════════════════════════════════════
87
+
88
+ function formatDoctorOutput(result, options = {}) {
89
+ const {
90
+ checks = [],
91
+ summary = {},
92
+ healthy = true,
93
+ errors = [],
94
+ warnings = [],
95
+ } = result;
96
+
97
+ const { total = 0, passed = 0, failed = 0, warnings: warnCount = 0 } = summary;
98
+
99
+ const themeColor = healthy ? chalk.green : chalk.red;
100
+ const statusText = healthy ? 'SYSTEM HEALTHY' : 'ISSUES DETECTED';
101
+
102
+ const lines = [];
103
+
104
+ // 1. Render External Header
105
+ lines.push(chalk.cyan + LOGO_VIBECHECK.trim() + chalk.reset);
106
+ lines.push('');
107
+
108
+ // 2. Render Box Top
109
+ lines.push(`${chalk.gray}${BOX.topLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.topRight}${chalk.reset}`);
110
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
111
+
112
+ // 3. Render Hero Logo (DOCTOR)
113
+ LOGO_DOCTOR.trim().split('\n').filter(l => l.trim().length > 0).forEach(l => {
114
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${themeColor}${padCenter(l.trim(), WIDTH - 2)}${chalk.reset}${chalk.gray}${BOX.vertical}${chalk.reset}`);
115
+ });
116
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
117
+
118
+ // 4. Info Row
119
+ const version = options.version || 'v3.5.5';
120
+ const infoText = `${chalk.bold}${version} DOCTOR${chalk.reset} :: STATUS: ${themeColor}${statusText}${chalk.reset}`;
121
+ const metaText = `Checks: ${total} | Passed: ${passed}`;
122
+ const infoLine = ` ${infoText}${' '.repeat(Math.max(1, WIDTH - 6 - stripAnsi(infoText).length - stripAnsi(metaText).length))}${chalk.dim}${metaText}${chalk.reset}`;
123
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${infoLine} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
124
+
125
+ const projectLine = ` Project: ${padRight(path.basename(options.projectPath || process.cwd()), 50)}`;
126
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${projectLine}${' '.repeat(WIDTH - 2 - stripAnsi(projectLine).length)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
127
+
128
+ // 5. Split Pane
129
+ lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(44)}${BOX.teeTop}${BOX.horizontal.repeat(WIDTH - 47)}${BOX.teeLeft}${chalk.reset}`);
130
+
131
+ function printSplitRow(left, right) {
132
+ const leftContent = padRight(left || '', 42);
133
+ const rightContent = padRight(right || '', WIDTH - 48);
134
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${leftContent} ${chalk.gray}${BOX.lightV}${chalk.reset} ${rightContent} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
135
+ }
136
+
137
+ // Row 1: Headers
138
+ printSplitRow(`${chalk.bold}HEALTH CHECKS${chalk.reset}`, `${chalk.bold}ISSUES & WARNINGS${chalk.reset}`);
139
+
140
+ // Row 2: Divider
141
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${BOX.lightH.repeat(42)} ${chalk.gray}${BOX.lightCross}${chalk.reset} ${BOX.lightH.repeat(WIDTH - 48)} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
142
+
143
+ // Prepare Left Column
144
+ const leftCol = [
145
+ '',
146
+ ` TOTAL CHECKS [${total}]`,
147
+ ` Passed: ${chalk.green}${passed}${chalk.reset}`,
148
+ ` Failed: ${chalk.red}${failed}${chalk.reset}`,
149
+ ` Warnings: ${chalk.yellow}${warnCount}${chalk.reset}`,
150
+ '',
151
+ `${chalk.gray}${BOX.lightH.repeat(42)}${chalk.reset}`,
152
+ `${chalk.bold} CHECK RESULTS${chalk.reset}`,
153
+ '',
154
+ ];
155
+
156
+ // Add check results
157
+ checks.slice(0, 5).forEach((check, i) => {
158
+ const status = check.status === 'pass' ? chalk.green + '✓' : check.status === 'fail' ? chalk.red + '✖' : chalk.yellow + '⚠';
159
+ const name = (check.name || check.category || 'Check').substring(0, 25);
160
+ leftCol.push(` ${status}${chalk.reset} ${name}`);
161
+ });
162
+
163
+ if (checks.length > 5) {
164
+ leftCol.push(` ${chalk.dim}... ${checks.length - 5} more${chalk.reset}`);
165
+ }
166
+
167
+ // Prepare Right Column
168
+ const rightCol = [];
169
+ rightCol.push('');
170
+
171
+ if (errors.length > 0) {
172
+ rightCol.push(`${chalk.bold} ERRORS${chalk.reset}`);
173
+ errors.slice(0, 3).forEach((error, i) => {
174
+ const msg = (error.message || error).substring(0, 25);
175
+ rightCol.push(` ${chalk.red}✖${chalk.reset} ${msg}`);
176
+ });
177
+ if (errors.length > 3) {
178
+ rightCol.push(` ${chalk.dim}... ${errors.length - 3} more${chalk.reset}`);
179
+ }
180
+ rightCol.push('');
181
+ }
182
+
183
+ if (warnings.length > 0) {
184
+ rightCol.push(`${chalk.bold} WARNINGS${chalk.reset}`);
185
+ warnings.slice(0, 3).forEach((warning, i) => {
186
+ const msg = (warning.message || warning).substring(0, 25);
187
+ rightCol.push(` ${chalk.yellow}⚠${chalk.reset} ${msg}`);
188
+ });
189
+ if (warnings.length > 3) {
190
+ rightCol.push(` ${chalk.dim}... ${warnings.length - 3} more${chalk.reset}`);
191
+ }
192
+ }
193
+
194
+ if (errors.length === 0 && warnings.length === 0) {
195
+ rightCol.push(`${chalk.green} [✓] NO ISSUES${chalk.reset}`);
196
+ rightCol.push(` All checks passed.`);
197
+ }
198
+
199
+ // Merge Columns
200
+ const maxRows = Math.max(leftCol.length, rightCol.length);
201
+ for (let i = 0; i < maxRows; i++) {
202
+ printSplitRow(leftCol[i] || '', rightCol[i] || '');
203
+ }
204
+
205
+ // 6. Action Footer
206
+ lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.teeLeft}${chalk.reset}`);
207
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.bold}DIAGNOSTIC CONTROL${chalk.reset}${' '.repeat(WIDTH - 20)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
208
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${BOX.lightH.repeat(WIDTH - 4)} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
209
+
210
+ if (healthy) {
211
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.green}[✓] SYSTEM HEALTHY. ALL CHECKS PASSED.${chalk.reset}${' '.repeat(WIDTH - 40)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
212
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
213
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} > ${chalk.cyan}vibecheck scan${chalk.reset} [SCAN] Run codebase analysis ${chalk.gray}${BOX.vertical}${chalk.reset}`);
214
+ } else {
215
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.red}[!] ISSUES DETECTED. REVIEW REQUIRED.${chalk.reset}${' '.repeat(WIDTH - 40)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
216
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
217
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} > ${chalk.cyan}vibecheck doctor --fix${chalk.reset} [AUTO-FIX] Attempt automatic fixes ${chalk.gray}${BOX.vertical}${chalk.reset}`);
218
+ }
219
+
220
+ // 7. Box Bottom
221
+ lines.push(`${chalk.gray}${BOX.bottomLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.bottomRight}${chalk.reset}`);
222
+
223
+ return lines.join('\n');
224
+ }
225
+
226
+ module.exports = { formatDoctorOutput };
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Enterprise Fix Output - V5 "Mission Control" Format
3
+ * Features:
4
+ * - Dynamic "FIX" ASCII Art
5
+ * - Mission Planning Status
6
+ * - Fix Progress Tracking
7
+ * - Pixel-perfect 80-char alignment
8
+ */
9
+
10
+ const path = require('path');
11
+
12
+ // ANSI Color Helpers
13
+ const ESC = '\x1b';
14
+ const chalk = {
15
+ reset: `${ESC}[0m`,
16
+ bold: `${ESC}[1m`,
17
+ dim: `${ESC}[2m`,
18
+ red: `${ESC}[31m`,
19
+ green: `${ESC}[32m`,
20
+ yellow: `${ESC}[33m`,
21
+ cyan: `${ESC}[36m`,
22
+ magenta: `${ESC}[35m`,
23
+ white: `${ESC}[37m`,
24
+ gray: `${ESC}[90m`,
25
+ };
26
+
27
+ // ═══════════════════════════════════════════════════════════════════════════════
28
+ // CONFIGURATION
29
+ // ═══════════════════════════════════════════════════════════════════════════════
30
+
31
+ const WIDTH = 80;
32
+
33
+ const BOX = {
34
+ topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
35
+ horizontal: '═', vertical: '║',
36
+ teeRight: '╠', teeLeft: '╣', teeTop: '╤', teeBottom: '╧',
37
+ cross: '╪',
38
+ lightH: '─', lightV: '│',
39
+ lightTeeLeft: '├', lightTeeRight: '┤', lightCross: '┼'
40
+ };
41
+
42
+ // EXTERNAL HEADER
43
+ const LOGO_VIBECHECK = `
44
+ ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗
45
+ ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝
46
+ ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝
47
+ ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗
48
+ ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗
49
+ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝
50
+ `;
51
+
52
+ // HERO: FIX (Magenta/Yellow)
53
+ const LOGO_FIX = `
54
+ ███████╗██╗██╗ ██╗
55
+ ██╔════╝██║╚██╗██╔╝
56
+ █████╗ ██║ ╚███╔╝
57
+ ██╔══╝ ██║ ██╔██╗
58
+ ██║ ██║██╔╝ ██╗
59
+ ╚═╝ ╚═╝╚═╝ ╚═╝
60
+ `;
61
+
62
+ // ═══════════════════════════════════════════════════════════════════════════════
63
+ // UTILITIES
64
+ // ═══════════════════════════════════════════════════════════════════════════════
65
+
66
+ function stripAnsi(str) {
67
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
68
+ }
69
+
70
+ function padCenter(str, width) {
71
+ const visibleLen = stripAnsi(str).length;
72
+ const padding = Math.max(0, width - visibleLen);
73
+ const left = Math.floor(padding / 2);
74
+ const right = padding - left;
75
+ return ' '.repeat(left) + str + ' '.repeat(right);
76
+ }
77
+
78
+ function padRight(str, len) {
79
+ const visibleLen = stripAnsi(str).length;
80
+ const truncated = visibleLen > len ? str.substring(0, len - 3) + '...' : str;
81
+ return truncated + ' '.repeat(Math.max(0, len - visibleLen));
82
+ }
83
+
84
+ // ═══════════════════════════════════════════════════════════════════════════════
85
+ // MAIN FORMATTER
86
+ // ═══════════════════════════════════════════════════════════════════════════════
87
+
88
+ function formatFixOutput(result, options = {}) {
89
+ const {
90
+ missions = [],
91
+ verdict = null,
92
+ beforeVerdict = null,
93
+ afterVerdict = null,
94
+ appliedMissions = 0,
95
+ totalMissions = 0,
96
+ duration = 0,
97
+ success = false,
98
+ error = null,
99
+ } = result;
100
+
101
+ const isSuccess = success || (afterVerdict === 'SHIP');
102
+ const themeColor = isSuccess ? chalk.green : error ? chalk.red : chalk.yellow;
103
+ const statusText = isSuccess ? 'MISSIONS COMPLETE' : error ? 'MISSION FAILED' : 'IN PROGRESS';
104
+
105
+ const lines = [];
106
+
107
+ // 1. Render External Header
108
+ lines.push(chalk.cyan + LOGO_VIBECHECK.trim() + chalk.reset);
109
+ lines.push('');
110
+
111
+ // 2. Render Box Top
112
+ lines.push(`${chalk.gray}${BOX.topLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.topRight}${chalk.reset}`);
113
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
114
+
115
+ // 3. Render Hero Logo (FIX)
116
+ LOGO_FIX.trim().split('\n').filter(l => l.trim().length > 0).forEach(l => {
117
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${themeColor}${padCenter(l.trim(), WIDTH - 2)}${chalk.reset}${chalk.gray}${BOX.vertical}${chalk.reset}`);
118
+ });
119
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
120
+
121
+ // 4. Info Row
122
+ const version = options.version || 'v3.5.5';
123
+ const infoText = `${chalk.bold}${version} FIX${chalk.reset} :: STATUS: ${themeColor}${statusText}${chalk.reset}`;
124
+ const metaText = `Missions: ${appliedMissions}/${totalMissions} | Time: ${(duration / 1000).toFixed(1)}s`;
125
+ const infoLine = ` ${infoText}${' '.repeat(Math.max(1, WIDTH - 6 - stripAnsi(infoText).length - stripAnsi(metaText).length))}${chalk.dim}${metaText}${chalk.reset}`;
126
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${infoLine} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
127
+
128
+ const projectLine = ` Project: ${padRight(path.basename(process.cwd()), 50)}`;
129
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${projectLine}${' '.repeat(WIDTH - 2 - stripAnsi(projectLine).length)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
130
+
131
+ // 5. Split Pane
132
+ lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(44)}${BOX.teeTop}${BOX.horizontal.repeat(WIDTH - 47)}${BOX.teeLeft}${chalk.reset}`);
133
+
134
+ function printSplitRow(left, right) {
135
+ const leftContent = padRight(left || '', 42);
136
+ const rightContent = padRight(right || '', WIDTH - 48);
137
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${leftContent} ${chalk.gray}${BOX.lightV}${chalk.reset} ${rightContent} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
138
+ }
139
+
140
+ // Row 1: Headers
141
+ printSplitRow(`${chalk.bold}MISSION STATUS${chalk.reset}`, `${chalk.bold}VERDICT PROGRESSION${chalk.reset}`);
142
+
143
+ // Row 2: Divider
144
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${BOX.lightH.repeat(42)} ${chalk.gray}${BOX.lightCross}${chalk.reset} ${BOX.lightH.repeat(WIDTH - 48)} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
145
+
146
+ // Prepare Left Column
147
+ const progress = totalMissions > 0 ? Math.round((appliedMissions / totalMissions) * 100) : 0;
148
+ const leftCol = [
149
+ '',
150
+ ` PROGRESS [${progress}%]`,
151
+ ` Applied: ${chalk.cyan}${appliedMissions}${chalk.reset}`,
152
+ ` Remaining: ${chalk.yellow}${totalMissions - appliedMissions}${chalk.reset}`,
153
+ '',
154
+ `${chalk.gray}${BOX.lightH.repeat(42)}${chalk.reset}`,
155
+ `${chalk.bold} MODE${chalk.reset}`,
156
+ '',
157
+ ` ${options.apply ? chalk.green + '✓' : chalk.gray + '—'}${chalk.reset} Auto-Apply`,
158
+ ` ${options.promptOnly ? chalk.green + '✓' : chalk.gray + '—'}${chalk.reset} Prompt-Only`,
159
+ ` ${options.loop ? chalk.green + '✓' : chalk.gray + '—'}${chalk.reset} Loop Mode`,
160
+ '',
161
+ `${chalk.gray}${BOX.lightH.repeat(42)}${chalk.reset}`,
162
+ `${chalk.bold} MISSIONS${chalk.reset}`,
163
+ '',
164
+ ];
165
+
166
+ // Add mission list
167
+ missions.slice(0, 5).forEach((mission, i) => {
168
+ const status = i < appliedMissions ? chalk.green + '✓' : chalk.yellow + '○';
169
+ const name = (mission.type || mission.category || 'Mission').substring(0, 25);
170
+ leftCol.push(` ${status}${chalk.reset} ${name}`);
171
+ });
172
+
173
+ if (missions.length > 5) {
174
+ leftCol.push(` ${chalk.dim}... ${missions.length - 5} more${chalk.reset}`);
175
+ }
176
+
177
+ // Prepare Right Column
178
+ const rightCol = [];
179
+ rightCol.push('');
180
+
181
+ if (isSuccess) {
182
+ rightCol.push(`${chalk.green} [✓] SHIP ACHIEVED${chalk.reset}`);
183
+ rightCol.push(` All missions complete.`);
184
+ rightCol.push('');
185
+ rightCol.push(` Before: ${beforeVerdict || 'N/A'}`);
186
+ rightCol.push(` After: ${chalk.green}${afterVerdict || 'SHIP'}${chalk.reset}`);
187
+ } else if (error) {
188
+ rightCol.push(`${chalk.red} [!] ERROR${chalk.reset}`);
189
+ rightCol.push(` Mission failed.`);
190
+ rightCol.push('');
191
+ let msg = error.message || 'Unknown error';
192
+ if (msg.length > 25) msg = msg.substring(0, 25) + '...';
193
+ rightCol.push(` ${chalk.red}${msg}${chalk.reset}`);
194
+ } else {
195
+ rightCol.push(`${chalk.yellow} [○] IN PROGRESS${chalk.reset}`);
196
+ rightCol.push(` Applying fixes...`);
197
+ rightCol.push('');
198
+ rightCol.push(` Before: ${beforeVerdict || 'N/A'}`);
199
+ rightCol.push(` Target: ${chalk.green}SHIP${chalk.reset}`);
200
+ }
201
+
202
+ // Merge Columns
203
+ const maxRows = Math.max(leftCol.length, rightCol.length);
204
+ for (let i = 0; i < maxRows; i++) {
205
+ printSplitRow(leftCol[i] || '', rightCol[i] || '');
206
+ }
207
+
208
+ // 6. Action Footer
209
+ lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.teeLeft}${chalk.reset}`);
210
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.bold}MISSION CONTROL${chalk.reset}${' '.repeat(WIDTH - 18)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
211
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${BOX.lightH.repeat(WIDTH - 4)} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
212
+
213
+ if (isSuccess) {
214
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.green}[✓] ALL SYSTEMS OPERATIONAL.${chalk.reset}${' '.repeat(WIDTH - 32)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
215
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
216
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} > ${chalk.cyan}vibecheck ship${chalk.reset} [VERIFY] Confirm deployment readiness ${chalk.gray}${BOX.vertical}${chalk.reset}`);
217
+ } else {
218
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} > ${chalk.cyan}vibecheck fix --apply${chalk.reset} [CONTINUE] Apply remaining missions ${chalk.gray}${BOX.vertical}${chalk.reset}`);
219
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} > ${chalk.cyan}vibecheck fix --loop${chalk.reset} [AUTO] Loop until SHIP ${chalk.gray}${BOX.vertical}${chalk.reset}`);
220
+ }
221
+
222
+ // 7. Box Bottom
223
+ lines.push(`${chalk.gray}${BOX.bottomLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.bottomRight}${chalk.reset}`);
224
+
225
+ return lines.join('\n');
226
+ }
227
+
228
+ module.exports = { formatFixOutput };
@@ -228,7 +228,7 @@ function computeNextAction(state) {
228
228
  return {
229
229
  action: "badge",
230
230
  command: "vibecheck ship --badge",
231
- dashboardLink: `${DASHBOARD_URL}/runs/${runId}`,
231
+ dashboardLink: runId ? `${DASHBOARD_URL}/runs/${runId}` : undefined,
232
232
  why: "SHIP passed! Generate a verified badge for your README.",
233
233
  timeEstimate: TIME_ESTIMATES.badge,
234
234
  tier: "pro",
@@ -255,7 +255,7 @@ function computeNextAction(state) {
255
255
  return {
256
256
  action: "report",
257
257
  command: "vibecheck report",
258
- dashboardLink: `${DASHBOARD_URL}/runs/${runId}`,
258
+ dashboardLink: runId ? `${DASHBOARD_URL}/runs/${runId}` : undefined,
259
259
  why: "Scan complete. Export report or upgrade to get verdict.",
260
260
  timeEstimate: TIME_ESTIMATES.report,
261
261
  tier: "free",
@@ -328,7 +328,7 @@ function getNextActionForCommand(completedCmd, result = {}, tier = "free") {
328
328
  return {
329
329
  action: "report",
330
330
  command: "vibecheck report",
331
- dashboardLink: `${DASHBOARD_URL}/runs/${runId}`,
331
+ dashboardLink: runId ? `${DASHBOARD_URL}/runs/${runId}` : undefined,
332
332
  why: `Scan complete. Export report.`,
333
333
  timeEstimate: TIME_ESTIMATES.report,
334
334
  tier: "free",
@@ -344,7 +344,7 @@ function getNextActionForCommand(completedCmd, result = {}, tier = "free") {
344
344
  return {
345
345
  action: "badge",
346
346
  command: "vibecheck ship --badge",
347
- dashboardLink: `${DASHBOARD_URL}/runs/${runId}`,
347
+ dashboardLink: runId ? `${DASHBOARD_URL}/runs/${runId}` : undefined,
348
348
  why: "SHIP passed! Generate a verified badge.",
349
349
  timeEstimate: TIME_ESTIMATES.badge,
350
350
  tier: "pro",
@@ -363,7 +363,7 @@ function getNextActionForCommand(completedCmd, result = {}, tier = "free") {
363
363
  return {
364
364
  action: "report",
365
365
  command: "vibecheck report",
366
- dashboardLink: `${DASHBOARD_URL}/runs/${runId}`,
366
+ dashboardLink: runId ? `${DASHBOARD_URL}/runs/${runId}` : undefined,
367
367
  why: "Export detailed report.",
368
368
  timeEstimate: TIME_ESTIMATES.report,
369
369
  tier: "free",