@vibecheckai/cli 3.1.8 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/registry.js +106 -116
- package/bin/runners/context/generators/mcp.js +18 -0
- package/bin/runners/context/index.js +72 -4
- package/bin/runners/context/proof-context.js +293 -1
- package/bin/runners/context/security-scanner.js +311 -73
- package/bin/runners/lib/analyzers.js +607 -20
- package/bin/runners/lib/detectors-v2.js +172 -15
- package/bin/runners/lib/entitlements-v2.js +48 -1
- package/bin/runners/lib/evidence-pack.js +678 -0
- package/bin/runners/lib/html-proof-report.js +913 -0
- package/bin/runners/lib/missions/plan.js +231 -41
- package/bin/runners/lib/missions/templates.js +125 -0
- package/bin/runners/lib/scan-output.js +492 -253
- package/bin/runners/lib/ship-output.js +901 -641
- package/bin/runners/runCheckpoint.js +44 -3
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runDoctor.js +10 -2
- package/bin/runners/runFix.js +51 -341
- package/bin/runners/runInit.js +11 -0
- package/bin/runners/runPolish.d.ts +4 -0
- package/bin/runners/runPolish.js +608 -29
- package/bin/runners/runProve.js +210 -25
- package/bin/runners/runReality.js +846 -101
- package/bin/runners/runScan.js +238 -4
- package/bin/runners/runShip.js +19 -3
- package/bin/runners/runWatch.js +14 -1
- package/bin/vibecheck.js +32 -2
- package/mcp-server/consolidated-tools.js +408 -42
- package/mcp-server/index.js +152 -15
- package/mcp-server/proof-tools.js +571 -0
- package/mcp-server/tier-auth.js +22 -19
- package/mcp-server/tools-v3.js +744 -0
- package/mcp-server/truth-firewall-tools.js +190 -4
- package/package.json +3 -1
- package/bin/runners/runInstall.js +0 -281
- package/bin/runners/runLabs.js +0 -341
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scan Output - Premium
|
|
2
|
+
* Scan Output - World-Class Premium Terminal Display
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
4
|
+
* 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
|
|
8
9
|
* - JSON/SARIF export
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -24,195 +25,248 @@ const {
|
|
|
24
25
|
} = require('./terminal-ui');
|
|
25
26
|
|
|
26
27
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
27
|
-
//
|
|
28
|
+
// ENHANCED COLOR PALETTE
|
|
28
29
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
};
|
|
63
72
|
|
|
64
73
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
65
|
-
//
|
|
74
|
+
// BOX DRAWING HELPERS
|
|
66
75
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const auth = cluster.requiresAuth ? ` ${ansi.dim}(auth required)${ansi.reset}` : '';
|
|
108
|
-
lines.push(` ${ansi.dim}├─${ansi.reset} ${ansi.bold}${cluster.name}${ansi.reset}${auth} ${ansi.dim}(${cluster.nodeIds?.length || 0} routes)${ansi.reset}`);
|
|
109
|
-
}
|
|
110
|
-
if (coverage.isolatedClusters.length > 3) {
|
|
111
|
-
lines.push(` ${ansi.dim}└─ ... and ${coverage.isolatedClusters.length - 3} more clusters${ansi.reset}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Unreachable routes
|
|
116
|
-
if (coverage.unreachableRoutes?.length > 0) {
|
|
117
|
-
lines.push('');
|
|
118
|
-
lines.push(` ${colors.error}${icons.error}${ansi.reset} ${ansi.dim}Unreachable routes:${ansi.reset}`);
|
|
119
|
-
for (const route of coverage.unreachableRoutes.slice(0, 5)) {
|
|
120
|
-
lines.push(` ${ansi.dim}├─${ansi.reset} ${colors.error}${route}${ansi.reset}`);
|
|
121
|
-
}
|
|
122
|
-
if (coverage.unreachableRoutes.length > 5) {
|
|
123
|
-
lines.push(` ${ansi.dim}└─ ... and ${coverage.unreachableRoutes.length - 5} more${ansi.reset}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return lines.join('\n');
|
|
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: '▄',
|
|
97
|
+
|
|
98
|
+
// Progress
|
|
99
|
+
pb: ['░', '▒', '▓', '█'],
|
|
100
|
+
spark: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'],
|
|
101
|
+
};
|
|
102
|
+
|
|
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
|
+
}
|
|
113
|
+
|
|
114
|
+
function stripAnsi(str) {
|
|
115
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
128
116
|
}
|
|
129
117
|
|
|
130
118
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
131
|
-
//
|
|
119
|
+
// PREMIUM SCORE DISPLAY
|
|
132
120
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
133
121
|
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
function renderPremiumScoreCard(score, options = {}) {
|
|
123
|
+
const { verdict = 'BLOCK', findings = {}, duration, cached } = options;
|
|
137
124
|
const lines = [];
|
|
138
|
-
lines.push(renderSection('SCORE BREAKDOWN', '📊'));
|
|
139
|
-
lines.push('');
|
|
140
125
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
}
|
|
151
144
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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}`);
|
|
164
203
|
}
|
|
165
204
|
|
|
205
|
+
// Bottom border
|
|
206
|
+
lines.push(` ${palette.border}${B.dbl}${B.dh.repeat(width - 2)}${B.dbr}${ansi.reset}`);
|
|
207
|
+
|
|
166
208
|
return lines.join('\n');
|
|
167
209
|
}
|
|
168
210
|
|
|
169
211
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
170
|
-
// BLOCKERS
|
|
212
|
+
// BLOCKERS TABLE
|
|
171
213
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
172
214
|
|
|
173
|
-
function
|
|
174
|
-
const { maxItems =
|
|
215
|
+
function renderBlockersTable(blockers, options = {}) {
|
|
216
|
+
const { maxItems = 10 } = options;
|
|
175
217
|
|
|
176
218
|
if (!blockers || blockers.length === 0) {
|
|
177
219
|
const lines = [];
|
|
178
|
-
lines.push(renderSection('SHIP BLOCKERS', '🚀'));
|
|
179
220
|
lines.push('');
|
|
180
|
-
lines.push(` ${
|
|
221
|
+
lines.push(` ${palette.pass}${ansi.bold}✓ NO BLOCKERS${ansi.reset} ${palette.muted}— Your code is ready to ship!${ansi.reset}`);
|
|
181
222
|
return lines.join('\n');
|
|
182
223
|
}
|
|
183
224
|
|
|
184
225
|
const lines = [];
|
|
185
|
-
|
|
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}`);
|
|
186
231
|
lines.push('');
|
|
187
232
|
|
|
188
|
-
for (
|
|
233
|
+
for (let i = 0; i < Math.min(blockers.length, maxItems); i++) {
|
|
234
|
+
const blocker = blockers[i];
|
|
189
235
|
const sevColor = blocker.severity === 'critical' || blocker.severity === 'BLOCK'
|
|
190
|
-
?
|
|
191
|
-
:
|
|
236
|
+
? palette.criticalBg
|
|
237
|
+
: palette.highBg;
|
|
192
238
|
const sevLabel = blocker.severity === 'critical' || blocker.severity === 'BLOCK'
|
|
193
|
-
? '
|
|
194
|
-
: '
|
|
239
|
+
? ' BLOCK '
|
|
240
|
+
: ' HIGH ';
|
|
195
241
|
|
|
196
|
-
|
|
242
|
+
// Row number
|
|
243
|
+
const num = `${palette.muted}${String(i + 1).padStart(2)}.${ansi.reset}`;
|
|
197
244
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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);
|
|
201
250
|
|
|
251
|
+
lines.push(` ${num} ${badge} ${ansi.bold}${title}${ansi.reset}`);
|
|
252
|
+
|
|
253
|
+
// File location
|
|
202
254
|
if (blocker.file) {
|
|
203
255
|
const fileDisplay = blocker.file + (blocker.line ? `:${blocker.line}` : '');
|
|
204
|
-
lines.push(`
|
|
256
|
+
lines.push(` ${palette.muted}└─${ansi.reset} ${palette.accent}${truncate(fileDisplay, 55)}${ansi.reset}`);
|
|
205
257
|
}
|
|
206
258
|
|
|
207
|
-
|
|
208
|
-
|
|
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}`);
|
|
209
263
|
}
|
|
210
264
|
|
|
211
265
|
lines.push('');
|
|
212
266
|
}
|
|
213
267
|
|
|
214
268
|
if (blockers.length > maxItems) {
|
|
215
|
-
lines.push(` ${
|
|
269
|
+
lines.push(` ${palette.muted}... and ${blockers.length - maxItems} more blockers${ansi.reset}`);
|
|
216
270
|
lines.push('');
|
|
217
271
|
}
|
|
218
272
|
|
|
@@ -220,20 +274,23 @@ function renderBlockers(blockers, options = {}) {
|
|
|
220
274
|
}
|
|
221
275
|
|
|
222
276
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
223
|
-
// CATEGORY
|
|
277
|
+
// CATEGORY TABLE WITH VISUAL BARS
|
|
224
278
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
225
279
|
|
|
226
|
-
function
|
|
280
|
+
function renderCategoryTable(findings) {
|
|
227
281
|
if (!findings || findings.length === 0) return '';
|
|
228
282
|
|
|
229
283
|
// Group by category
|
|
230
284
|
const categories = {};
|
|
285
|
+
let maxTotal = 0;
|
|
286
|
+
|
|
231
287
|
for (const f of findings) {
|
|
232
|
-
const cat = f.category || f.ruleId?.split('/')[0] || '
|
|
288
|
+
const cat = f.category || f.ruleId?.split('/')[0] || 'Other';
|
|
233
289
|
if (!categories[cat]) {
|
|
234
290
|
categories[cat] = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
|
|
235
291
|
}
|
|
236
292
|
categories[cat].total++;
|
|
293
|
+
maxTotal = Math.max(maxTotal, categories[cat].total);
|
|
237
294
|
|
|
238
295
|
const sev = (f.severity || '').toLowerCase();
|
|
239
296
|
if (sev === 'critical' || sev === 'block') categories[cat].critical++;
|
|
@@ -242,46 +299,277 @@ function renderCategorySummary(findings) {
|
|
|
242
299
|
else categories[cat].low++;
|
|
243
300
|
}
|
|
244
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
|
+
};
|
|
308
|
+
|
|
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
|
+
|
|
245
315
|
const lines = [];
|
|
246
|
-
|
|
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}`);
|
|
247
321
|
lines.push('');
|
|
248
322
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
SECRET: '🔑',
|
|
253
|
-
BILLING: '💳',
|
|
254
|
-
MOCK: '🎭',
|
|
255
|
-
DEAD_UI: '👻',
|
|
256
|
-
FAKE_SUCCESS: '✨',
|
|
257
|
-
REALITY: '🔬',
|
|
258
|
-
QUALITY: '📋',
|
|
259
|
-
CONFIG: '⚙️',
|
|
260
|
-
};
|
|
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}`);
|
|
261
326
|
|
|
327
|
+
// Sort by total count descending
|
|
262
328
|
const sortedCategories = Object.entries(categories)
|
|
263
329
|
.sort((a, b) => {
|
|
264
|
-
// Sort by criticality
|
|
265
|
-
const aCrit = a[1].critical * 1000 + a[1].high * 100 + a[1].
|
|
266
|
-
const bCrit = b[1].critical * 1000 + b[1].high * 100 + b[1].
|
|
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;
|
|
267
333
|
return bCrit - aCrit;
|
|
268
334
|
});
|
|
269
335
|
|
|
336
|
+
let colorIdx = 0;
|
|
270
337
|
for (const [cat, counts] of sortedCategories) {
|
|
271
338
|
const icon = categoryIcons[cat.toUpperCase()] || '•';
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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}`;
|
|
276
353
|
|
|
277
|
-
|
|
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}`);
|
|
278
367
|
}
|
|
279
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
|
+
|
|
280
380
|
return lines.join('\n');
|
|
281
381
|
}
|
|
282
382
|
|
|
283
383
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
284
|
-
//
|
|
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');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
432
|
+
// ANALYSIS LAYERS STATUS
|
|
433
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
434
|
+
|
|
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
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
482
|
+
|
|
483
|
+
function renderCoverageMap(coverage) {
|
|
484
|
+
if (!coverage) return '';
|
|
485
|
+
|
|
486
|
+
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
|
+
|
|
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}`);
|
|
558
|
+
} 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
|
|
285
573
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
286
574
|
|
|
287
575
|
function formatScanOutput(result, options = {}) {
|
|
@@ -307,64 +595,56 @@ function formatScanOutput(result, options = {}) {
|
|
|
307
595
|
|
|
308
596
|
const lines = [];
|
|
309
597
|
|
|
310
|
-
//
|
|
311
|
-
lines.push(
|
|
598
|
+
// Premium score card
|
|
599
|
+
lines.push(renderPremiumScoreCard(score, {
|
|
312
600
|
verdict: verdictStatus,
|
|
313
601
|
findings: severityCounts,
|
|
314
602
|
duration: timings.total,
|
|
315
603
|
cached: result.cached,
|
|
316
604
|
}));
|
|
317
605
|
|
|
318
|
-
// Blockers
|
|
606
|
+
// Blockers table
|
|
319
607
|
const blockers = findings.filter(f =>
|
|
320
608
|
f.severity === 'critical' || f.severity === 'BLOCK' || f.severity === 'high'
|
|
321
609
|
);
|
|
322
|
-
lines.push(
|
|
610
|
+
lines.push(renderBlockersTable(blockers));
|
|
323
611
|
|
|
324
|
-
// Category
|
|
612
|
+
// Category table with visual bars
|
|
325
613
|
if (findings.length > 0) {
|
|
326
|
-
lines.push(
|
|
614
|
+
lines.push(renderCategoryTable(findings));
|
|
327
615
|
}
|
|
328
616
|
|
|
329
617
|
// Verbose output
|
|
330
618
|
if (verbose) {
|
|
619
|
+
// Top findings with fix hints
|
|
620
|
+
lines.push(renderTopFindings(findings));
|
|
621
|
+
|
|
331
622
|
// Coverage map
|
|
332
623
|
if (coverage) {
|
|
333
|
-
lines.push('');
|
|
334
624
|
lines.push(renderCoverageMap(coverage));
|
|
335
625
|
}
|
|
336
626
|
|
|
337
|
-
//
|
|
338
|
-
if (breakdown) {
|
|
339
|
-
lines.push('');
|
|
340
|
-
lines.push(renderBreakdown(breakdown));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Layers
|
|
627
|
+
// Layers status
|
|
344
628
|
if (layers.length > 0) {
|
|
345
|
-
lines.push('');
|
|
346
629
|
lines.push(renderLayers(layers));
|
|
347
630
|
}
|
|
348
|
-
|
|
349
|
-
// All findings
|
|
350
|
-
if (findings.length > 0) {
|
|
351
|
-
lines.push('');
|
|
352
|
-
lines.push(renderFindingsList(findings, { maxItems: 20, groupBySeverity: true }));
|
|
353
|
-
}
|
|
354
631
|
}
|
|
355
632
|
|
|
356
|
-
// Timing
|
|
633
|
+
// Timing
|
|
357
634
|
if (timings.total) {
|
|
358
635
|
lines.push('');
|
|
359
|
-
lines.push(` ${
|
|
636
|
+
lines.push(` ${palette.muted}Completed in ${formatDuration(timings.total)}${ansi.reset}`);
|
|
360
637
|
}
|
|
361
638
|
|
|
639
|
+
// Upsell banner
|
|
640
|
+
lines.push(renderUpsellBanner(severityCounts));
|
|
641
|
+
|
|
362
642
|
lines.push('');
|
|
363
643
|
return lines.join('\n');
|
|
364
644
|
}
|
|
365
645
|
|
|
366
646
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
367
|
-
//
|
|
647
|
+
// UTILITIES
|
|
368
648
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
369
649
|
|
|
370
650
|
function calculateScore(severityCounts) {
|
|
@@ -377,10 +657,6 @@ function calculateScore(severityCounts) {
|
|
|
377
657
|
return Math.max(0, 100 - deductions);
|
|
378
658
|
}
|
|
379
659
|
|
|
380
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
381
|
-
// EXIT CODE DETERMINATION
|
|
382
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
383
|
-
|
|
384
660
|
const EXIT_CODES = {
|
|
385
661
|
SUCCESS: 0,
|
|
386
662
|
WARNING: 1,
|
|
@@ -390,9 +666,7 @@ const EXIT_CODES = {
|
|
|
390
666
|
|
|
391
667
|
function getExitCode(verdict) {
|
|
392
668
|
if (!verdict) return EXIT_CODES.ERROR;
|
|
393
|
-
|
|
394
669
|
const status = verdict.verdict || verdict;
|
|
395
|
-
|
|
396
670
|
switch (status) {
|
|
397
671
|
case 'PASS':
|
|
398
672
|
case 'SHIP':
|
|
@@ -407,57 +681,34 @@ function getExitCode(verdict) {
|
|
|
407
681
|
}
|
|
408
682
|
}
|
|
409
683
|
|
|
410
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
411
|
-
// ERROR DISPLAY
|
|
412
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
413
|
-
|
|
414
684
|
function printError(error, context = '') {
|
|
415
685
|
const prefix = context ? `${context}: ` : '';
|
|
416
|
-
|
|
417
686
|
console.error('');
|
|
418
|
-
console.error(` ${
|
|
419
|
-
|
|
687
|
+
console.error(` ${palette.fail}✗${ansi.reset} ${ansi.bold}${prefix}${error.message || error}${ansi.reset}`);
|
|
420
688
|
if (error.code) {
|
|
421
|
-
console.error(` ${
|
|
689
|
+
console.error(` ${palette.muted}Error code: ${error.code}${ansi.reset}`);
|
|
422
690
|
}
|
|
423
|
-
|
|
424
691
|
if (error.suggestion || error.fix) {
|
|
425
|
-
console.error(` ${
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (error.docs || error.helpUrl) {
|
|
429
|
-
console.error(` ${ansi.dim}See: ${error.docs || error.helpUrl}${ansi.reset}`);
|
|
692
|
+
console.error(` ${palette.pass}→${ansi.reset} ${error.suggestion || error.fix}`);
|
|
430
693
|
}
|
|
431
|
-
|
|
432
694
|
console.error('');
|
|
433
|
-
|
|
434
|
-
// Return appropriate exit code
|
|
435
|
-
if (error.code === 'VALIDATION_ERROR') return EXIT_CODES.FAILURE;
|
|
436
|
-
if (error.code === 'LIMIT_EXCEEDED') return EXIT_CODES.WARNING;
|
|
437
695
|
return EXIT_CODES.ERROR;
|
|
438
696
|
}
|
|
439
697
|
|
|
440
|
-
//
|
|
441
|
-
// SARIF OUTPUT
|
|
442
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
443
|
-
|
|
698
|
+
// SARIF export
|
|
444
699
|
function formatSARIF(findings, options = {}) {
|
|
445
700
|
const { projectPath = '.', version = '1.0.0' } = options;
|
|
446
|
-
|
|
447
701
|
const rules = new Map();
|
|
448
702
|
const results = [];
|
|
449
703
|
|
|
450
704
|
for (const f of findings) {
|
|
451
705
|
const ruleId = f.ruleId || f.id || `vibecheck/${f.category || 'general'}`;
|
|
452
|
-
|
|
453
706
|
if (!rules.has(ruleId)) {
|
|
454
707
|
rules.set(ruleId, {
|
|
455
708
|
id: ruleId,
|
|
456
709
|
name: f.category || 'general',
|
|
457
710
|
shortDescription: { text: f.title || f.message },
|
|
458
|
-
defaultConfiguration: {
|
|
459
|
-
level: sarifLevel(f.severity),
|
|
460
|
-
},
|
|
711
|
+
defaultConfiguration: { level: sarifLevel(f.severity) },
|
|
461
712
|
helpUri: 'https://vibecheck.dev/docs/rules/' + ruleId,
|
|
462
713
|
});
|
|
463
714
|
}
|
|
@@ -498,46 +749,34 @@ function formatSARIF(findings, options = {}) {
|
|
|
498
749
|
},
|
|
499
750
|
},
|
|
500
751
|
results,
|
|
501
|
-
invocations: [{
|
|
502
|
-
executionSuccessful: true,
|
|
503
|
-
endTimeUtc: new Date().toISOString(),
|
|
504
|
-
}],
|
|
752
|
+
invocations: [{ executionSuccessful: true, endTimeUtc: new Date().toISOString() }],
|
|
505
753
|
}],
|
|
506
754
|
};
|
|
507
755
|
}
|
|
508
756
|
|
|
509
757
|
function sarifLevel(severity) {
|
|
510
758
|
const levels = {
|
|
511
|
-
critical: 'error',
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
medium: 'warning',
|
|
515
|
-
WARN: 'warning',
|
|
516
|
-
warning: 'warning',
|
|
517
|
-
low: 'note',
|
|
518
|
-
INFO: 'note',
|
|
519
|
-
info: 'none',
|
|
759
|
+
critical: 'error', BLOCK: 'error', high: 'error',
|
|
760
|
+
medium: 'warning', WARN: 'warning', warning: 'warning',
|
|
761
|
+
low: 'note', INFO: 'note', info: 'none',
|
|
520
762
|
};
|
|
521
763
|
return levels[severity] || 'warning';
|
|
522
764
|
}
|
|
523
765
|
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
//
|
|
766
|
+
// Legacy compatibility exports
|
|
767
|
+
function renderBreakdown(breakdown) {
|
|
768
|
+
// Simplified - can expand later
|
|
769
|
+
return '';
|
|
770
|
+
}
|
|
527
771
|
|
|
528
772
|
module.exports = {
|
|
529
|
-
// Main formatters
|
|
530
773
|
formatScanOutput,
|
|
531
774
|
formatSARIF,
|
|
532
|
-
|
|
533
|
-
// Component renderers
|
|
534
775
|
renderLayers,
|
|
535
776
|
renderCoverageMap,
|
|
536
777
|
renderBreakdown,
|
|
537
|
-
renderBlockers,
|
|
538
|
-
renderCategorySummary,
|
|
539
|
-
|
|
540
|
-
// Utilities
|
|
778
|
+
renderBlockers: renderBlockersTable,
|
|
779
|
+
renderCategorySummary: renderCategoryTable,
|
|
541
780
|
calculateScore,
|
|
542
781
|
getExitCode,
|
|
543
782
|
printError,
|