@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.
package/README.md CHANGED
@@ -89,7 +89,7 @@ vibecheck reality --url http://localhost:3000
89
89
  |------|-------|----------|
90
90
  | FREE | $0 | init --local, scan, ship (static), doctor, polish, report (HTML/MD), context, guard, checkpoint (basic) |
91
91
  | STARTER | $39/mo | + init --connect, scan --autofix, report (SARIF/CSV), mcp, reality (basic) |
92
- | PRO | $99/mo | + prove, fix --apply, checkpoint (hallucination), reality (advanced), ai-test |
92
+ | PRO | $49/mo | + prove, fix --apply, checkpoint (hallucination), reality (advanced), ai-test |
93
93
 
94
94
  ## What It Catches
95
95
 
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Enterprise Approve Output - V5 "Mission Control" Format
3
+ * Features:
4
+ * - Dynamic "APPROVE" ASCII Art
5
+ * - Authority Verdict Display
6
+ * - Proof Status
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: APPROVE (Green/Yellow)
53
+ const LOGO_APPROVE = `
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 formatApproveOutput(verdict, options = {}) {
89
+ const {
90
+ action = null,
91
+ authority = 'unknown',
92
+ version = '1.0.0',
93
+ riskLevel = 'UNKNOWN',
94
+ confidence = 0,
95
+ exitCode = 0,
96
+ hardStopsTriggered = [],
97
+ proofs = {},
98
+ analysis = null,
99
+ notes = '',
100
+ timestamp = new Date().toISOString(),
101
+ } = verdict;
102
+
103
+ const actionColor = action === 'PROCEED' ? chalk.green : action === 'DEFER' ? chalk.yellow : chalk.red;
104
+ const actionIcon = action === 'PROCEED' ? '✓' : action === 'DEFER' ? '⚠' : '✗';
105
+ const themeColor = actionColor;
106
+ const statusText = action ? `${action} VERDICT` : 'PENDING';
107
+
108
+ const lines = [];
109
+
110
+ // 1. Render External Header
111
+ lines.push(chalk.cyan + LOGO_VIBECHECK.trim() + chalk.reset);
112
+ lines.push('');
113
+
114
+ // 2. Render Box Top
115
+ lines.push(`${chalk.gray}${BOX.topLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.topRight}${chalk.reset}`);
116
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
117
+
118
+ // 3. Render Hero Logo (APPROVE)
119
+ LOGO_APPROVE.trim().split('\n').filter(l => l.trim().length > 0).forEach(l => {
120
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${themeColor}${padCenter(l.trim(), WIDTH - 2)}${chalk.reset}${chalk.gray}${BOX.vertical}${chalk.reset}`);
121
+ });
122
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
123
+
124
+ // 4. Info Row
125
+ const ver = options.version || 'v3.5.5';
126
+ const infoText = `${chalk.bold}${ver} APPROVE${chalk.reset} :: VERDICT: ${actionColor}${actionIcon} ${action || 'PENDING'}${chalk.reset}`;
127
+ const metaText = `Authority: ${authority} v${version}`;
128
+ const infoLine = ` ${infoText}${' '.repeat(Math.max(1, WIDTH - 6 - stripAnsi(infoText).length - stripAnsi(metaText).length))}${chalk.dim}${metaText}${chalk.reset}`;
129
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${infoLine} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
130
+
131
+ const projectLine = ` Project: ${padRight(path.basename(options.projectPath || process.cwd()), 50)}`;
132
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${projectLine}${' '.repeat(WIDTH - 2 - stripAnsi(projectLine).length)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
133
+
134
+ // 5. Split Pane
135
+ lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(44)}${BOX.teeTop}${BOX.horizontal.repeat(WIDTH - 47)}${BOX.teeLeft}${chalk.reset}`);
136
+
137
+ function printSplitRow(left, right) {
138
+ const leftContent = padRight(left || '', 42);
139
+ const rightContent = padRight(right || '', WIDTH - 48);
140
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${leftContent} ${chalk.gray}${BOX.lightV}${chalk.reset} ${rightContent} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
141
+ }
142
+
143
+ // Row 1: Headers
144
+ printSplitRow(`${chalk.bold}AUTHORITY VERDICT${chalk.reset}`, `${chalk.bold}PROOFS & ANALYSIS${chalk.reset}`);
145
+
146
+ // Row 2: Divider
147
+ 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}`);
148
+
149
+ // Prepare Left Column
150
+ const confidencePercent = Math.round(confidence * 100);
151
+ const leftCol = [
152
+ '',
153
+ ` VERDICT [${actionColor}${actionIcon} ${action || 'N/A'}${chalk.reset}]`,
154
+ ` Action: ${actionColor}${action || 'N/A'}${chalk.reset}`,
155
+ ` Risk Level: ${riskLevel}`,
156
+ ` Confidence: ${chalk.cyan}${confidencePercent}%${chalk.reset}`,
157
+ ` Exit Code: ${exitCode}`,
158
+ '',
159
+ `${chalk.gray}${BOX.lightH.repeat(42)}${chalk.reset}`,
160
+ `${chalk.bold} HARD STOPS${chalk.reset}`,
161
+ '',
162
+ ];
163
+
164
+ if (hardStopsTriggered.length > 0) {
165
+ hardStopsTriggered.slice(0, 3).forEach((stop, i) => {
166
+ const msg = stop.substring(0, 30);
167
+ leftCol.push(` ${chalk.red}✗${chalk.reset} ${msg}`);
168
+ });
169
+ if (hardStopsTriggered.length > 3) {
170
+ leftCol.push(` ${chalk.dim}... ${hardStopsTriggered.length - 3} more${chalk.reset}`);
171
+ }
172
+ } else {
173
+ leftCol.push(` ${chalk.green}✓${chalk.reset} None`);
174
+ }
175
+
176
+ // Prepare Right Column
177
+ const rightCol = [];
178
+ rightCol.push('');
179
+
180
+ // Proofs
181
+ const proofEntries = Object.entries(proofs || {});
182
+ if (proofEntries.length > 0) {
183
+ rightCol.push(`${chalk.bold} PROOFS${chalk.reset}`);
184
+ proofEntries.slice(0, 4).forEach(([key, value]) => {
185
+ const icon = value.startsWith('PASSED') ? chalk.green + '✓' : value.startsWith('FAILED') ? chalk.red + '✗' : chalk.yellow + '○';
186
+ const name = key.substring(0, 20);
187
+ rightCol.push(` ${icon}${chalk.reset} ${name}`);
188
+ });
189
+ if (proofEntries.length > 4) {
190
+ rightCol.push(` ${chalk.dim}... ${proofEntries.length - 4} more${chalk.reset}`);
191
+ }
192
+ rightCol.push('');
193
+ }
194
+
195
+ // Analysis summary
196
+ if (analysis && analysis.summary) {
197
+ const { summary } = analysis;
198
+ rightCol.push(`${chalk.bold} ANALYSIS${chalk.reset}`);
199
+ rightCol.push(` Safe: ${chalk.green}${summary.safeCount || 0}${chalk.reset}`);
200
+ rightCol.push(` Review: ${chalk.yellow}${summary.reviewCount || 0}${chalk.reset}`);
201
+ rightCol.push(` Blocked: ${chalk.red}${summary.blockedCount || 0}${chalk.reset}`);
202
+ if (summary.totalLinesSavings) {
203
+ rightCol.push(` Lines Saved: ${chalk.cyan}${summary.totalLinesSavings}${chalk.reset}`);
204
+ }
205
+ }
206
+
207
+ // Merge Columns
208
+ const maxRows = Math.max(leftCol.length, rightCol.length);
209
+ for (let i = 0; i < maxRows; i++) {
210
+ printSplitRow(leftCol[i] || '', rightCol[i] || '');
211
+ }
212
+
213
+ // 6. Action Footer
214
+ lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.teeLeft}${chalk.reset}`);
215
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.bold}AUTHORITY CONTROL${chalk.reset}${' '.repeat(WIDTH - 19)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
216
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${BOX.lightH.repeat(WIDTH - 4)} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
217
+
218
+ if (action === 'PROCEED') {
219
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.green}[✓] AUTHORITY APPROVED. PROCEED WITH CAUTION.${chalk.reset}${' '.repeat(WIDTH - 48)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
220
+ } else if (action === 'STOP') {
221
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.red}[!] AUTHORITY BLOCKED. DEPLOYMENT HALTED.${chalk.reset}${' '.repeat(WIDTH - 45)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
222
+ } else if (action === 'DEFER') {
223
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.yellow}[⚠] AUTHORITY DEFERRED. REVIEW REQUIRED.${chalk.reset}${' '.repeat(WIDTH - 46)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
224
+ }
225
+
226
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
227
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.dim}${timestamp}${chalk.reset}${' '.repeat(WIDTH - 2 - timestamp.length - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
228
+
229
+ // 7. Box Bottom
230
+ lines.push(`${chalk.gray}${BOX.bottomLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.bottomRight}${chalk.reset}`);
231
+
232
+ return lines.join('\n');
233
+ }
234
+
235
+ module.exports = { formatApproveOutput };
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Enterprise Classify Output - V5 "Mission Control" Format
3
+ * Features:
4
+ * - Dynamic "CLASSIFY" ASCII Art
5
+ * - Inventory Summary
6
+ * - Duplication & Legacy Analysis
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: CLASSIFY (Cyan/Magenta)
53
+ const LOGO_CLASSIFY = `
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 formatClassifyOutput(result, options = {}) {
89
+ const {
90
+ duplicationMap = [],
91
+ legacyMap = [],
92
+ riskClassifications = [],
93
+ summary = {},
94
+ } = result;
95
+
96
+ const { totalFiles = 0, duplicatedFiles = 0, legacyFiles = 0, highRiskFiles = 0, totalDuplicateLines = 0, analysisTimeMs = 0 } = summary;
97
+
98
+ const themeColor = chalk.cyan;
99
+ const statusText = 'INVENTORY COMPLETE';
100
+
101
+ const lines = [];
102
+
103
+ // 1. Render External Header
104
+ lines.push(chalk.cyan + LOGO_VIBECHECK.trim() + chalk.reset);
105
+ lines.push('');
106
+
107
+ // 2. Render Box Top
108
+ lines.push(`${chalk.gray}${BOX.topLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.topRight}${chalk.reset}`);
109
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
110
+
111
+ // 3. Render Hero Logo (CLASSIFY)
112
+ LOGO_CLASSIFY.trim().split('\n').filter(l => l.trim().length > 0).forEach(l => {
113
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${themeColor}${padCenter(l.trim(), WIDTH - 2)}${chalk.reset}${chalk.gray}${BOX.vertical}${chalk.reset}`);
114
+ });
115
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
116
+
117
+ // 4. Info Row
118
+ const version = options.version || 'v3.5.5';
119
+ const infoText = `${chalk.bold}${version} CLASSIFY${chalk.reset} :: STATUS: ${themeColor}${statusText}${chalk.reset}`;
120
+ const metaText = `Files: ${totalFiles} | Time: ${(analysisTimeMs / 1000).toFixed(1)}s`;
121
+ const infoLine = ` ${infoText}${' '.repeat(Math.max(1, WIDTH - 6 - stripAnsi(infoText).length - stripAnsi(metaText).length))}${chalk.dim}${metaText}${chalk.reset}`;
122
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${infoLine} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
123
+
124
+ const projectLine = ` Project: ${padRight(path.basename(options.projectPath || process.cwd()), 50)}`;
125
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${projectLine}${' '.repeat(WIDTH - 2 - stripAnsi(projectLine).length)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
126
+
127
+ // 5. Split Pane
128
+ lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(44)}${BOX.teeTop}${BOX.horizontal.repeat(WIDTH - 47)}${BOX.teeLeft}${chalk.reset}`);
129
+
130
+ function printSplitRow(left, right) {
131
+ const leftContent = padRight(left || '', 42);
132
+ const rightContent = padRight(right || '', WIDTH - 48);
133
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${leftContent} ${chalk.gray}${BOX.lightV}${chalk.reset} ${rightContent} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
134
+ }
135
+
136
+ // Row 1: Headers
137
+ printSplitRow(`${chalk.bold}INVENTORY SUMMARY${chalk.reset}`, `${chalk.bold}RISK ANALYSIS${chalk.reset}`);
138
+
139
+ // Row 2: Divider
140
+ 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}`);
141
+
142
+ // Prepare Left Column
143
+ const leftCol = [
144
+ '',
145
+ ` FILES ANALYZED [${totalFiles}]`,
146
+ ` Total: ${chalk.cyan}${totalFiles}${chalk.reset}`,
147
+ '',
148
+ `${chalk.gray}${BOX.lightH.repeat(42)}${chalk.reset}`,
149
+ `${chalk.bold} DUPLICATIONS${chalk.reset}`,
150
+ '',
151
+ ` Groups: ${chalk.yellow}${duplicationMap.length}${chalk.reset}`,
152
+ ` Files: ${chalk.yellow}${duplicatedFiles}${chalk.reset}`,
153
+ ` Lines: ${chalk.yellow}${totalDuplicateLines}${chalk.reset}`,
154
+ '',
155
+ `${chalk.gray}${BOX.lightH.repeat(42)}${chalk.reset}`,
156
+ `${chalk.bold} LEGACY CODE${chalk.reset}`,
157
+ '',
158
+ ` Files: ${chalk.magenta}${legacyFiles}${chalk.reset}`,
159
+ ];
160
+
161
+ // Prepare Right Column
162
+ const highRisk = riskClassifications.filter(r => r.level === 'HIGH' || r.level === 'CRITICAL');
163
+ const rightCol = [];
164
+ rightCol.push('');
165
+
166
+ rightCol.push(` HIGH RISK [${highRisk.length}]`);
167
+ rightCol.push(` Critical: ${chalk.red}${highRisk.filter(r => r.level === 'CRITICAL').length}${chalk.reset}`);
168
+ rightCol.push(` High: ${chalk.yellow}${highRisk.filter(r => r.level === 'HIGH').length}${chalk.reset}`);
169
+ rightCol.push('');
170
+
171
+ if (highRisk.length > 0) {
172
+ rightCol.push(`${chalk.bold} TOP RISKS${chalk.reset}`);
173
+ highRisk.slice(0, 3).forEach((risk, i) => {
174
+ const levelIcon = risk.level === 'CRITICAL' ? chalk.red + '🔴' : chalk.yellow + '🟠';
175
+ const name = (risk.file || risk.reason || 'Unknown').substring(0, 20);
176
+ rightCol.push(` ${levelIcon}${chalk.reset} ${name}`);
177
+ });
178
+ if (highRisk.length > 3) {
179
+ rightCol.push(` ${chalk.dim}... ${highRisk.length - 3} more${chalk.reset}`);
180
+ }
181
+ }
182
+
183
+ // Merge Columns
184
+ const maxRows = Math.max(leftCol.length, rightCol.length);
185
+ for (let i = 0; i < maxRows; i++) {
186
+ printSplitRow(leftCol[i] || '', rightCol[i] || '');
187
+ }
188
+
189
+ // 6. Action Footer
190
+ lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.teeLeft}${chalk.reset}`);
191
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.bold}AUTHORITY CONTROL${chalk.reset}${' '.repeat(WIDTH - 19)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
192
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${BOX.lightH.repeat(WIDTH - 4)} ${chalk.gray}${BOX.vertical}${chalk.reset}`);
193
+
194
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} ${chalk.dim}This is a read-only inventory. Use 'vibecheck approve' for verdicts.${chalk.reset}${' '.repeat(WIDTH - 65)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
195
+ lines.push(`${chalk.gray}${BOX.vertical}${' '.repeat(WIDTH - 2)}${BOX.vertical}${chalk.reset}`);
196
+ lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset} > ${chalk.cyan}vibecheck approve${chalk.reset} [VERDICT] Get authority verdicts ${chalk.gray}${BOX.vertical}${chalk.reset}`);
197
+
198
+ // 7. Box Bottom
199
+ lines.push(`${chalk.gray}${BOX.bottomLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.bottomRight}${chalk.reset}`);
200
+
201
+ return lines.join('\n');
202
+ }
203
+
204
+ module.exports = { formatClassifyOutput };