@vibecheckai/cli 3.1.2 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -33
- package/bin/registry.js +319 -34
- package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
- package/bin/runners/REPORT_AUDIT.md +64 -0
- package/bin/runners/lib/entitlements-v2.js +97 -28
- package/bin/runners/lib/entitlements.js +3 -6
- package/bin/runners/lib/init-wizard.js +1 -1
- package/bin/runners/lib/report-engine.js +459 -280
- package/bin/runners/lib/report-html.js +1154 -1423
- package/bin/runners/lib/report-output.js +187 -0
- package/bin/runners/lib/report-templates.js +848 -850
- package/bin/runners/lib/scan-output.js +545 -0
- package/bin/runners/lib/server-usage.js +0 -12
- package/bin/runners/lib/ship-output.js +641 -0
- package/bin/runners/lib/status-output.js +253 -0
- package/bin/runners/lib/terminal-ui.js +853 -0
- package/bin/runners/runCheckpoint.js +502 -0
- package/bin/runners/runContracts.js +105 -0
- package/bin/runners/runExport.js +93 -0
- package/bin/runners/runFix.js +31 -24
- package/bin/runners/runInit.js +377 -112
- package/bin/runners/runInstall.js +1 -5
- package/bin/runners/runLabs.js +3 -3
- package/bin/runners/runPolish.js +2452 -0
- package/bin/runners/runProve.js +2 -2
- package/bin/runners/runReport.js +251 -200
- package/bin/runners/runRuntime.js +110 -0
- package/bin/runners/runScan.js +477 -379
- package/bin/runners/runSecurity.js +92 -0
- package/bin/runners/runShip.js +137 -207
- package/bin/runners/runStatus.js +16 -68
- package/bin/runners/utils.js +5 -5
- package/bin/vibecheck.js +25 -11
- package/mcp-server/index.js +150 -18
- package/mcp-server/package.json +2 -2
- package/mcp-server/premium-tools.js +13 -13
- package/mcp-server/tier-auth.js +292 -27
- package/mcp-server/vibecheck-tools.js +9 -9
- package/package.json +1 -1
- package/bin/runners/runClaimVerifier.js +0 -483
- package/bin/runners/runContextCompiler.js +0 -385
- package/bin/runners/runGate.js +0 -17
- package/bin/runners/runInitGha.js +0 -164
- package/bin/runners/runInteractive.js +0 -388
- package/bin/runners/runMdc.js +0 -204
- package/bin/runners/runMissionGenerator.js +0 -282
- package/bin/runners/runTruthpack.js +0 -636
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck checkpoint - Compare Baseline vs Current
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* Compares baseline scan results with current state to track progress:
|
|
6
|
+
* - Fixed issues
|
|
7
|
+
* - Remaining issues
|
|
8
|
+
* - Regressions (new issues)
|
|
9
|
+
* - Hallucination scoring (PRO)
|
|
10
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const path = require("path");
|
|
15
|
+
const entitlements = require("./lib/entitlements-v2");
|
|
16
|
+
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
// TERMINAL STYLING
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
|
|
21
|
+
const c = {
|
|
22
|
+
reset: '\x1b[0m',
|
|
23
|
+
bold: '\x1b[1m',
|
|
24
|
+
dim: '\x1b[2m',
|
|
25
|
+
red: '\x1b[31m',
|
|
26
|
+
green: '\x1b[32m',
|
|
27
|
+
yellow: '\x1b[33m',
|
|
28
|
+
blue: '\x1b[34m',
|
|
29
|
+
magenta: '\x1b[35m',
|
|
30
|
+
cyan: '\x1b[36m',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const icons = {
|
|
34
|
+
check: '✓',
|
|
35
|
+
cross: '✗',
|
|
36
|
+
arrow: '→',
|
|
37
|
+
warning: '⚠',
|
|
38
|
+
info: 'ℹ',
|
|
39
|
+
star: '★',
|
|
40
|
+
rocket: '🚀',
|
|
41
|
+
target: '🎯',
|
|
42
|
+
chart: '📊',
|
|
43
|
+
brain: '🧠',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
47
|
+
// CHECKPOINT CORE
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
49
|
+
|
|
50
|
+
function loadResults(filePath) {
|
|
51
|
+
if (!fs.existsSync(filePath)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getFindingId(finding) {
|
|
62
|
+
// Create a stable ID for a finding based on its properties
|
|
63
|
+
return `${finding.id || finding.type || 'unknown'}-${finding.file || ''}-${finding.line || 0}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function compareFindingSets(baseline, current) {
|
|
67
|
+
const baselineSet = new Set(baseline.map(getFindingId));
|
|
68
|
+
const currentSet = new Set(current.map(getFindingId));
|
|
69
|
+
const baselineMap = new Map(baseline.map(f => [getFindingId(f), f]));
|
|
70
|
+
const currentMap = new Map(current.map(f => [getFindingId(f), f]));
|
|
71
|
+
|
|
72
|
+
const fixed = [];
|
|
73
|
+
const remaining = [];
|
|
74
|
+
const regressions = [];
|
|
75
|
+
|
|
76
|
+
// Find fixed (in baseline but not in current)
|
|
77
|
+
for (const id of baselineSet) {
|
|
78
|
+
if (!currentSet.has(id)) {
|
|
79
|
+
fixed.push(baselineMap.get(id));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Find remaining (in both)
|
|
84
|
+
for (const id of baselineSet) {
|
|
85
|
+
if (currentSet.has(id)) {
|
|
86
|
+
remaining.push(currentMap.get(id));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Find regressions (in current but not in baseline)
|
|
91
|
+
for (const id of currentSet) {
|
|
92
|
+
if (!baselineSet.has(id)) {
|
|
93
|
+
regressions.push(currentMap.get(id));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { fixed, remaining, regressions };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
101
|
+
// HALLUCINATION DETECTION (PRO)
|
|
102
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
103
|
+
|
|
104
|
+
function detectHallucinations(baseline, current, projectPath) {
|
|
105
|
+
const hallucinations = {
|
|
106
|
+
nonexistentRoutes: [],
|
|
107
|
+
nonexistentEnvVars: [],
|
|
108
|
+
nonexistentImports: [],
|
|
109
|
+
nonexistentFunctions: [],
|
|
110
|
+
contractDriftViolations: [],
|
|
111
|
+
score: 0,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Load truthpack if available
|
|
115
|
+
const truthpackPath = path.join(projectPath, '.vibecheck', 'truthpack.json');
|
|
116
|
+
let truthpack = null;
|
|
117
|
+
if (fs.existsSync(truthpackPath)) {
|
|
118
|
+
try {
|
|
119
|
+
truthpack = JSON.parse(fs.readFileSync(truthpackPath, 'utf8'));
|
|
120
|
+
} catch (e) {
|
|
121
|
+
// Ignore
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check for edits introducing nonexistent routes
|
|
126
|
+
if (truthpack?.routes?.server) {
|
|
127
|
+
const validRoutes = new Set(truthpack.routes.server.map(r => r.path || r.route));
|
|
128
|
+
|
|
129
|
+
for (const finding of current) {
|
|
130
|
+
if (finding.type === 'route-reference' || finding.category === 'routes') {
|
|
131
|
+
const referencedRoute = finding.route || finding.path;
|
|
132
|
+
if (referencedRoute && !validRoutes.has(referencedRoute)) {
|
|
133
|
+
hallucinations.nonexistentRoutes.push({
|
|
134
|
+
finding,
|
|
135
|
+
referencedRoute,
|
|
136
|
+
reason: 'Route does not exist in truthpack',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check for env vars
|
|
144
|
+
if (truthpack?.env?.vars) {
|
|
145
|
+
const validEnvVars = new Set(truthpack.env.vars.map(v => v.name || v));
|
|
146
|
+
|
|
147
|
+
for (const finding of current) {
|
|
148
|
+
if (finding.type === 'env-reference' || finding.category === 'env') {
|
|
149
|
+
const referencedVar = finding.envVar || finding.name;
|
|
150
|
+
if (referencedVar && !validEnvVars.has(referencedVar)) {
|
|
151
|
+
hallucinations.nonexistentEnvVars.push({
|
|
152
|
+
finding,
|
|
153
|
+
referencedVar,
|
|
154
|
+
reason: 'Environment variable not declared',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Load contracts and check for drift violations
|
|
162
|
+
const contractsPath = path.join(projectPath, '.vibecheck', 'contracts');
|
|
163
|
+
if (fs.existsSync(contractsPath)) {
|
|
164
|
+
// Check route contract
|
|
165
|
+
const routeContractPath = path.join(contractsPath, 'routes.contract.json');
|
|
166
|
+
if (fs.existsSync(routeContractPath)) {
|
|
167
|
+
try {
|
|
168
|
+
const routeContract = JSON.parse(fs.readFileSync(routeContractPath, 'utf8'));
|
|
169
|
+
const contractedRoutes = new Set(routeContract.routes?.map(r => r.path) || []);
|
|
170
|
+
|
|
171
|
+
// Check if current introduces routes not in contract
|
|
172
|
+
if (truthpack?.routes?.server) {
|
|
173
|
+
for (const route of truthpack.routes.server) {
|
|
174
|
+
const routePath = route.path || route.route;
|
|
175
|
+
if (routePath && !contractedRoutes.has(routePath)) {
|
|
176
|
+
hallucinations.contractDriftViolations.push({
|
|
177
|
+
type: 'route-drift',
|
|
178
|
+
route: routePath,
|
|
179
|
+
reason: 'Route exists but not in contract',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} catch (e) {
|
|
185
|
+
// Ignore
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Calculate hallucination score (0-100, lower is better)
|
|
191
|
+
const totalHallucinations =
|
|
192
|
+
hallucinations.nonexistentRoutes.length +
|
|
193
|
+
hallucinations.nonexistentEnvVars.length +
|
|
194
|
+
hallucinations.nonexistentImports.length +
|
|
195
|
+
hallucinations.nonexistentFunctions.length +
|
|
196
|
+
hallucinations.contractDriftViolations.length;
|
|
197
|
+
|
|
198
|
+
hallucinations.score = Math.min(100, totalHallucinations * 10);
|
|
199
|
+
|
|
200
|
+
return hallucinations;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
204
|
+
// CLI
|
|
205
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
206
|
+
|
|
207
|
+
function parseArgs(args) {
|
|
208
|
+
const opts = {
|
|
209
|
+
help: false,
|
|
210
|
+
json: false,
|
|
211
|
+
baseline: null,
|
|
212
|
+
current: null,
|
|
213
|
+
hallucination: false,
|
|
214
|
+
save: false,
|
|
215
|
+
path: process.cwd(),
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
for (let i = 0; i < args.length; i++) {
|
|
219
|
+
const arg = args[i];
|
|
220
|
+
if (arg === '--help' || arg === '-h') opts.help = true;
|
|
221
|
+
else if (arg === '--json') opts.json = true;
|
|
222
|
+
else if (arg === '--baseline' || arg === '-b') opts.baseline = args[++i];
|
|
223
|
+
else if (arg === '--current' || arg === '-c') opts.current = args[++i];
|
|
224
|
+
else if (arg === '--hallucination' || arg === '--hall') opts.hallucination = true;
|
|
225
|
+
else if (arg === '--save' || arg === '-s') opts.save = true;
|
|
226
|
+
else if (arg === '--path' || arg === '-p') opts.path = args[++i];
|
|
227
|
+
else if (!arg.startsWith('-')) opts.path = arg;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return opts;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function printHelp() {
|
|
234
|
+
console.log(`
|
|
235
|
+
${c.bold}${icons.target} vibecheck checkpoint${c.reset} - Compare Baseline vs Current
|
|
236
|
+
|
|
237
|
+
${c.dim}Track progress by comparing scan results over time.${c.reset}
|
|
238
|
+
|
|
239
|
+
${c.bold}USAGE${c.reset}
|
|
240
|
+
vibecheck checkpoint [options]
|
|
241
|
+
|
|
242
|
+
${c.bold}OPTIONS${c.reset}
|
|
243
|
+
-b, --baseline <file> Path to baseline results file
|
|
244
|
+
-c, --current <file> Path to current results file (default: latest)
|
|
245
|
+
--hallucination Include hallucination scoring ${c.magenta}[PRO]${c.reset}
|
|
246
|
+
-s, --save Save checkpoint to .vibecheck/checkpoints/
|
|
247
|
+
--json Output as JSON
|
|
248
|
+
-p, --path <dir> Project path (default: current directory)
|
|
249
|
+
-h, --help Show this help
|
|
250
|
+
|
|
251
|
+
${c.bold}EXAMPLES${c.reset}
|
|
252
|
+
vibecheck checkpoint # Compare latest vs baseline
|
|
253
|
+
vibecheck checkpoint --baseline scan-v1.json # Use specific baseline
|
|
254
|
+
vibecheck checkpoint --hallucination # Include AI hallucination check
|
|
255
|
+
vibecheck checkpoint --save # Save checkpoint report
|
|
256
|
+
|
|
257
|
+
${c.bold}OUTPUT${c.reset}
|
|
258
|
+
• Fixed issues - Issues resolved since baseline
|
|
259
|
+
• Remaining issues - Issues still present
|
|
260
|
+
• Regressions - New issues introduced
|
|
261
|
+
• Hallucination score - AI-generated code issues ${c.magenta}[PRO]${c.reset}
|
|
262
|
+
|
|
263
|
+
${c.bold}TIER${c.reset}
|
|
264
|
+
${c.cyan}FREE${c.reset} Basic comparison (fixed, remaining, regressions)
|
|
265
|
+
${c.magenta}PRO${c.reset} + Hallucination scoring
|
|
266
|
+
`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function runCheckpoint(args) {
|
|
270
|
+
const opts = parseArgs(args);
|
|
271
|
+
|
|
272
|
+
if (opts.help) {
|
|
273
|
+
printHelp();
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const projectPath = path.resolve(opts.path);
|
|
278
|
+
const vibecheckDir = path.join(projectPath, '.vibecheck');
|
|
279
|
+
const resultsDir = path.join(vibecheckDir, 'results');
|
|
280
|
+
const checkpointsDir = path.join(vibecheckDir, 'checkpoints');
|
|
281
|
+
|
|
282
|
+
// Check hallucination entitlement
|
|
283
|
+
if (opts.hallucination) {
|
|
284
|
+
const access = await entitlements.enforce("checkpoint.hallucination", { silent: true });
|
|
285
|
+
if (!access.allowed) {
|
|
286
|
+
console.log(`\n${c.yellow}${icons.warning}${c.reset} Hallucination scoring requires ${c.magenta}PRO${c.reset} plan.`);
|
|
287
|
+
console.log(`${c.dim}Running basic checkpoint instead...${c.reset}\n`);
|
|
288
|
+
opts.hallucination = false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Load baseline
|
|
293
|
+
let baseline = null;
|
|
294
|
+
if (opts.baseline) {
|
|
295
|
+
baseline = loadResults(opts.baseline);
|
|
296
|
+
} else {
|
|
297
|
+
// Try to load from default location
|
|
298
|
+
const baselinePath = path.join(resultsDir, 'baseline.json');
|
|
299
|
+
baseline = loadResults(baselinePath);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!baseline) {
|
|
303
|
+
console.log(`\n${c.yellow}${icons.warning}${c.reset} No baseline found.`);
|
|
304
|
+
console.log(`${c.dim}Run 'vibecheck scan' first to create a baseline.${c.reset}`);
|
|
305
|
+
console.log(`${c.dim}Or specify --baseline <file> to use a specific file.${c.reset}\n`);
|
|
306
|
+
|
|
307
|
+
if (!opts.json) {
|
|
308
|
+
console.log(`${c.bold}TIP:${c.reset} Create a baseline with:`);
|
|
309
|
+
console.log(` ${c.cyan}vibecheck scan${c.reset}`);
|
|
310
|
+
console.log(` ${c.cyan}cp .vibecheck/results/latest.json .vibecheck/results/baseline.json${c.reset}\n`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Load current
|
|
317
|
+
let current = null;
|
|
318
|
+
if (opts.current) {
|
|
319
|
+
current = loadResults(opts.current);
|
|
320
|
+
} else {
|
|
321
|
+
// Try to load latest
|
|
322
|
+
const latestPath = path.join(resultsDir, 'latest.json');
|
|
323
|
+
current = loadResults(latestPath);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!current) {
|
|
327
|
+
console.log(`\n${c.yellow}${icons.warning}${c.reset} No current results found.`);
|
|
328
|
+
console.log(`${c.dim}Run 'vibecheck scan' to generate current results.${c.reset}\n`);
|
|
329
|
+
return 1;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Extract findings arrays
|
|
333
|
+
const baselineFindings = baseline.findings || baseline.results?.findings || [];
|
|
334
|
+
const currentFindings = current.findings || current.results?.findings || [];
|
|
335
|
+
|
|
336
|
+
// Compare
|
|
337
|
+
const comparison = compareFindingSets(baselineFindings, currentFindings);
|
|
338
|
+
|
|
339
|
+
// Hallucination detection (PRO)
|
|
340
|
+
let hallucinations = null;
|
|
341
|
+
if (opts.hallucination) {
|
|
342
|
+
hallucinations = detectHallucinations(baselineFindings, currentFindings, projectPath);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Build checkpoint result
|
|
346
|
+
const checkpoint = {
|
|
347
|
+
timestamp: new Date().toISOString(),
|
|
348
|
+
baseline: {
|
|
349
|
+
path: opts.baseline || path.join(resultsDir, 'baseline.json'),
|
|
350
|
+
totalFindings: baselineFindings.length,
|
|
351
|
+
generatedAt: baseline.generatedAt || baseline.timestamp,
|
|
352
|
+
},
|
|
353
|
+
current: {
|
|
354
|
+
path: opts.current || path.join(resultsDir, 'latest.json'),
|
|
355
|
+
totalFindings: currentFindings.length,
|
|
356
|
+
generatedAt: current.generatedAt || current.timestamp,
|
|
357
|
+
},
|
|
358
|
+
summary: {
|
|
359
|
+
fixed: comparison.fixed.length,
|
|
360
|
+
remaining: comparison.remaining.length,
|
|
361
|
+
regressions: comparison.regressions.length,
|
|
362
|
+
progressPercent: baselineFindings.length > 0
|
|
363
|
+
? Math.round((comparison.fixed.length / baselineFindings.length) * 100)
|
|
364
|
+
: 0,
|
|
365
|
+
},
|
|
366
|
+
fixed: comparison.fixed,
|
|
367
|
+
remaining: comparison.remaining,
|
|
368
|
+
regressions: comparison.regressions,
|
|
369
|
+
hallucinations: hallucinations,
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Save if requested
|
|
373
|
+
if (opts.save) {
|
|
374
|
+
if (!fs.existsSync(checkpointsDir)) {
|
|
375
|
+
fs.mkdirSync(checkpointsDir, { recursive: true });
|
|
376
|
+
}
|
|
377
|
+
const checkpointFile = path.join(
|
|
378
|
+
checkpointsDir,
|
|
379
|
+
`checkpoint-${new Date().toISOString().split('T')[0]}.json`
|
|
380
|
+
);
|
|
381
|
+
fs.writeFileSync(checkpointFile, JSON.stringify(checkpoint, null, 2));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// JSON output
|
|
385
|
+
if (opts.json) {
|
|
386
|
+
console.log(JSON.stringify(checkpoint, null, 2));
|
|
387
|
+
return checkpoint.summary.regressions > 0 ? 1 : 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Human-readable output
|
|
391
|
+
console.log(`
|
|
392
|
+
${c.bold}╔══════════════════════════════════════════════════════════════════════════════╗
|
|
393
|
+
║ ║
|
|
394
|
+
║ ${icons.target} ${c.cyan}VIBECHECK CHECKPOINT${c.reset}${c.bold} ║
|
|
395
|
+
║ ║
|
|
396
|
+
╚══════════════════════════════════════════════════════════════════════════════╝${c.reset}
|
|
397
|
+
`);
|
|
398
|
+
|
|
399
|
+
// Summary card
|
|
400
|
+
console.log(`${c.bold}${icons.chart} PROGRESS SUMMARY${c.reset}`);
|
|
401
|
+
console.log(`${'─'.repeat(60)}`);
|
|
402
|
+
console.log(` Baseline: ${baselineFindings.length} findings`);
|
|
403
|
+
console.log(` Current: ${currentFindings.length} findings`);
|
|
404
|
+
console.log(` Progress: ${c.bold}${checkpoint.summary.progressPercent}%${c.reset}`);
|
|
405
|
+
console.log();
|
|
406
|
+
|
|
407
|
+
// Stats
|
|
408
|
+
console.log(` ${c.green}${icons.check} Fixed:${c.reset} ${comparison.fixed.length}`);
|
|
409
|
+
console.log(` ${c.yellow}${icons.warning} Remaining:${c.reset} ${comparison.remaining.length}`);
|
|
410
|
+
console.log(` ${c.red}${icons.cross} Regressions:${c.reset} ${comparison.regressions.length}`);
|
|
411
|
+
console.log();
|
|
412
|
+
|
|
413
|
+
// Fixed issues
|
|
414
|
+
if (comparison.fixed.length > 0) {
|
|
415
|
+
console.log(`${c.bold}${c.green}${icons.check} FIXED ISSUES${c.reset} (${comparison.fixed.length})`);
|
|
416
|
+
console.log(`${'─'.repeat(60)}`);
|
|
417
|
+
for (const f of comparison.fixed.slice(0, 10)) {
|
|
418
|
+
console.log(` ${c.green}${icons.check}${c.reset} ${f.title || f.id || f.type}`);
|
|
419
|
+
if (f.file) console.log(` ${c.dim}${f.file}${f.line ? `:${f.line}` : ''}${c.reset}`);
|
|
420
|
+
}
|
|
421
|
+
if (comparison.fixed.length > 10) {
|
|
422
|
+
console.log(` ${c.dim}... and ${comparison.fixed.length - 10} more${c.reset}`);
|
|
423
|
+
}
|
|
424
|
+
console.log();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Regressions (new issues)
|
|
428
|
+
if (comparison.regressions.length > 0) {
|
|
429
|
+
console.log(`${c.bold}${c.red}${icons.cross} REGRESSIONS${c.reset} (${comparison.regressions.length})`);
|
|
430
|
+
console.log(`${'─'.repeat(60)}`);
|
|
431
|
+
for (const f of comparison.regressions.slice(0, 10)) {
|
|
432
|
+
console.log(` ${c.red}${icons.cross}${c.reset} ${f.title || f.id || f.type}`);
|
|
433
|
+
if (f.file) console.log(` ${c.dim}${f.file}${f.line ? `:${f.line}` : ''}${c.reset}`);
|
|
434
|
+
}
|
|
435
|
+
if (comparison.regressions.length > 10) {
|
|
436
|
+
console.log(` ${c.dim}... and ${comparison.regressions.length - 10} more${c.reset}`);
|
|
437
|
+
}
|
|
438
|
+
console.log();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Hallucination report (PRO)
|
|
442
|
+
if (hallucinations) {
|
|
443
|
+
console.log(`${c.bold}${icons.brain} HALLUCINATION ANALYSIS${c.reset} ${c.magenta}[PRO]${c.reset}`);
|
|
444
|
+
console.log(`${'─'.repeat(60)}`);
|
|
445
|
+
console.log(` Score: ${hallucinations.score <= 20 ? c.green : hallucinations.score <= 50 ? c.yellow : c.red}${hallucinations.score}/100${c.reset} ${c.dim}(lower is better)${c.reset}`);
|
|
446
|
+
|
|
447
|
+
if (hallucinations.nonexistentRoutes.length > 0) {
|
|
448
|
+
console.log(`\n ${c.red}Nonexistent routes referenced:${c.reset} ${hallucinations.nonexistentRoutes.length}`);
|
|
449
|
+
for (const h of hallucinations.nonexistentRoutes.slice(0, 5)) {
|
|
450
|
+
console.log(` ${c.red}${icons.cross}${c.reset} ${h.referencedRoute}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (hallucinations.nonexistentEnvVars.length > 0) {
|
|
455
|
+
console.log(`\n ${c.red}Undeclared env vars used:${c.reset} ${hallucinations.nonexistentEnvVars.length}`);
|
|
456
|
+
for (const h of hallucinations.nonexistentEnvVars.slice(0, 5)) {
|
|
457
|
+
console.log(` ${c.red}${icons.cross}${c.reset} ${h.referencedVar}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (hallucinations.contractDriftViolations.length > 0) {
|
|
462
|
+
console.log(`\n ${c.yellow}Contract drift violations:${c.reset} ${hallucinations.contractDriftViolations.length}`);
|
|
463
|
+
for (const h of hallucinations.contractDriftViolations.slice(0, 5)) {
|
|
464
|
+
console.log(` ${c.yellow}${icons.warning}${c.reset} ${h.route || h.type}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (hallucinations.score === 0) {
|
|
469
|
+
console.log(` ${c.green}${icons.check} No hallucinations detected${c.reset}`);
|
|
470
|
+
}
|
|
471
|
+
console.log();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Verdict
|
|
475
|
+
console.log(`${c.bold}${'━'.repeat(60)}${c.reset}`);
|
|
476
|
+
if (comparison.regressions.length === 0 && comparison.fixed.length > 0) {
|
|
477
|
+
console.log(`${c.green}${c.bold}${icons.rocket} GREAT PROGRESS!${c.reset} ${comparison.fixed.length} issues fixed, no regressions.`);
|
|
478
|
+
} else if (comparison.regressions.length === 0) {
|
|
479
|
+
console.log(`${c.green}${icons.check} No regressions detected.${c.reset}`);
|
|
480
|
+
} else {
|
|
481
|
+
console.log(`${c.red}${icons.warning} ${comparison.regressions.length} regression(s) detected. Review before shipping.${c.reset}`);
|
|
482
|
+
}
|
|
483
|
+
console.log();
|
|
484
|
+
|
|
485
|
+
// Next steps
|
|
486
|
+
console.log(`${c.bold}NEXT STEPS${c.reset}`);
|
|
487
|
+
if (comparison.regressions.length > 0) {
|
|
488
|
+
console.log(` 1. Fix regressions: ${c.cyan}vibecheck scan --autofix${c.reset}`);
|
|
489
|
+
console.log(` 2. Re-run checkpoint: ${c.cyan}vibecheck checkpoint${c.reset}`);
|
|
490
|
+
} else if (comparison.remaining.length > 0) {
|
|
491
|
+
console.log(` 1. Fix remaining issues: ${c.cyan}vibecheck scan --autofix${c.reset}`);
|
|
492
|
+
console.log(` 2. Verify with: ${c.cyan}vibecheck ship${c.reset}`);
|
|
493
|
+
} else {
|
|
494
|
+
console.log(` ${c.green}${icons.check} Ready to ship: ${c.cyan}vibecheck ship${c.reset}`);
|
|
495
|
+
}
|
|
496
|
+
console.log();
|
|
497
|
+
|
|
498
|
+
// Return code: 1 if regressions, 0 otherwise
|
|
499
|
+
return comparison.regressions.length > 0 ? 1 : 0;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
module.exports = { runCheckpoint };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck contracts - CI Gate for Contract Drift / Invariants
|
|
3
|
+
*
|
|
4
|
+
* Replaces: ctx guard
|
|
5
|
+
* Purpose: Validate code against contracts (SHIP/WARN/BLOCK)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const { runCtxGuard } = require("./runCtxGuard");
|
|
11
|
+
|
|
12
|
+
const c = {
|
|
13
|
+
reset: '\x1b[0m',
|
|
14
|
+
bold: '\x1b[1m',
|
|
15
|
+
dim: '\x1b[2m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
red: '\x1b[31m',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function parseArgs(args) {
|
|
23
|
+
const opts = {
|
|
24
|
+
repoRoot: process.cwd(),
|
|
25
|
+
fastifyEntry: null,
|
|
26
|
+
json: false,
|
|
27
|
+
failOnWarn: false,
|
|
28
|
+
failOnMissing: false,
|
|
29
|
+
strict: false,
|
|
30
|
+
help: false
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < args.length; i++) {
|
|
34
|
+
const arg = args[i];
|
|
35
|
+
if (arg === "--json") opts.json = true;
|
|
36
|
+
else if (arg === "--strict") opts.strict = true;
|
|
37
|
+
else if (arg === "--fail-on-warn") opts.failOnWarn = true;
|
|
38
|
+
else if (arg === "--fail-on-missing") opts.failOnMissing = true;
|
|
39
|
+
else if (arg === "--fastify-entry") opts.fastifyEntry = args[++i];
|
|
40
|
+
else if (arg === "--path" || arg === "-p") opts.repoRoot = args[++i];
|
|
41
|
+
else if (arg === "--help" || arg === "-h") opts.help = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --strict implies --fail-on-warn
|
|
45
|
+
if (opts.strict) opts.failOnWarn = true;
|
|
46
|
+
|
|
47
|
+
return opts;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function printHelp() {
|
|
51
|
+
console.log(`
|
|
52
|
+
${c.cyan}${c.bold}🛡️ vibecheck contracts${c.reset} - CI Gate for Contract Drift
|
|
53
|
+
|
|
54
|
+
Validates current code against contracts. Use in CI to block drift.
|
|
55
|
+
|
|
56
|
+
${c.bold}USAGE${c.reset}
|
|
57
|
+
vibecheck contracts ${c.dim}# Validate against contracts${c.reset}
|
|
58
|
+
vibecheck contracts --strict ${c.dim}# Fail on warnings${c.reset}
|
|
59
|
+
vibecheck contracts --json ${c.dim}# Output JSON for CI${c.reset}
|
|
60
|
+
|
|
61
|
+
${c.bold}OPTIONS${c.reset}
|
|
62
|
+
--strict Fail on warnings (exit 1 on WARN)
|
|
63
|
+
--json Output JSON result to .vibecheck/contracts-result.json
|
|
64
|
+
--fail-on-warn Exit 1 on warnings (same as --strict)
|
|
65
|
+
--fail-on-missing Exit 2 if no contracts exist
|
|
66
|
+
--fastify-entry Fastify entry file (e.g. src/server.ts)
|
|
67
|
+
--path, -p Project path (default: current directory)
|
|
68
|
+
--help, -h Show this help
|
|
69
|
+
|
|
70
|
+
${c.bold}VIOLATIONS (BLOCK)${c.reset}
|
|
71
|
+
• Route used in code but not in routes.json
|
|
72
|
+
• Webhook without signature verification
|
|
73
|
+
• Protected route accessible anonymously
|
|
74
|
+
|
|
75
|
+
${c.bold}WARNINGS${c.reset}
|
|
76
|
+
• Env var used but not in env.json
|
|
77
|
+
• Required env var not used
|
|
78
|
+
• Undeclared external service
|
|
79
|
+
|
|
80
|
+
${c.bold}EXIT CODES${c.reset}
|
|
81
|
+
0 = SHIP (or WARN without --strict)
|
|
82
|
+
1 = WARN (with --strict)
|
|
83
|
+
2 = BLOCK
|
|
84
|
+
|
|
85
|
+
${c.bold}EXAMPLES${c.reset}
|
|
86
|
+
vibecheck contracts ${c.dim}# Local validation${c.reset}
|
|
87
|
+
vibecheck contracts --strict --json ${c.dim}# CI mode${c.reset}
|
|
88
|
+
|
|
89
|
+
${c.dim}Note: This command replaces 'vibecheck ctx guard'.${c.reset}
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function runContracts(args) {
|
|
94
|
+
const opts = parseArgs(args);
|
|
95
|
+
|
|
96
|
+
if (opts.help) {
|
|
97
|
+
printHelp();
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Delegate to runCtxGuard
|
|
102
|
+
return await runCtxGuard(opts);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { runContracts };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck export - Lightweight Collaboration Outputs
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* export pr = current pr
|
|
6
|
+
* export badge = current badge
|
|
7
|
+
* export bundle = current share
|
|
8
|
+
*
|
|
9
|
+
* Replaces: pr, badge, share
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
const c = {
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
bold: '\x1b[1m',
|
|
17
|
+
dim: '\x1b[2m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
yellow: '\x1b[33m',
|
|
20
|
+
red: '\x1b[31m',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function printHelp() {
|
|
24
|
+
console.log(`
|
|
25
|
+
${c.cyan}${c.bold}📤 vibecheck export${c.reset} - Generate Collaboration Outputs
|
|
26
|
+
|
|
27
|
+
Lightweight outputs from latest results for sharing and collaboration.
|
|
28
|
+
|
|
29
|
+
${c.bold}SUBCOMMANDS${c.reset}
|
|
30
|
+
${c.cyan}pr${c.reset} ${c.dim}Generate GitHub PR comment (replaces 'pr')${c.reset}
|
|
31
|
+
${c.cyan}badge${c.reset} ${c.dim}Generate ship badge for README (replaces 'badge')${c.reset}
|
|
32
|
+
${c.cyan}bundle${c.reset} ${c.dim}Generate share pack for PR/docs (replaces 'share')${c.reset}
|
|
33
|
+
|
|
34
|
+
${c.bold}EXAMPLES${c.reset}
|
|
35
|
+
vibecheck export pr
|
|
36
|
+
vibecheck export badge --style gradient
|
|
37
|
+
vibecheck export bundle
|
|
38
|
+
|
|
39
|
+
${c.dim}Note: Old commands still work as aliases:
|
|
40
|
+
vibecheck pr → vibecheck export pr
|
|
41
|
+
vibecheck badge → vibecheck export badge
|
|
42
|
+
vibecheck share → vibecheck export bundle${c.reset}
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function runExport(args) {
|
|
47
|
+
if (!args || args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
48
|
+
printHelp();
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const subcommand = args[0];
|
|
53
|
+
const subArgs = args.slice(1);
|
|
54
|
+
|
|
55
|
+
switch (subcommand) {
|
|
56
|
+
case "pr":
|
|
57
|
+
// Delegate to runPR
|
|
58
|
+
try {
|
|
59
|
+
const { runPR } = require("./runPR");
|
|
60
|
+
return await runPR(subArgs);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error(`${c.red}Error:${c.reset} Export PR unavailable: ${e.message}`);
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case "badge":
|
|
67
|
+
// Delegate to runBadge
|
|
68
|
+
try {
|
|
69
|
+
const { runBadge } = require("./runBadge");
|
|
70
|
+
return await runBadge(subArgs);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error(`${c.red}Error:${c.reset} Export badge unavailable: ${e.message}`);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
case "bundle":
|
|
77
|
+
// Delegate to runShare
|
|
78
|
+
try {
|
|
79
|
+
const { runShare } = require("./runShare");
|
|
80
|
+
return await runShare(subArgs);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error(`${c.red}Error:${c.reset} Export bundle unavailable: ${e.message}`);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
default:
|
|
87
|
+
console.error(`${c.red}Unknown subcommand:${c.reset} ${subcommand}`);
|
|
88
|
+
console.log(`\n${c.dim}Run 'vibecheck export --help' for available subcommands.${c.reset}\n`);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { runExport };
|