@vibecheckai/cli 3.1.8 → 3.2.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/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/agent-firewall/change-packet/builder.js +214 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/analysis-core.js +198 -180
- package/bin/runners/lib/analyzers.js +1394 -224
- package/bin/runners/lib/detectors-v2.js +560 -641
- package/bin/runners/lib/entitlements-v2.js +48 -1
- package/bin/runners/lib/evidence-pack.js +678 -0
- package/bin/runners/lib/fingerprint.js +377 -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/route-truth.js +1167 -322
- package/bin/runners/lib/scan-output.js +558 -235
- package/bin/runners/lib/ship-output.js +901 -641
- package/bin/runners/lib/truth.js +1004 -321
- package/bin/runners/runAgent.js +161 -0
- 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/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.js +56 -0
- 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 +351 -14
- package/bin/runners/runShip.js +19 -3
- package/bin/runners/runTruth.js +89 -0
- package/bin/runners/runWatch.js +14 -1
- package/bin/vibecheck.js +32 -2
- package/mcp-server/agent-firewall-interceptor.js +164 -0
- package/mcp-server/consolidated-tools.js +408 -42
- package/mcp-server/index.js +498 -327
- 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-context.js +131 -90
- package/mcp-server/truth-firewall-tools.js +1494 -941
- 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,327 @@ const {
|
|
|
24
25
|
} = require('./terminal-ui');
|
|
25
26
|
|
|
26
27
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
27
|
-
//
|
|
28
|
+
// ENHANCED COLOR PALETTE
|
|
28
29
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
29
30
|
|
|
30
|
-
|
|
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
|
+
};
|
|
72
|
+
|
|
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: '▄',
|
|
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, '');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
119
|
+
// PREMIUM SCORE DISPLAY
|
|
120
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
121
|
+
|
|
122
|
+
function renderPremiumScoreCard(score, options = {}) {
|
|
123
|
+
const { verdict = 'BLOCK', findings = {}, duration, cached } = options;
|
|
31
124
|
const lines = [];
|
|
32
|
-
lines.push(renderSection('ANALYSIS LAYERS', '⚡'));
|
|
33
|
-
lines.push('');
|
|
34
125
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
}
|
|
42
144
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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}`;
|
|
51
171
|
} else {
|
|
52
|
-
|
|
172
|
+
progressBar += `${palette.muted}${B.light}${ansi.reset}`;
|
|
53
173
|
}
|
|
54
|
-
|
|
55
|
-
const duration = layer.duration ? `${ansi.dim}${layer.duration}ms${ansi.reset}` : '';
|
|
56
|
-
const findings = layer.findings !== undefined ? `${colors.accent}${layer.findings}${ansi.reset} ${ansi.dim}findings${ansi.reset}` : '';
|
|
57
|
-
|
|
58
|
-
lines.push(` ${status} ${config.icon} ${config.name.padEnd(20)} ${duration.padEnd(15)} ${findings}`);
|
|
59
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}`);
|
|
60
207
|
|
|
61
208
|
return lines.join('\n');
|
|
62
209
|
}
|
|
63
210
|
|
|
64
211
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
65
|
-
//
|
|
212
|
+
// DIFF SUMMARY (NEW / FIXED / PERSISTING)
|
|
66
213
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
67
214
|
|
|
68
|
-
function
|
|
69
|
-
if (!
|
|
215
|
+
function renderDiffSummary(diff) {
|
|
216
|
+
if (!diff) return '';
|
|
70
217
|
|
|
71
218
|
const lines = [];
|
|
72
|
-
|
|
73
|
-
lines.push('');
|
|
219
|
+
const width = 70;
|
|
74
220
|
|
|
75
|
-
|
|
76
|
-
|
|
221
|
+
lines.push('');
|
|
222
|
+
lines.push(` ${palette.header}${ansi.bold}📊 CHANGES SINCE LAST SCAN${ansi.reset}`);
|
|
223
|
+
lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
|
|
224
|
+
lines.push('');
|
|
77
225
|
|
|
78
|
-
|
|
79
|
-
const barWidth = 50;
|
|
80
|
-
const filled = Math.round((pct / 100) * barWidth);
|
|
81
|
-
const bar = `${color}${'█'.repeat(filled)}${ansi.dim}${'░'.repeat(barWidth - filled)}${ansi.reset}`;
|
|
226
|
+
const parts = [];
|
|
82
227
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
228
|
+
// NEW findings (bad - things got worse)
|
|
229
|
+
if (diff.summary.newCount > 0) {
|
|
230
|
+
parts.push(` ${ansi.bgRgb(180, 50, 50)}${ansi.bold} +${diff.summary.newCount} NEW ${ansi.reset}`);
|
|
231
|
+
lines.push(` ${palette.critical}▲${ansi.reset} ${ansi.bold}${diff.summary.newCount}${ansi.reset} ${palette.critical}new issues${ansi.reset} introduced`);
|
|
232
|
+
}
|
|
86
233
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
['Dead Links', coverage.deadLinks || 0],
|
|
93
|
-
];
|
|
234
|
+
// FIXED findings (good - things got better)
|
|
235
|
+
if (diff.summary.fixedCount > 0) {
|
|
236
|
+
parts.push(` ${ansi.bgRgb(50, 150, 80)}${ansi.bold} -${diff.summary.fixedCount} FIXED ${ansi.reset}`);
|
|
237
|
+
lines.push(` ${palette.pass}▼${ansi.reset} ${ansi.bold}${diff.summary.fixedCount}${ansi.reset} ${palette.pass}issues fixed${ansi.reset} since last scan`);
|
|
238
|
+
}
|
|
94
239
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
: ansi.reset;
|
|
99
|
-
lines.push(` ${ansi.dim}${label}:${ansi.reset} ${valueColor}${ansi.bold}${value}${ansi.reset}`);
|
|
240
|
+
// PERSISTING (neutral - still there)
|
|
241
|
+
if (diff.summary.persistingCount > 0) {
|
|
242
|
+
lines.push(` ${palette.muted}●${ansi.reset} ${ansi.bold}${diff.summary.persistingCount}${ansi.reset} ${palette.muted}persisting issues${ansi.reset}`);
|
|
100
243
|
}
|
|
101
244
|
|
|
102
|
-
//
|
|
103
|
-
if (
|
|
245
|
+
// Show top new issues
|
|
246
|
+
if (diff.new && diff.new.length > 0) {
|
|
104
247
|
lines.push('');
|
|
105
|
-
lines.push(` ${
|
|
106
|
-
for (const
|
|
107
|
-
const
|
|
108
|
-
|
|
248
|
+
lines.push(` ${palette.critical}${ansi.bold}New issues:${ansi.reset}`);
|
|
249
|
+
for (const finding of diff.new.slice(0, 3)) {
|
|
250
|
+
const file = finding.file || finding.evidence?.[0]?.file || '';
|
|
251
|
+
const shortFile = file.split('/').slice(-2).join('/');
|
|
252
|
+
lines.push(` ${palette.critical}+${ansi.reset} ${finding.category}: ${palette.muted}${shortFile}${ansi.reset}`);
|
|
109
253
|
}
|
|
110
|
-
if (
|
|
111
|
-
lines.push(` ${
|
|
254
|
+
if (diff.new.length > 3) {
|
|
255
|
+
lines.push(` ${palette.muted}... and ${diff.new.length - 3} more${ansi.reset}`);
|
|
112
256
|
}
|
|
113
257
|
}
|
|
114
258
|
|
|
115
|
-
//
|
|
116
|
-
if (
|
|
259
|
+
// Show top fixed issues
|
|
260
|
+
if (diff.fixed && diff.fixed.length > 0) {
|
|
117
261
|
lines.push('');
|
|
118
|
-
lines.push(` ${
|
|
119
|
-
for (const
|
|
120
|
-
|
|
262
|
+
lines.push(` ${palette.pass}${ansi.bold}Fixed issues:${ansi.reset}`);
|
|
263
|
+
for (const finding of diff.fixed.slice(0, 3)) {
|
|
264
|
+
const file = finding.file || '';
|
|
265
|
+
const shortFile = file.split('/').slice(-2).join('/');
|
|
266
|
+
lines.push(` ${palette.pass}-${ansi.reset} ${finding.category}: ${palette.muted}${shortFile}${ansi.reset}`);
|
|
121
267
|
}
|
|
122
|
-
if (
|
|
123
|
-
lines.push(` ${
|
|
268
|
+
if (diff.fixed.length > 3) {
|
|
269
|
+
lines.push(` ${palette.muted}... and ${diff.fixed.length - 3} more${ansi.reset}`);
|
|
124
270
|
}
|
|
125
271
|
}
|
|
126
272
|
|
|
127
|
-
return lines.join('\n');
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
131
|
-
// BREAKDOWN DISPLAY
|
|
132
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
133
|
-
|
|
134
|
-
function renderBreakdown(breakdown) {
|
|
135
|
-
if (!breakdown) return '';
|
|
136
|
-
|
|
137
|
-
const lines = [];
|
|
138
|
-
lines.push(renderSection('SCORE BREAKDOWN', '📊'));
|
|
139
273
|
lines.push('');
|
|
140
274
|
|
|
141
|
-
const items = [
|
|
142
|
-
{ key: 'deadLinks', label: 'Dead Links', icon: '🔗' },
|
|
143
|
-
{ key: 'orphanRoutes', label: 'Orphan Routes', icon: '👻' },
|
|
144
|
-
{ key: 'runtimeFailures', label: 'Runtime 404s', icon: '💥' },
|
|
145
|
-
{ key: 'unresolvedDynamic', label: 'Unresolved Dynamic', icon: '❓' },
|
|
146
|
-
{ key: 'placeholders', label: 'Placeholders', icon: '📝' },
|
|
147
|
-
{ key: 'secretsExposed', label: 'Secrets Exposed', icon: '🔐' },
|
|
148
|
-
{ key: 'authBypass', label: 'Auth Bypass', icon: '🚪' },
|
|
149
|
-
{ key: 'mockData', label: 'Mock Data', icon: '🎭' },
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
for (const item of items) {
|
|
153
|
-
const data = breakdown[item.key];
|
|
154
|
-
if (!data && data !== 0) continue;
|
|
155
|
-
|
|
156
|
-
const count = typeof data === 'object' ? data.count : data;
|
|
157
|
-
const penalty = typeof data === 'object' ? data.penalty : 0;
|
|
158
|
-
|
|
159
|
-
const status = count === 0 ? `${colors.success}${icons.success}${ansi.reset}` : `${colors.error}${icons.error}${ansi.reset}`;
|
|
160
|
-
const countColor = count === 0 ? colors.success : colors.error;
|
|
161
|
-
const penaltyStr = penalty > 0 ? `${ansi.dim}-${penalty} pts${ansi.reset}` : `${ansi.dim} ---${ansi.reset}`;
|
|
162
|
-
|
|
163
|
-
lines.push(` ${status} ${item.icon} ${item.label.padEnd(22)} ${countColor}${ansi.bold}${String(count).padStart(3)}${ansi.reset} ${penaltyStr}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
275
|
return lines.join('\n');
|
|
167
276
|
}
|
|
168
277
|
|
|
169
278
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
170
|
-
// BLOCKERS
|
|
279
|
+
// BLOCKERS TABLE
|
|
171
280
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
172
281
|
|
|
173
|
-
function
|
|
174
|
-
const { maxItems =
|
|
282
|
+
function renderBlockersTable(blockers, options = {}) {
|
|
283
|
+
const { maxItems = 10, showStatus = false } = options;
|
|
175
284
|
|
|
176
285
|
if (!blockers || blockers.length === 0) {
|
|
177
286
|
const lines = [];
|
|
178
|
-
lines.push(renderSection('SHIP BLOCKERS', '🚀'));
|
|
179
287
|
lines.push('');
|
|
180
|
-
lines.push(` ${
|
|
288
|
+
lines.push(` ${palette.pass}${ansi.bold}✓ NO BLOCKERS${ansi.reset} ${palette.muted}— Your code is ready to ship!${ansi.reset}`);
|
|
181
289
|
return lines.join('\n');
|
|
182
290
|
}
|
|
183
291
|
|
|
184
292
|
const lines = [];
|
|
185
|
-
|
|
293
|
+
const width = 70;
|
|
294
|
+
|
|
295
|
+
// Count new blockers for header
|
|
296
|
+
const newCount = blockers.filter(b => b.status === 'NEW').length;
|
|
297
|
+
const headerExtra = newCount > 0 ? ` ${ansi.bgRgb(180, 50, 50)}${ansi.bold} +${newCount} NEW ${ansi.reset}` : '';
|
|
298
|
+
|
|
299
|
+
lines.push('');
|
|
300
|
+
lines.push(` ${palette.critical}${ansi.bold}🚨 SHIP BLOCKERS (${blockers.length})${ansi.reset}${headerExtra}`);
|
|
301
|
+
lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
|
|
186
302
|
lines.push('');
|
|
187
303
|
|
|
188
|
-
for (
|
|
304
|
+
for (let i = 0; i < Math.min(blockers.length, maxItems); i++) {
|
|
305
|
+
const blocker = blockers[i];
|
|
189
306
|
const sevColor = blocker.severity === 'critical' || blocker.severity === 'BLOCK'
|
|
190
|
-
?
|
|
191
|
-
:
|
|
307
|
+
? palette.criticalBg
|
|
308
|
+
: palette.highBg;
|
|
192
309
|
const sevLabel = blocker.severity === 'critical' || blocker.severity === 'BLOCK'
|
|
193
|
-
? '
|
|
194
|
-
: '
|
|
310
|
+
? ' BLOCK '
|
|
311
|
+
: ' HIGH ';
|
|
195
312
|
|
|
196
|
-
|
|
313
|
+
// Row number
|
|
314
|
+
const num = `${palette.muted}${String(i + 1).padStart(2)}.${ansi.reset}`;
|
|
197
315
|
|
|
198
|
-
|
|
199
|
-
|
|
316
|
+
// Severity badge
|
|
317
|
+
const badge = `${sevColor}${ansi.bold}${sevLabel}${ansi.reset}`;
|
|
318
|
+
|
|
319
|
+
// Status badge (NEW/PERSISTING)
|
|
320
|
+
let statusBadge = '';
|
|
321
|
+
if (showStatus && blocker.status === 'NEW') {
|
|
322
|
+
statusBadge = ` ${ansi.bgRgb(180, 50, 50)}${ansi.bold} NEW ${ansi.reset}`;
|
|
200
323
|
}
|
|
201
324
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
325
|
+
// Title (truncated)
|
|
326
|
+
const title = truncate(blocker.title || blocker.message || 'Unknown issue', 42);
|
|
327
|
+
|
|
328
|
+
lines.push(` ${num} ${badge}${statusBadge} ${ansi.bold}${title}${ansi.reset}`);
|
|
329
|
+
|
|
330
|
+
// File location
|
|
331
|
+
if (blocker.file || blocker.evidence?.[0]?.file) {
|
|
332
|
+
const file = blocker.file || blocker.evidence?.[0]?.file;
|
|
333
|
+
const line = blocker.line || blocker.evidence?.[0]?.lines;
|
|
334
|
+
const fileDisplay = file + (line ? `:${line}` : '');
|
|
335
|
+
lines.push(` ${palette.muted}└─${ansi.reset} ${palette.accent}${truncate(fileDisplay, 55)}${ansi.reset}`);
|
|
205
336
|
}
|
|
206
337
|
|
|
207
|
-
|
|
208
|
-
|
|
338
|
+
// Evidence/why
|
|
339
|
+
if (blocker.description || blocker.why) {
|
|
340
|
+
const desc = truncate(blocker.description || blocker.why, 55);
|
|
341
|
+
lines.push(` ${palette.muted}${desc}${ansi.reset}`);
|
|
209
342
|
}
|
|
210
343
|
|
|
211
344
|
lines.push('');
|
|
212
345
|
}
|
|
213
346
|
|
|
214
347
|
if (blockers.length > maxItems) {
|
|
215
|
-
lines.push(` ${
|
|
348
|
+
lines.push(` ${palette.muted}... and ${blockers.length - maxItems} more blockers${ansi.reset}`);
|
|
216
349
|
lines.push('');
|
|
217
350
|
}
|
|
218
351
|
|
|
@@ -220,20 +353,23 @@ function renderBlockers(blockers, options = {}) {
|
|
|
220
353
|
}
|
|
221
354
|
|
|
222
355
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
223
|
-
// CATEGORY
|
|
356
|
+
// CATEGORY TABLE WITH VISUAL BARS
|
|
224
357
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
225
358
|
|
|
226
|
-
function
|
|
359
|
+
function renderCategoryTable(findings) {
|
|
227
360
|
if (!findings || findings.length === 0) return '';
|
|
228
361
|
|
|
229
362
|
// Group by category
|
|
230
363
|
const categories = {};
|
|
364
|
+
let maxTotal = 0;
|
|
365
|
+
|
|
231
366
|
for (const f of findings) {
|
|
232
|
-
const cat = f.category || f.ruleId?.split('/')[0] || '
|
|
367
|
+
const cat = f.category || f.ruleId?.split('/')[0] || 'Other';
|
|
233
368
|
if (!categories[cat]) {
|
|
234
369
|
categories[cat] = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
|
|
235
370
|
}
|
|
236
371
|
categories[cat].total++;
|
|
372
|
+
maxTotal = Math.max(maxTotal, categories[cat].total);
|
|
237
373
|
|
|
238
374
|
const sev = (f.severity || '').toLowerCase();
|
|
239
375
|
if (sev === 'critical' || sev === 'block') categories[cat].critical++;
|
|
@@ -242,46 +378,277 @@ function renderCategorySummary(findings) {
|
|
|
242
378
|
else categories[cat].low++;
|
|
243
379
|
}
|
|
244
380
|
|
|
381
|
+
const categoryIcons = {
|
|
382
|
+
ENVCONTRACT: '📋', MISSINGROUTE: '🔗', HARDCODEDSECRET: '🔑', TODOFIXME: '📝',
|
|
383
|
+
FAKESUCCESS: '✨', GHOSTAUTH: '👻', MOCKDATA: '🎭', CONSOLELOG: '🖥️',
|
|
384
|
+
DEADCODE: '💀', DEPRECATEDAPI: '⚠️', EMPTYCATCH: '🕳️', UNSAFEREGEX: '⚡',
|
|
385
|
+
SECURITY: '🛡️', BILLING: '💳', ROUTE: '🔗', AUTH: '🔐', SECRET: '🔑',
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const categoryColors = [
|
|
389
|
+
palette.cat1, palette.cat2, palette.cat3, palette.cat4, palette.cat5,
|
|
390
|
+
palette.cat6, palette.cat7, palette.cat8, palette.cat9, palette.cat10,
|
|
391
|
+
palette.cat11, palette.cat12,
|
|
392
|
+
];
|
|
393
|
+
|
|
245
394
|
const lines = [];
|
|
246
|
-
|
|
395
|
+
const width = 70;
|
|
396
|
+
|
|
397
|
+
lines.push('');
|
|
398
|
+
lines.push(` ${palette.header}${ansi.bold}📊 FINDINGS BY CATEGORY${ansi.reset}`);
|
|
399
|
+
lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
|
|
247
400
|
lines.push('');
|
|
248
401
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
SECRET: '🔑',
|
|
253
|
-
BILLING: '💳',
|
|
254
|
-
MOCK: '🎭',
|
|
255
|
-
DEAD_UI: '👻',
|
|
256
|
-
FAKE_SUCCESS: '✨',
|
|
257
|
-
REALITY: '🔬',
|
|
258
|
-
QUALITY: '📋',
|
|
259
|
-
CONFIG: '⚙️',
|
|
260
|
-
};
|
|
402
|
+
// Table header
|
|
403
|
+
lines.push(` ${palette.muted} CATEGORY COUNT SEVERITY BREAKDOWN BAR${ansi.reset}`);
|
|
404
|
+
lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
|
|
261
405
|
|
|
406
|
+
// Sort by total count descending
|
|
262
407
|
const sortedCategories = Object.entries(categories)
|
|
263
408
|
.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].
|
|
409
|
+
// Sort by criticality first, then by total
|
|
410
|
+
const aCrit = a[1].critical * 1000 + a[1].high * 100 + a[1].total;
|
|
411
|
+
const bCrit = b[1].critical * 1000 + b[1].high * 100 + b[1].total;
|
|
267
412
|
return bCrit - aCrit;
|
|
268
413
|
});
|
|
269
414
|
|
|
415
|
+
let colorIdx = 0;
|
|
270
416
|
for (const [cat, counts] of sortedCategories) {
|
|
271
417
|
const icon = categoryIcons[cat.toUpperCase()] || '•';
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
418
|
+
const catColor = categoryColors[colorIdx % categoryColors.length];
|
|
419
|
+
colorIdx++;
|
|
420
|
+
|
|
421
|
+
// Category name
|
|
422
|
+
const catName = truncate(cat, 15).padEnd(15);
|
|
423
|
+
|
|
424
|
+
// Count
|
|
425
|
+
const countStr = String(counts.total).padStart(4);
|
|
426
|
+
|
|
427
|
+
// Severity breakdown
|
|
428
|
+
const critStr = counts.critical > 0 ? `${palette.critical}${counts.critical}C${ansi.reset}` : `${palette.muted}--${ansi.reset}`;
|
|
429
|
+
const highStr = counts.high > 0 ? `${palette.high}${counts.high}H${ansi.reset}` : `${palette.muted}--${ansi.reset}`;
|
|
430
|
+
const medStr = counts.medium > 0 ? `${palette.medium}${counts.medium}M${ansi.reset}` : `${palette.muted}--${ansi.reset}`;
|
|
431
|
+
const lowStr = counts.low > 0 ? `${palette.low}${counts.low}L${ansi.reset}` : `${palette.muted}--${ansi.reset}`;
|
|
432
|
+
|
|
433
|
+
// Visual bar
|
|
434
|
+
const barWidth = 15;
|
|
435
|
+
const barFill = Math.round((counts.total / maxTotal) * barWidth);
|
|
436
|
+
let bar = '';
|
|
437
|
+
for (let i = 0; i < barWidth; i++) {
|
|
438
|
+
if (i < barFill) {
|
|
439
|
+
bar += `${catColor}${B.full}${ansi.reset}`;
|
|
440
|
+
} else {
|
|
441
|
+
bar += `${palette.muted}${B.light}${ansi.reset}`;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
276
444
|
|
|
277
|
-
lines.push(` ${icon} ${
|
|
445
|
+
lines.push(` ${icon} ${catColor}${catName}${ansi.reset} ${ansi.bold}${countStr}${ansi.reset} ${critStr} ${highStr} ${medStr} ${lowStr} ${bar}`);
|
|
278
446
|
}
|
|
279
447
|
|
|
448
|
+
lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
|
|
449
|
+
|
|
450
|
+
// Total
|
|
451
|
+
const totalCount = findings.length;
|
|
452
|
+
const totalCrit = findings.filter(f => f.severity === 'critical' || f.severity === 'BLOCK').length;
|
|
453
|
+
const totalHigh = findings.filter(f => f.severity === 'high').length;
|
|
454
|
+
const totalMed = findings.filter(f => f.severity === 'medium' || f.severity === 'WARN' || f.severity === 'warning').length;
|
|
455
|
+
const totalLow = findings.filter(f => f.severity === 'low' || f.severity === 'INFO').length;
|
|
456
|
+
|
|
457
|
+
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}`);
|
|
458
|
+
|
|
280
459
|
return lines.join('\n');
|
|
281
460
|
}
|
|
282
461
|
|
|
283
462
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
284
|
-
//
|
|
463
|
+
// TOP FINDINGS DETAILS
|
|
464
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
465
|
+
|
|
466
|
+
function renderTopFindings(findings, options = {}) {
|
|
467
|
+
const { maxItems = 5, showFix = true } = options;
|
|
468
|
+
|
|
469
|
+
if (!findings || findings.length === 0) return '';
|
|
470
|
+
|
|
471
|
+
// Get top critical/high findings
|
|
472
|
+
const topFindings = findings
|
|
473
|
+
.filter(f => f.severity === 'critical' || f.severity === 'BLOCK' || f.severity === 'high')
|
|
474
|
+
.slice(0, maxItems);
|
|
475
|
+
|
|
476
|
+
if (topFindings.length === 0) return '';
|
|
477
|
+
|
|
478
|
+
const lines = [];
|
|
479
|
+
const width = 70;
|
|
480
|
+
|
|
481
|
+
lines.push('');
|
|
482
|
+
lines.push(` ${palette.header}${ansi.bold}🔍 TOP ISSUES TO FIX${ansi.reset}`);
|
|
483
|
+
lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
|
|
484
|
+
|
|
485
|
+
for (let i = 0; i < topFindings.length; i++) {
|
|
486
|
+
const f = topFindings[i];
|
|
487
|
+
const num = `${palette.accent}${i + 1}${ansi.reset}`;
|
|
488
|
+
const icon = f.severity === 'critical' || f.severity === 'BLOCK' ? '🔴' : '🟠';
|
|
489
|
+
|
|
490
|
+
lines.push('');
|
|
491
|
+
lines.push(` ${num}. ${icon} ${ansi.bold}${truncate(f.title || f.message, 55)}${ansi.reset}`);
|
|
492
|
+
|
|
493
|
+
if (f.file) {
|
|
494
|
+
lines.push(` ${palette.accent}${f.file}${f.line ? `:${f.line}` : ''}${ansi.reset}`);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (f.description || f.why) {
|
|
498
|
+
lines.push(` ${palette.muted}${truncate(f.description || f.why, 60)}${ansi.reset}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (showFix && (f.fix || f.fixHints?.[0])) {
|
|
502
|
+
const fixHint = f.fix || f.fixHints?.[0];
|
|
503
|
+
lines.push(` ${palette.pass}→ ${truncate(fixHint, 58)}${ansi.reset}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return lines.join('\n');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
511
|
+
// ANALYSIS LAYERS STATUS
|
|
512
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
513
|
+
|
|
514
|
+
function renderLayers(layers) {
|
|
515
|
+
if (!layers || layers.length === 0) return '';
|
|
516
|
+
|
|
517
|
+
const lines = [];
|
|
518
|
+
const width = 70;
|
|
519
|
+
|
|
520
|
+
lines.push('');
|
|
521
|
+
lines.push(` ${palette.header}${ansi.bold}⚡ ANALYSIS LAYERS${ansi.reset}`);
|
|
522
|
+
lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
|
|
523
|
+
lines.push('');
|
|
524
|
+
|
|
525
|
+
const layerConfig = {
|
|
526
|
+
ast: { name: 'AST Analysis', icon: '🔍', desc: 'Static code parsing' },
|
|
527
|
+
truth: { name: 'Build Truth', icon: '📦', desc: 'Manifest verification' },
|
|
528
|
+
reality: { name: 'Reality Check', icon: '🎭', desc: 'Runtime testing' },
|
|
529
|
+
realitySniff: { name: 'Reality Sniff', icon: '🔬', desc: 'AI artifact detection' },
|
|
530
|
+
detection: { name: 'Detection', icon: '🛡️', desc: 'Security patterns' },
|
|
531
|
+
codeQuality: { name: 'Code Quality', icon: '📋', desc: 'TODO, MOCK, console.log' },
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
for (const layer of layers) {
|
|
535
|
+
const config = layerConfig[layer.name] || { name: layer.name, icon: '○', desc: '' };
|
|
536
|
+
|
|
537
|
+
let status, statusIcon;
|
|
538
|
+
if (layer.skipped) {
|
|
539
|
+
status = `${palette.muted}skipped${ansi.reset}`;
|
|
540
|
+
statusIcon = `${palette.muted}○${ansi.reset}`;
|
|
541
|
+
} else if (layer.error) {
|
|
542
|
+
status = `${palette.fail}error${ansi.reset}`;
|
|
543
|
+
statusIcon = `${palette.fail}✗${ansi.reset}`;
|
|
544
|
+
} else {
|
|
545
|
+
status = `${palette.pass}done${ansi.reset}`;
|
|
546
|
+
statusIcon = `${palette.pass}✓${ansi.reset}`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const duration = layer.duration ? `${palette.muted}${layer.duration}ms${ansi.reset}` : '';
|
|
550
|
+
const findingsCount = layer.findings !== undefined ? `${palette.accent}${layer.findings}${ansi.reset} findings` : '';
|
|
551
|
+
|
|
552
|
+
lines.push(` ${statusIcon} ${config.icon} ${config.name.padEnd(18)} ${status.padEnd(20)} ${duration.padEnd(12)} ${findingsCount}`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return lines.join('\n');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
559
|
+
// COVERAGE MAP
|
|
560
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
561
|
+
|
|
562
|
+
function renderCoverageMap(coverage) {
|
|
563
|
+
if (!coverage) return '';
|
|
564
|
+
|
|
565
|
+
const lines = [];
|
|
566
|
+
const width = 70;
|
|
567
|
+
|
|
568
|
+
lines.push('');
|
|
569
|
+
lines.push(` ${palette.header}${ansi.bold}🗺️ ROUTE COVERAGE${ansi.reset}`);
|
|
570
|
+
lines.push(` ${palette.border}${B.lh.repeat(width - 4)}${ansi.reset}`);
|
|
571
|
+
lines.push('');
|
|
572
|
+
|
|
573
|
+
const pct = coverage.coveragePercent || 0;
|
|
574
|
+
const color = pct >= 80 ? palette.pass : pct >= 60 ? palette.warn : palette.fail;
|
|
575
|
+
|
|
576
|
+
// Coverage percentage with big number
|
|
577
|
+
lines.push(` ${color}${ansi.bold}${pct}%${ansi.reset} ${palette.muted}of routes reachable from root${ansi.reset}`);
|
|
578
|
+
|
|
579
|
+
// Progress bar
|
|
580
|
+
const barWidth = 50;
|
|
581
|
+
const filled = Math.round((pct / 100) * barWidth);
|
|
582
|
+
let bar = '';
|
|
583
|
+
for (let i = 0; i < barWidth; i++) {
|
|
584
|
+
if (i < filled) {
|
|
585
|
+
bar += `${color}${B.full}${ansi.reset}`;
|
|
586
|
+
} else {
|
|
587
|
+
bar += `${palette.muted}${B.light}${ansi.reset}`;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
lines.push(` ${bar}`);
|
|
591
|
+
lines.push('');
|
|
592
|
+
|
|
593
|
+
// Stats grid
|
|
594
|
+
const stats = [
|
|
595
|
+
['Total Routes', coverage.totalRoutes || 0, palette.accent],
|
|
596
|
+
['Reachable', coverage.reachableFromRoot || 0, palette.pass],
|
|
597
|
+
['Orphaned', coverage.orphanedRoutes || 0, coverage.orphanedRoutes > 0 ? palette.warn : palette.pass],
|
|
598
|
+
['Dead Links', coverage.deadLinks || 0, coverage.deadLinks > 0 ? palette.fail : palette.pass],
|
|
599
|
+
];
|
|
600
|
+
|
|
601
|
+
let statsLine = ' ';
|
|
602
|
+
for (const [label, value, col] of stats) {
|
|
603
|
+
statsLine += `${palette.muted}${label}:${ansi.reset} ${col}${ansi.bold}${value}${ansi.reset} `;
|
|
604
|
+
}
|
|
605
|
+
lines.push(statsLine);
|
|
606
|
+
|
|
607
|
+
return lines.join('\n');
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
611
|
+
// UPSELL BANNER
|
|
612
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
613
|
+
|
|
614
|
+
function renderUpsellBanner(severityCounts) {
|
|
615
|
+
const { critical = 0, high = 0, medium = 0, low = 0 } = severityCounts;
|
|
616
|
+
const total = critical + high + medium + low;
|
|
617
|
+
|
|
618
|
+
if (total === 0) return '';
|
|
619
|
+
|
|
620
|
+
const lines = [];
|
|
621
|
+
const width = 68;
|
|
622
|
+
|
|
623
|
+
lines.push('');
|
|
624
|
+
lines.push(` ${palette.border}${B.rtl}${B.lh.repeat(width)}${B.rtr}${ansi.reset}`);
|
|
625
|
+
lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
|
|
626
|
+
|
|
627
|
+
if (critical > 0) {
|
|
628
|
+
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}`);
|
|
629
|
+
lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
|
|
630
|
+
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}`);
|
|
631
|
+
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}`);
|
|
632
|
+
} else if (medium > 0) {
|
|
633
|
+
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}`);
|
|
634
|
+
lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
|
|
635
|
+
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}`);
|
|
636
|
+
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}`);
|
|
637
|
+
} else {
|
|
638
|
+
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}`);
|
|
639
|
+
lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
|
|
640
|
+
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}`);
|
|
641
|
+
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}`);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
lines.push(` ${palette.border}${B.lv}${ansi.reset}${' '.repeat(width)}${palette.border}${B.lv}${ansi.reset}`);
|
|
645
|
+
lines.push(` ${palette.border}${B.rbl}${B.lh.repeat(width)}${B.rbr}${ansi.reset}`);
|
|
646
|
+
|
|
647
|
+
return lines.join('\n');
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
651
|
+
// MAIN OUTPUT FORMATTER
|
|
285
652
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
286
653
|
|
|
287
654
|
function formatScanOutput(result, options = {}) {
|
|
@@ -291,7 +658,7 @@ function formatScanOutput(result, options = {}) {
|
|
|
291
658
|
return JSON.stringify(result, null, 2);
|
|
292
659
|
}
|
|
293
660
|
|
|
294
|
-
const { verdict, findings = [], layers = [], coverage, breakdown, timings = {} } = result;
|
|
661
|
+
const { verdict, findings = [], layers = [], coverage, breakdown, timings = {}, diff } = result;
|
|
295
662
|
|
|
296
663
|
// Count findings by severity
|
|
297
664
|
const severityCounts = {
|
|
@@ -307,64 +674,61 @@ function formatScanOutput(result, options = {}) {
|
|
|
307
674
|
|
|
308
675
|
const lines = [];
|
|
309
676
|
|
|
310
|
-
//
|
|
311
|
-
|
|
677
|
+
// Diff summary (if baseline comparison was done)
|
|
678
|
+
if (diff && (diff.summary.newCount > 0 || diff.summary.fixedCount > 0)) {
|
|
679
|
+
lines.push(renderDiffSummary(diff));
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Premium score card
|
|
683
|
+
lines.push(renderPremiumScoreCard(score, {
|
|
312
684
|
verdict: verdictStatus,
|
|
313
685
|
findings: severityCounts,
|
|
314
686
|
duration: timings.total,
|
|
315
687
|
cached: result.cached,
|
|
316
688
|
}));
|
|
317
689
|
|
|
318
|
-
// Blockers (
|
|
690
|
+
// Blockers table (with status badges)
|
|
319
691
|
const blockers = findings.filter(f =>
|
|
320
692
|
f.severity === 'critical' || f.severity === 'BLOCK' || f.severity === 'high'
|
|
321
693
|
);
|
|
322
|
-
lines.push(
|
|
694
|
+
lines.push(renderBlockersTable(blockers, { showStatus: !!diff }));
|
|
323
695
|
|
|
324
|
-
// Category
|
|
696
|
+
// Category table with visual bars
|
|
325
697
|
if (findings.length > 0) {
|
|
326
|
-
lines.push(
|
|
698
|
+
lines.push(renderCategoryTable(findings));
|
|
327
699
|
}
|
|
328
700
|
|
|
329
701
|
// Verbose output
|
|
330
702
|
if (verbose) {
|
|
703
|
+
// Top findings with fix hints
|
|
704
|
+
lines.push(renderTopFindings(findings));
|
|
705
|
+
|
|
331
706
|
// Coverage map
|
|
332
707
|
if (coverage) {
|
|
333
|
-
lines.push('');
|
|
334
708
|
lines.push(renderCoverageMap(coverage));
|
|
335
709
|
}
|
|
336
710
|
|
|
337
|
-
//
|
|
338
|
-
if (breakdown) {
|
|
339
|
-
lines.push('');
|
|
340
|
-
lines.push(renderBreakdown(breakdown));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Layers
|
|
711
|
+
// Layers status
|
|
344
712
|
if (layers.length > 0) {
|
|
345
|
-
lines.push('');
|
|
346
713
|
lines.push(renderLayers(layers));
|
|
347
714
|
}
|
|
348
|
-
|
|
349
|
-
// All findings
|
|
350
|
-
if (findings.length > 0) {
|
|
351
|
-
lines.push('');
|
|
352
|
-
lines.push(renderFindingsList(findings, { maxItems: 20, groupBySeverity: true }));
|
|
353
|
-
}
|
|
354
715
|
}
|
|
355
716
|
|
|
356
|
-
// Timing
|
|
717
|
+
// Timing
|
|
357
718
|
if (timings.total) {
|
|
358
719
|
lines.push('');
|
|
359
|
-
lines.push(` ${
|
|
720
|
+
lines.push(` ${palette.muted}Completed in ${formatDuration(timings.total)}${ansi.reset}`);
|
|
360
721
|
}
|
|
361
722
|
|
|
723
|
+
// Upsell banner
|
|
724
|
+
lines.push(renderUpsellBanner(severityCounts));
|
|
725
|
+
|
|
362
726
|
lines.push('');
|
|
363
727
|
return lines.join('\n');
|
|
364
728
|
}
|
|
365
729
|
|
|
366
730
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
367
|
-
//
|
|
731
|
+
// UTILITIES
|
|
368
732
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
369
733
|
|
|
370
734
|
function calculateScore(severityCounts) {
|
|
@@ -377,10 +741,6 @@ function calculateScore(severityCounts) {
|
|
|
377
741
|
return Math.max(0, 100 - deductions);
|
|
378
742
|
}
|
|
379
743
|
|
|
380
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
381
|
-
// EXIT CODE DETERMINATION
|
|
382
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
383
|
-
|
|
384
744
|
const EXIT_CODES = {
|
|
385
745
|
SUCCESS: 0,
|
|
386
746
|
WARNING: 1,
|
|
@@ -390,9 +750,7 @@ const EXIT_CODES = {
|
|
|
390
750
|
|
|
391
751
|
function getExitCode(verdict) {
|
|
392
752
|
if (!verdict) return EXIT_CODES.ERROR;
|
|
393
|
-
|
|
394
753
|
const status = verdict.verdict || verdict;
|
|
395
|
-
|
|
396
754
|
switch (status) {
|
|
397
755
|
case 'PASS':
|
|
398
756
|
case 'SHIP':
|
|
@@ -407,57 +765,34 @@ function getExitCode(verdict) {
|
|
|
407
765
|
}
|
|
408
766
|
}
|
|
409
767
|
|
|
410
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
411
|
-
// ERROR DISPLAY
|
|
412
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
413
|
-
|
|
414
768
|
function printError(error, context = '') {
|
|
415
769
|
const prefix = context ? `${context}: ` : '';
|
|
416
|
-
|
|
417
770
|
console.error('');
|
|
418
|
-
console.error(` ${
|
|
419
|
-
|
|
771
|
+
console.error(` ${palette.fail}✗${ansi.reset} ${ansi.bold}${prefix}${error.message || error}${ansi.reset}`);
|
|
420
772
|
if (error.code) {
|
|
421
|
-
console.error(` ${
|
|
773
|
+
console.error(` ${palette.muted}Error code: ${error.code}${ansi.reset}`);
|
|
422
774
|
}
|
|
423
|
-
|
|
424
775
|
if (error.suggestion || error.fix) {
|
|
425
|
-
console.error(` ${
|
|
776
|
+
console.error(` ${palette.pass}→${ansi.reset} ${error.suggestion || error.fix}`);
|
|
426
777
|
}
|
|
427
|
-
|
|
428
|
-
if (error.docs || error.helpUrl) {
|
|
429
|
-
console.error(` ${ansi.dim}See: ${error.docs || error.helpUrl}${ansi.reset}`);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
778
|
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
779
|
return EXIT_CODES.ERROR;
|
|
438
780
|
}
|
|
439
781
|
|
|
440
|
-
//
|
|
441
|
-
// SARIF OUTPUT
|
|
442
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
443
|
-
|
|
782
|
+
// SARIF export
|
|
444
783
|
function formatSARIF(findings, options = {}) {
|
|
445
784
|
const { projectPath = '.', version = '1.0.0' } = options;
|
|
446
|
-
|
|
447
785
|
const rules = new Map();
|
|
448
786
|
const results = [];
|
|
449
787
|
|
|
450
788
|
for (const f of findings) {
|
|
451
789
|
const ruleId = f.ruleId || f.id || `vibecheck/${f.category || 'general'}`;
|
|
452
|
-
|
|
453
790
|
if (!rules.has(ruleId)) {
|
|
454
791
|
rules.set(ruleId, {
|
|
455
792
|
id: ruleId,
|
|
456
793
|
name: f.category || 'general',
|
|
457
794
|
shortDescription: { text: f.title || f.message },
|
|
458
|
-
defaultConfiguration: {
|
|
459
|
-
level: sarifLevel(f.severity),
|
|
460
|
-
},
|
|
795
|
+
defaultConfiguration: { level: sarifLevel(f.severity) },
|
|
461
796
|
helpUri: 'https://vibecheck.dev/docs/rules/' + ruleId,
|
|
462
797
|
});
|
|
463
798
|
}
|
|
@@ -498,46 +833,34 @@ function formatSARIF(findings, options = {}) {
|
|
|
498
833
|
},
|
|
499
834
|
},
|
|
500
835
|
results,
|
|
501
|
-
invocations: [{
|
|
502
|
-
executionSuccessful: true,
|
|
503
|
-
endTimeUtc: new Date().toISOString(),
|
|
504
|
-
}],
|
|
836
|
+
invocations: [{ executionSuccessful: true, endTimeUtc: new Date().toISOString() }],
|
|
505
837
|
}],
|
|
506
838
|
};
|
|
507
839
|
}
|
|
508
840
|
|
|
509
841
|
function sarifLevel(severity) {
|
|
510
842
|
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',
|
|
843
|
+
critical: 'error', BLOCK: 'error', high: 'error',
|
|
844
|
+
medium: 'warning', WARN: 'warning', warning: 'warning',
|
|
845
|
+
low: 'note', INFO: 'note', info: 'none',
|
|
520
846
|
};
|
|
521
847
|
return levels[severity] || 'warning';
|
|
522
848
|
}
|
|
523
849
|
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
//
|
|
850
|
+
// Legacy compatibility exports
|
|
851
|
+
function renderBreakdown(breakdown) {
|
|
852
|
+
// Simplified - can expand later
|
|
853
|
+
return '';
|
|
854
|
+
}
|
|
527
855
|
|
|
528
856
|
module.exports = {
|
|
529
|
-
// Main formatters
|
|
530
857
|
formatScanOutput,
|
|
531
858
|
formatSARIF,
|
|
532
|
-
|
|
533
|
-
// Component renderers
|
|
534
859
|
renderLayers,
|
|
535
860
|
renderCoverageMap,
|
|
536
861
|
renderBreakdown,
|
|
537
|
-
renderBlockers,
|
|
538
|
-
renderCategorySummary,
|
|
539
|
-
|
|
540
|
-
// Utilities
|
|
862
|
+
renderBlockers: renderBlockersTable,
|
|
863
|
+
renderCategorySummary: renderCategoryTable,
|
|
541
864
|
calculateScore,
|
|
542
865
|
getExitCode,
|
|
543
866
|
printError,
|