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