@vibecheckai/cli 3.0.9 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/.generated +25 -0
- package/bin/registry.js +105 -0
- package/bin/runners/lib/cli-output.js +368 -0
- package/bin/runners/lib/entitlements-v2.js +26 -30
- package/bin/runners/lib/receipts.js +179 -0
- package/bin/runners/lib/report-html.js +378 -1
- package/bin/runners/lib/upsell.js +510 -0
- package/bin/runners/lib/usage.js +153 -0
- package/bin/runners/runBadge.js +850 -116
- package/bin/runners/runCtx.js +602 -119
- package/bin/runners/runDoctor.js +400 -44
- package/bin/runners/runFix.js +557 -85
- package/bin/runners/runGraph.js +245 -74
- package/bin/runners/runInit.js +647 -88
- package/bin/runners/runInstall.js +207 -46
- package/bin/runners/runMcp.js +865 -42
- package/bin/runners/runPR.js +123 -32
- package/bin/runners/runPermissions.js +14 -0
- package/bin/runners/runPreflight.js +553 -0
- package/bin/runners/runProve.js +884 -104
- package/bin/runners/runReality.js +812 -92
- package/bin/runners/runReport.js +68 -2
- package/bin/runners/runShare.js +156 -38
- package/bin/runners/runShip.js +999 -889
- package/bin/runners/runVerify.js +272 -0
- package/bin/runners/runWatch.js +175 -55
- package/bin/vibecheck.js +108 -94
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
package/bin/runners/runProve.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* vibecheck prove - One Command Reality Proof
|
|
3
3
|
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* ENTERPRISE EDITION - World-Class Terminal Experience
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
4
8
|
* Orchestrates the complete "make it real" loop:
|
|
5
9
|
* 1. ctx - Refresh truthpack
|
|
6
10
|
* 2. reality --verify-auth - Runtime proof
|
|
@@ -18,6 +22,14 @@ const path = require("path");
|
|
|
18
22
|
const { buildTruthpack, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
|
|
19
23
|
const { shipCore } = require("./runShip");
|
|
20
24
|
const { findContractDrift, loadContracts, hasContracts, getDriftSummary } = require("./lib/drift");
|
|
25
|
+
const {
|
|
26
|
+
generateRunId,
|
|
27
|
+
createJsonOutput,
|
|
28
|
+
writeJsonOutput,
|
|
29
|
+
exitCodeToVerdict,
|
|
30
|
+
verdictToExitCode,
|
|
31
|
+
saveArtifact
|
|
32
|
+
} = require("./lib/cli-output");
|
|
21
33
|
|
|
22
34
|
let runReality;
|
|
23
35
|
try {
|
|
@@ -33,7 +45,677 @@ try {
|
|
|
33
45
|
runFixCore = null;
|
|
34
46
|
}
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
49
|
+
// ADVANCED TERMINAL - ANSI CODES & UTILITIES
|
|
50
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
51
|
+
|
|
52
|
+
const c = {
|
|
53
|
+
reset: '\x1b[0m',
|
|
54
|
+
bold: '\x1b[1m',
|
|
55
|
+
dim: '\x1b[2m',
|
|
56
|
+
italic: '\x1b[3m',
|
|
57
|
+
underline: '\x1b[4m',
|
|
58
|
+
blink: '\x1b[5m',
|
|
59
|
+
inverse: '\x1b[7m',
|
|
60
|
+
hidden: '\x1b[8m',
|
|
61
|
+
strike: '\x1b[9m',
|
|
62
|
+
// Colors
|
|
63
|
+
black: '\x1b[30m',
|
|
64
|
+
red: '\x1b[31m',
|
|
65
|
+
green: '\x1b[32m',
|
|
66
|
+
yellow: '\x1b[33m',
|
|
67
|
+
blue: '\x1b[34m',
|
|
68
|
+
magenta: '\x1b[35m',
|
|
69
|
+
cyan: '\x1b[36m',
|
|
70
|
+
white: '\x1b[37m',
|
|
71
|
+
// Bright colors
|
|
72
|
+
gray: '\x1b[90m',
|
|
73
|
+
brightRed: '\x1b[91m',
|
|
74
|
+
brightGreen: '\x1b[92m',
|
|
75
|
+
brightYellow: '\x1b[93m',
|
|
76
|
+
brightBlue: '\x1b[94m',
|
|
77
|
+
brightMagenta: '\x1b[95m',
|
|
78
|
+
brightCyan: '\x1b[96m',
|
|
79
|
+
brightWhite: '\x1b[97m',
|
|
80
|
+
// Background
|
|
81
|
+
bgBlack: '\x1b[40m',
|
|
82
|
+
bgRed: '\x1b[41m',
|
|
83
|
+
bgGreen: '\x1b[42m',
|
|
84
|
+
bgYellow: '\x1b[43m',
|
|
85
|
+
bgBlue: '\x1b[44m',
|
|
86
|
+
bgMagenta: '\x1b[45m',
|
|
87
|
+
bgCyan: '\x1b[46m',
|
|
88
|
+
bgWhite: '\x1b[47m',
|
|
89
|
+
bgBrightBlack: '\x1b[100m',
|
|
90
|
+
// Cursor control
|
|
91
|
+
cursorUp: (n = 1) => `\x1b[${n}A`,
|
|
92
|
+
cursorDown: (n = 1) => `\x1b[${n}B`,
|
|
93
|
+
cursorRight: (n = 1) => `\x1b[${n}C`,
|
|
94
|
+
cursorLeft: (n = 1) => `\x1b[${n}D`,
|
|
95
|
+
clearLine: '\x1b[2K',
|
|
96
|
+
clearScreen: '\x1b[2J',
|
|
97
|
+
saveCursor: '\x1b[s',
|
|
98
|
+
restoreCursor: '\x1b[u',
|
|
99
|
+
hideCursor: '\x1b[?25l',
|
|
100
|
+
showCursor: '\x1b[?25h',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// True color support
|
|
104
|
+
const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
|
|
105
|
+
const bgRgb = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
|
|
106
|
+
|
|
107
|
+
// Premium color palette
|
|
108
|
+
const colors = {
|
|
109
|
+
// Gradient for banner (purple/magenta theme for "prove")
|
|
110
|
+
gradient1: rgb(180, 100, 255), // Light purple
|
|
111
|
+
gradient2: rgb(150, 80, 255), // Purple
|
|
112
|
+
gradient3: rgb(120, 60, 255), // Deep purple
|
|
113
|
+
gradient4: rgb(100, 50, 220), // Blue-purple
|
|
114
|
+
gradient5: rgb(80, 40, 200), // Dark purple
|
|
115
|
+
gradient6: rgb(60, 30, 180), // Deepest
|
|
116
|
+
|
|
117
|
+
// Accent colors
|
|
118
|
+
science: rgb(180, 100, 255), // Scientific purple
|
|
119
|
+
reality: rgb(255, 150, 100), // Reality orange
|
|
120
|
+
truth: rgb(100, 200, 255), // Truth blue
|
|
121
|
+
fix: rgb(100, 255, 200), // Fix green
|
|
122
|
+
|
|
123
|
+
// Verdict colors
|
|
124
|
+
shipGreen: rgb(0, 255, 150),
|
|
125
|
+
warnAmber: rgb(255, 200, 0),
|
|
126
|
+
blockRed: rgb(255, 80, 80),
|
|
127
|
+
|
|
128
|
+
// UI colors
|
|
129
|
+
accent: rgb(180, 130, 255),
|
|
130
|
+
muted: rgb(120, 100, 140),
|
|
131
|
+
subtle: rgb(80, 70, 100),
|
|
132
|
+
highlight: rgb(255, 255, 255),
|
|
133
|
+
|
|
134
|
+
// Step colors
|
|
135
|
+
step1: rgb(100, 200, 255), // Context - blue
|
|
136
|
+
step2: rgb(255, 150, 100), // Reality - orange
|
|
137
|
+
step3: rgb(0, 255, 150), // Ship - green
|
|
138
|
+
step4: rgb(255, 200, 100), // Fix - yellow
|
|
139
|
+
step5: rgb(180, 100, 255), // Verify - purple
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
143
|
+
// PREMIUM BANNER
|
|
144
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
145
|
+
|
|
146
|
+
const PROVE_BANNER = `
|
|
147
|
+
${rgb(200, 120, 255)} ██████╗ ██████╗ ██████╗ ██╗ ██╗███████╗${c.reset}
|
|
148
|
+
${rgb(180, 100, 255)} ██╔══██╗██╔══██╗██╔═══██╗██║ ██║██╔════╝${c.reset}
|
|
149
|
+
${rgb(160, 80, 255)} ██████╔╝██████╔╝██║ ██║██║ ██║█████╗ ${c.reset}
|
|
150
|
+
${rgb(140, 60, 255)} ██╔═══╝ ██╔══██╗██║ ██║╚██╗ ██╔╝██╔══╝ ${c.reset}
|
|
151
|
+
${rgb(120, 40, 255)} ██║ ██║ ██║╚██████╔╝ ╚████╔╝ ███████╗${c.reset}
|
|
152
|
+
${rgb(100, 20, 255)} ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═══╝ ╚══════╝${c.reset}
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
const BANNER_FULL = `
|
|
156
|
+
${rgb(200, 120, 255)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${c.reset}
|
|
157
|
+
${rgb(180, 100, 255)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${c.reset}
|
|
158
|
+
${rgb(160, 80, 255)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${c.reset}
|
|
159
|
+
${rgb(140, 60, 255)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${c.reset}
|
|
160
|
+
${rgb(120, 40, 255)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${c.reset}
|
|
161
|
+
${rgb(100, 20, 255)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${c.reset}
|
|
162
|
+
|
|
163
|
+
${c.dim} ┌─────────────────────────────────────────────────────────────────────┐${c.reset}
|
|
164
|
+
${c.dim} │${c.reset} ${rgb(180, 100, 255)}🔬${c.reset} ${c.bold}PROVE${c.reset} ${c.dim}•${c.reset} ${rgb(200, 200, 200)}One Command${c.reset} ${c.dim}•${c.reset} ${rgb(150, 150, 150)}Reality Proof${c.reset} ${c.dim}•${c.reset} ${rgb(100, 100, 100)}Make It Real${c.reset} ${c.dim}│${c.reset}
|
|
165
|
+
${c.dim} └─────────────────────────────────────────────────────────────────────┘${c.reset}
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
169
|
+
// ICONS & SYMBOLS
|
|
170
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
171
|
+
|
|
172
|
+
const ICONS = {
|
|
173
|
+
// Steps
|
|
174
|
+
context: '📋',
|
|
175
|
+
reality: '🎭',
|
|
176
|
+
ship: '🚀',
|
|
177
|
+
fix: '🔧',
|
|
178
|
+
verify: '✅',
|
|
179
|
+
prove: '🔬',
|
|
180
|
+
|
|
181
|
+
// Status
|
|
182
|
+
check: '✓',
|
|
183
|
+
cross: '✗',
|
|
184
|
+
warning: '⚠',
|
|
185
|
+
info: 'ℹ',
|
|
186
|
+
arrow: '→',
|
|
187
|
+
bullet: '•',
|
|
188
|
+
|
|
189
|
+
// Actions
|
|
190
|
+
running: '▶',
|
|
191
|
+
complete: '●',
|
|
192
|
+
pending: '○',
|
|
193
|
+
skip: '◌',
|
|
194
|
+
|
|
195
|
+
// Objects
|
|
196
|
+
route: '🛤️',
|
|
197
|
+
env: '🌍',
|
|
198
|
+
auth: '🔒',
|
|
199
|
+
clock: '⏱',
|
|
200
|
+
target: '🎯',
|
|
201
|
+
lightning: '⚡',
|
|
202
|
+
loop: '🔄',
|
|
203
|
+
doc: '📄',
|
|
204
|
+
folder: '📁',
|
|
205
|
+
graph: '📊',
|
|
206
|
+
|
|
207
|
+
// Misc
|
|
208
|
+
sparkle: '✨',
|
|
209
|
+
fire: '🔥',
|
|
210
|
+
star: '★',
|
|
211
|
+
microscope: '🔬',
|
|
212
|
+
beaker: '🧪',
|
|
213
|
+
dna: '🧬',
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
217
|
+
// BOX DRAWING
|
|
218
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
219
|
+
|
|
220
|
+
const BOX = {
|
|
221
|
+
topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯',
|
|
222
|
+
horizontal: '─', vertical: '│',
|
|
223
|
+
teeRight: '├', teeLeft: '┤', teeDown: '┬', teeUp: '┴',
|
|
224
|
+
cross: '┼',
|
|
225
|
+
// Double line
|
|
226
|
+
dTopLeft: '╔', dTopRight: '╗', dBottomLeft: '╚', dBottomRight: '╝',
|
|
227
|
+
dHorizontal: '═', dVertical: '║',
|
|
228
|
+
// Heavy
|
|
229
|
+
hTopLeft: '┏', hTopRight: '┓', hBottomLeft: '┗', hBottomRight: '┛',
|
|
230
|
+
hHorizontal: '━', hVertical: '┃',
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
234
|
+
// SPINNER & PROGRESS
|
|
235
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
236
|
+
|
|
237
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
238
|
+
const SPINNER_DOTS = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
|
|
239
|
+
const SPINNER_DNA = ['🧬', '🔬', '🧪', '⚗️', '🧫', '🔭'];
|
|
240
|
+
|
|
241
|
+
let spinnerIndex = 0;
|
|
242
|
+
let spinnerInterval = null;
|
|
243
|
+
let spinnerStartTime = null;
|
|
244
|
+
|
|
245
|
+
function formatDuration(ms) {
|
|
246
|
+
if (ms < 1000) return `${ms}ms`;
|
|
247
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
248
|
+
const mins = Math.floor(ms / 60000);
|
|
249
|
+
const secs = Math.floor((ms % 60000) / 1000);
|
|
250
|
+
return `${mins}m ${secs}s`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function formatNumber(num) {
|
|
254
|
+
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function truncate(str, len) {
|
|
258
|
+
if (!str) return '';
|
|
259
|
+
if (str.length <= len) return str;
|
|
260
|
+
return str.slice(0, len - 3) + '...';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function padCenter(str, width) {
|
|
264
|
+
const padding = Math.max(0, width - str.length);
|
|
265
|
+
const left = Math.floor(padding / 2);
|
|
266
|
+
const right = padding - left;
|
|
267
|
+
return ' '.repeat(left) + str + ' '.repeat(right);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function progressBar(percent, width = 30, opts = {}) {
|
|
271
|
+
const filled = Math.round((percent / 100) * width);
|
|
272
|
+
const empty = width - filled;
|
|
273
|
+
|
|
274
|
+
let filledColor;
|
|
275
|
+
if (opts.color) {
|
|
276
|
+
filledColor = opts.color;
|
|
277
|
+
} else if (percent >= 80) {
|
|
278
|
+
filledColor = colors.shipGreen;
|
|
279
|
+
} else if (percent >= 50) {
|
|
280
|
+
filledColor = colors.warnAmber;
|
|
281
|
+
} else {
|
|
282
|
+
filledColor = colors.blockRed;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const filledChar = opts.filled || '█';
|
|
286
|
+
const emptyChar = opts.empty || '░';
|
|
287
|
+
|
|
288
|
+
return `${filledColor}${filledChar.repeat(filled)}${c.dim}${emptyChar.repeat(empty)}${c.reset}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function startSpinner(message, color = colors.science) {
|
|
292
|
+
spinnerStartTime = Date.now();
|
|
293
|
+
process.stdout.write(c.hideCursor);
|
|
294
|
+
|
|
295
|
+
spinnerInterval = setInterval(() => {
|
|
296
|
+
const elapsed = formatDuration(Date.now() - spinnerStartTime);
|
|
297
|
+
process.stdout.write(`\r${c.clearLine} ${color}${SPINNER_DOTS[spinnerIndex]}${c.reset} ${message} ${c.dim}${elapsed}${c.reset}`);
|
|
298
|
+
spinnerIndex = (spinnerIndex + 1) % SPINNER_DOTS.length;
|
|
299
|
+
}, 80);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function stopSpinner(message, success = true) {
|
|
303
|
+
if (spinnerInterval) {
|
|
304
|
+
clearInterval(spinnerInterval);
|
|
305
|
+
spinnerInterval = null;
|
|
306
|
+
}
|
|
307
|
+
const elapsed = spinnerStartTime ? formatDuration(Date.now() - spinnerStartTime) : '';
|
|
308
|
+
const icon = success ? `${colors.shipGreen}${ICONS.check}${c.reset}` : `${colors.blockRed}${ICONS.cross}${c.reset}`;
|
|
309
|
+
process.stdout.write(`\r${c.clearLine} ${icon} ${message} ${c.dim}${elapsed}${c.reset}\n`);
|
|
310
|
+
process.stdout.write(c.showCursor);
|
|
311
|
+
spinnerStartTime = null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function updateSpinner(message) {
|
|
315
|
+
if (spinnerInterval) {
|
|
316
|
+
// Just update the message, spinner keeps running
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
321
|
+
// SECTION HEADERS
|
|
322
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
323
|
+
|
|
324
|
+
function printBanner() {
|
|
325
|
+
console.log(BANNER_FULL);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function printCompactBanner() {
|
|
329
|
+
console.log(PROVE_BANNER);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function printDivider(char = '─', width = 69, color = c.dim) {
|
|
333
|
+
console.log(`${color} ${char.repeat(width)}${c.reset}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function printSection(title, icon = '◆') {
|
|
337
|
+
console.log();
|
|
338
|
+
console.log(` ${colors.accent}${icon}${c.reset} ${c.bold}${title}${c.reset}`);
|
|
339
|
+
printDivider();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
343
|
+
// STEP DISPLAY - THE PROVE PIPELINE VISUALIZATION
|
|
344
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
345
|
+
|
|
346
|
+
function getStepConfig(stepNum) {
|
|
347
|
+
const configs = {
|
|
348
|
+
1: { icon: ICONS.context, name: 'CONTEXT', color: colors.step1, desc: 'Refresh truthpack' },
|
|
349
|
+
2: { icon: ICONS.reality, name: 'REALITY', color: colors.step2, desc: 'Runtime UI proof' },
|
|
350
|
+
3: { icon: ICONS.ship, name: 'SHIP', color: colors.step3, desc: 'Static verdict' },
|
|
351
|
+
4: { icon: ICONS.fix, name: 'FIX', color: colors.step4, desc: 'Auto-fix loop' },
|
|
352
|
+
5: { icon: ICONS.verify, name: 'VERIFY', color: colors.step5, desc: 'Final verification' },
|
|
353
|
+
};
|
|
354
|
+
return configs[stepNum] || { icon: ICONS.bullet, name: `STEP ${stepNum}`, color: colors.accent, desc: '' };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function printStepHeader(stepNum, subtitle = null) {
|
|
358
|
+
const config = getStepConfig(stepNum);
|
|
359
|
+
|
|
360
|
+
console.log();
|
|
361
|
+
console.log(` ${config.color}${BOX.hTopLeft}${BOX.hHorizontal.repeat(3)}${c.reset} ${config.icon} ${c.bold}Step ${stepNum}: ${config.name}${c.reset}`);
|
|
362
|
+
|
|
363
|
+
if (subtitle) {
|
|
364
|
+
console.log(` ${config.color}${BOX.hVertical}${c.reset} ${c.dim}${subtitle}${c.reset}`);
|
|
365
|
+
} else {
|
|
366
|
+
console.log(` ${config.color}${BOX.hVertical}${c.reset} ${c.dim}${config.desc}${c.reset}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
console.log(` ${config.color}${BOX.hBottomLeft}${BOX.hHorizontal.repeat(66)}${c.reset}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function printStepResult(stepNum, status, details = {}) {
|
|
373
|
+
const config = getStepConfig(stepNum);
|
|
374
|
+
|
|
375
|
+
let statusIcon, statusColor, statusText;
|
|
376
|
+
|
|
377
|
+
switch (status) {
|
|
378
|
+
case 'ok':
|
|
379
|
+
case 'SHIP':
|
|
380
|
+
case 'CLEAN':
|
|
381
|
+
statusIcon = ICONS.check;
|
|
382
|
+
statusColor = colors.shipGreen;
|
|
383
|
+
statusText = status === 'SHIP' ? 'SHIP' : status === 'CLEAN' ? 'CLEAN' : 'Complete';
|
|
384
|
+
break;
|
|
385
|
+
case 'WARN':
|
|
386
|
+
statusIcon = ICONS.warning;
|
|
387
|
+
statusColor = colors.warnAmber;
|
|
388
|
+
statusText = 'WARN';
|
|
389
|
+
break;
|
|
390
|
+
case 'BLOCK':
|
|
391
|
+
case 'error':
|
|
392
|
+
statusIcon = ICONS.cross;
|
|
393
|
+
statusColor = colors.blockRed;
|
|
394
|
+
statusText = status === 'BLOCK' ? 'BLOCK' : 'Failed';
|
|
395
|
+
break;
|
|
396
|
+
case 'skipped':
|
|
397
|
+
statusIcon = ICONS.skip;
|
|
398
|
+
statusColor = colors.muted;
|
|
399
|
+
statusText = 'Skipped';
|
|
400
|
+
break;
|
|
401
|
+
default:
|
|
402
|
+
statusIcon = ICONS.info;
|
|
403
|
+
statusColor = colors.accent;
|
|
404
|
+
statusText = status;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let resultLine = ` ${statusColor}${statusIcon}${c.reset} ${c.bold}${statusText}${c.reset}`;
|
|
408
|
+
|
|
409
|
+
// Add details
|
|
410
|
+
const detailParts = [];
|
|
411
|
+
if (details.routes !== undefined) detailParts.push(`${details.routes} routes`);
|
|
412
|
+
if (details.envVars !== undefined) detailParts.push(`${details.envVars} env vars`);
|
|
413
|
+
if (details.findings !== undefined) {
|
|
414
|
+
if (typeof details.findings === 'object') {
|
|
415
|
+
detailParts.push(`${details.findings.BLOCK || 0} blockers, ${details.findings.WARN || 0} warnings`);
|
|
416
|
+
} else {
|
|
417
|
+
detailParts.push(`${details.findings} findings`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (details.coverage !== undefined && details.coverage !== null) {
|
|
421
|
+
detailParts.push(`${details.coverage}% coverage`);
|
|
422
|
+
}
|
|
423
|
+
if (details.pagesVisited !== undefined) detailParts.push(`${details.pagesVisited} pages`);
|
|
424
|
+
if (details.reason) detailParts.push(details.reason);
|
|
425
|
+
if (details.error) detailParts.push(`error: ${truncate(details.error, 40)}`);
|
|
426
|
+
|
|
427
|
+
if (detailParts.length > 0) {
|
|
428
|
+
resultLine += ` ${c.dim}(${detailParts.join(', ')})${c.reset}`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
console.log(resultLine);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
435
|
+
// PIPELINE PROGRESS VISUALIZATION
|
|
436
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
437
|
+
|
|
438
|
+
function printPipelineOverview(steps = []) {
|
|
439
|
+
console.log();
|
|
440
|
+
console.log(` ${c.dim}Pipeline:${c.reset}`);
|
|
441
|
+
|
|
442
|
+
let pipeline = ' ';
|
|
443
|
+
const stepConfigs = [1, 2, 3, 4, 5].map(n => getStepConfig(n));
|
|
444
|
+
|
|
445
|
+
for (let i = 0; i < stepConfigs.length; i++) {
|
|
446
|
+
const step = stepConfigs[i];
|
|
447
|
+
const stepStatus = steps[i];
|
|
448
|
+
|
|
449
|
+
let color = colors.muted;
|
|
450
|
+
let icon = ICONS.pending;
|
|
451
|
+
|
|
452
|
+
if (stepStatus === 'complete') {
|
|
453
|
+
color = colors.shipGreen;
|
|
454
|
+
icon = ICONS.complete;
|
|
455
|
+
} else if (stepStatus === 'running') {
|
|
456
|
+
color = step.color;
|
|
457
|
+
icon = ICONS.running;
|
|
458
|
+
} else if (stepStatus === 'error') {
|
|
459
|
+
color = colors.blockRed;
|
|
460
|
+
icon = ICONS.cross;
|
|
461
|
+
} else if (stepStatus === 'skipped') {
|
|
462
|
+
color = colors.muted;
|
|
463
|
+
icon = ICONS.skip;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
pipeline += `${color}${icon}${c.reset}`;
|
|
467
|
+
|
|
468
|
+
if (i < stepConfigs.length - 1) {
|
|
469
|
+
const connectorColor = stepStatus === 'complete' ? colors.shipGreen : colors.muted;
|
|
470
|
+
pipeline += `${connectorColor}───${c.reset}`;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
console.log(pipeline);
|
|
475
|
+
|
|
476
|
+
// Labels
|
|
477
|
+
let labels = ' ';
|
|
478
|
+
for (let i = 0; i < stepConfigs.length; i++) {
|
|
479
|
+
const step = stepConfigs[i];
|
|
480
|
+
labels += `${c.dim}${step.name.slice(0, 3)}${c.reset}`;
|
|
481
|
+
if (i < stepConfigs.length - 1) {
|
|
482
|
+
labels += ' ';
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
console.log(labels);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
489
|
+
// FIX LOOP VISUALIZATION
|
|
490
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
491
|
+
|
|
492
|
+
function printFixLoopHeader(round, maxRounds) {
|
|
493
|
+
const progress = Math.round((round / maxRounds) * 100);
|
|
494
|
+
|
|
495
|
+
console.log();
|
|
496
|
+
console.log(` ${colors.step4}${BOX.topLeft}${BOX.horizontal.repeat(3)}${c.reset} ${ICONS.loop} ${c.bold}Fix Round ${round}/${maxRounds}${c.reset}`);
|
|
497
|
+
console.log(` ${colors.step4}${BOX.vertical}${c.reset} ${progressBar(progress, 20, { color: colors.step4 })} ${c.dim}${progress}%${c.reset}`);
|
|
498
|
+
console.log(` ${colors.step4}${BOX.bottomLeft}${BOX.horizontal.repeat(50)}${c.reset}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function printMissionStatus(mission, status, detail = null) {
|
|
502
|
+
let icon, color;
|
|
503
|
+
|
|
504
|
+
switch (status) {
|
|
505
|
+
case 'planning':
|
|
506
|
+
icon = ICONS.target;
|
|
507
|
+
color = colors.accent;
|
|
508
|
+
break;
|
|
509
|
+
case 'applying':
|
|
510
|
+
icon = ICONS.lightning;
|
|
511
|
+
color = colors.step4;
|
|
512
|
+
break;
|
|
513
|
+
case 'success':
|
|
514
|
+
icon = ICONS.check;
|
|
515
|
+
color = colors.shipGreen;
|
|
516
|
+
break;
|
|
517
|
+
case 'failed':
|
|
518
|
+
icon = ICONS.cross;
|
|
519
|
+
color = colors.blockRed;
|
|
520
|
+
break;
|
|
521
|
+
case 'skipped':
|
|
522
|
+
icon = ICONS.skip;
|
|
523
|
+
color = colors.muted;
|
|
524
|
+
break;
|
|
525
|
+
default:
|
|
526
|
+
icon = ICONS.bullet;
|
|
527
|
+
color = colors.muted;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let line = ` ${color}${icon}${c.reset} ${truncate(mission.title || mission, 50)}`;
|
|
531
|
+
if (detail) {
|
|
532
|
+
line += ` ${c.dim}${detail}${c.reset}`;
|
|
533
|
+
}
|
|
534
|
+
console.log(line);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
538
|
+
// FINAL VERDICT DISPLAY
|
|
539
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
540
|
+
|
|
541
|
+
function getVerdictConfig(verdict) {
|
|
542
|
+
if (verdict === 'SHIP') {
|
|
543
|
+
return {
|
|
544
|
+
icon: '🚀',
|
|
545
|
+
headline: 'PROVED REAL',
|
|
546
|
+
tagline: 'Your app is verified and ready to ship!',
|
|
547
|
+
color: colors.shipGreen,
|
|
548
|
+
bgColor: bgRgb(0, 80, 50),
|
|
549
|
+
borderColor: rgb(0, 200, 120),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (verdict === 'WARN') {
|
|
554
|
+
return {
|
|
555
|
+
icon: '⚠️',
|
|
556
|
+
headline: 'PARTIALLY PROVED',
|
|
557
|
+
tagline: 'Some warnings remain - review before shipping',
|
|
558
|
+
color: colors.warnAmber,
|
|
559
|
+
bgColor: bgRgb(80, 60, 0),
|
|
560
|
+
borderColor: rgb(200, 160, 0),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
icon: '🛑',
|
|
566
|
+
headline: 'NOT PROVED',
|
|
567
|
+
tagline: 'Blockers remain - cannot verify readiness',
|
|
568
|
+
color: colors.blockRed,
|
|
569
|
+
bgColor: bgRgb(80, 20, 20),
|
|
570
|
+
borderColor: rgb(200, 60, 60),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function printFinalVerdict(verdict, duration, fixRounds, findings) {
|
|
575
|
+
const config = getVerdictConfig(verdict);
|
|
576
|
+
const w = 68;
|
|
577
|
+
|
|
578
|
+
console.log();
|
|
579
|
+
console.log();
|
|
580
|
+
|
|
581
|
+
// Top border
|
|
582
|
+
console.log(` ${config.borderColor}${BOX.dTopLeft}${BOX.dHorizontal.repeat(w)}${BOX.dTopRight}${c.reset}`);
|
|
583
|
+
|
|
584
|
+
// Empty line
|
|
585
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
586
|
+
|
|
587
|
+
// Icon and headline
|
|
588
|
+
const headlineText = `${config.icon} ${config.headline}`;
|
|
589
|
+
const headlinePadded = padCenter(headlineText, w);
|
|
590
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${config.color}${c.bold}${headlinePadded}${c.reset}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
591
|
+
|
|
592
|
+
// Tagline
|
|
593
|
+
const taglinePadded = padCenter(config.tagline, w);
|
|
594
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${c.dim}${taglinePadded}${c.reset}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
595
|
+
|
|
596
|
+
// Empty line
|
|
597
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
598
|
+
|
|
599
|
+
// Stats row
|
|
600
|
+
const stats = `Duration: ${c.bold}${duration}${c.reset} ${c.dim}•${c.reset} Fix Rounds: ${c.bold}${fixRounds}${c.reset} ${c.dim}•${c.reset} Findings: ${c.bold}${findings}${c.reset}`;
|
|
601
|
+
const statsPadded = padCenter(stats.replace(/\x1b\[[0-9;]*m/g, '').length < w ? stats : stats, w);
|
|
602
|
+
// We need to calculate visible length for padding
|
|
603
|
+
const visibleStats = `Duration: ${duration} • Fix Rounds: ${fixRounds} • Findings: ${findings}`;
|
|
604
|
+
const leftPad = Math.floor((w - visibleStats.length) / 2);
|
|
605
|
+
const rightPad = w - visibleStats.length - leftPad;
|
|
606
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(leftPad)}Duration: ${c.bold}${duration}${c.reset} ${c.dim}•${c.reset} Fix Rounds: ${c.bold}${fixRounds}${c.reset} ${c.dim}•${c.reset} Findings: ${c.bold}${findings}${c.reset}${' '.repeat(rightPad)}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
607
|
+
|
|
608
|
+
// Empty line
|
|
609
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
610
|
+
|
|
611
|
+
// Bottom border
|
|
612
|
+
console.log(` ${config.borderColor}${BOX.dBottomLeft}${BOX.dHorizontal.repeat(w)}${BOX.dBottomRight}${c.reset}`);
|
|
613
|
+
|
|
614
|
+
console.log();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
618
|
+
// TIMELINE SUMMARY
|
|
619
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
620
|
+
|
|
621
|
+
function printTimelineSummary(timeline) {
|
|
622
|
+
printSection('PROOF TIMELINE', ICONS.graph);
|
|
623
|
+
console.log();
|
|
624
|
+
|
|
625
|
+
for (const entry of timeline) {
|
|
626
|
+
const stepNum = typeof entry.step === 'number' ? entry.step : parseInt(entry.step) || 0;
|
|
627
|
+
const config = getStepConfig(stepNum);
|
|
628
|
+
|
|
629
|
+
let statusIcon, statusColor;
|
|
630
|
+
|
|
631
|
+
if (entry.status === 'ok' || entry.status === 'SHIP' || entry.status === 'CLEAN') {
|
|
632
|
+
statusIcon = ICONS.check;
|
|
633
|
+
statusColor = colors.shipGreen;
|
|
634
|
+
} else if (entry.status === 'WARN') {
|
|
635
|
+
statusIcon = ICONS.warning;
|
|
636
|
+
statusColor = colors.warnAmber;
|
|
637
|
+
} else if (entry.status === 'BLOCK' || entry.status === 'error') {
|
|
638
|
+
statusIcon = ICONS.cross;
|
|
639
|
+
statusColor = colors.blockRed;
|
|
640
|
+
} else if (entry.status === 'skipped') {
|
|
641
|
+
statusIcon = ICONS.skip;
|
|
642
|
+
statusColor = colors.muted;
|
|
643
|
+
} else {
|
|
644
|
+
statusIcon = ICONS.bullet;
|
|
645
|
+
statusColor = colors.accent;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
let line = ` ${statusColor}${statusIcon}${c.reset} ${c.bold}${entry.action.toUpperCase().padEnd(10)}${c.reset}`;
|
|
649
|
+
|
|
650
|
+
// Add relevant details
|
|
651
|
+
const details = [];
|
|
652
|
+
if (entry.routes !== undefined) details.push(`${entry.routes} routes`);
|
|
653
|
+
if (entry.verdict) details.push(entry.verdict);
|
|
654
|
+
if (entry.findingsCount !== undefined) details.push(`${entry.findingsCount} findings`);
|
|
655
|
+
if (entry.coverage !== undefined && entry.coverage !== null) details.push(`${entry.coverage}% coverage`);
|
|
656
|
+
if (entry.reason) details.push(entry.reason);
|
|
657
|
+
|
|
658
|
+
if (details.length > 0) {
|
|
659
|
+
line += ` ${c.dim}${details.join(' · ')}${c.reset}`;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
console.log(line);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
667
|
+
// HELP DISPLAY
|
|
668
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
669
|
+
|
|
670
|
+
function printHelp() {
|
|
671
|
+
console.log(BANNER_FULL);
|
|
672
|
+
console.log(`
|
|
673
|
+
${c.bold}Usage:${c.reset} vibecheck prove [options]
|
|
674
|
+
|
|
675
|
+
${c.bold}One Command Reality Proof${c.reset} — Make it real or keep fixing until it is.
|
|
676
|
+
|
|
677
|
+
${c.bold}Pipeline:${c.reset}
|
|
678
|
+
${colors.step1}1. CTX${c.reset} Refresh truthpack (ground truth)
|
|
679
|
+
${colors.step2}2. REALITY${c.reset} Runtime UI proof ${c.dim}(if --url provided)${c.reset}
|
|
680
|
+
${colors.step3}3. SHIP${c.reset} Static + runtime verdict
|
|
681
|
+
${colors.step4}4. FIX${c.reset} Auto-fix if BLOCK ${c.dim}(up to N rounds)${c.reset}
|
|
682
|
+
${colors.step5}5. VERIFY${c.reset} Re-run to confirm SHIP
|
|
683
|
+
|
|
684
|
+
${c.bold}Options:${c.reset}
|
|
685
|
+
${colors.accent}--url, -u <url>${c.reset} Base URL for runtime testing
|
|
686
|
+
${colors.accent}--auth <email:pass>${c.reset} Login credentials for auth verification
|
|
687
|
+
${colors.accent}--storage-state <path>${c.reset} Playwright session state file
|
|
688
|
+
${colors.accent}--fastify-entry <path>${c.reset} Fastify entry file for route extraction
|
|
689
|
+
${colors.accent}--max-fix-rounds <n>${c.reset} Max auto-fix attempts ${c.dim}(default: 3)${c.reset}
|
|
690
|
+
${colors.accent}--skip-reality${c.reset} Skip runtime crawling (static only)
|
|
691
|
+
${colors.accent}--skip-fix${c.reset} Don't auto-fix, just diagnose
|
|
692
|
+
${colors.accent}--headed${c.reset} Run browser in headed mode
|
|
693
|
+
${colors.accent}--danger${c.reset} Allow clicking destructive elements
|
|
694
|
+
${colors.accent}--help, -h${c.reset} Show this help
|
|
695
|
+
|
|
696
|
+
${c.bold}Exit Codes:${c.reset}
|
|
697
|
+
${colors.shipGreen}0${c.reset} SHIP — Proved real, ready to deploy
|
|
698
|
+
${colors.warnAmber}1${c.reset} WARN — Warnings found, review recommended
|
|
699
|
+
${colors.blockRed}2${c.reset} BLOCK — Blockers found, not proved
|
|
700
|
+
|
|
701
|
+
${c.bold}Examples:${c.reset}
|
|
702
|
+
${c.dim}# Full proof with runtime testing${c.reset}
|
|
703
|
+
vibecheck prove --url http://localhost:3000
|
|
704
|
+
|
|
705
|
+
${c.dim}# With authentication${c.reset}
|
|
706
|
+
vibecheck prove --url http://localhost:3000 --auth user@test.com:pass
|
|
707
|
+
|
|
708
|
+
${c.dim}# Static only (no runtime)${c.reset}
|
|
709
|
+
vibecheck prove --skip-reality
|
|
710
|
+
|
|
711
|
+
${c.dim}# More fix attempts${c.reset}
|
|
712
|
+
vibecheck prove --url http://localhost:3000 --max-fix-rounds 5
|
|
713
|
+
`);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
717
|
+
// UTILITIES
|
|
718
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
37
719
|
|
|
38
720
|
function ensureDir(p) {
|
|
39
721
|
fs.mkdirSync(p, { recursive: true });
|
|
@@ -45,46 +727,15 @@ function stamp() {
|
|
|
45
727
|
return `${d.getFullYear()}${z(d.getMonth() + 1)}${z(d.getDate())}_${z(d.getHours())}${z(d.getMinutes())}${z(d.getSeconds())}`;
|
|
46
728
|
}
|
|
47
729
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
${c.bold}USAGE${c.reset}
|
|
53
|
-
vibecheck prove [options]
|
|
54
|
-
|
|
55
|
-
${c.bold}OPTIONS${c.reset}
|
|
56
|
-
--url, -u <url> Base URL for runtime testing
|
|
57
|
-
--auth <email:pass> Login credentials for auth verification
|
|
58
|
-
--storage-state <path> Playwright session state file
|
|
59
|
-
--fastify-entry <path> Fastify entry file for route extraction
|
|
60
|
-
--max-fix-rounds <n> Max auto-fix attempts (default: 3)
|
|
61
|
-
--skip-reality Skip runtime crawling (static only)
|
|
62
|
-
--skip-fix Don't auto-fix, just diagnose
|
|
63
|
-
--headed Run browser in headed mode
|
|
64
|
-
--danger Allow clicking destructive elements
|
|
65
|
-
--help, -h Show this help
|
|
66
|
-
|
|
67
|
-
${c.bold}WHAT IT DOES${c.reset}
|
|
68
|
-
1. ${c.cyan}ctx${c.reset} - Refresh truthpack (ground truth)
|
|
69
|
-
2. ${c.cyan}reality${c.reset} - Runtime UI proof (if --url provided)
|
|
70
|
-
3. ${c.cyan}ship${c.reset} - Static + runtime verdict
|
|
71
|
-
4. ${c.cyan}fix${c.reset} - Auto-fix if BLOCK (up to N rounds)
|
|
72
|
-
5. ${c.cyan}verify${c.reset} - Re-run to confirm SHIP
|
|
73
|
-
|
|
74
|
-
${c.bold}EXIT CODES${c.reset}
|
|
75
|
-
0 = SHIP (ready to deploy)
|
|
76
|
-
1 = WARN (warnings found)
|
|
77
|
-
2 = BLOCK (blockers found)
|
|
78
|
-
|
|
79
|
-
${c.bold}EXAMPLES${c.reset}
|
|
80
|
-
vibecheck prove --url http://localhost:3000
|
|
81
|
-
vibecheck prove --url http://localhost:3000 --auth user@test.com:pass
|
|
82
|
-
vibecheck prove --skip-reality
|
|
83
|
-
vibecheck prove --url http://localhost:3000 --max-fix-rounds 5
|
|
84
|
-
`);
|
|
85
|
-
}
|
|
730
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
731
|
+
// MAIN PROVE FUNCTION
|
|
732
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
86
733
|
|
|
87
|
-
async function runProve(argsOrOpts = {}) {
|
|
734
|
+
async function runProve(argsOrOpts = {}, context = {}) {
|
|
735
|
+
// Extract runId from context or generate new one
|
|
736
|
+
const runId = context.runId || generateRunId();
|
|
737
|
+
const startTime = context.startTime || new Date().toISOString();
|
|
738
|
+
|
|
88
739
|
// Handle array args from CLI
|
|
89
740
|
if (Array.isArray(argsOrOpts)) {
|
|
90
741
|
if (argsOrOpts.includes("--help") || argsOrOpts.includes("-h")) {
|
|
@@ -108,6 +759,9 @@ async function runProve(argsOrOpts = {}) {
|
|
|
108
759
|
skipFix: argsOrOpts.includes("--skip-fix"),
|
|
109
760
|
headed: argsOrOpts.includes("--headed"),
|
|
110
761
|
danger: argsOrOpts.includes("--danger"),
|
|
762
|
+
json: argsOrOpts.includes("--json"),
|
|
763
|
+
output: getArg(["--output", "-o"]),
|
|
764
|
+
ci: argsOrOpts.includes("--ci"),
|
|
111
765
|
};
|
|
112
766
|
}
|
|
113
767
|
|
|
@@ -126,35 +780,53 @@ async function runProve(argsOrOpts = {}) {
|
|
|
126
780
|
danger = false,
|
|
127
781
|
maxPages = 18,
|
|
128
782
|
maxDepth = 2,
|
|
129
|
-
timeoutMs = 15000
|
|
783
|
+
timeoutMs = 15000,
|
|
784
|
+
json = false,
|
|
785
|
+
output = null,
|
|
786
|
+
ci = false
|
|
130
787
|
} = argsOrOpts;
|
|
131
788
|
|
|
132
789
|
const root = repoRoot || process.cwd();
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
console.log(`
|
|
136
|
-
${c.cyan}${c.bold}╔════════════════════════════════════════════════════════════╗
|
|
137
|
-
║ 🔬 vibecheck prove ║
|
|
138
|
-
║ One command to make it real ║
|
|
139
|
-
╚════════════════════════════════════════════════════════════╝${c.reset}
|
|
140
|
-
`);
|
|
790
|
+
const projectName = path.basename(root);
|
|
141
791
|
|
|
792
|
+
// Initialize pipeline status
|
|
793
|
+
const pipelineStatus = ['pending', 'pending', 'pending', 'pending', 'pending'];
|
|
142
794
|
const outDir = path.join(root, ".vibecheck", "prove", stamp());
|
|
143
795
|
ensureDir(outDir);
|
|
144
|
-
|
|
796
|
+
|
|
145
797
|
const timeline = [];
|
|
146
798
|
let finalVerdict = "UNKNOWN";
|
|
147
799
|
|
|
800
|
+
// Print banner (unless JSON or CI mode)
|
|
801
|
+
if (!json && !ci) {
|
|
802
|
+
printBanner();
|
|
803
|
+
|
|
804
|
+
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
805
|
+
console.log(` ${c.dim}Path:${c.reset} ${root}`);
|
|
806
|
+
if (url) {
|
|
807
|
+
console.log(` ${c.dim}URL:${c.reset} ${colors.accent}${url}${c.reset}`);
|
|
808
|
+
}
|
|
809
|
+
console.log(` ${c.dim}Fix Rounds:${c.reset} ${maxFixRounds} max`);
|
|
810
|
+
|
|
811
|
+
// Show pipeline overview
|
|
812
|
+
printPipelineOverview(pipelineStatus);
|
|
813
|
+
}
|
|
814
|
+
|
|
148
815
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
149
816
|
// STEP 1: CTX - Refresh truthpack
|
|
150
817
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
151
|
-
|
|
818
|
+
printStepHeader(1);
|
|
819
|
+
pipelineStatus[0] = 'running';
|
|
820
|
+
|
|
821
|
+
startSpinner('Refreshing truthpack...', colors.step1);
|
|
152
822
|
|
|
153
823
|
try {
|
|
154
824
|
const fastEntry = fastifyEntry || (await detectFastifyEntry(root));
|
|
155
825
|
const truthpack = await buildTruthpack({ repoRoot: root, fastifyEntry: fastEntry });
|
|
156
826
|
writeTruthpack(root, truthpack);
|
|
157
827
|
|
|
828
|
+
stopSpinner('Truthpack refreshed', true);
|
|
829
|
+
|
|
158
830
|
timeline.push({
|
|
159
831
|
step: 1,
|
|
160
832
|
action: "ctx",
|
|
@@ -163,7 +835,10 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
163
835
|
envVars: truthpack.env?.vars?.length || 0
|
|
164
836
|
});
|
|
165
837
|
|
|
166
|
-
|
|
838
|
+
printStepResult(1, 'ok', {
|
|
839
|
+
routes: truthpack.routes?.server?.length || 0,
|
|
840
|
+
envVars: truthpack.env?.vars?.length || 0
|
|
841
|
+
});
|
|
167
842
|
|
|
168
843
|
// Check for contract drift after truthpack refresh
|
|
169
844
|
if (hasContracts(root)) {
|
|
@@ -172,7 +847,7 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
172
847
|
const driftSummary = getDriftSummary(driftFindings);
|
|
173
848
|
|
|
174
849
|
if (driftSummary.hasDrift) {
|
|
175
|
-
console.log(`
|
|
850
|
+
console.log(` ${driftSummary.blocks > 0 ? colors.warnAmber + ICONS.warning : colors.muted + ICONS.info}${c.reset} Contract drift: ${driftSummary.blocks} blocks, ${driftSummary.warns} warnings`);
|
|
176
851
|
timeline.push({
|
|
177
852
|
step: "1.5",
|
|
178
853
|
action: "drift_check",
|
|
@@ -182,20 +857,26 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
182
857
|
});
|
|
183
858
|
|
|
184
859
|
if (driftSummary.blocks > 0) {
|
|
185
|
-
console.log(`
|
|
860
|
+
console.log(` ${c.dim}Run 'vibecheck ctx sync' to update contracts${c.reset}`);
|
|
186
861
|
}
|
|
187
862
|
}
|
|
188
863
|
}
|
|
864
|
+
|
|
865
|
+
pipelineStatus[0] = 'complete';
|
|
189
866
|
} catch (err) {
|
|
867
|
+
stopSpinner(`Context refresh failed: ${err.message}`, false);
|
|
190
868
|
timeline.push({ step: 1, action: "ctx", status: "error", error: err.message });
|
|
191
|
-
|
|
869
|
+
pipelineStatus[0] = 'error';
|
|
192
870
|
}
|
|
193
871
|
|
|
194
872
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
195
873
|
// STEP 2: REALITY - Runtime UI proof (if URL provided)
|
|
196
874
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
197
875
|
if (url && !skipReality && runReality) {
|
|
198
|
-
|
|
876
|
+
printStepHeader(2, url);
|
|
877
|
+
pipelineStatus[1] = 'running';
|
|
878
|
+
|
|
879
|
+
startSpinner('Running reality check...', colors.step2);
|
|
199
880
|
|
|
200
881
|
try {
|
|
201
882
|
await runReality({
|
|
@@ -218,6 +899,8 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
218
899
|
const blocks = (reality.findings || []).filter(f => f.severity === "BLOCK").length;
|
|
219
900
|
const warns = (reality.findings || []).filter(f => f.severity === "WARN").length;
|
|
220
901
|
|
|
902
|
+
stopSpinner('Reality check complete', true);
|
|
903
|
+
|
|
221
904
|
timeline.push({
|
|
222
905
|
step: 2,
|
|
223
906
|
action: "reality",
|
|
@@ -227,30 +910,39 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
227
910
|
coverage: reality.coverage?.percent || null
|
|
228
911
|
});
|
|
229
912
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
913
|
+
printStepResult(2, blocks ? 'BLOCK' : warns ? 'WARN' : 'CLEAN', {
|
|
914
|
+
findings: { BLOCK: blocks, WARN: warns },
|
|
915
|
+
pagesVisited: reality.passes?.anon?.pagesVisited?.length || 0,
|
|
916
|
+
coverage: reality.coverage?.percent
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
pipelineStatus[1] = blocks ? 'error' : 'complete';
|
|
234
920
|
}
|
|
235
921
|
} catch (err) {
|
|
922
|
+
stopSpinner(`Reality check failed: ${err.message}`, false);
|
|
236
923
|
timeline.push({ step: 2, action: "reality", status: "error", error: err.message });
|
|
237
|
-
|
|
924
|
+
pipelineStatus[1] = 'error';
|
|
238
925
|
}
|
|
239
|
-
} else
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
926
|
+
} else {
|
|
927
|
+
const reason = !url ? 'no --url provided' : '--skip-reality flag';
|
|
928
|
+
console.log();
|
|
929
|
+
console.log(` ${colors.muted}${ICONS.skip}${c.reset} ${c.dim}Step 2: REALITY skipped (${reason})${c.reset}`);
|
|
930
|
+
timeline.push({ step: 2, action: "reality", status: "skipped", reason });
|
|
931
|
+
pipelineStatus[1] = 'skipped';
|
|
245
932
|
}
|
|
246
933
|
|
|
247
934
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
248
935
|
// STEP 3: SHIP - Get initial verdict
|
|
249
936
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
250
|
-
|
|
937
|
+
printStepHeader(3);
|
|
938
|
+
pipelineStatus[2] = 'running';
|
|
939
|
+
|
|
940
|
+
startSpinner('Running ship check...', colors.step3);
|
|
251
941
|
|
|
252
942
|
let shipResult = await shipCore({ repoRoot: root, fastifyEntry, noWrite: false });
|
|
253
943
|
|
|
944
|
+
stopSpinner('Ship check complete', true);
|
|
945
|
+
|
|
254
946
|
timeline.push({
|
|
255
947
|
step: 3,
|
|
256
948
|
action: "ship",
|
|
@@ -258,8 +950,11 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
258
950
|
findingsCount: shipResult.report?.findings?.length || 0
|
|
259
951
|
});
|
|
260
952
|
|
|
261
|
-
|
|
262
|
-
|
|
953
|
+
printStepResult(3, shipResult.verdict, {
|
|
954
|
+
findings: shipResult.report?.findings?.length || 0
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
pipelineStatus[2] = shipResult.verdict === 'SHIP' ? 'complete' : shipResult.verdict === 'WARN' ? 'complete' : 'error';
|
|
263
958
|
|
|
264
959
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
265
960
|
// STEP 4: FIX LOOP - If BLOCK, attempt autopilot fix
|
|
@@ -268,7 +963,13 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
268
963
|
|
|
269
964
|
while (shipResult.verdict === "BLOCK" && !skipFix && fixRound < maxFixRounds) {
|
|
270
965
|
fixRound++;
|
|
271
|
-
|
|
966
|
+
|
|
967
|
+
if (fixRound === 1) {
|
|
968
|
+
printStepHeader(4, `Up to ${maxFixRounds} rounds`);
|
|
969
|
+
pipelineStatus[3] = 'running';
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
printFixLoopHeader(fixRound, maxFixRounds);
|
|
272
973
|
|
|
273
974
|
try {
|
|
274
975
|
// Import fix dependencies lazily
|
|
@@ -285,16 +986,18 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
285
986
|
const missions = planMissions(findings, { maxMissions, blocksOnlyFirst: true });
|
|
286
987
|
|
|
287
988
|
if (!missions.length) {
|
|
288
|
-
console.log(`
|
|
989
|
+
console.log(` ${colors.warnAmber}${ICONS.warning}${c.reset} No fixable missions found`);
|
|
289
990
|
timeline.push({ step: `4.${fixRound}`, action: "fix", status: "no_missions" });
|
|
290
991
|
break;
|
|
291
992
|
}
|
|
292
993
|
|
|
293
|
-
console.log(`
|
|
994
|
+
console.log(` ${c.dim}Planning ${missions.length} missions...${c.reset}`);
|
|
294
995
|
|
|
295
996
|
let fixedAny = false;
|
|
296
997
|
|
|
297
998
|
for (const mission of missions.slice(0, 3)) {
|
|
999
|
+
printMissionStatus(mission, 'planning');
|
|
1000
|
+
|
|
298
1001
|
const targetFindings = findings.filter(f => mission.targetFindingIds.includes(f.id));
|
|
299
1002
|
const template = templateForMissionType(mission.type);
|
|
300
1003
|
|
|
@@ -318,13 +1021,13 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
318
1021
|
allowedFiles: expanded.allowedFiles
|
|
319
1022
|
});
|
|
320
1023
|
|
|
321
|
-
|
|
1024
|
+
printMissionStatus(mission, 'applying');
|
|
322
1025
|
|
|
323
1026
|
try {
|
|
324
1027
|
const patchResponse = await generatePatchJson(prompt);
|
|
325
1028
|
|
|
326
1029
|
if (!patchResponse || patchResponse.status !== "ok" || !patchResponse.edits?.length) {
|
|
327
|
-
|
|
1030
|
+
printMissionStatus(mission, 'skipped', 'no patch generated');
|
|
328
1031
|
continue;
|
|
329
1032
|
}
|
|
330
1033
|
|
|
@@ -334,7 +1037,7 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
334
1037
|
});
|
|
335
1038
|
|
|
336
1039
|
if (!validation.valid) {
|
|
337
|
-
|
|
1040
|
+
printMissionStatus(mission, 'failed', `validation: ${validation.reason}`);
|
|
338
1041
|
continue;
|
|
339
1042
|
}
|
|
340
1043
|
|
|
@@ -351,17 +1054,19 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
351
1054
|
try {
|
|
352
1055
|
await applyUnifiedDiff(root, edit.diff);
|
|
353
1056
|
appliedAny = true;
|
|
354
|
-
console.log(` ${c.green}✓${c.reset} Applied: ${edit.path}`);
|
|
355
1057
|
} catch (e) {
|
|
356
|
-
|
|
1058
|
+
// Patch failed
|
|
357
1059
|
}
|
|
358
1060
|
}
|
|
359
1061
|
|
|
360
1062
|
if (appliedAny) {
|
|
361
1063
|
fixedAny = true;
|
|
1064
|
+
printMissionStatus(mission, 'success', `${patchResponse.edits.length} edits`);
|
|
1065
|
+
} else {
|
|
1066
|
+
printMissionStatus(mission, 'failed', 'patch apply failed');
|
|
362
1067
|
}
|
|
363
1068
|
} catch (e) {
|
|
364
|
-
|
|
1069
|
+
printMissionStatus(mission, 'failed', `LLM error: ${truncate(e.message, 30)}`);
|
|
365
1070
|
}
|
|
366
1071
|
}
|
|
367
1072
|
|
|
@@ -373,29 +1078,42 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
373
1078
|
});
|
|
374
1079
|
|
|
375
1080
|
if (!fixedAny) {
|
|
376
|
-
console.log(`
|
|
1081
|
+
console.log(` ${colors.warnAmber}${ICONS.warning}${c.reset} No fixes applied this round`);
|
|
377
1082
|
break;
|
|
378
1083
|
}
|
|
379
1084
|
|
|
380
1085
|
// Re-run ship to check progress
|
|
381
|
-
|
|
1086
|
+
startSpinner('Re-checking ship verdict...', colors.step3);
|
|
382
1087
|
shipResult = await shipCore({ repoRoot: root, fastifyEntry, noWrite: false });
|
|
1088
|
+
stopSpinner('Ship check complete', true);
|
|
383
1089
|
|
|
384
|
-
|
|
385
|
-
|
|
1090
|
+
printStepResult(3, shipResult.verdict, {
|
|
1091
|
+
findings: shipResult.report?.findings?.length || 0
|
|
1092
|
+
});
|
|
386
1093
|
|
|
387
1094
|
} catch (err) {
|
|
388
|
-
console.log(`
|
|
1095
|
+
console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} Fix error: ${err.message}`);
|
|
389
1096
|
timeline.push({ step: `4.${fixRound}`, action: "fix", status: "error", error: err.message });
|
|
390
1097
|
break;
|
|
391
1098
|
}
|
|
392
1099
|
}
|
|
1100
|
+
|
|
1101
|
+
if (fixRound > 0) {
|
|
1102
|
+
pipelineStatus[3] = shipResult.verdict === 'SHIP' ? 'complete' : 'error';
|
|
1103
|
+
} else if (skipFix) {
|
|
1104
|
+
pipelineStatus[3] = 'skipped';
|
|
1105
|
+
} else if (shipResult.verdict !== 'BLOCK') {
|
|
1106
|
+
pipelineStatus[3] = 'skipped';
|
|
1107
|
+
}
|
|
393
1108
|
|
|
394
1109
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
395
1110
|
// STEP 5: FINAL VERIFICATION - Re-run reality + ship
|
|
396
1111
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
397
1112
|
if (url && !skipReality && runReality && fixRound > 0) {
|
|
398
|
-
|
|
1113
|
+
printStepHeader(5);
|
|
1114
|
+
pipelineStatus[4] = 'running';
|
|
1115
|
+
|
|
1116
|
+
startSpinner('Running final verification...', colors.step5);
|
|
399
1117
|
|
|
400
1118
|
try {
|
|
401
1119
|
await runReality({
|
|
@@ -413,6 +1131,8 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
413
1131
|
|
|
414
1132
|
shipResult = await shipCore({ repoRoot: root, fastifyEntry, noWrite: false });
|
|
415
1133
|
|
|
1134
|
+
stopSpinner('Final verification complete', true);
|
|
1135
|
+
|
|
416
1136
|
timeline.push({
|
|
417
1137
|
step: 5,
|
|
418
1138
|
action: "verify",
|
|
@@ -420,11 +1140,17 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
420
1140
|
findingsCount: shipResult.report?.findings?.length || 0
|
|
421
1141
|
});
|
|
422
1142
|
|
|
423
|
-
|
|
424
|
-
|
|
1143
|
+
printStepResult(5, shipResult.verdict, {
|
|
1144
|
+
findings: shipResult.report?.findings?.length || 0
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
pipelineStatus[4] = shipResult.verdict === 'SHIP' ? 'complete' : 'error';
|
|
425
1148
|
} catch (err) {
|
|
426
|
-
|
|
1149
|
+
stopSpinner(`Final verification failed: ${err.message}`, false);
|
|
1150
|
+
pipelineStatus[4] = 'error';
|
|
427
1151
|
}
|
|
1152
|
+
} else {
|
|
1153
|
+
pipelineStatus[4] = 'skipped';
|
|
428
1154
|
}
|
|
429
1155
|
|
|
430
1156
|
finalVerdict = shipResult.verdict;
|
|
@@ -432,13 +1158,14 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
432
1158
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
433
1159
|
// SUMMARY
|
|
434
1160
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
435
|
-
const duration =
|
|
1161
|
+
const duration = Date.now() - executionStart;
|
|
1162
|
+
const durationStr = formatDuration(duration);
|
|
436
1163
|
|
|
437
1164
|
const report = {
|
|
438
1165
|
meta: {
|
|
439
|
-
startedAt: new Date(
|
|
1166
|
+
startedAt: new Date(executionStart).toISOString(),
|
|
440
1167
|
finishedAt: new Date().toISOString(),
|
|
441
|
-
|
|
1168
|
+
durationMs: duration,
|
|
442
1169
|
url: url || null,
|
|
443
1170
|
fixRounds: fixRound
|
|
444
1171
|
},
|
|
@@ -454,20 +1181,73 @@ ${c.cyan}${c.bold}╔═══════════════════
|
|
|
454
1181
|
ensureDir(path.dirname(latestPath));
|
|
455
1182
|
fs.writeFileSync(latestPath, JSON.stringify(report, null, 2), "utf8");
|
|
456
1183
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
1184
|
+
// Save artifacts
|
|
1185
|
+
saveArtifact(runId, "prove-report", report);
|
|
1186
|
+
saveArtifact(runId, "timeline", timeline);
|
|
1187
|
+
|
|
1188
|
+
// JSON output mode
|
|
1189
|
+
if (json) {
|
|
1190
|
+
const output = createJsonOutput({
|
|
1191
|
+
runId,
|
|
1192
|
+
command: "prove",
|
|
1193
|
+
startTime: executionStart,
|
|
1194
|
+
exitCode: verdictToExitCode(finalVerdict),
|
|
1195
|
+
verdict: finalVerdict,
|
|
1196
|
+
result: {
|
|
1197
|
+
meta: report.meta,
|
|
1198
|
+
timeline,
|
|
1199
|
+
finalVerdict,
|
|
1200
|
+
fixRounds: fixRound,
|
|
1201
|
+
duration: duration
|
|
1202
|
+
},
|
|
1203
|
+
tier: "pro",
|
|
1204
|
+
version: require("../../package.json").version,
|
|
1205
|
+
artifacts: [
|
|
1206
|
+
{
|
|
1207
|
+
type: "report",
|
|
1208
|
+
path: path.join(outDir, "prove_report.json"),
|
|
1209
|
+
description: "Prove report with timeline"
|
|
1210
|
+
}
|
|
1211
|
+
]
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
writeJsonOutput(output, output);
|
|
1215
|
+
}
|
|
465
1216
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1217
|
+
// Final verdict display (unless JSON or CI mode)
|
|
1218
|
+
if (!json && !ci) {
|
|
1219
|
+
printFinalVerdict(finalVerdict, durationStr, fixRound, shipResult.report?.findings?.length || 0);
|
|
1220
|
+
|
|
1221
|
+
// Timeline summary
|
|
1222
|
+
printTimelineSummary(timeline);
|
|
1223
|
+
|
|
1224
|
+
// Report links
|
|
1225
|
+
printSection('REPORTS', ICONS.doc);
|
|
1226
|
+
console.log();
|
|
1227
|
+
console.log(` ${colors.accent}${outDir}/prove_report.json${c.reset}`);
|
|
1228
|
+
console.log(` ${c.dim}${path.join(root, '.vibecheck', 'prove', 'last_prove.json')}${c.reset}`);
|
|
1229
|
+
console.log();
|
|
1230
|
+
|
|
1231
|
+
// Next steps if not proved
|
|
1232
|
+
if (finalVerdict !== 'SHIP') {
|
|
1233
|
+
printSection('NEXT STEPS', ICONS.lightning);
|
|
1234
|
+
console.log();
|
|
1235
|
+
if (skipFix) {
|
|
1236
|
+
console.log(` ${colors.accent}vibecheck prove --url ${url || '<url>'}${c.reset} ${c.dim}Enable auto-fix${c.reset}`);
|
|
1237
|
+
} else if (fixRound >= maxFixRounds) {
|
|
1238
|
+
console.log(` ${colors.accent}vibecheck prove --max-fix-rounds ${maxFixRounds + 2}${c.reset} ${c.dim}Try more fix rounds${c.reset}`);
|
|
1239
|
+
}
|
|
1240
|
+
console.log(` ${colors.accent}vibecheck ship --fix${c.reset} ${c.dim}Manual fix mode${c.reset}`);
|
|
1241
|
+
console.log();
|
|
1242
|
+
}
|
|
1243
|
+
} else if (ci) {
|
|
1244
|
+
// CI mode - minimal output
|
|
1245
|
+
console.log(`VERDICT=${finalVerdict}`);
|
|
1246
|
+
console.log(`DURATION=${duration}ms`);
|
|
1247
|
+
console.log(`FIX_ROUNDS=${fixRound}`);
|
|
1248
|
+
}
|
|
469
1249
|
|
|
470
|
-
|
|
1250
|
+
return verdictToExitCode(finalVerdict);
|
|
471
1251
|
}
|
|
472
1252
|
|
|
473
|
-
module.exports = { runProve };
|
|
1253
|
+
module.exports = { runProve };
|