@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
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* runVerify.js - CLI Verify Command
|
|
4
|
+
* Verifies AI-generated agent output against vibecheck-v1 protocol
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* vibecheck verify [options]
|
|
8
|
+
* vibecheck verify --input response.json
|
|
9
|
+
* vibecheck verify --stdin
|
|
10
|
+
* vibecheck verify --mode ship --strict
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
"use strict";
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { parseArgs } = require('util');
|
|
18
|
+
|
|
19
|
+
const HELP = `
|
|
20
|
+
vibecheck verify - Verify AI-generated agent output
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
vibecheck verify [options]
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--input, -i <file> Input file containing agent response JSON
|
|
27
|
+
--stdin Read agent response from stdin
|
|
28
|
+
--root, -r <dir> Project root directory (default: cwd)
|
|
29
|
+
--mode, -m <mode> Verification mode: explore|build|ship (default: build)
|
|
30
|
+
--strict Enable strict mode (fail on warnings)
|
|
31
|
+
--json Output results as JSON
|
|
32
|
+
--help, -h Show this help message
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
vibecheck verify --input agent-response.json
|
|
36
|
+
echo '{"format":"vibecheck-v1",...}' | vibecheck verify --stdin
|
|
37
|
+
vibecheck verify --input response.json --mode ship --strict
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
const { values, positionals } = parseArgs({
|
|
42
|
+
options: {
|
|
43
|
+
input: { type: 'string', short: 'i' },
|
|
44
|
+
stdin: { type: 'boolean', default: false },
|
|
45
|
+
root: { type: 'string', short: 'r' },
|
|
46
|
+
mode: { type: 'string', short: 'm', default: 'build' },
|
|
47
|
+
strict: { type: 'boolean', default: false },
|
|
48
|
+
json: { type: 'boolean', default: false },
|
|
49
|
+
help: { type: 'boolean', short: 'h' },
|
|
50
|
+
},
|
|
51
|
+
allowPositionals: true,
|
|
52
|
+
strict: false,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (values.help) {
|
|
56
|
+
console.log(HELP);
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const projectRoot = values.root ? path.resolve(values.root) : process.cwd();
|
|
61
|
+
const mode = values.mode || 'build';
|
|
62
|
+
const strict = values.strict || false;
|
|
63
|
+
const outputJson = values.json || false;
|
|
64
|
+
|
|
65
|
+
// Validate mode
|
|
66
|
+
if (!['explore', 'build', 'ship'].includes(mode)) {
|
|
67
|
+
console.error(`Error: Invalid mode "${mode}". Must be one of: explore, build, ship`);
|
|
68
|
+
process.exit(2);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get input
|
|
72
|
+
let rawInput = '';
|
|
73
|
+
|
|
74
|
+
if (values.stdin) {
|
|
75
|
+
rawInput = await readStdin();
|
|
76
|
+
} else if (values.input) {
|
|
77
|
+
const inputPath = path.resolve(values.input);
|
|
78
|
+
if (!fs.existsSync(inputPath)) {
|
|
79
|
+
console.error(`Error: Input file not found: ${inputPath}`);
|
|
80
|
+
process.exit(2);
|
|
81
|
+
}
|
|
82
|
+
rawInput = fs.readFileSync(inputPath, 'utf-8');
|
|
83
|
+
} else if (positionals.length > 0) {
|
|
84
|
+
// Allow positional argument as input file
|
|
85
|
+
const inputPath = path.resolve(positionals[0]);
|
|
86
|
+
if (!fs.existsSync(inputPath)) {
|
|
87
|
+
console.error(`Error: Input file not found: ${inputPath}`);
|
|
88
|
+
process.exit(2);
|
|
89
|
+
}
|
|
90
|
+
rawInput = fs.readFileSync(inputPath, 'utf-8');
|
|
91
|
+
} else {
|
|
92
|
+
console.error('Error: No input provided. Use --input <file> or --stdin');
|
|
93
|
+
console.log(HELP);
|
|
94
|
+
process.exit(2);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!rawInput.trim()) {
|
|
98
|
+
console.error('Error: Empty input');
|
|
99
|
+
process.exit(2);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Load the verification module
|
|
103
|
+
let validateFormat, verifyAgentOutput, formatCheckResults, buildJsonReport;
|
|
104
|
+
try {
|
|
105
|
+
// Try loading from the built TypeScript output
|
|
106
|
+
const verification = require('../../dist/lib/verification/index.js');
|
|
107
|
+
validateFormat = verification.validateFormat;
|
|
108
|
+
verifyAgentOutput = verification.verifyAgentOutput;
|
|
109
|
+
formatCheckResults = verification.formatCheckResults;
|
|
110
|
+
buildJsonReport = verification.buildJsonReport;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
// Fallback: try loading directly (for development)
|
|
113
|
+
try {
|
|
114
|
+
const verification = require('../../src/lib/verification/index.ts');
|
|
115
|
+
validateFormat = verification.validateFormat;
|
|
116
|
+
verifyAgentOutput = verification.verifyAgentOutput;
|
|
117
|
+
formatCheckResults = verification.formatCheckResults;
|
|
118
|
+
buildJsonReport = verification.buildJsonReport;
|
|
119
|
+
} catch (err2) {
|
|
120
|
+
console.error('Error loading verification module:', err.message);
|
|
121
|
+
console.error('Make sure the project is built: pnpm build');
|
|
122
|
+
process.exit(2);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Step 1: Validate format
|
|
127
|
+
if (!outputJson) {
|
|
128
|
+
console.log('🔍 Validating agent output format...');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const formatResult = validateFormat(rawInput);
|
|
132
|
+
|
|
133
|
+
if (!formatResult.valid) {
|
|
134
|
+
if (outputJson) {
|
|
135
|
+
console.log(JSON.stringify({
|
|
136
|
+
status: 'fail',
|
|
137
|
+
phase: 'format-validation',
|
|
138
|
+
error: formatResult.error,
|
|
139
|
+
retryPrompt: formatResult.retryPrompt,
|
|
140
|
+
}, null, 2));
|
|
141
|
+
} else {
|
|
142
|
+
console.error('❌ Format validation failed:', formatResult.error);
|
|
143
|
+
console.error('\n📝 Retry prompt:');
|
|
144
|
+
console.error(formatResult.retryPrompt);
|
|
145
|
+
}
|
|
146
|
+
process.exit(2);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if it's an error response
|
|
150
|
+
if ('error' in formatResult && formatResult.error) {
|
|
151
|
+
if (outputJson) {
|
|
152
|
+
console.log(JSON.stringify({
|
|
153
|
+
status: 'error',
|
|
154
|
+
phase: 'agent-error',
|
|
155
|
+
error: formatResult.error.error,
|
|
156
|
+
}, null, 2));
|
|
157
|
+
} else {
|
|
158
|
+
console.log('⚠️ Agent returned an error response:');
|
|
159
|
+
console.log(formatResult.error.error);
|
|
160
|
+
}
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const agentOutput = formatResult.output;
|
|
165
|
+
|
|
166
|
+
if (!outputJson) {
|
|
167
|
+
console.log('✅ Format valid');
|
|
168
|
+
console.log(` Files in diff: ${countFilesInDiff(agentOutput.diff)}`);
|
|
169
|
+
console.log(` Commands: ${agentOutput.commands?.length || 0}`);
|
|
170
|
+
console.log(` Tests: ${agentOutput.tests?.length || 0}`);
|
|
171
|
+
console.log('');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Step 2: Run verification pipeline
|
|
175
|
+
if (!outputJson) {
|
|
176
|
+
console.log('🔬 Running verification pipeline...');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const context = {
|
|
180
|
+
projectRoot,
|
|
181
|
+
mode,
|
|
182
|
+
runTests: mode === 'ship',
|
|
183
|
+
strictMode: strict,
|
|
184
|
+
maxFilesChanged: mode === 'ship' ? 10 : mode === 'build' ? 20 : 50,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
let result;
|
|
188
|
+
try {
|
|
189
|
+
result = await verifyAgentOutput(agentOutput, context);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if (outputJson) {
|
|
192
|
+
console.log(JSON.stringify({
|
|
193
|
+
status: 'fail',
|
|
194
|
+
phase: 'verification',
|
|
195
|
+
error: err.message,
|
|
196
|
+
}, null, 2));
|
|
197
|
+
} else {
|
|
198
|
+
console.error('❌ Verification error:', err.message);
|
|
199
|
+
}
|
|
200
|
+
process.exit(2);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Output results
|
|
204
|
+
if (outputJson) {
|
|
205
|
+
console.log(JSON.stringify(buildJsonReport(result), null, 2));
|
|
206
|
+
} else {
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log(formatCheckResults(result.checks));
|
|
209
|
+
console.log('');
|
|
210
|
+
|
|
211
|
+
if (result.status === 'pass') {
|
|
212
|
+
console.log('✅ VERIFICATION PASSED');
|
|
213
|
+
} else if (result.status === 'warn') {
|
|
214
|
+
console.log('⚠️ VERIFICATION PASSED WITH WARNINGS');
|
|
215
|
+
} else {
|
|
216
|
+
console.log('❌ VERIFICATION FAILED');
|
|
217
|
+
if (result.failureContext) {
|
|
218
|
+
console.log('\n📝 Failure context for retry:');
|
|
219
|
+
console.log(result.failureContext);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Exit codes: 0 = pass, 1 = warn (if strict), 2 = fail
|
|
225
|
+
if (result.status === 'fail') {
|
|
226
|
+
process.exit(2);
|
|
227
|
+
} else if (result.status === 'warn' && strict) {
|
|
228
|
+
process.exit(1);
|
|
229
|
+
} else {
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Read all input from stdin
|
|
236
|
+
*/
|
|
237
|
+
function readStdin() {
|
|
238
|
+
return new Promise((resolve, reject) => {
|
|
239
|
+
let data = '';
|
|
240
|
+
process.stdin.setEncoding('utf-8');
|
|
241
|
+
process.stdin.on('data', chunk => { data += chunk; });
|
|
242
|
+
process.stdin.on('end', () => resolve(data));
|
|
243
|
+
process.stdin.on('error', reject);
|
|
244
|
+
|
|
245
|
+
// Timeout after 5 seconds if no input
|
|
246
|
+
setTimeout(() => {
|
|
247
|
+
if (data === '') {
|
|
248
|
+
reject(new Error('No input received from stdin'));
|
|
249
|
+
}
|
|
250
|
+
}, 5000);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Count files in a diff string
|
|
256
|
+
*/
|
|
257
|
+
function countFilesInDiff(diff) {
|
|
258
|
+
if (!diff) return 0;
|
|
259
|
+
const matches = diff.match(/^diff --git/gm);
|
|
260
|
+
return matches ? matches.length : 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Export for CLI registry
|
|
264
|
+
module.exports = { main };
|
|
265
|
+
|
|
266
|
+
// Run if executed directly
|
|
267
|
+
if (require.main === module) {
|
|
268
|
+
main().catch(err => {
|
|
269
|
+
console.error('Fatal error:', err.message);
|
|
270
|
+
process.exit(2);
|
|
271
|
+
});
|
|
272
|
+
}
|
package/bin/runners/runWatch.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* vibecheck watch - Continuous Dev Mode
|
|
3
3
|
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* ENTERPRISE EDITION - World-Class Terminal Experience
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
4
8
|
* Watches for file changes and re-runs ship automatically.
|
|
5
9
|
* Shows a persistent status line with current verdict.
|
|
6
10
|
* Perfect for development workflow.
|
|
@@ -12,16 +16,92 @@ const fs = require("fs");
|
|
|
12
16
|
const path = require("path");
|
|
13
17
|
const { shipCore } = require("./runShip");
|
|
14
18
|
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
// ADVANCED TERMINAL - ANSI CODES & UTILITIES
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
15
23
|
const c = {
|
|
16
|
-
reset:
|
|
17
|
-
bold:
|
|
18
|
-
dim:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
reset: '\x1b[0m',
|
|
25
|
+
bold: '\x1b[1m',
|
|
26
|
+
dim: '\x1b[2m',
|
|
27
|
+
italic: '\x1b[3m',
|
|
28
|
+
underline: '\x1b[4m',
|
|
29
|
+
red: '\x1b[31m',
|
|
30
|
+
green: '\x1b[32m',
|
|
31
|
+
yellow: '\x1b[33m',
|
|
32
|
+
blue: '\x1b[34m',
|
|
33
|
+
magenta: '\x1b[35m',
|
|
34
|
+
cyan: '\x1b[36m',
|
|
35
|
+
white: '\x1b[37m',
|
|
36
|
+
gray: '\x1b[90m',
|
|
37
|
+
clear: '\x1b[2J\x1b[H',
|
|
38
|
+
clearLine: '\x1b[2K',
|
|
39
|
+
hideCursor: '\x1b[?25l',
|
|
40
|
+
showCursor: '\x1b[?25h',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
|
|
44
|
+
const bgRgb = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
|
|
45
|
+
|
|
46
|
+
const colors = {
|
|
47
|
+
gradient1: rgb(150, 100, 255),
|
|
48
|
+
gradient2: rgb(130, 80, 255),
|
|
49
|
+
gradient3: rgb(110, 60, 255),
|
|
50
|
+
shipGreen: rgb(0, 255, 150),
|
|
51
|
+
warnAmber: rgb(255, 200, 0),
|
|
52
|
+
blockRed: rgb(255, 80, 80),
|
|
53
|
+
accent: rgb(150, 100, 255),
|
|
54
|
+
muted: rgb(120, 120, 140),
|
|
55
|
+
watching: rgb(100, 200, 255),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
59
|
+
// PREMIUM BANNER
|
|
60
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
61
|
+
|
|
62
|
+
const WATCH_BANNER = `
|
|
63
|
+
${rgb(150, 100, 255)} ██╗ ██╗ █████╗ ████████╗ ██████╗██╗ ██╗${c.reset}
|
|
64
|
+
${rgb(130, 80, 255)} ██║ ██║██╔══██╗╚══██╔══╝██╔════╝██║ ██║${c.reset}
|
|
65
|
+
${rgb(110, 60, 255)} ██║ █╗ ██║███████║ ██║ ██║ ███████║${c.reset}
|
|
66
|
+
${rgb(90, 40, 255)} ██║███╗██║██╔══██║ ██║ ██║ ██╔══██║${c.reset}
|
|
67
|
+
${rgb(70, 20, 255)} ╚███╔███╔╝██║ ██║ ██║ ╚██████╗██║ ██║${c.reset}
|
|
68
|
+
${rgb(50, 0, 255)} ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝${c.reset}
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const BANNER_FULL = `
|
|
72
|
+
${rgb(150, 100, 255)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${c.reset}
|
|
73
|
+
${rgb(140, 90, 255)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${c.reset}
|
|
74
|
+
${rgb(130, 80, 255)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${c.reset}
|
|
75
|
+
${rgb(110, 60, 255)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${c.reset}
|
|
76
|
+
${rgb(90, 40, 255)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${c.reset}
|
|
77
|
+
${rgb(70, 20, 255)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${c.reset}
|
|
78
|
+
|
|
79
|
+
${c.dim} ┌─────────────────────────────────────────────────────────────────────┐${c.reset}
|
|
80
|
+
${c.dim} │${c.reset} ${rgb(150, 100, 255)}👁️${c.reset} ${c.bold}WATCH${c.reset} ${c.dim}•${c.reset} ${rgb(200, 200, 200)}Live Reload${c.reset} ${c.dim}•${c.reset} ${rgb(150, 150, 150)}Auto Analysis${c.reset} ${c.dim}•${c.reset} ${rgb(100, 100, 100)}Dev Mode${c.reset} ${c.dim}│${c.reset}
|
|
81
|
+
${c.dim} └─────────────────────────────────────────────────────────────────────┘${c.reset}
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
const BOX = {
|
|
85
|
+
topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯',
|
|
86
|
+
horizontal: '─', vertical: '│',
|
|
87
|
+
dTopLeft: '╔', dTopRight: '╗', dBottomLeft: '╚', dBottomRight: '╝',
|
|
88
|
+
dHorizontal: '═', dVertical: '║',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const ICONS = {
|
|
92
|
+
eye: '👁️',
|
|
93
|
+
check: '✓',
|
|
94
|
+
cross: '✗',
|
|
95
|
+
warning: '⚠',
|
|
96
|
+
arrow: '→',
|
|
97
|
+
bullet: '•',
|
|
98
|
+
ship: '🚀',
|
|
99
|
+
clock: '⏱',
|
|
100
|
+
file: '📄',
|
|
101
|
+
sparkle: '✨',
|
|
102
|
+
lightning: '⚡',
|
|
103
|
+
refresh: '🔄',
|
|
104
|
+
watching: '◉',
|
|
25
105
|
};
|
|
26
106
|
|
|
27
107
|
const IGNORE_PATTERNS = [
|
|
@@ -58,29 +138,63 @@ function formatDuration(ms) {
|
|
|
58
138
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
59
139
|
}
|
|
60
140
|
|
|
141
|
+
function progressBar(percent, width = 20) {
|
|
142
|
+
const filled = Math.round((percent / 100) * width);
|
|
143
|
+
const empty = width - filled;
|
|
144
|
+
const color = percent >= 80 ? colors.shipGreen : percent >= 50 ? colors.warnAmber : colors.blockRed;
|
|
145
|
+
return `${color}${'█'.repeat(filled)}${c.dim}${'░'.repeat(empty)}${c.reset}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function printDivider(char = '─', width = 62) {
|
|
149
|
+
console.log(` ${c.dim}${char.repeat(width)}${c.reset}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
61
152
|
function printStatus({ verdict, findings, duration, lastFile, runCount }) {
|
|
62
|
-
const verdictColor = verdict === "SHIP" ?
|
|
153
|
+
const verdictColor = verdict === "SHIP" ? colors.shipGreen : verdict === "WARN" ? colors.warnAmber : colors.blockRed;
|
|
63
154
|
const blocks = findings.filter(f => f.severity === "BLOCK").length;
|
|
64
155
|
const warns = findings.filter(f => f.severity === "WARN").length;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
${c.dim}
|
|
72
|
-
${c.dim}
|
|
73
|
-
${c.dim}Duration:${c.reset} ${formatDuration(duration)}
|
|
74
|
-
${c.dim}Changed:${c.reset} ${lastFile || "(initial)"}
|
|
75
|
-
|
|
76
|
-
${c.bold} VERDICT: ${verdictColor}${verdict}${c.reset}
|
|
156
|
+
const total = blocks + warns;
|
|
157
|
+
const health = total === 0 ? 100 : Math.max(0, 100 - (blocks * 20) - (warns * 5));
|
|
158
|
+
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(` ${c.dim}${BOX.dTopLeft}${BOX.dHorizontal.repeat(62)}${BOX.dTopRight}${c.reset}`);
|
|
161
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${colors.watching}${ICONS.eye}${c.reset} ${c.bold}VIBECHECK WATCH${c.reset} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
162
|
+
console.log(` ${c.dim}${BOX.dVertical}${BOX.dHorizontal.repeat(62)}${BOX.dVertical}${c.reset}`);
|
|
163
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
77
164
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
`);
|
|
165
|
+
// Stats row
|
|
166
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}${ICONS.clock} Time:${c.reset} ${formatTime()} ${c.dim}${ICONS.refresh} Runs:${c.reset} ${runCount} ${c.dim}${ICONS.lightning} Duration:${c.reset} ${formatDuration(duration)} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
167
|
+
|
|
168
|
+
// Changed file
|
|
169
|
+
const changedDisplay = lastFile ? lastFile.slice(0, 40) : '(initial scan)';
|
|
170
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}${ICONS.file} Changed:${c.reset} ${colors.accent}${changedDisplay}${c.reset}${' '.repeat(Math.max(0, 45 - changedDisplay.length))}${c.dim}${BOX.dVertical}${c.reset}`);
|
|
171
|
+
|
|
172
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
173
|
+
console.log(` ${c.dim}${BOX.dVertical}${BOX.dHorizontal.repeat(62)}${BOX.dVertical}${c.reset}`);
|
|
174
|
+
|
|
175
|
+
// Verdict
|
|
176
|
+
const verdictIcon = verdict === "SHIP" ? ICONS.ship : verdict === "WARN" ? ICONS.warning : ICONS.cross;
|
|
177
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
178
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.bold}VERDICT:${c.reset} ${verdictColor}${c.bold}${verdictIcon} ${verdict}${c.reset} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
179
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
180
|
+
|
|
181
|
+
// Health bar
|
|
182
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}Health:${c.reset} ${progressBar(health)} ${health}% ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
183
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}Findings:${c.reset} ${colors.blockRed}${blocks}${c.reset} ${c.dim}BLOCK${c.reset} ${colors.warnAmber}${warns}${c.reset} ${c.dim}WARN${c.reset} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
184
|
+
|
|
185
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
186
|
+
|
|
187
|
+
// Status message
|
|
188
|
+
const statusMsg = verdict === "SHIP"
|
|
189
|
+
? `${colors.shipGreen}${ICONS.check}${c.reset} Ready to ship`
|
|
190
|
+
: `${colors.warnAmber}${ICONS.arrow}${c.reset} Run ${colors.accent}vibecheck fix${c.reset} to resolve`;
|
|
191
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${statusMsg} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
192
|
+
|
|
193
|
+
console.log(` ${c.dim}${BOX.dVertical}${c.reset} ${c.dim}${BOX.dVertical}${c.reset}`);
|
|
194
|
+
console.log(` ${c.dim}${BOX.dBottomLeft}${BOX.dHorizontal.repeat(62)}${BOX.dBottomRight}${c.reset}`);
|
|
195
|
+
console.log();
|
|
196
|
+
console.log(` ${c.dim}${ICONS.watching} Watching for changes... (Ctrl+C to stop)${c.reset}`);
|
|
197
|
+
console.log();
|
|
84
198
|
}
|
|
85
199
|
|
|
86
200
|
function printTopFindings(findings, max = 5) {
|
|
@@ -90,12 +204,13 @@ function printTopFindings(findings, max = 5) {
|
|
|
90
204
|
|
|
91
205
|
if (!top.length) return;
|
|
92
206
|
|
|
93
|
-
console.log(
|
|
207
|
+
console.log(` ${c.bold}Top findings:${c.reset}`);
|
|
94
208
|
for (const f of top) {
|
|
95
|
-
const icon = f.severity === "BLOCK" ?
|
|
96
|
-
|
|
209
|
+
const icon = f.severity === "BLOCK" ? `${colors.blockRed}●` : `${colors.warnAmber}●`;
|
|
210
|
+
const title = f.title?.length > 50 ? f.title.slice(0, 47) + '...' : f.title;
|
|
211
|
+
console.log(` ${icon}${c.reset} ${title}`);
|
|
97
212
|
}
|
|
98
|
-
console.log(
|
|
213
|
+
console.log();
|
|
99
214
|
}
|
|
100
215
|
|
|
101
216
|
async function runShipQuiet(root, fastifyEntry) {
|
|
@@ -159,37 +274,42 @@ function watchDirectory(dir, callback) {
|
|
|
159
274
|
}
|
|
160
275
|
|
|
161
276
|
function printHelp() {
|
|
277
|
+
console.log(BANNER_FULL);
|
|
162
278
|
console.log(`
|
|
163
|
-
${c.
|
|
279
|
+
${c.bold}Usage:${c.reset} vibecheck watch [options]
|
|
280
|
+
|
|
281
|
+
${c.bold}Continuous Dev Mode${c.reset} — Live reload with automatic ship analysis.
|
|
282
|
+
|
|
283
|
+
${c.bold}Options:${c.reset}
|
|
284
|
+
${colors.accent}--fastify-entry <path>${c.reset} Fastify entry file for route extraction
|
|
285
|
+
${colors.accent}--debounce <ms>${c.reset} Debounce delay in ms ${c.dim}(default: 500)${c.reset}
|
|
286
|
+
${colors.accent}--no-clear${c.reset} Don't clear screen between runs
|
|
287
|
+
${colors.accent}--help, -h${c.reset} Show this help
|
|
164
288
|
|
|
165
|
-
${c.bold}
|
|
166
|
-
|
|
289
|
+
${c.bold}What It Does:${c.reset}
|
|
290
|
+
${colors.shipGreen}1.${c.reset} Runs initial ship analysis
|
|
291
|
+
${colors.shipGreen}2.${c.reset} Watches for file changes in your project
|
|
292
|
+
${colors.shipGreen}3.${c.reset} Re-runs analysis on each change
|
|
293
|
+
${colors.shipGreen}4.${c.reset} Shows live verdict dashboard
|
|
167
294
|
|
|
168
|
-
${c.bold}
|
|
169
|
-
|
|
170
|
-
--debounce <ms> Debounce delay in ms (default: 500)
|
|
171
|
-
--no-clear Don't clear screen between runs
|
|
172
|
-
--help, -h Show this help
|
|
295
|
+
${c.bold}Watched Files:${c.reset}
|
|
296
|
+
${colors.accent}.ts${c.reset} ${colors.accent}.tsx${c.reset} ${colors.accent}.js${c.reset} ${colors.accent}.jsx${c.reset} ${colors.accent}.json${c.reset} ${colors.accent}.env${c.reset} ${colors.accent}.md${c.reset} ${colors.accent}.yml${c.reset} ${colors.accent}.yaml${c.reset}
|
|
173
297
|
|
|
174
|
-
${c.bold}
|
|
175
|
-
|
|
176
|
-
2. Watches for file changes in your project
|
|
177
|
-
3. Re-runs analysis on each change
|
|
178
|
-
4. Shows live verdict dashboard
|
|
298
|
+
${c.bold}Ignored:${c.reset}
|
|
299
|
+
${c.dim}node_modules, .next, .vibecheck, dist, build, .git${c.reset}
|
|
179
300
|
|
|
180
|
-
${c.bold}
|
|
181
|
-
|
|
301
|
+
${c.bold}Examples:${c.reset}
|
|
302
|
+
${c.dim}# Start watching${c.reset}
|
|
303
|
+
vibecheck watch
|
|
182
304
|
|
|
183
|
-
${c.
|
|
184
|
-
|
|
305
|
+
${c.dim}# 1 second debounce${c.reset}
|
|
306
|
+
vibecheck watch --debounce 1000
|
|
185
307
|
|
|
186
|
-
${c.
|
|
187
|
-
|
|
188
|
-
vibecheck watch --debounce 1000 # 1s debounce
|
|
189
|
-
vibecheck watch --no-clear # Keep history
|
|
308
|
+
${c.dim}# Keep history (don't clear)${c.reset}
|
|
309
|
+
vibecheck watch --no-clear
|
|
190
310
|
|
|
191
|
-
${c.dim}Press Ctrl+C to stop watching${c.reset}
|
|
192
|
-
`);
|
|
311
|
+
${c.dim}Press Ctrl+C to stop watching${c.reset}
|
|
312
|
+
`);
|
|
193
313
|
}
|
|
194
314
|
|
|
195
315
|
async function runWatch(argsOrOpts = {}) {
|