@vibecheckai/cli 3.2.1 → 3.2.2

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,239 @@
1
+ /**
2
+ * Enterprise Ship Output - Fixed Alignment
3
+ * * fixes: Table border misalignment and logo centering.
4
+ */
5
+
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ // ═══════════════════════════════════════════════════════════════════════════════
10
+ // ASCII ART LOGOS
11
+ // ═══════════════════════════════════════════════════════════════════════════════
12
+
13
+ const LOGO_VIBECHECK = `
14
+ █████████ █████ █████ █████ ███████████
15
+ ███░░░░░███░░███ ░░███ ░░███ ░░███░░░░░███
16
+ ░███ ░░░ ░███ ░███ ░███ ░███ ░███ VIBECHECK CLI
17
+ ░░█████████ ░███████████ ░███ ░██████████ ENTERPRISE SUITE
18
+ ░░░░░░░░███ ░███░░░░░███ ░███ ░███░░░░░░ v2.4.0-stable
19
+ ███ ░███ ░███ ░███ ░███ ░███
20
+ ░░█████████ █████ █████ █████ █████ © 2026 Vibecheck Inc.
21
+ ░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░
22
+ `;
23
+
24
+ const LOGO_NOT_SHIP_READY = `
25
+ ███ ███ █████████ █████████ █████ █████ █████ ███████
26
+ ████ ███ ███░░░░░███ ███░░░░░███░░███ ░░███ ░░███ ░░███░░█
27
+ ███░██ ██████ ███ ░███ ░░░ ░███ ░███ ░███ ░███ ░█
28
+ ███ ░██ ██████ ███ ░█████████ ░███████████ ░███ ░██████
29
+ ███ ░████████ ███ ░░░░░░░░███ ░███░░░░░███ ░███ ░███░░
30
+ ███ ░███████ ███ ███ ░███ ░███ ░███ ░███ ░███
31
+ ███ ░████░█████████ ░░█████████ █████ █████ █████ █████
32
+ ░░░ ░░░ ░░░░░░░░░ ░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░
33
+ `;
34
+
35
+ // ═══════════════════════════════════════════════════════════════════════════════
36
+ // CONFIGURATION
37
+ // ═══════════════════════════════════════════════════════════════════════════════
38
+
39
+ const BOX = {
40
+ topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
41
+ horizontal: '═', vertical: '║',
42
+ teeRight: '╠', teeLeft: '╣',
43
+
44
+ // Table Borders (Light)
45
+ tTopLeft: '┌', tTopRight: '┐', tBottomLeft: '└', tBottomRight: '┘',
46
+ tHorizontal: '─', tVertical: '│',
47
+ tTeeTop: '┬', tTeeBottom: '┴', tTee: '┼', tTeeLeft: '├', tTeeRight: '┤'
48
+ };
49
+
50
+ const WIDTH = 76;
51
+ // Column widths for the findings table (Must equal WIDTH - 4 when summed + separators)
52
+ // 10 + 13 + 47 + 2 borders + 2 internal separators = 76? Let's calibrate:
53
+ // Total internal width = WIDTH - 2 (borders) - 2 (padding) = 72
54
+ // Let's use: Col1: 10, Col2: 13, Col3: 45. 10+13+45 = 68. + 4 padding spaces + 2 internal bars = 74.
55
+ // We need exact pixel perfect match.
56
+ // Border: 76. Inner: 74.
57
+ // Row: "│ " + COL1 + " │ " + COL2 + " │ " + COL3 + " │"
58
+ // chars: 1+1+10 + 1+1+1 + 13 + 1+1+1 + 41 + 1+1 = 74.
59
+ // Let's stick to the previous column sizes but FIX the padding math.
60
+ const COL_1 = 10;
61
+ const COL_2 = 13;
62
+ const COL_3 = 41;
63
+
64
+ // ═══════════════════════════════════════════════════════════════════════════════
65
+ // UTILITIES
66
+ // ═══════════════════════════════════════════════════════════════════════════════
67
+
68
+ function padCenter(str, width) {
69
+ const visibleLen = str.length; // Simplified for no-ansi
70
+ const padding = Math.max(0, width - visibleLen);
71
+ const left = Math.floor(padding / 2);
72
+ const right = padding - left;
73
+ return ' '.repeat(left) + str + ' '.repeat(right);
74
+ }
75
+
76
+ function padRight(str, len) {
77
+ // Ensure we don't overflow if string is too long
78
+ const truncated = str.length > len ? str.substring(0, len - 3) + '...' : str;
79
+ return truncated + ' '.repeat(Math.max(0, len - truncated.length));
80
+ }
81
+
82
+ // Fixes the "Wobbly Logo" by padding lines to a block first
83
+ function padLogoBlock(ascii, width) {
84
+ const lines = ascii.trim().split('\n');
85
+ const maxContentWidth = Math.max(...lines.map(l => l.length));
86
+
87
+ return lines.map(line => {
88
+ // 1. Make the line itself consistent width
89
+ const solidLine = line + ' '.repeat(maxContentWidth - line.length);
90
+ // 2. Center that solid block
91
+ return padCenter(solidLine, width);
92
+ });
93
+ }
94
+
95
+ function renderProgressBar(score, width = 20) {
96
+ const filled = Math.round((score / 100) * width);
97
+ return '█'.repeat(filled) + '░'.repeat(width - filled);
98
+ }
99
+
100
+ function getMemoryUsage() {
101
+ const usage = process.memoryUsage();
102
+ return Math.round(usage.heapUsed / 1024 / 1024);
103
+ }
104
+
105
+ // ═══════════════════════════════════════════════════════════════════════════════
106
+ // MAIN OUTPUT FORMATTER
107
+ // ═══════════════════════════════════════════════════════════════════════════════
108
+
109
+ function formatShipOutput(result) {
110
+ const { verdict, score = 0, blockers = [], warnings = [], duration = 0 } = result;
111
+ const canShip = verdict === 'SHIP';
112
+ const heapMB = getMemoryUsage();
113
+
114
+ const lines = [];
115
+
116
+ // 1. OUTER FRAME TOP
117
+ lines.push(BOX.topLeft + BOX.horizontal.repeat(WIDTH - 2) + BOX.topRight);
118
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
119
+
120
+ // 2. LOGO
121
+ const logoSrc = canShip ? LOGO_VIBECHECK : LOGO_NOT_SHIP_READY;
122
+ padLogoBlock(logoSrc, WIDTH - 2).forEach(line => {
123
+ lines.push(BOX.vertical + line + BOX.vertical);
124
+ });
125
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
126
+
127
+ // 3. TELEMETRY
128
+ lines.push(BOX.teeRight + BOX.horizontal.repeat(WIDTH - 2) + BOX.teeLeft);
129
+ const exitTxt = canShip ? '✅ Exit: 0' : '❌ Exit: 1';
130
+ const abortTxt = canShip ? '' : ' 🛑 ABORTED';
131
+ const telemetry = `📡 TELEMETRY │ ⏱ Duration: ${duration}ms │ 📦 Heap: ${heapMB}MB │ ${exitTxt}${abortTxt}`;
132
+ lines.push(BOX.vertical + padCenter(telemetry, WIDTH - 2) + BOX.vertical);
133
+ lines.push(BOX.teeRight + BOX.horizontal.repeat(WIDTH - 2) + BOX.teeLeft);
134
+
135
+ // 4. STATUS & SCORES
136
+ if (canShip) {
137
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
138
+ lines.push(BOX.vertical + padCenter('✅ SHIPMENT CLEARED: INTEGRITY VERIFIED', WIDTH - 2) + BOX.vertical);
139
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
140
+ } else {
141
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
142
+ lines.push(BOX.vertical + padCenter('⛔ SHIPMENT ABORTED: INTEGRITY FAILURE', WIDTH - 2) + BOX.vertical);
143
+ lines.push(BOX.vertical + padCenter('────────────────────────────────────────────────────────────────────', WIDTH - 2) + BOX.vertical);
144
+
145
+ const scoreBar = renderProgressBar(score, 20);
146
+ lines.push(BOX.vertical + padCenter(`VIOLATION SCORE [${scoreBar}] ${score} / 100`, WIDTH - 2) + BOX.vertical);
147
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
148
+
149
+ // 5. THE TABLE (FIXED ALIGNMENT)
150
+ lines.push(BOX.vertical + padCenter('CRITICAL BLOCKERS (VISIBLE TIER)', WIDTH - 2) + BOX.vertical);
151
+
152
+ // Construct Table Borders
153
+ // We need to match: " │ COL1 │ COL2 │ COL3 │ "
154
+ // The padding inside the outer box is 2 spaces.
155
+ // Inner Width = WIDTH - 2 (borders) - 4 (padding) = 70.
156
+ // Our columns: 10 + 13 + 41 = 64.
157
+ // We have 3 separators (3 chars) + 2 outer table borders (2 chars) = 5 chars.
158
+ // Total table width = 69. We have 1 char slack. Let's adjust COL_3 to 42.
159
+
160
+ const C1 = 10, C2 = 13, C3 = 41;
161
+
162
+ const tTop = ` ${BOX.tTopLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTeeTop}${BOX.tHorizontal.repeat(C2)}${BOX.tTeeTop}${BOX.tHorizontal.repeat(C3)}${BOX.tTopRight} `;
163
+ const tMid = ` ${BOX.tTeeLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTee}${BOX.tHorizontal.repeat(C2)}${BOX.tTee}${BOX.tHorizontal.repeat(C3)}${BOX.tTeeRight} `;
164
+ const tBot = ` ${BOX.tBottomLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTeeBottom}${BOX.tHorizontal.repeat(C2)}${BOX.tTeeBottom}${BOX.tHorizontal.repeat(C3)}${BOX.tBottomRight} `;
165
+
166
+ lines.push(BOX.vertical + tTop + BOX.vertical);
167
+
168
+ // Table Header
169
+ const header = ` ${BOX.tVertical}${padRight(' SEVERITY', C1)}${BOX.tVertical}${padRight(' COMPONENT', C2)}${BOX.tVertical}${padRight(' FAILURE CONDITION', C3)}${BOX.tVertical} `;
170
+ lines.push(BOX.vertical + header + BOX.vertical);
171
+ lines.push(BOX.vertical + tMid + BOX.vertical);
172
+
173
+ // Table Rows
174
+ const items = [...blockers, ...warnings].slice(0, 5);
175
+ items.forEach(item => {
176
+ // Fix Emoji Length: '🛑 FATAL' is 8 chars.
177
+ // We manually construct the string to ensure it fits the 10 char column.
178
+ const severity = item.severity === 'block' ? '🛑 FATAL ' : '🟡 WARN '; // 9 visual chars?
179
+ // Note: 🛑 is 2 chars wide visually. " FATAL " is 7. Total 9.
180
+ // If column is 10, we add 1 space.
181
+
182
+ const comp = padRight(' ' + (getComponentName(item.category)), C2);
183
+ const desc = padRight(' ' + (item.title || item.message), C3);
184
+
185
+ // Manual row construction
186
+ const row = ` ${BOX.tVertical}${padRight(severity, C1)}${BOX.tVertical}${comp}${BOX.tVertical}${desc}${BOX.tVertical} `;
187
+ lines.push(BOX.vertical + row + BOX.vertical);
188
+ });
189
+
190
+ lines.push(BOX.vertical + tBot + BOX.vertical);
191
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
192
+
193
+ // 6. LOCKED SECTION
194
+ lines.push(BOX.teeRight + BOX.horizontal.repeat(WIDTH - 2) + BOX.teeLeft);
195
+ lines.push(BOX.vertical + padCenter('🔒 ROOT CAUSE ANALYSIS: ACCESS DENIED', WIDTH - 2) + BOX.vertical);
196
+ lines.push(BOX.vertical + padCenter('────────────────────────────────────────────────────────────────────', WIDTH - 2) + BOX.vertical);
197
+ lines.push(BOX.vertical + padCenter('The specific lines of code causing these breaks are deeper in the', WIDTH - 2) + BOX.vertical);
198
+ lines.push(BOX.vertical + padCenter('dependency graph. Starter/Pro context is required to trace them.', WIDTH - 2) + BOX.vertical);
199
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
200
+
201
+ // Redacted bars
202
+ const bar = '░'.repeat(60);
203
+ lines.push(BOX.vertical + padCenter(bar, WIDTH - 2) + BOX.vertical);
204
+
205
+ const redacted = [
206
+ '░░ [REDACTED] 🔒 Schema definition mismatch in node_modules/@api...',
207
+ '░░ [REDACTED] 🔒 Environment leakage detected in webpack.config...',
208
+ '░░ [REDACTED] 🔒 Route /checkout defined in backend but not exp...'
209
+ ];
210
+
211
+ redacted.forEach(r => {
212
+ lines.push(BOX.vertical + padCenter(r, WIDTH - 2) + BOX.vertical);
213
+ });
214
+
215
+ lines.push(BOX.vertical + padCenter(bar, WIDTH - 2) + BOX.vertical);
216
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
217
+
218
+ lines.push(BOX.vertical + padCenter('👉 RESOLUTION:', WIDTH - 2) + BOX.vertical);
219
+ lines.push(BOX.vertical + padCenter('Run `vibecheck upgrade starter` to reveal root causes.', WIDTH - 2) + BOX.vertical);
220
+ }
221
+
222
+ // 7. BOTTOM FRAME
223
+ lines.push(BOX.vertical + ' '.repeat(WIDTH - 2) + BOX.vertical);
224
+ lines.push(BOX.bottomLeft + BOX.horizontal.repeat(WIDTH - 2) + BOX.bottomRight);
225
+
226
+ return lines.join('\n');
227
+ }
228
+
229
+ function getComponentName(category) {
230
+ const map = {
231
+ 'MissingRoute': 'routes',
232
+ 'EnvContract': 'env_sec',
233
+ 'FakeSuccess': 'ui_flow',
234
+ 'GhostAuth': 'auth',
235
+ };
236
+ return map[category] || 'unknown';
237
+ }
238
+
239
+ module.exports = { formatShipOutput };