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