@vibecheckai/cli 3.2.0 → 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.
Files changed (60) hide show
  1. package/bin/runners/lib/agent-firewall/change-packet/builder.js +214 -0
  2. package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
  3. package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
  4. package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
  5. package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
  6. package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
  7. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
  8. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
  9. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
  10. package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
  11. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
  12. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
  13. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
  14. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
  15. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
  16. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
  17. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
  18. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
  19. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
  20. package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
  21. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
  22. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
  23. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
  24. package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
  25. package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
  26. package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
  27. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
  28. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
  29. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
  30. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
  31. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
  32. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
  33. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
  34. package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
  35. package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
  36. package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
  37. package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
  38. package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
  39. package/bin/runners/lib/analysis-core.js +198 -180
  40. package/bin/runners/lib/analyzers.js +1119 -536
  41. package/bin/runners/lib/cli-output.js +236 -210
  42. package/bin/runners/lib/detectors-v2.js +547 -785
  43. package/bin/runners/lib/fingerprint.js +377 -0
  44. package/bin/runners/lib/route-truth.js +1167 -322
  45. package/bin/runners/lib/scan-output.js +144 -738
  46. package/bin/runners/lib/ship-output-enterprise.js +239 -0
  47. package/bin/runners/lib/terminal-ui.js +188 -770
  48. package/bin/runners/lib/truth.js +1004 -321
  49. package/bin/runners/lib/unified-output.js +162 -158
  50. package/bin/runners/runAgent.js +161 -0
  51. package/bin/runners/runFirewall.js +134 -0
  52. package/bin/runners/runFirewallHook.js +56 -0
  53. package/bin/runners/runScan.js +113 -10
  54. package/bin/runners/runShip.js +7 -8
  55. package/bin/runners/runTruth.js +89 -0
  56. package/mcp-server/agent-firewall-interceptor.js +164 -0
  57. package/mcp-server/index.js +347 -313
  58. package/mcp-server/truth-context.js +131 -90
  59. package/mcp-server/truth-firewall-tools.js +1412 -1045
  60. package/package.json +1 -1
@@ -1,784 +1,190 @@
1
1
  /**
2
- * Scan Output - World-Class Premium Terminal Display
3
- *
2
+ * Enterprise Scan Output - Premium Format
4
3
  * Features:
5
- * - Beautiful ASCII tables with box drawing
6
- * - Color-coded severity with gradients
7
- * - Progress bars and sparklines
8
- * - Category breakdown with visual hierarchy
9
- * - JSON/SARIF export
4
+ * - "SCAN" ASCII Art
5
+ * - Fixed alignment tables
6
+ * - "Redacted" Upsell section for Scan context
10
7
  */
11
8
 
12
- const {
13
- ansi,
14
- colors,
15
- box,
16
- icons,
17
- renderScoreCard,
18
- renderFindingsList,
19
- renderSection,
20
- renderDivider,
21
- renderTable,
22
- formatDuration,
23
- formatNumber,
24
- truncate,
25
- } = require('./terminal-ui');
9
+ const chalk = require('chalk');
26
10
 
27
11
  // ═══════════════════════════════════════════════════════════════════════════════
28
- // ENHANCED COLOR PALETTE
12
+ // CONFIGURATION
29
13
  // ═══════════════════════════════════════════════════════════════════════════════
30
14
 
31
- const palette = {
32
- // Severity gradient
33
- critical: ansi.rgb(255, 50, 50),
34
- criticalBg: ansi.bgRgb(180, 20, 20),
35
- high: ansi.rgb(255, 120, 50),
36
- highBg: ansi.bgRgb(180, 80, 20),
37
- medium: ansi.rgb(255, 200, 50),
38
- mediumBg: ansi.bgRgb(150, 120, 20),
39
- low: ansi.rgb(100, 180, 255),
40
- lowBg: ansi.bgRgb(40, 80, 150),
41
-
42
- // Status colors
43
- pass: ansi.rgb(50, 255, 120),
44
- passBg: ansi.bgRgb(20, 120, 50),
45
- warn: ansi.rgb(255, 200, 50),
46
- warnBg: ansi.bgRgb(150, 120, 20),
47
- fail: ansi.rgb(255, 50, 50),
48
- failBg: ansi.bgRgb(180, 20, 20),
49
-
50
- // UI elements
51
- border: ansi.rgb(70, 70, 90),
52
- borderBright: ansi.rgb(100, 100, 130),
53
- header: ansi.rgb(150, 150, 200),
54
- muted: ansi.rgb(100, 100, 120),
55
- accent: ansi.rgb(100, 200, 255),
56
- highlight: ansi.rgb(255, 220, 100),
57
-
58
- // Category colors (rainbow)
59
- cat1: ansi.rgb(255, 100, 100),
60
- cat2: ansi.rgb(255, 150, 100),
61
- cat3: ansi.rgb(255, 200, 100),
62
- cat4: ansi.rgb(200, 255, 100),
63
- cat5: ansi.rgb(100, 255, 150),
64
- cat6: ansi.rgb(100, 255, 200),
65
- cat7: ansi.rgb(100, 200, 255),
66
- cat8: ansi.rgb(100, 150, 255),
67
- cat9: ansi.rgb(150, 100, 255),
68
- cat10: ansi.rgb(200, 100, 255),
69
- cat11: ansi.rgb(255, 100, 200),
70
- cat12: ansi.rgb(255, 100, 150),
71
- };
15
+ const WIDTH = 76;
72
16
 
73
- // ═══════════════════════════════════════════════════════════════════════════════
74
- // BOX DRAWING HELPERS
75
- // ═══════════════════════════════════════════════════════════════════════════════
76
-
77
- const B = {
78
- // Heavy box
79
- tl: '┏', tr: '┓', bl: '┗', br: '┛',
80
- h: '━', v: '┃',
81
- lT: '┣', rT: '┫', tT: '┳', bT: '┻', x: '╋',
82
-
83
- // Light box
84
- ltl: '┌', ltr: '┐', lbl: '└', lbr: '┘',
85
- lh: '─', lv: '│',
86
-
87
- // Double box
88
- dtl: '╔', dtr: '╗', dbl: '╚', dbr: '╝',
89
- dh: '═', dv: '║',
90
-
91
- // Rounded
92
- rtl: '╭', rtr: '╮', rbl: '╰', rbr: '╯',
93
-
94
- // Blocks
95
- full: '█', light: '░', medium: '▒', dark: '▓',
96
- left: '▌', right: '▐', top: '▀', bottom: '▄',
17
+ const BOX = {
18
+ topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
19
+ horizontal: '═', vertical: '║',
20
+ teeRight: '╠', teeLeft: '╣',
97
21
 
98
- // Progress
99
- pb: ['', '', '', ''],
100
- spark: ['', '▂', '', '▄', '▅', '▆', '▇', '█'],
22
+ // Table Borders (Light)
23
+ tTopLeft: '', tTopRight: '', tBottomLeft: '', tBottomRight: '',
24
+ tHorizontal: '', tVertical: '',
25
+ tTeeTop: '┬', tTeeBottom: '┴', tTee: '┼', tTeeLeft: '├', tTeeRight: '┤'
101
26
  };
102
27
 
103
- function repeatChar(char, n) {
104
- return char.repeat(Math.max(0, n));
105
- }
106
-
107
- function padCenter(str, width) {
108
- const len = stripAnsi(str).length;
109
- const left = Math.floor((width - len) / 2);
110
- const right = width - len - left;
111
- return ' '.repeat(left) + str + ' '.repeat(right);
112
- }
28
+ const LOGO_SCAN = `
29
+ █████████ █████████ █████████ █████████
30
+ ███░░░░░███ ███░░░░░███ ███░░░░░███ ███░░░░░███
31
+ ░███ ░░░ ░███ ░░░ ░███ ░███ ░███ ░███
32
+ ░░█████████ ░███ ░███████████ ░███ ░███
33
+ ░░░░░░░░███░███ ░███░░░░░███ ░███ ░███
34
+ ███ ░███░███ ███ ░███ ░███ ░███ ░███
35
+ ░░█████████ ░░█████████ ░███ ░███ ░███ ░███
36
+ ░░░░░░░░░ ░░░░░░░░░ ░░░░ ░░░░ ░░░░ ░░░░
37
+ `;
113
38
 
114
- function stripAnsi(str) {
115
- return str.replace(/\x1b\[[0-9;]*m/g, '');
116
- }
39
+ // Column widths for the table (Must correspond to math below)
40
+ const COL_1 = 10; // Severity
41
+ const COL_2 = 13; // Component
42
+ const COL_3 = 41; // Message
117
43
 
118
44
  // ═══════════════════════════════════════════════════════════════════════════════
119
- // PREMIUM SCORE DISPLAY
45
+ // UTILITIES
120
46
  // ═══════════════════════════════════════════════════════════════════════════════
121
47
 
122
- function renderPremiumScoreCard(score, options = {}) {
123
- const { verdict = 'BLOCK', findings = {}, duration, cached } = options;
124
- const lines = [];
125
-
126
- const width = 70;
127
- const innerWidth = width - 4;
128
-
129
- // Determine colors based on verdict/score
130
- let verdictColor, verdictBg, gradientColors;
131
- if (verdict === 'SHIP' || verdict === 'PASS' || score >= 80) {
132
- verdictColor = palette.pass;
133
- verdictBg = palette.passBg;
134
- gradientColors = [ansi.rgb(50, 200, 100), ansi.rgb(100, 255, 150), ansi.rgb(150, 255, 200)];
135
- } else if (verdict === 'WARN' || score >= 50) {
136
- verdictColor = palette.warn;
137
- verdictBg = palette.warnBg;
138
- gradientColors = [ansi.rgb(200, 150, 50), ansi.rgb(255, 200, 100), ansi.rgb(255, 220, 150)];
139
- } else {
140
- verdictColor = palette.fail;
141
- verdictBg = palette.failBg;
142
- gradientColors = [ansi.rgb(200, 50, 50), ansi.rgb(255, 100, 100), ansi.rgb(255, 150, 150)];
143
- }
144
-
145
- // Top border with gradient
146
- lines.push('');
147
- lines.push(` ${palette.border}${B.dtl}${B.dh.repeat(width - 2)}${B.dtr}${ansi.reset}`);
148
-
149
- // Score display
150
- const scoreStr = String(score).padStart(3);
151
- const gradeStr = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
152
- const gradeColor = score >= 80 ? palette.pass : score >= 60 ? palette.warn : palette.fail;
153
-
154
- lines.push(` ${palette.border}${B.dv}${ansi.reset}${' '.repeat(innerWidth)}${palette.border}${B.dv}${ansi.reset}`);
155
-
156
- // Big score
157
- const scoreLine = `${palette.muted}SCORE${ansi.reset} ${verdictColor}${ansi.bold}${scoreStr}${ansi.reset}${palette.muted}/100${ansi.reset} ${palette.muted}GRADE${ansi.reset} ${gradeColor}${ansi.bold}${gradeStr}${ansi.reset}`;
158
- lines.push(` ${palette.border}${B.dv}${ansi.reset} ${scoreLine}${' '.repeat(14)}${palette.border}${B.dv}${ansi.reset}`);
159
-
160
- lines.push(` ${palette.border}${B.dv}${ansi.reset}${' '.repeat(innerWidth)}${palette.border}${B.dv}${ansi.reset}`);
161
-
162
- // Progress bar
163
- const barWidth = 40;
164
- const filled = Math.round((score / 100) * barWidth);
165
- let progressBar = '';
166
- for (let i = 0; i < barWidth; i++) {
167
- if (i < filled) {
168
- // Gradient fill
169
- const gradientIdx = Math.floor((i / filled) * gradientColors.length);
170
- progressBar += `${gradientColors[Math.min(gradientIdx, gradientColors.length - 1)]}${B.full}${ansi.reset}`;
171
- } else {
172
- progressBar += `${palette.muted}${B.light}${ansi.reset}`;
173
- }
174
- }
175
- lines.push(` ${palette.border}${B.dv}${ansi.reset} ${progressBar}${' '.repeat(innerWidth - barWidth - 4)}${palette.border}${B.dv}${ansi.reset}`);
176
-
177
- lines.push(` ${palette.border}${B.dv}${ansi.reset}${' '.repeat(innerWidth)}${palette.border}${B.dv}${ansi.reset}`);
178
-
179
- // Verdict badge
180
- const verdictText = verdict === 'SHIP' ? ' ✓ SHIP ' : verdict === 'WARN' ? ' ⚠ WARN ' : ' ✗ BLOCK ';
181
- const verdictDesc = verdict === 'SHIP' ? 'Ready to ship!' : verdict === 'WARN' ? 'Review before shipping' : 'Fix issues before shipping';
182
- const badgeLine = `${verdictBg}${ansi.bold}${verdictText}${ansi.reset} ${palette.muted}${verdictDesc}${ansi.reset}`;
183
- lines.push(` ${palette.border}${B.dv}${ansi.reset} ${badgeLine} ${palette.border}${B.dv}${ansi.reset}`);
184
-
185
- lines.push(` ${palette.border}${B.dv}${ansi.reset}${' '.repeat(innerWidth)}${palette.border}${B.dv}${ansi.reset}`);
186
-
187
- // Findings summary
188
- const { critical = 0, high = 0, medium = 0, low = 0 } = findings;
189
- const critStr = `${palette.critical}${critical}${ansi.reset} critical`;
190
- const highStr = `${palette.high}${high}${ansi.reset} high`;
191
- const medStr = `${palette.medium}${medium}${ansi.reset} medium`;
192
- const lowStr = `${palette.low}${low}${ansi.reset} low`;
193
- const findingsLine = ` ${critStr} ${palette.muted}│${ansi.reset} ${highStr} ${palette.muted}│${ansi.reset} ${medStr} ${palette.muted}│${ansi.reset} ${lowStr}`;
194
- lines.push(` ${palette.border}${B.dv}${ansi.reset}${findingsLine}${' '.repeat(9)}${palette.border}${B.dv}${ansi.reset}`);
195
-
196
- lines.push(` ${palette.border}${B.dv}${ansi.reset}${' '.repeat(innerWidth)}${palette.border}${B.dv}${ansi.reset}`);
197
-
198
- // Duration
199
- if (duration) {
200
- const durationStr = `${palette.muted}Completed in ${formatDuration(duration)}${cached ? ' (cached)' : ''}${ansi.reset}`;
201
- lines.push(` ${palette.border}${B.dv}${ansi.reset} ${durationStr}${' '.repeat(innerWidth - stripAnsi(durationStr).length - 4)}${palette.border}${B.dv}${ansi.reset}`);
202
- lines.push(` ${palette.border}${B.dv}${ansi.reset}${' '.repeat(innerWidth)}${palette.border}${B.dv}${ansi.reset}`);
203
- }
204
-
205
- // Bottom border
206
- lines.push(` ${palette.border}${B.dbl}${B.dh.repeat(width - 2)}${B.dbr}${ansi.reset}`);
207
-
208
- return lines.join('\n');
48
+ function padCenter(str, width) {
49
+ const visibleLen = str.replace(/\u001b\[\d+m/g, '').length;
50
+ const padding = Math.max(0, width - visibleLen);
51
+ const left = Math.floor(padding / 2);
52
+ const right = padding - left;
53
+ return ' '.repeat(left) + str + ' '.repeat(right);
209
54
  }
210
55
 
211
- // ═══════════════════════════════════════════════════════════════════════════════
212
- // BLOCKERS TABLE
213
- // ═══════════════════════════════════════════════════════════════════════════════
214
-
215
- function renderBlockersTable(blockers, options = {}) {
216
- const { maxItems = 10 } = options;
217
-
218
- if (!blockers || blockers.length === 0) {
219
- const lines = [];
220
- lines.push('');
221
- lines.push(` ${palette.pass}${ansi.bold}✓ NO BLOCKERS${ansi.reset} ${palette.muted}— Your code is ready to ship!${ansi.reset}`);
222
- return lines.join('\n');
223
- }
224
-
225
- const lines = [];
226
- const width = 70;
227
-
228
- lines.push('');
229
- lines.push(` ${palette.critical}${ansi.bold}🚨 SHIP BLOCKERS (${blockers.length})${ansi.reset}`);
230
- lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
231
- lines.push('');
232
-
233
- for (let i = 0; i < Math.min(blockers.length, maxItems); i++) {
234
- const blocker = blockers[i];
235
- const sevColor = blocker.severity === 'critical' || blocker.severity === 'BLOCK'
236
- ? palette.criticalBg
237
- : palette.highBg;
238
- const sevLabel = blocker.severity === 'critical' || blocker.severity === 'BLOCK'
239
- ? ' BLOCK '
240
- : ' HIGH ';
241
-
242
- // Row number
243
- const num = `${palette.muted}${String(i + 1).padStart(2)}.${ansi.reset}`;
244
-
245
- // Severity badge
246
- const badge = `${sevColor}${ansi.bold}${sevLabel}${ansi.reset}`;
247
-
248
- // Title (truncated)
249
- const title = truncate(blocker.title || blocker.message || 'Unknown issue', 48);
250
-
251
- lines.push(` ${num} ${badge} ${ansi.bold}${title}${ansi.reset}`);
252
-
253
- // File location
254
- if (blocker.file) {
255
- const fileDisplay = blocker.file + (blocker.line ? `:${blocker.line}` : '');
256
- lines.push(` ${palette.muted}└─${ansi.reset} ${palette.accent}${truncate(fileDisplay, 55)}${ansi.reset}`);
257
- }
258
-
259
- // Evidence/why
260
- if (blocker.description || blocker.why) {
261
- const desc = truncate(blocker.description || blocker.why, 55);
262
- lines.push(` ${palette.muted}${desc}${ansi.reset}`);
263
- }
264
-
265
- lines.push('');
266
- }
267
-
268
- if (blockers.length > maxItems) {
269
- lines.push(` ${palette.muted}... and ${blockers.length - maxItems} more blockers${ansi.reset}`);
270
- lines.push('');
271
- }
272
-
273
- return lines.join('\n');
56
+ function padRight(str, len) {
57
+ const visibleLen = str.length; // Simplified for this usage
58
+ const truncated = visibleLen > len ? str.substring(0, len - 3) + '...' : str;
59
+ return truncated + ' '.repeat(Math.max(0, len - truncated.length));
274
60
  }
275
61
 
276
- // ═══════════════════════════════════════════════════════════════════════════════
277
- // CATEGORY TABLE WITH VISUAL BARS
278
- // ═══════════════════════════════════════════════════════════════════════════════
279
-
280
- function renderCategoryTable(findings) {
281
- if (!findings || findings.length === 0) return '';
282
-
283
- // Group by category
284
- const categories = {};
285
- let maxTotal = 0;
286
-
287
- for (const f of findings) {
288
- const cat = f.category || f.ruleId?.split('/')[0] || 'Other';
289
- if (!categories[cat]) {
290
- categories[cat] = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
291
- }
292
- categories[cat].total++;
293
- maxTotal = Math.max(maxTotal, categories[cat].total);
294
-
295
- const sev = (f.severity || '').toLowerCase();
296
- if (sev === 'critical' || sev === 'block') categories[cat].critical++;
297
- else if (sev === 'high') categories[cat].high++;
298
- else if (sev === 'medium' || sev === 'warn' || sev === 'warning') categories[cat].medium++;
299
- else categories[cat].low++;
300
- }
301
-
302
- const categoryIcons = {
303
- ENVCONTRACT: '📋', MISSINGROUTE: '🔗', HARDCODEDSECRET: '🔑', TODOFIXME: '📝',
304
- FAKESUCCESS: '✨', GHOSTAUTH: '👻', MOCKDATA: '🎭', CONSOLELOG: '🖥️',
305
- DEADCODE: '💀', DEPRECATEDAPI: '⚠️', EMPTYCATCH: '🕳️', UNSAFEREGEX: '⚡',
306
- SECURITY: '🛡️', BILLING: '💳', ROUTE: '🔗', AUTH: '🔐', SECRET: '🔑',
307
- };
62
+ function padLogoBlock(ascii, width) {
63
+ const lines = ascii.trim().split('\n');
64
+ const maxContentWidth = Math.max(...lines.map(l => l.length));
308
65
 
309
- const categoryColors = [
310
- palette.cat1, palette.cat2, palette.cat3, palette.cat4, palette.cat5,
311
- palette.cat6, palette.cat7, palette.cat8, palette.cat9, palette.cat10,
312
- palette.cat11, palette.cat12,
313
- ];
314
-
315
- const lines = [];
316
- const width = 70;
317
-
318
- lines.push('');
319
- lines.push(` ${palette.header}${ansi.bold}📊 FINDINGS BY CATEGORY${ansi.reset}`);
320
- lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
321
- lines.push('');
322
-
323
- // Table header
324
- lines.push(` ${palette.muted} CATEGORY COUNT SEVERITY BREAKDOWN BAR${ansi.reset}`);
325
- lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
326
-
327
- // Sort by total count descending
328
- const sortedCategories = Object.entries(categories)
329
- .sort((a, b) => {
330
- // Sort by criticality first, then by total
331
- const aCrit = a[1].critical * 1000 + a[1].high * 100 + a[1].total;
332
- const bCrit = b[1].critical * 1000 + b[1].high * 100 + b[1].total;
333
- return bCrit - aCrit;
334
- });
335
-
336
- let colorIdx = 0;
337
- for (const [cat, counts] of sortedCategories) {
338
- const icon = categoryIcons[cat.toUpperCase()] || '•';
339
- const catColor = categoryColors[colorIdx % categoryColors.length];
340
- colorIdx++;
341
-
342
- // Category name
343
- const catName = truncate(cat, 15).padEnd(15);
344
-
345
- // Count
346
- const countStr = String(counts.total).padStart(4);
347
-
348
- // Severity breakdown
349
- const critStr = counts.critical > 0 ? `${palette.critical}${counts.critical}C${ansi.reset}` : `${palette.muted}--${ansi.reset}`;
350
- const highStr = counts.high > 0 ? `${palette.high}${counts.high}H${ansi.reset}` : `${palette.muted}--${ansi.reset}`;
351
- const medStr = counts.medium > 0 ? `${palette.medium}${counts.medium}M${ansi.reset}` : `${palette.muted}--${ansi.reset}`;
352
- const lowStr = counts.low > 0 ? `${palette.low}${counts.low}L${ansi.reset}` : `${palette.muted}--${ansi.reset}`;
353
-
354
- // Visual bar
355
- const barWidth = 15;
356
- const barFill = Math.round((counts.total / maxTotal) * barWidth);
357
- let bar = '';
358
- for (let i = 0; i < barWidth; i++) {
359
- if (i < barFill) {
360
- bar += `${catColor}${B.full}${ansi.reset}`;
361
- } else {
362
- bar += `${palette.muted}${B.light}${ansi.reset}`;
363
- }
364
- }
365
-
366
- lines.push(` ${icon} ${catColor}${catName}${ansi.reset} ${ansi.bold}${countStr}${ansi.reset} ${critStr} ${highStr} ${medStr} ${lowStr} ${bar}`);
367
- }
368
-
369
- lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
370
-
371
- // Total
372
- const totalCount = findings.length;
373
- const totalCrit = findings.filter(f => f.severity === 'critical' || f.severity === 'BLOCK').length;
374
- const totalHigh = findings.filter(f => f.severity === 'high').length;
375
- const totalMed = findings.filter(f => f.severity === 'medium' || f.severity === 'WARN' || f.severity === 'warning').length;
376
- const totalLow = findings.filter(f => f.severity === 'low' || f.severity === 'INFO').length;
377
-
378
- lines.push(` ${palette.muted} TOTAL${ansi.reset} ${ansi.bold}${String(totalCount).padStart(4)}${ansi.reset} ${palette.critical}${totalCrit}C${ansi.reset} ${palette.high}${totalHigh}H${ansi.reset} ${palette.medium}${totalMed}M${ansi.reset} ${palette.low}${totalLow}L${ansi.reset}`);
379
-
380
- return lines.join('\n');
66
+ return lines.map(line => {
67
+ const solidLine = line + ' '.repeat(maxContentWidth - line.length);
68
+ return padCenter(solidLine, width);
69
+ });
381
70
  }
382
71
 
383
- // ═══════════════════════════════════════════════════════════════════════════════
384
- // TOP FINDINGS DETAILS
385
- // ═══════════════════════════════════════════════════════════════════════════════
386
-
387
- function renderTopFindings(findings, options = {}) {
388
- const { maxItems = 5, showFix = true } = options;
389
-
390
- if (!findings || findings.length === 0) return '';
391
-
392
- // Get top critical/high findings
393
- const topFindings = findings
394
- .filter(f => f.severity === 'critical' || f.severity === 'BLOCK' || f.severity === 'high')
395
- .slice(0, maxItems);
396
-
397
- if (topFindings.length === 0) return '';
398
-
399
- const lines = [];
400
- const width = 70;
401
-
402
- lines.push('');
403
- lines.push(` ${palette.header}${ansi.bold}🔍 TOP ISSUES TO FIX${ansi.reset}`);
404
- lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
405
-
406
- for (let i = 0; i < topFindings.length; i++) {
407
- const f = topFindings[i];
408
- const num = `${palette.accent}${i + 1}${ansi.reset}`;
409
- const icon = f.severity === 'critical' || f.severity === 'BLOCK' ? '🔴' : '🟠';
410
-
411
- lines.push('');
412
- lines.push(` ${num}. ${icon} ${ansi.bold}${truncate(f.title || f.message, 55)}${ansi.reset}`);
413
-
414
- if (f.file) {
415
- lines.push(` ${palette.accent}${f.file}${f.line ? `:${f.line}` : ''}${ansi.reset}`);
416
- }
417
-
418
- if (f.description || f.why) {
419
- lines.push(` ${palette.muted}${truncate(f.description || f.why, 60)}${ansi.reset}`);
420
- }
421
-
422
- if (showFix && (f.fix || f.fixHints?.[0])) {
423
- const fixHint = f.fix || f.fixHints?.[0];
424
- lines.push(` ${palette.pass}→ ${truncate(fixHint, 58)}${ansi.reset}`);
425
- }
426
- }
427
-
428
- return lines.join('\n');
72
+ function renderProgressBar(score, width = 20) {
73
+ const filled = Math.round((score / 100) * width);
74
+ const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(width - filled));
75
+ return bar;
429
76
  }
430
77
 
431
78
  // ═══════════════════════════════════════════════════════════════════════════════
432
- // ANALYSIS LAYERS STATUS
79
+ // MAIN FORMATTER
433
80
  // ═══════════════════════════════════════════════════════════════════════════════
434
81
 
435
- function renderLayers(layers) {
436
- if (!layers || layers.length === 0) return '';
437
-
438
- const lines = [];
439
- const width = 70;
440
-
441
- lines.push('');
442
- lines.push(` ${palette.header}${ansi.bold}⚡ ANALYSIS LAYERS${ansi.reset}`);
443
- lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
444
- lines.push('');
445
-
446
- const layerConfig = {
447
- ast: { name: 'AST Analysis', icon: '🔍', desc: 'Static code parsing' },
448
- truth: { name: 'Build Truth', icon: '📦', desc: 'Manifest verification' },
449
- reality: { name: 'Reality Check', icon: '🎭', desc: 'Runtime testing' },
450
- realitySniff: { name: 'Reality Sniff', icon: '🔬', desc: 'AI artifact detection' },
451
- detection: { name: 'Detection', icon: '🛡️', desc: 'Security patterns' },
452
- codeQuality: { name: 'Code Quality', icon: '📋', desc: 'TODO, MOCK, console.log' },
453
- };
454
-
455
- for (const layer of layers) {
456
- const config = layerConfig[layer.name] || { name: layer.name, icon: '○', desc: '' };
457
-
458
- let status, statusIcon;
459
- if (layer.skipped) {
460
- status = `${palette.muted}skipped${ansi.reset}`;
461
- statusIcon = `${palette.muted}○${ansi.reset}`;
462
- } else if (layer.error) {
463
- status = `${palette.fail}error${ansi.reset}`;
464
- statusIcon = `${palette.fail}✗${ansi.reset}`;
465
- } else {
466
- status = `${palette.pass}done${ansi.reset}`;
467
- statusIcon = `${palette.pass}✓${ansi.reset}`;
468
- }
469
-
470
- const duration = layer.duration ? `${palette.muted}${layer.duration}ms${ansi.reset}` : '';
471
- const findingsCount = layer.findings !== undefined ? `${palette.accent}${layer.findings}${ansi.reset} findings` : '';
472
-
473
- lines.push(` ${statusIcon} ${config.icon} ${config.name.padEnd(18)} ${status.padEnd(20)} ${duration.padEnd(12)} ${findingsCount}`);
474
- }
475
-
476
- return lines.join('\n');
477
- }
478
-
479
- // ═══════════════════════════════════════════════════════════════════════════════
480
- // COVERAGE MAP
481
- // ═══════════════════════════════════════════════════════════════════════════════
82
+ function formatScanOutput(result) {
83
+ const {
84
+ score = 0,
85
+ findings = [],
86
+ duration = 0,
87
+ scannedFiles = 0
88
+ } = result;
482
89
 
483
- function renderCoverageMap(coverage) {
484
- if (!coverage) return '';
485
-
90
+ const hasIssues = findings.length > 0;
91
+ const heapMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
486
92
  const lines = [];
487
- const width = 70;
488
-
489
- lines.push('');
490
- lines.push(` ${palette.header}${ansi.bold}🗺️ ROUTE COVERAGE${ansi.reset}`);
491
- lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
492
- lines.push('');
493
-
494
- const pct = coverage.coveragePercent || 0;
495
- const color = pct >= 80 ? palette.pass : pct >= 60 ? palette.warn : palette.fail;
496
-
497
- // Coverage percentage with big number
498
- lines.push(` ${color}${ansi.bold}${pct}%${ansi.reset} ${palette.muted}of routes reachable from root${ansi.reset}`);
499
-
500
- // Progress bar
501
- const barWidth = 50;
502
- const filled = Math.round((pct / 100) * barWidth);
503
- let bar = '';
504
- for (let i = 0; i < barWidth; i++) {
505
- if (i < filled) {
506
- bar += `${color}${B.full}${ansi.reset}`;
507
- } else {
508
- bar += `${palette.muted}${B.light}${ansi.reset}`;
509
- }
510
- }
511
- lines.push(` ${bar}`);
512
- lines.push('');
513
-
514
- // Stats grid
515
- const stats = [
516
- ['Total Routes', coverage.totalRoutes || 0, palette.accent],
517
- ['Reachable', coverage.reachableFromRoot || 0, palette.pass],
518
- ['Orphaned', coverage.orphanedRoutes || 0, coverage.orphanedRoutes > 0 ? palette.warn : palette.pass],
519
- ['Dead Links', coverage.deadLinks || 0, coverage.deadLinks > 0 ? palette.fail : palette.pass],
520
- ];
521
-
522
- let statsLine = ' ';
523
- for (const [label, value, col] of stats) {
524
- statsLine += `${palette.muted}${label}:${ansi.reset} ${col}${ansi.bold}${value}${ansi.reset} `;
525
- }
526
- lines.push(statsLine);
527
-
528
- return lines.join('\n');
529
- }
530
93
 
531
- // ═══════════════════════════════════════════════════════════════════════════════
532
- // UPSELL BANNER
533
- // ═══════════════════════════════════════════════════════════════════════════════
534
-
535
- function renderUpsellBanner(severityCounts) {
536
- const { critical = 0, high = 0, medium = 0, low = 0 } = severityCounts;
537
- const total = critical + high + medium + low;
538
-
539
- if (total === 0) return '';
540
-
541
- const lines = [];
542
- const width = 68;
543
-
544
- lines.push('');
545
- lines.push(` ${palette.border}${B.rtl}${B.lh.repeat(width)}${B.rtr}${ansi.reset}`);
546
- lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
547
-
548
- if (critical > 0) {
549
- lines.push(` ${palette.border}${B.lv}${ansi.reset} ${palette.fail}⚠ ${critical} critical issues${ansi.reset} blocking your ship!${' '.repeat(width - 42)}${palette.border}${B.lv}${ansi.reset}`);
550
- lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
551
- lines.push(` ${palette.border}${B.lv}${ansi.reset} ${palette.accent}vibecheck fix${ansi.reset} ${palette.muted}→ AI-generated fixes for each issue${ansi.reset}${' '.repeat(width - 52)}${palette.border}${B.lv}${ansi.reset}`);
552
- lines.push(` ${palette.border}${B.lv}${ansi.reset} ${palette.muted}Requires STARTER vibecheck.dev/pricing${ansi.reset}${' '.repeat(width - 43)}${palette.border}${B.lv}${ansi.reset}`);
553
- } else if (medium > 0) {
554
- lines.push(` ${palette.border}${B.lv}${ansi.reset} ${palette.warn}⚡ ${medium} warnings${ansi.reset} detected in your codebase${' '.repeat(width - 46)}${palette.border}${B.lv}${ansi.reset}`);
555
- lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
556
- lines.push(` ${palette.border}${B.lv}${ansi.reset} ${palette.accent}vibecheck ship${ansi.reset} ${palette.muted}→ Full verdict with AI risk score${ansi.reset}${' '.repeat(width - 51)}${palette.border}${B.lv}${ansi.reset}`);
557
- lines.push(` ${palette.border}${B.lv}${ansi.reset} ${palette.accent}vibecheck fix${ansi.reset} ${palette.muted}→ AI fixes • Requires STARTER${ansi.reset}${' '.repeat(width - 48)}${palette.border}${B.lv}${ansi.reset}`);
94
+ // 1. OUTER FRAME TOP
95
+ lines.push(chalk.gray(BOX.topLeft + BOX.horizontal.repeat(WIDTH - 2) + BOX.topRight));
96
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
97
+
98
+ // 2. LOGO (Cyan if clean, Red if issues)
99
+ const logoColor = hasIssues ? chalk.red : chalk.cyan;
100
+ padLogoBlock(LOGO_SCAN, WIDTH - 2).forEach(line => {
101
+ lines.push(chalk.gray(BOX.vertical) + logoColor(line) + chalk.gray(BOX.vertical));
102
+ });
103
+
104
+ const subTitle = hasIssues ? 'INTEGRITY SCAN • ISSUES DETECTED' : 'INTEGRITY SCAN • CLEAN';
105
+ lines.push(chalk.gray(BOX.vertical) + padCenter(chalk.bold.white(subTitle), WIDTH - 2) + chalk.gray(BOX.vertical));
106
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
107
+
108
+ // 3. TELEMETRY
109
+ lines.push(chalk.gray(BOX.teeRight + BOX.horizontal.repeat(WIDTH - 2) + BOX.teeLeft));
110
+ const stats = `📡 TELEMETRY │ ⏱ ${duration}ms │ 📂 ${scannedFiles} Files │ 📦 ${heapMB}MB`;
111
+ lines.push(chalk.gray(BOX.vertical) + padCenter(stats, WIDTH - 2) + chalk.gray(BOX.vertical));
112
+ lines.push(chalk.gray(BOX.teeRight + BOX.horizontal.repeat(WIDTH - 2) + BOX.teeLeft));
113
+
114
+ if (!hasIssues) {
115
+ // -- CLEAN STATE --
116
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
117
+ lines.push(chalk.gray(BOX.vertical) + padCenter(chalk.green('✅ NO STATIC ISSUES FOUND'), WIDTH - 2) + chalk.gray(BOX.vertical));
118
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
558
119
  } else {
559
- lines.push(` ${palette.border}${B.lv}${ansi.reset} ${palette.pass}✓ Looking good!${ansi.reset} Only minor issues found${' '.repeat(width - 40)}${palette.border}${B.lv}${ansi.reset}`);
560
- lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
561
- lines.push(` ${palette.border}${B.lv}${ansi.reset} ${palette.accent}vibecheck prove${ansi.reset} ${palette.muted}→ Runtime proof + Verified Badge${ansi.reset}${' '.repeat(width - 50)}${palette.border}${B.lv}${ansi.reset}`);
562
- lines.push(` ${palette.border}${B.lv}${ansi.reset} ${palette.muted}Requires PRO vibecheck.dev/pricing${ansi.reset}${' '.repeat(width - 39)}${palette.border}${B.lv}${ansi.reset}`);
563
- }
564
-
565
- lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
566
- lines.push(` ${palette.border}${B.rbl}${B.lh.repeat(width)}${B.rbr}${ansi.reset}`);
567
-
568
- return lines.join('\n');
569
- }
570
-
571
- // ═══════════════════════════════════════════════════════════════════════════════
572
- // MAIN OUTPUT FORMATTER
573
- // ═══════════════════════════════════════════════════════════════════════════════
120
+ // -- ISSUES STATE --
121
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
122
+ const scoreBar = renderProgressBar(score, 20);
123
+ lines.push(chalk.gray(BOX.vertical) + padCenter(`HEALTH SCORE [${scoreBar}] ${score} / 100`, WIDTH + 18) + chalk.gray(BOX.vertical));
124
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
125
+
126
+ // 4. FINDINGS TABLE
127
+ lines.push(chalk.gray(BOX.vertical) + padCenter('DETECTED VULNERABILITIES (STATIC)', WIDTH - 2) + chalk.gray(BOX.vertical));
128
+
129
+ // Table Construction
130
+ const C1 = COL_1, C2 = COL_2, C3 = COL_3;
131
+ const tTop = ` ${BOX.tTopLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTeeTop}${BOX.tHorizontal.repeat(C2)}${BOX.tTeeTop}${BOX.tHorizontal.repeat(C3)}${BOX.tTopRight} `;
132
+ const tMid = ` ${BOX.tTeeLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTee}${BOX.tHorizontal.repeat(C2)}${BOX.tTee}${BOX.tHorizontal.repeat(C3)}${BOX.tTeeRight} `;
133
+ const tBot = ` ${BOX.tBottomLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTeeBottom}${BOX.tHorizontal.repeat(C2)}${BOX.tTeeBottom}${BOX.tHorizontal.repeat(C3)}${BOX.tBottomRight} `;
134
+
135
+ lines.push(chalk.gray(BOX.vertical) + chalk.gray(tTop) + chalk.gray(BOX.vertical));
136
+ const header = ` ${BOX.tVertical}${padRight(' SEVERITY', C1)}${BOX.tVertical}${padRight(' TYPE', C2)}${BOX.tVertical}${padRight(' FINDING', C3)}${BOX.tVertical} `;
137
+ lines.push(chalk.gray(BOX.vertical) + chalk.bold(header) + chalk.gray(BOX.vertical));
138
+ lines.push(chalk.gray(BOX.vertical) + chalk.gray(tMid) + chalk.gray(BOX.vertical));
139
+
140
+ // Rows
141
+ const topItems = findings.slice(0, 5);
142
+ topItems.forEach(item => {
143
+ const severity = item.severity === 'critical' ? chalk.red('🛑 CRIT ') : chalk.yellow('🟡 WARN ');
144
+ const type = padRight(' ' + (item.type || 'Unknown'), C2);
145
+ const desc = padRight(' ' + (item.message || ''), C3);
146
+
147
+ const row = ` ${chalk.gray(BOX.tVertical)}${padRight(severity, C1)}${chalk.gray(BOX.tVertical)}${type}${chalk.gray(BOX.tVertical)}${desc}${chalk.gray(BOX.tVertical)} `;
148
+ lines.push(chalk.gray(BOX.vertical) + row + chalk.gray(BOX.vertical));
149
+ });
574
150
 
575
- function formatScanOutput(result, options = {}) {
576
- const { verbose = false, json = false } = options;
577
-
578
- if (json) {
579
- return JSON.stringify(result, null, 2);
151
+ lines.push(chalk.gray(BOX.vertical) + chalk.gray(tBot) + chalk.gray(BOX.vertical));
152
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
580
153
  }
581
-
582
- const { verdict, findings = [], layers = [], coverage, breakdown, timings = {} } = result;
583
-
584
- // Count findings by severity
585
- const severityCounts = {
586
- critical: findings.filter(f => f.severity === 'critical' || f.severity === 'BLOCK').length,
587
- high: findings.filter(f => f.severity === 'high').length,
588
- medium: findings.filter(f => f.severity === 'medium' || f.severity === 'WARN' || f.severity === 'warning').length,
589
- low: findings.filter(f => f.severity === 'low' || f.severity === 'INFO' || f.severity === 'info').length,
590
- };
591
-
592
- // Calculate score
593
- const score = verdict?.score ?? calculateScore(severityCounts);
594
- const verdictStatus = verdict?.verdict || (severityCounts.critical > 0 ? 'BLOCK' : severityCounts.high > 0 ? 'WARN' : 'SHIP');
595
-
596
- const lines = [];
597
-
598
- // Premium score card
599
- lines.push(renderPremiumScoreCard(score, {
600
- verdict: verdictStatus,
601
- findings: severityCounts,
602
- duration: timings.total,
603
- cached: result.cached,
604
- }));
605
-
606
- // Blockers table
607
- const blockers = findings.filter(f =>
608
- f.severity === 'critical' || f.severity === 'BLOCK' || f.severity === 'high'
609
- );
610
- lines.push(renderBlockersTable(blockers));
611
-
612
- // Category table with visual bars
613
- if (findings.length > 0) {
614
- lines.push(renderCategoryTable(findings));
615
- }
616
-
617
- // Verbose output
618
- if (verbose) {
619
- // Top findings with fix hints
620
- lines.push(renderTopFindings(findings));
621
-
622
- // Coverage map
623
- if (coverage) {
624
- lines.push(renderCoverageMap(coverage));
625
- }
626
-
627
- // Layers status
628
- if (layers.length > 0) {
629
- lines.push(renderLayers(layers));
630
- }
631
- }
632
-
633
- // Timing
634
- if (timings.total) {
635
- lines.push('');
636
- lines.push(` ${palette.muted}Completed in ${formatDuration(timings.total)}${ansi.reset}`);
637
- }
638
-
639
- // Upsell banner
640
- lines.push(renderUpsellBanner(severityCounts));
641
-
642
- lines.push('');
643
- return lines.join('\n');
644
- }
645
154
 
646
- // ═══════════════════════════════════════════════════════════════════════════════
647
- // UTILITIES
648
- // ═══════════════════════════════════════════════════════════════════════════════
649
-
650
- function calculateScore(severityCounts) {
651
- const deductions =
652
- (severityCounts.critical || 0) * 25 +
653
- (severityCounts.high || 0) * 15 +
654
- (severityCounts.medium || 0) * 5 +
655
- (severityCounts.low || 0) * 1;
155
+ // 5. LOCKED / UPSELL SECTION (Redacted for SCAN context)
156
+ lines.push(chalk.gray(BOX.teeRight + BOX.horizontal.repeat(WIDTH - 2) + BOX.teeLeft));
656
157
 
657
- return Math.max(0, 100 - deductions);
658
- }
158
+ const lockTitle = chalk.white.bold('🔒 DEEP SCAN ANALYSIS: ') + chalk.gray('UNAVAILABLE');
159
+ lines.push(chalk.gray(BOX.vertical) + padCenter(lockTitle, WIDTH + 9) + chalk.gray(BOX.vertical));
160
+ lines.push(chalk.gray(BOX.vertical) + padCenter(chalk.gray('─'.repeat(60)), WIDTH + 9) + chalk.gray(BOX.vertical));
659
161
 
660
- const EXIT_CODES = {
661
- SUCCESS: 0,
662
- WARNING: 1,
663
- FAILURE: 2,
664
- ERROR: 3,
665
- };
162
+ lines.push(chalk.gray(BOX.vertical) + padCenter(chalk.dim('Static analysis is limited. The following runtime vectors'), WIDTH + 9) + chalk.gray(BOX.vertical));
163
+ lines.push(chalk.gray(BOX.vertical) + padCenter(chalk.dim('cannot be verified without the Vibecheck Truth Engine.'), WIDTH + 9) + chalk.gray(BOX.vertical));
164
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
666
165
 
667
- function getExitCode(verdict) {
668
- if (!verdict) return EXIT_CODES.ERROR;
669
- const status = verdict.verdict || verdict;
670
- switch (status) {
671
- case 'PASS':
672
- case 'SHIP':
673
- return EXIT_CODES.SUCCESS;
674
- case 'WARN':
675
- return EXIT_CODES.WARNING;
676
- case 'FAIL':
677
- case 'BLOCK':
678
- return EXIT_CODES.FAILURE;
679
- default:
680
- return EXIT_CODES.ERROR;
681
- }
682
- }
166
+ // Redacted Lines
167
+ const redacted = [
168
+ '░░ [REDACTED] 🔒 Runtime API Schema Validation (Drift Detection)',
169
+ '░░ [REDACTED] 🔒 Live Auth Boundary Verification (RBAC)',
170
+ '░░ [REDACTED] 🔒 Environment Variable Resolution (Config)'
171
+ ];
683
172
 
684
- function printError(error, context = '') {
685
- const prefix = context ? `${context}: ` : '';
686
- console.error('');
687
- console.error(` ${palette.fail}✗${ansi.reset} ${ansi.bold}${prefix}${error.message || error}${ansi.reset}`);
688
- if (error.code) {
689
- console.error(` ${palette.muted}Error code: ${error.code}${ansi.reset}`);
690
- }
691
- if (error.suggestion || error.fix) {
692
- console.error(` ${palette.pass}→${ansi.reset} ${error.suggestion || error.fix}`);
693
- }
694
- console.error('');
695
- return EXIT_CODES.ERROR;
696
- }
173
+ redacted.forEach(r => {
174
+ lines.push(chalk.gray(BOX.vertical) + padCenter(chalk.dim(r), WIDTH + 9) + chalk.gray(BOX.vertical));
175
+ });
697
176
 
698
- // SARIF export
699
- function formatSARIF(findings, options = {}) {
700
- const { projectPath = '.', version = '1.0.0' } = options;
701
- const rules = new Map();
702
- const results = [];
703
-
704
- for (const f of findings) {
705
- const ruleId = f.ruleId || f.id || `vibecheck/${f.category || 'general'}`;
706
- if (!rules.has(ruleId)) {
707
- rules.set(ruleId, {
708
- id: ruleId,
709
- name: f.category || 'general',
710
- shortDescription: { text: f.title || f.message },
711
- defaultConfiguration: { level: sarifLevel(f.severity) },
712
- helpUri: 'https://vibecheck.dev/docs/rules/' + ruleId,
713
- });
714
- }
715
-
716
- const result = {
717
- ruleId,
718
- level: sarifLevel(f.severity),
719
- message: { text: f.message || f.title },
720
- locations: [],
721
- };
722
-
723
- if (f.file) {
724
- result.locations.push({
725
- physicalLocation: {
726
- artifactLocation: { uri: f.file },
727
- region: f.line ? { startLine: f.line, startColumn: f.column || 1 } : undefined,
728
- },
729
- });
730
- }
731
-
732
- if (f.fix) {
733
- result.fixes = [{ description: { text: f.fix } }];
734
- }
735
-
736
- results.push(result);
737
- }
177
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
738
178
 
739
- return {
740
- $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
741
- version: '2.1.0',
742
- runs: [{
743
- tool: {
744
- driver: {
745
- name: 'vibecheck',
746
- version,
747
- informationUri: 'https://vibecheck.dev',
748
- rules: Array.from(rules.values()),
749
- },
750
- },
751
- results,
752
- invocations: [{ executionSuccessful: true, endTimeUtc: new Date().toISOString() }],
753
- }],
754
- };
755
- }
179
+ // CTA
180
+ lines.push(chalk.gray(BOX.vertical) + padCenter(chalk.bold('👉 UNLOCK INSIGHTS:'), WIDTH + 4) + chalk.gray(BOX.vertical));
181
+ lines.push(chalk.gray(BOX.vertical) + padCenter('Run ' + chalk.cyan('vibecheck upgrade pro') + ' to enable Deep Scan.', WIDTH + 13) + chalk.gray(BOX.vertical));
756
182
 
757
- function sarifLevel(severity) {
758
- const levels = {
759
- critical: 'error', BLOCK: 'error', high: 'error',
760
- medium: 'warning', WARN: 'warning', warning: 'warning',
761
- low: 'note', INFO: 'note', info: 'none',
762
- };
763
- return levels[severity] || 'warning';
764
- }
183
+ // BOTTOM FRAME
184
+ lines.push(chalk.gray(BOX.vertical) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.vertical));
185
+ lines.push(chalk.gray(BOX.bottomLeft + BOX.horizontal.repeat(WIDTH - 2) + BOX.bottomRight));
765
186
 
766
- // Legacy compatibility exports
767
- function renderBreakdown(breakdown) {
768
- // Simplified - can expand later
769
- return '';
187
+ return lines.join('\n');
770
188
  }
771
189
 
772
- module.exports = {
773
- formatScanOutput,
774
- formatSARIF,
775
- renderLayers,
776
- renderCoverageMap,
777
- renderBreakdown,
778
- renderBlockers: renderBlockersTable,
779
- renderCategorySummary: renderCategoryTable,
780
- calculateScore,
781
- getExitCode,
782
- printError,
783
- EXIT_CODES,
784
- };
190
+ module.exports = { formatScanOutput };