@vibecheckai/cli 3.1.2 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -33
- package/bin/registry.js +319 -34
- package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
- package/bin/runners/REPORT_AUDIT.md +64 -0
- package/bin/runners/lib/entitlements-v2.js +97 -28
- package/bin/runners/lib/entitlements.js +3 -6
- package/bin/runners/lib/init-wizard.js +1 -1
- package/bin/runners/lib/report-engine.js +459 -280
- package/bin/runners/lib/report-html.js +1154 -1423
- package/bin/runners/lib/report-output.js +187 -0
- package/bin/runners/lib/report-templates.js +848 -850
- package/bin/runners/lib/scan-output.js +545 -0
- package/bin/runners/lib/server-usage.js +0 -12
- package/bin/runners/lib/ship-output.js +641 -0
- package/bin/runners/lib/status-output.js +253 -0
- package/bin/runners/lib/terminal-ui.js +853 -0
- package/bin/runners/runCheckpoint.js +502 -0
- package/bin/runners/runContracts.js +105 -0
- package/bin/runners/runExport.js +93 -0
- package/bin/runners/runFix.js +31 -24
- package/bin/runners/runInit.js +377 -112
- package/bin/runners/runInstall.js +1 -5
- package/bin/runners/runLabs.js +3 -3
- package/bin/runners/runPolish.js +2452 -0
- package/bin/runners/runProve.js +2 -2
- package/bin/runners/runReport.js +251 -200
- package/bin/runners/runRuntime.js +110 -0
- package/bin/runners/runScan.js +477 -379
- package/bin/runners/runSecurity.js +92 -0
- package/bin/runners/runShip.js +137 -207
- package/bin/runners/runStatus.js +16 -68
- package/bin/runners/utils.js +5 -5
- package/bin/vibecheck.js +25 -11
- package/mcp-server/index.js +150 -18
- package/mcp-server/package.json +2 -2
- package/mcp-server/premium-tools.js +13 -13
- package/mcp-server/tier-auth.js +292 -27
- package/mcp-server/vibecheck-tools.js +9 -9
- package/package.json +1 -1
- package/bin/runners/runClaimVerifier.js +0 -483
- package/bin/runners/runContextCompiler.js +0 -385
- package/bin/runners/runGate.js +0 -17
- package/bin/runners/runInitGha.js +0 -164
- package/bin/runners/runInteractive.js +0 -388
- package/bin/runners/runMdc.js +0 -204
- package/bin/runners/runMissionGenerator.js +0 -282
- package/bin/runners/runTruthpack.js +0 -636
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ship Output - Premium Ship Command Display
|
|
3
|
+
*
|
|
4
|
+
* Handles all ship-specific output formatting:
|
|
5
|
+
* - Verdict card (the hero moment)
|
|
6
|
+
* - Proof graph visualization
|
|
7
|
+
* - Findings breakdown by category
|
|
8
|
+
* - Fix mode display
|
|
9
|
+
* - Badge generation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const {
|
|
14
|
+
ansi,
|
|
15
|
+
colors,
|
|
16
|
+
box,
|
|
17
|
+
icons,
|
|
18
|
+
renderSection,
|
|
19
|
+
formatDuration,
|
|
20
|
+
truncate,
|
|
21
|
+
} = require('./terminal-ui');
|
|
22
|
+
|
|
23
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
24
|
+
// SHIP-SPECIFIC ICONS
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
26
|
+
|
|
27
|
+
const shipIcons = {
|
|
28
|
+
ship: '🚀',
|
|
29
|
+
sparkle: '✨',
|
|
30
|
+
fire: '🔥',
|
|
31
|
+
lock: '🔐',
|
|
32
|
+
key: '🔑',
|
|
33
|
+
link: '🔗',
|
|
34
|
+
graph: '📊',
|
|
35
|
+
map: '🗺️',
|
|
36
|
+
doc: '📄',
|
|
37
|
+
folder: '📁',
|
|
38
|
+
clock: '⏱',
|
|
39
|
+
target: '🎯',
|
|
40
|
+
shield: '🛡️',
|
|
41
|
+
bug: '🐛',
|
|
42
|
+
wrench: '🔧',
|
|
43
|
+
lightning: '⚡',
|
|
44
|
+
package: '📦',
|
|
45
|
+
route: '🛤️',
|
|
46
|
+
env: '🌍',
|
|
47
|
+
auth: '🔒',
|
|
48
|
+
money: '💰',
|
|
49
|
+
ghost: '👻',
|
|
50
|
+
dead: '💀',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
54
|
+
// VERDICT CONFIGURATION
|
|
55
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
56
|
+
|
|
57
|
+
function getVerdictConfig(verdict, score, blockers = 0, warnings = 0) {
|
|
58
|
+
if (verdict === 'SHIP' || (score >= 90 && blockers === 0)) {
|
|
59
|
+
return {
|
|
60
|
+
verdict: 'SHIP',
|
|
61
|
+
icon: '🚀',
|
|
62
|
+
headline: 'CLEAR TO SHIP',
|
|
63
|
+
tagline: 'Your app is production ready!',
|
|
64
|
+
color: colors.success,
|
|
65
|
+
borderColor: ansi.rgb(0, 200, 120),
|
|
66
|
+
bgColor: ansi.bgRgb(0, 60, 40),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (verdict === 'WARN' || (score >= 50 && blockers <= 2)) {
|
|
71
|
+
return {
|
|
72
|
+
verdict: 'WARN',
|
|
73
|
+
icon: '⚠️',
|
|
74
|
+
headline: 'REVIEW BEFORE SHIP',
|
|
75
|
+
tagline: `${warnings} warning${warnings !== 1 ? 's' : ''} to address`,
|
|
76
|
+
color: colors.warning,
|
|
77
|
+
borderColor: ansi.rgb(200, 160, 0),
|
|
78
|
+
bgColor: ansi.bgRgb(60, 50, 0),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
verdict: 'BLOCK',
|
|
84
|
+
icon: '🛑',
|
|
85
|
+
headline: 'NOT SHIP READY',
|
|
86
|
+
tagline: `${blockers} blocker${blockers !== 1 ? 's' : ''} must be fixed`,
|
|
87
|
+
color: colors.error,
|
|
88
|
+
borderColor: ansi.rgb(200, 60, 60),
|
|
89
|
+
bgColor: ansi.bgRgb(60, 20, 20),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
94
|
+
// VERDICT CARD - THE HERO MOMENT
|
|
95
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
96
|
+
|
|
97
|
+
function renderVerdictCard(options = {}) {
|
|
98
|
+
const {
|
|
99
|
+
verdict = 'WARN',
|
|
100
|
+
score = 0,
|
|
101
|
+
blockers = 0,
|
|
102
|
+
warnings = 0,
|
|
103
|
+
duration = 0,
|
|
104
|
+
cached = false,
|
|
105
|
+
} = options;
|
|
106
|
+
|
|
107
|
+
const config = getVerdictConfig(verdict, score, blockers, warnings);
|
|
108
|
+
const w = 68; // Inner width
|
|
109
|
+
|
|
110
|
+
const lines = [];
|
|
111
|
+
lines.push('');
|
|
112
|
+
lines.push('');
|
|
113
|
+
|
|
114
|
+
// Top border
|
|
115
|
+
lines.push(` ${config.borderColor}${box.doubleTopLeft}${box.doubleHorizontal.repeat(w)}${box.doubleTopRight}${ansi.reset}`);
|
|
116
|
+
|
|
117
|
+
// Empty line
|
|
118
|
+
lines.push(` ${config.borderColor}${box.doubleVertical}${ansi.reset}${' '.repeat(w)}${config.borderColor}${box.doubleVertical}${ansi.reset}`);
|
|
119
|
+
|
|
120
|
+
// Verdict icon and headline
|
|
121
|
+
const headlineText = `${config.icon} ${config.headline}`;
|
|
122
|
+
const headlinePadded = padCenter(headlineText, w);
|
|
123
|
+
lines.push(` ${config.borderColor}${box.doubleVertical}${ansi.reset}${config.color}${ansi.bold}${headlinePadded}${ansi.reset}${config.borderColor}${box.doubleVertical}${ansi.reset}`);
|
|
124
|
+
|
|
125
|
+
// Tagline
|
|
126
|
+
const taglinePadded = padCenter(config.tagline, w);
|
|
127
|
+
lines.push(` ${config.borderColor}${box.doubleVertical}${ansi.reset}${ansi.dim}${taglinePadded}${ansi.reset}${config.borderColor}${box.doubleVertical}${ansi.reset}`);
|
|
128
|
+
|
|
129
|
+
// Empty line
|
|
130
|
+
lines.push(` ${config.borderColor}${box.doubleVertical}${ansi.reset}${' '.repeat(w)}${config.borderColor}${box.doubleVertical}${ansi.reset}`);
|
|
131
|
+
|
|
132
|
+
// Score bar
|
|
133
|
+
const scoreBar = renderProgressBar(score, 35, config.color);
|
|
134
|
+
const scoreLabel = `VIBE SCORE`;
|
|
135
|
+
const scoreValue = `${score}/100`;
|
|
136
|
+
const scoreLine = ` ${scoreLabel} ${scoreBar} ${config.color}${ansi.bold}${scoreValue}${ansi.reset}`;
|
|
137
|
+
const scoreLinePadRight = ' '.repeat(Math.max(0, w - stripAnsi(scoreLine).length + 4));
|
|
138
|
+
lines.push(` ${config.borderColor}${box.doubleVertical}${ansi.reset}${scoreLine}${scoreLinePadRight}${config.borderColor}${box.doubleVertical}${ansi.reset}`);
|
|
139
|
+
|
|
140
|
+
// Empty line
|
|
141
|
+
lines.push(` ${config.borderColor}${box.doubleVertical}${ansi.reset}${' '.repeat(w)}${config.borderColor}${box.doubleVertical}${ansi.reset}`);
|
|
142
|
+
|
|
143
|
+
// Stats row
|
|
144
|
+
const blockerColor = blockers > 0 ? colors.error : colors.success;
|
|
145
|
+
const warningColor = warnings > 0 ? colors.warning : colors.success;
|
|
146
|
+
const statsLine = ` ${ansi.dim}Blockers:${ansi.reset} ${blockerColor}${ansi.bold}${blockers}${ansi.reset} ${ansi.dim}Warnings:${ansi.reset} ${warningColor}${ansi.bold}${warnings}${ansi.reset} ${ansi.dim}Duration:${ansi.reset} ${colors.accent}${ansi.bold}${formatDuration(duration)}${ansi.reset}${cached ? ` ${ansi.dim}(cached)${ansi.reset}` : ''}`;
|
|
147
|
+
const statsLinePadRight = ' '.repeat(Math.max(0, w - stripAnsi(statsLine).length + 4));
|
|
148
|
+
lines.push(` ${config.borderColor}${box.doubleVertical}${ansi.reset}${statsLine}${statsLinePadRight}${config.borderColor}${box.doubleVertical}${ansi.reset}`);
|
|
149
|
+
|
|
150
|
+
// Empty line
|
|
151
|
+
lines.push(` ${config.borderColor}${box.doubleVertical}${ansi.reset}${' '.repeat(w)}${config.borderColor}${box.doubleVertical}${ansi.reset}`);
|
|
152
|
+
|
|
153
|
+
// Bottom border
|
|
154
|
+
lines.push(` ${config.borderColor}${box.doubleBottomLeft}${box.doubleHorizontal.repeat(w)}${box.doubleBottomRight}${ansi.reset}`);
|
|
155
|
+
|
|
156
|
+
lines.push('');
|
|
157
|
+
return lines.join('\n');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
161
|
+
// PROOF GRAPH VISUALIZATION
|
|
162
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
163
|
+
|
|
164
|
+
function renderProofGraph(proofGraph) {
|
|
165
|
+
if (!proofGraph || !proofGraph.summary) return '';
|
|
166
|
+
|
|
167
|
+
const lines = [];
|
|
168
|
+
lines.push(renderSection('PROOF GRAPH', shipIcons.graph));
|
|
169
|
+
lines.push('');
|
|
170
|
+
|
|
171
|
+
const { summary } = proofGraph;
|
|
172
|
+
|
|
173
|
+
// Confidence gauge
|
|
174
|
+
const confidence = Math.round((summary.confidence || 0) * 100);
|
|
175
|
+
const confColor = confidence >= 80 ? colors.success : confidence >= 50 ? colors.warning : colors.error;
|
|
176
|
+
|
|
177
|
+
lines.push(` ${ansi.bold}Analysis Confidence${ansi.reset}`);
|
|
178
|
+
lines.push(` ${renderProgressBar(confidence, 40, confColor)} ${confColor}${ansi.bold}${confidence}%${ansi.reset}`);
|
|
179
|
+
lines.push('');
|
|
180
|
+
|
|
181
|
+
// Claims summary
|
|
182
|
+
const verified = summary.verifiedClaims || 0;
|
|
183
|
+
const failed = summary.failedClaims || 0;
|
|
184
|
+
const total = summary.totalClaims || 0;
|
|
185
|
+
|
|
186
|
+
lines.push(` ${ansi.dim}Claims:${ansi.reset} ${colors.success}${verified}${ansi.reset} verified ${ansi.dim}/${ansi.reset} ${colors.error}${failed}${ansi.reset} failed ${ansi.dim}of${ansi.reset} ${total} total`);
|
|
187
|
+
lines.push(` ${ansi.dim}Gaps:${ansi.reset} ${summary.gaps || 0} identified`);
|
|
188
|
+
lines.push(` ${ansi.dim}Risk Score:${ansi.reset} ${summary.riskScore || 0}/100`);
|
|
189
|
+
|
|
190
|
+
// Top blockers
|
|
191
|
+
if (proofGraph.topBlockers && proofGraph.topBlockers.length > 0) {
|
|
192
|
+
lines.push('');
|
|
193
|
+
lines.push(` ${ansi.bold}Top Unverified Claims:${ansi.reset}`);
|
|
194
|
+
for (const blocker of proofGraph.topBlockers.slice(0, 3)) {
|
|
195
|
+
lines.push(` ${colors.error}${icons.error}${ansi.reset} ${truncate(blocker.assertion, 50)}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return lines.join('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
203
|
+
// ROUTE TRUTH MAP
|
|
204
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
205
|
+
|
|
206
|
+
function renderRouteTruthMap(truthpack) {
|
|
207
|
+
if (!truthpack) return '';
|
|
208
|
+
|
|
209
|
+
const lines = [];
|
|
210
|
+
lines.push(renderSection('ROUTE TRUTH MAP', shipIcons.map));
|
|
211
|
+
lines.push('');
|
|
212
|
+
|
|
213
|
+
const serverRoutes = truthpack.routes?.server?.length || 0;
|
|
214
|
+
const clientRefs = truthpack.routes?.clientRefs?.length || 0;
|
|
215
|
+
const envVars = truthpack.env?.vars?.length || 0;
|
|
216
|
+
const envDeclared = truthpack.env?.declared?.length || 0;
|
|
217
|
+
|
|
218
|
+
// Routes coverage
|
|
219
|
+
const routeCoverage = serverRoutes > 0 ? Math.round((clientRefs / serverRoutes) * 100) : 100;
|
|
220
|
+
const routeColor = routeCoverage >= 80 ? colors.success : routeCoverage >= 50 ? colors.warning : colors.error;
|
|
221
|
+
|
|
222
|
+
lines.push(` ${shipIcons.route} ${ansi.bold}Routes${ansi.reset}`);
|
|
223
|
+
lines.push(` Server: ${colors.accent}${serverRoutes}${ansi.reset} defined`);
|
|
224
|
+
lines.push(` Client: ${colors.accent}${clientRefs}${ansi.reset} references`);
|
|
225
|
+
lines.push(` Coverage: ${renderProgressBar(routeCoverage, 20, routeColor)} ${routeColor}${routeCoverage}%${ansi.reset}`);
|
|
226
|
+
lines.push('');
|
|
227
|
+
|
|
228
|
+
// Env coverage
|
|
229
|
+
const envCoverage = envVars > 0 ? Math.round((envDeclared / envVars) * 100) : 100;
|
|
230
|
+
const envColor = envCoverage >= 80 ? colors.success : envCoverage >= 50 ? colors.warning : colors.error;
|
|
231
|
+
|
|
232
|
+
lines.push(` ${shipIcons.env} ${ansi.bold}Environment${ansi.reset}`);
|
|
233
|
+
lines.push(` Used: ${colors.accent}${envVars}${ansi.reset} variables`);
|
|
234
|
+
lines.push(` Declared: ${colors.accent}${envDeclared}${ansi.reset} in .env`);
|
|
235
|
+
lines.push(` Coverage: ${renderProgressBar(envCoverage, 20, envColor)} ${envColor}${envCoverage}%${ansi.reset}`);
|
|
236
|
+
|
|
237
|
+
return lines.join('\n');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
241
|
+
// FINDINGS BREAKDOWN
|
|
242
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
243
|
+
|
|
244
|
+
function getCategoryIcon(category) {
|
|
245
|
+
const iconMap = {
|
|
246
|
+
'MissingRoute': shipIcons.route,
|
|
247
|
+
'EnvContract': shipIcons.env,
|
|
248
|
+
'EnvGap': shipIcons.env,
|
|
249
|
+
'FakeSuccess': shipIcons.ghost,
|
|
250
|
+
'GhostAuth': shipIcons.auth,
|
|
251
|
+
'StripeWebhook': shipIcons.money,
|
|
252
|
+
'PaidSurface': shipIcons.money,
|
|
253
|
+
'OwnerModeBypass': shipIcons.lock,
|
|
254
|
+
'DeadUI': shipIcons.dead,
|
|
255
|
+
'ContractDrift': icons.warning,
|
|
256
|
+
'Security': shipIcons.shield,
|
|
257
|
+
'Auth': shipIcons.lock,
|
|
258
|
+
'SECRET': shipIcons.key,
|
|
259
|
+
'ROUTE': shipIcons.route,
|
|
260
|
+
'BILLING': shipIcons.money,
|
|
261
|
+
'MOCK': shipIcons.ghost,
|
|
262
|
+
};
|
|
263
|
+
return iconMap[category] || shipIcons.bug;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function renderFindingsBreakdown(findings) {
|
|
267
|
+
if (!findings || findings.length === 0) {
|
|
268
|
+
const lines = [];
|
|
269
|
+
lines.push(renderSection('FINDINGS', icons.success));
|
|
270
|
+
lines.push('');
|
|
271
|
+
lines.push(` ${colors.success}${ansi.bold}${shipIcons.sparkle} No issues found! Your code is clean.${ansi.reset}`);
|
|
272
|
+
return lines.join('\n');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Group by category
|
|
276
|
+
const byCategory = {};
|
|
277
|
+
for (const f of findings) {
|
|
278
|
+
const cat = f.category || 'Other';
|
|
279
|
+
if (!byCategory[cat]) byCategory[cat] = [];
|
|
280
|
+
byCategory[cat].push(f);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const blockers = findings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical');
|
|
284
|
+
const warnings = findings.filter(f => f.severity === 'WARN' || f.severity === 'warning');
|
|
285
|
+
|
|
286
|
+
const lines = [];
|
|
287
|
+
lines.push(renderSection(`FINDINGS (${blockers.length} blockers, ${warnings.length} warnings)`, shipIcons.graph));
|
|
288
|
+
lines.push('');
|
|
289
|
+
|
|
290
|
+
// Sort categories by severity
|
|
291
|
+
const categories = Object.entries(byCategory).sort((a, b) => {
|
|
292
|
+
const aBlockers = a[1].filter(f => f.severity === 'BLOCK' || f.severity === 'critical').length;
|
|
293
|
+
const bBlockers = b[1].filter(f => f.severity === 'BLOCK' || f.severity === 'critical').length;
|
|
294
|
+
return bBlockers - aBlockers;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
for (const [category, catFindings] of categories) {
|
|
298
|
+
const catBlockers = catFindings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical').length;
|
|
299
|
+
const catWarnings = catFindings.filter(f => f.severity === 'WARN' || f.severity === 'warning').length;
|
|
300
|
+
const icon = getCategoryIcon(category);
|
|
301
|
+
|
|
302
|
+
const statusColor = catBlockers > 0 ? colors.error : catWarnings > 0 ? colors.warning : colors.success;
|
|
303
|
+
const statusIcon = catBlockers > 0 ? icons.error : catWarnings > 0 ? icons.warning : icons.success;
|
|
304
|
+
|
|
305
|
+
let stats = '';
|
|
306
|
+
if (catBlockers > 0) stats += `${colors.error}${catBlockers} blockers${ansi.reset}`;
|
|
307
|
+
if (catWarnings > 0) stats += `${stats ? ' ' : ''}${colors.warning}${catWarnings} warnings${ansi.reset}`;
|
|
308
|
+
|
|
309
|
+
lines.push(` ${statusColor}${statusIcon}${ansi.reset} ${icon} ${ansi.bold}${category.padEnd(20)}${ansi.reset} ${stats}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return lines.join('\n');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
316
|
+
// BLOCKER DETAILS
|
|
317
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
318
|
+
|
|
319
|
+
function renderBlockerDetails(findings, maxShow = 8) {
|
|
320
|
+
const blockers = findings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical');
|
|
321
|
+
|
|
322
|
+
if (blockers.length === 0) return '';
|
|
323
|
+
|
|
324
|
+
const lines = [];
|
|
325
|
+
lines.push(renderSection(`BLOCKERS (${blockers.length})`, '🚨'));
|
|
326
|
+
lines.push('');
|
|
327
|
+
|
|
328
|
+
for (const blocker of blockers.slice(0, maxShow)) {
|
|
329
|
+
const icon = getCategoryIcon(blocker.category);
|
|
330
|
+
const severityBg = ansi.bgRgb(80, 20, 20);
|
|
331
|
+
|
|
332
|
+
// Severity badge + title
|
|
333
|
+
lines.push(` ${severityBg}${ansi.bold} BLOCKER ${ansi.reset} ${icon} ${ansi.bold}${truncate(blocker.title || blocker.message, 50)}${ansi.reset}`);
|
|
334
|
+
|
|
335
|
+
// Why/description
|
|
336
|
+
if (blocker.why || blocker.description) {
|
|
337
|
+
lines.push(` ${ansi.dim}${truncate(blocker.why || blocker.description, 55)}${ansi.reset}`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// File location
|
|
341
|
+
if (blocker.evidence && blocker.evidence.length > 0) {
|
|
342
|
+
const ev = blocker.evidence[0];
|
|
343
|
+
const file = ev.file || blocker.file;
|
|
344
|
+
if (file) {
|
|
345
|
+
const fileDisplay = `${path.basename(file)}${ev.lines ? `:${ev.lines}` : blocker.line ? `:${blocker.line}` : ''}`;
|
|
346
|
+
lines.push(` ${colors.accent}${shipIcons.doc} ${fileDisplay}${ansi.reset}`);
|
|
347
|
+
}
|
|
348
|
+
} else if (blocker.file) {
|
|
349
|
+
const fileDisplay = `${path.basename(blocker.file)}${blocker.line ? `:${blocker.line}` : ''}`;
|
|
350
|
+
lines.push(` ${colors.accent}${shipIcons.doc} ${fileDisplay}${ansi.reset}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Fix hint
|
|
354
|
+
if (blocker.fixHints && blocker.fixHints.length > 0) {
|
|
355
|
+
lines.push(` ${colors.success}→ ${truncate(blocker.fixHints[0], 50)}${ansi.reset}`);
|
|
356
|
+
} else if (blocker.fix) {
|
|
357
|
+
lines.push(` ${colors.success}→ ${truncate(blocker.fix, 50)}${ansi.reset}`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
lines.push('');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (blockers.length > maxShow) {
|
|
364
|
+
lines.push(` ${ansi.dim}... and ${blockers.length - maxShow} more blockers (see full report)${ansi.reset}`);
|
|
365
|
+
lines.push('');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return lines.join('\n');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
372
|
+
// FIX MODE DISPLAY
|
|
373
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
374
|
+
|
|
375
|
+
function renderFixModeHeader() {
|
|
376
|
+
return `\n ${ansi.bgRgb(40, 80, 120)}${ansi.bold} ${shipIcons.wrench} AUTO-FIX MODE ${ansi.reset}\n`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function renderFixResults(fixResults) {
|
|
380
|
+
if (!fixResults) return '';
|
|
381
|
+
|
|
382
|
+
const lines = [];
|
|
383
|
+
lines.push(renderSection('FIX RESULTS', shipIcons.wrench));
|
|
384
|
+
lines.push('');
|
|
385
|
+
|
|
386
|
+
// Errors
|
|
387
|
+
if (fixResults.errors && fixResults.errors.length > 0) {
|
|
388
|
+
for (const err of fixResults.errors) {
|
|
389
|
+
lines.push(` ${colors.error}${icons.error}${ansi.reset} ${err}`);
|
|
390
|
+
}
|
|
391
|
+
lines.push('');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Actions taken
|
|
395
|
+
const actions = [
|
|
396
|
+
{ done: fixResults.envExampleCreated, label: 'Created .env.example', icon: shipIcons.env },
|
|
397
|
+
{ done: fixResults.gitignoreUpdated, label: 'Updated .gitignore', icon: shipIcons.shield },
|
|
398
|
+
{ done: fixResults.fixesMdCreated, label: 'Generated fixes.md', icon: shipIcons.doc },
|
|
399
|
+
];
|
|
400
|
+
|
|
401
|
+
for (const action of actions) {
|
|
402
|
+
const statusIcon = action.done ? `${colors.success}${icons.success}` : `${ansi.dim}•`;
|
|
403
|
+
const label = action.done ? `${ansi.reset}${action.label}` : `${ansi.dim}${action.label}${ansi.reset}`;
|
|
404
|
+
lines.push(` ${statusIcon}${ansi.reset} ${action.icon} ${label}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Secrets found
|
|
408
|
+
if (fixResults.secretsFound && fixResults.secretsFound.length > 0) {
|
|
409
|
+
lines.push('');
|
|
410
|
+
lines.push(` ${ansi.bold}Secrets to migrate:${ansi.reset}`);
|
|
411
|
+
for (const secret of fixResults.secretsFound.slice(0, 5)) {
|
|
412
|
+
lines.push(` ${shipIcons.key} ${secret.varName} ${ansi.dim}(${secret.type})${ansi.reset}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
lines.push('');
|
|
417
|
+
lines.push(` ${colors.success}${icons.success}${ansi.reset} ${ansi.bold}Safe fixes applied!${ansi.reset}`);
|
|
418
|
+
lines.push(` ${ansi.dim}Review changes and follow instructions in ${colors.accent}.vibecheck/fixes.md${ansi.reset}`);
|
|
419
|
+
|
|
420
|
+
return lines.join('\n');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
424
|
+
// BADGE OUTPUT
|
|
425
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
426
|
+
|
|
427
|
+
function renderBadgeOutput(projectPath, verdict, score) {
|
|
428
|
+
const projectName = path.basename(projectPath);
|
|
429
|
+
const projectId = projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
430
|
+
|
|
431
|
+
const config = getVerdictConfig(verdict, score);
|
|
432
|
+
|
|
433
|
+
const lines = [];
|
|
434
|
+
lines.push(renderSection('SHIP BADGE', '📛'));
|
|
435
|
+
lines.push('');
|
|
436
|
+
|
|
437
|
+
// Badge preview
|
|
438
|
+
const badgeText = `vibecheck | ${verdict} | ${score}`;
|
|
439
|
+
lines.push(` ${config.bgColor}${ansi.bold} ${badgeText} ${ansi.reset}`);
|
|
440
|
+
lines.push('');
|
|
441
|
+
|
|
442
|
+
const badgeUrl = `https://vibecheck.dev/badge/${projectId}.svg`;
|
|
443
|
+
const reportUrl = `https://vibecheck.dev/report/${projectId}`;
|
|
444
|
+
const markdown = `[](${reportUrl})`;
|
|
445
|
+
|
|
446
|
+
lines.push(` ${ansi.dim}Badge URL:${ansi.reset}`);
|
|
447
|
+
lines.push(` ${colors.accent}${badgeUrl}${ansi.reset}`);
|
|
448
|
+
lines.push('');
|
|
449
|
+
lines.push(` ${ansi.dim}Report URL:${ansi.reset}`);
|
|
450
|
+
lines.push(` ${colors.accent}${reportUrl}${ansi.reset}`);
|
|
451
|
+
lines.push('');
|
|
452
|
+
lines.push(` ${ansi.dim}Add to README.md:${ansi.reset}`);
|
|
453
|
+
lines.push(` ${colors.success}${markdown}${ansi.reset}`);
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
output: lines.join('\n'),
|
|
457
|
+
data: { projectId, badgeUrl, reportUrl, markdown },
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
462
|
+
// NEXT STEPS
|
|
463
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
464
|
+
|
|
465
|
+
function renderNextSteps(canShip, hasFix = false) {
|
|
466
|
+
if (canShip) return '';
|
|
467
|
+
|
|
468
|
+
const lines = [];
|
|
469
|
+
lines.push(renderSection('NEXT STEPS', shipIcons.lightning));
|
|
470
|
+
lines.push('');
|
|
471
|
+
|
|
472
|
+
if (!hasFix) {
|
|
473
|
+
lines.push(` ${colors.accent}vibecheck ship --fix${ansi.reset} ${ansi.dim}Auto-fix what can be fixed${ansi.reset}`);
|
|
474
|
+
}
|
|
475
|
+
lines.push(` ${colors.accent}vibecheck ship --assist${ansi.reset} ${ansi.dim}Get AI help for complex issues${ansi.reset}`);
|
|
476
|
+
lines.push(` ${colors.accent}vibecheck report${ansi.reset} ${ansi.dim}Generate detailed HTML report${ansi.reset}`);
|
|
477
|
+
lines.push('');
|
|
478
|
+
|
|
479
|
+
return lines.join('\n');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
483
|
+
// REPORT LINKS
|
|
484
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
485
|
+
|
|
486
|
+
function renderReportLinks(outputDir, hasFix = false) {
|
|
487
|
+
const lines = [];
|
|
488
|
+
lines.push(renderSection('REPORTS', shipIcons.doc));
|
|
489
|
+
lines.push('');
|
|
490
|
+
lines.push(` ${colors.accent}vibecheck report${ansi.reset} ${ansi.dim}Generate HTML report${ansi.reset}`);
|
|
491
|
+
lines.push(` ${ansi.dim}${outputDir}/last_ship.json${ansi.reset}`);
|
|
492
|
+
if (hasFix) {
|
|
493
|
+
lines.push(` ${colors.accent}${outputDir}/fixes.md${ansi.reset}`);
|
|
494
|
+
}
|
|
495
|
+
lines.push('');
|
|
496
|
+
return lines.join('\n');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
500
|
+
// FULL SHIP OUTPUT
|
|
501
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
502
|
+
|
|
503
|
+
function formatShipOutput(result, options = {}) {
|
|
504
|
+
const { verbose = false, showFix = false, showBadge = false, outputDir = '.vibecheck', projectPath = '.' } = options;
|
|
505
|
+
|
|
506
|
+
const {
|
|
507
|
+
verdict,
|
|
508
|
+
score,
|
|
509
|
+
findings = [],
|
|
510
|
+
blockers = [],
|
|
511
|
+
warnings = [],
|
|
512
|
+
truthpack,
|
|
513
|
+
proofGraph,
|
|
514
|
+
fixResults,
|
|
515
|
+
duration = 0,
|
|
516
|
+
cached = false,
|
|
517
|
+
} = result;
|
|
518
|
+
|
|
519
|
+
const lines = [];
|
|
520
|
+
|
|
521
|
+
// Verdict card (hero moment)
|
|
522
|
+
lines.push(renderVerdictCard({
|
|
523
|
+
verdict,
|
|
524
|
+
score,
|
|
525
|
+
blockers: blockers.length || findings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical').length,
|
|
526
|
+
warnings: warnings.length || findings.filter(f => f.severity === 'WARN' || f.severity === 'warning').length,
|
|
527
|
+
duration,
|
|
528
|
+
cached,
|
|
529
|
+
}));
|
|
530
|
+
|
|
531
|
+
// Findings breakdown
|
|
532
|
+
lines.push(renderFindingsBreakdown(findings));
|
|
533
|
+
|
|
534
|
+
// Blocker details
|
|
535
|
+
lines.push(renderBlockerDetails(findings));
|
|
536
|
+
|
|
537
|
+
// Verbose: Route truth map
|
|
538
|
+
if (verbose && truthpack) {
|
|
539
|
+
lines.push('');
|
|
540
|
+
lines.push(renderRouteTruthMap(truthpack));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Verbose: Proof graph
|
|
544
|
+
if (verbose && proofGraph) {
|
|
545
|
+
lines.push('');
|
|
546
|
+
lines.push(renderProofGraph(proofGraph));
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Fix results
|
|
550
|
+
if (showFix && fixResults) {
|
|
551
|
+
lines.push(renderFixResults(fixResults));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Badge (if requested)
|
|
555
|
+
if (showBadge) {
|
|
556
|
+
const badgeResult = renderBadgeOutput(projectPath, verdict, score);
|
|
557
|
+
lines.push(badgeResult.output);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Report links
|
|
561
|
+
lines.push(renderReportLinks(outputDir, showFix));
|
|
562
|
+
|
|
563
|
+
// Next steps (if not shipping)
|
|
564
|
+
const canShip = verdict === 'SHIP';
|
|
565
|
+
lines.push(renderNextSteps(canShip, showFix));
|
|
566
|
+
|
|
567
|
+
return lines.filter(Boolean).join('\n');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
571
|
+
// UTILITIES
|
|
572
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
573
|
+
|
|
574
|
+
function renderProgressBar(percent, width, color) {
|
|
575
|
+
const filled = Math.round((percent / 100) * width);
|
|
576
|
+
const empty = width - filled;
|
|
577
|
+
return `${color}${'█'.repeat(filled)}${ansi.dim}${'░'.repeat(empty)}${ansi.reset}`;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function padCenter(str, width) {
|
|
581
|
+
const visibleLen = stripAnsi(str).length;
|
|
582
|
+
const padding = Math.max(0, width - visibleLen);
|
|
583
|
+
const left = Math.floor(padding / 2);
|
|
584
|
+
const right = padding - left;
|
|
585
|
+
return ' '.repeat(left) + str + ' '.repeat(right);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function stripAnsi(str) {
|
|
589
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
593
|
+
// EXIT CODES
|
|
594
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
595
|
+
|
|
596
|
+
const EXIT_CODES = {
|
|
597
|
+
SHIP: 0,
|
|
598
|
+
WARN: 1,
|
|
599
|
+
BLOCK: 2,
|
|
600
|
+
ERROR: 3,
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
function getExitCode(verdict) {
|
|
604
|
+
return EXIT_CODES[verdict] || EXIT_CODES.ERROR;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function verdictFromExitCode(code) {
|
|
608
|
+
const map = { 0: 'SHIP', 1: 'WARN', 2: 'BLOCK', 3: 'ERROR' };
|
|
609
|
+
return map[code] || 'ERROR';
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
613
|
+
// EXPORTS
|
|
614
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
615
|
+
|
|
616
|
+
module.exports = {
|
|
617
|
+
// Main formatters
|
|
618
|
+
formatShipOutput,
|
|
619
|
+
|
|
620
|
+
// Component renderers
|
|
621
|
+
renderVerdictCard,
|
|
622
|
+
renderProofGraph,
|
|
623
|
+
renderRouteTruthMap,
|
|
624
|
+
renderFindingsBreakdown,
|
|
625
|
+
renderBlockerDetails,
|
|
626
|
+
renderFixModeHeader,
|
|
627
|
+
renderFixResults,
|
|
628
|
+
renderBadgeOutput,
|
|
629
|
+
renderNextSteps,
|
|
630
|
+
renderReportLinks,
|
|
631
|
+
|
|
632
|
+
// Utilities
|
|
633
|
+
getVerdictConfig,
|
|
634
|
+
getCategoryIcon,
|
|
635
|
+
getExitCode,
|
|
636
|
+
verdictFromExitCode,
|
|
637
|
+
EXIT_CODES,
|
|
638
|
+
|
|
639
|
+
// Icons
|
|
640
|
+
shipIcons,
|
|
641
|
+
};
|