@vibecheckai/cli 3.1.5 → 3.1.8
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 +27 -32
- package/bin/registry.js +216 -341
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
- package/bin/runners/lib/global-flags.js +213 -0
- package/bin/runners/lib/init-wizard.js +566 -273
- package/bin/runners/lib/interactive-menu.js +1496 -0
- package/bin/runners/runCheckpoint.js +503 -502
- package/bin/runners/runContext.js +16 -9
- package/bin/runners/runDoctor.js +20 -33
- package/bin/runners/runFix.js +19 -11
- package/bin/runners/runInit.js +866 -68
- package/bin/runners/runInstall.js +14 -6
- package/bin/runners/runProve.js +18 -12
- package/bin/runners/runReality.js +15 -6
- package/bin/runners/runReport.js +31 -18
- package/bin/runners/runScan.js +28 -17
- package/bin/runners/runShip.js +27 -21
- package/bin/runners/runWatch.js +11 -4
- package/bin/vibecheck.js +851 -212
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/bin/runners/runBadge.js +0 -916
- package/bin/runners/runContracts.js +0 -105
- package/bin/runners/runCtx.js +0 -675
- package/bin/runners/runCtxDiff.js +0 -301
- package/bin/runners/runCtxGuard.js +0 -176
- package/bin/runners/runCtxSync.js +0 -116
- package/bin/runners/runExport.js +0 -93
- package/bin/runners/runGraph.js +0 -454
- package/bin/runners/runLaunch.js +0 -181
- package/bin/runners/runPR.js +0 -255
- package/bin/runners/runPermissions.js +0 -304
- package/bin/runners/runPreflight.js +0 -580
- package/bin/runners/runReplay.js +0 -499
- package/bin/runners/runSecurity.js +0 -92
- package/bin/runners/runShare.js +0 -212
- package/bin/runners/runStatus.js +0 -86
- package/bin/runners/runVerify.js +0 -272
package/bin/runners/runInit.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require("fs");
|
|
13
13
|
const path = require("path");
|
|
14
|
+
const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
|
|
14
15
|
|
|
15
16
|
// Use enhanced wizard if available
|
|
16
17
|
let InitWizard;
|
|
@@ -182,6 +183,8 @@ const ICONS = {
|
|
|
182
183
|
update: '✏️',
|
|
183
184
|
skip: '⏭️',
|
|
184
185
|
detect: '🔍',
|
|
186
|
+
repair: '🔧',
|
|
187
|
+
tool: '🛠️',
|
|
185
188
|
|
|
186
189
|
// Objects
|
|
187
190
|
file: '📄',
|
|
@@ -295,6 +298,556 @@ function printSection(title, icon = '◆') {
|
|
|
295
298
|
printDivider();
|
|
296
299
|
}
|
|
297
300
|
|
|
301
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
302
|
+
// FRAMEWORK-SPECIFIC CONFIGURATION
|
|
303
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
304
|
+
|
|
305
|
+
function getFrameworkConfig(detection) {
|
|
306
|
+
if (!detection || !detection.frameworks) {
|
|
307
|
+
return {
|
|
308
|
+
framework: "node",
|
|
309
|
+
checks: ["routes", "env", "auth", "security"],
|
|
310
|
+
ignorePaths: [],
|
|
311
|
+
extra: {},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Find the primary detected framework
|
|
316
|
+
const detectedFrameworks = Object.entries(detection.frameworks)
|
|
317
|
+
.filter(([key, fw]) => fw.detected)
|
|
318
|
+
.map(([key, fw]) => ({ key, ...fw }));
|
|
319
|
+
|
|
320
|
+
if (detectedFrameworks.length === 0) {
|
|
321
|
+
return {
|
|
322
|
+
framework: "node",
|
|
323
|
+
checks: ["routes", "env", "auth", "security"],
|
|
324
|
+
ignorePaths: [],
|
|
325
|
+
extra: {},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Use the first detected framework (usually the most specific)
|
|
330
|
+
const primary = detectedFrameworks[0];
|
|
331
|
+
const frameworkKey = primary.key;
|
|
332
|
+
|
|
333
|
+
// Framework-specific configurations
|
|
334
|
+
const frameworkConfigs = {
|
|
335
|
+
nextjs: {
|
|
336
|
+
framework: "nextjs",
|
|
337
|
+
checks: ["routes", "auth", "api", "security", "ssr"],
|
|
338
|
+
ignorePaths: [".next/", "out/", ".vercel/"],
|
|
339
|
+
extra: {},
|
|
340
|
+
},
|
|
341
|
+
remix: {
|
|
342
|
+
framework: "remix",
|
|
343
|
+
checks: ["routes", "auth", "api", "security", "loaders"],
|
|
344
|
+
ignorePaths: [".remix/", "build/"],
|
|
345
|
+
extra: {},
|
|
346
|
+
},
|
|
347
|
+
nuxt: {
|
|
348
|
+
framework: "nuxt",
|
|
349
|
+
checks: ["routes", "auth", "api", "security"],
|
|
350
|
+
ignorePaths: [".nuxt/", ".output/"],
|
|
351
|
+
extra: {},
|
|
352
|
+
},
|
|
353
|
+
sveltekit: {
|
|
354
|
+
framework: "sveltekit",
|
|
355
|
+
checks: ["routes", "auth", "api", "security"],
|
|
356
|
+
ignorePaths: [".svelte-kit/", "build/"],
|
|
357
|
+
extra: {},
|
|
358
|
+
},
|
|
359
|
+
react: {
|
|
360
|
+
framework: "react",
|
|
361
|
+
checks: ["components", "api", "security"],
|
|
362
|
+
ignorePaths: ["build/", "dist/"],
|
|
363
|
+
extra: {},
|
|
364
|
+
},
|
|
365
|
+
vue: {
|
|
366
|
+
framework: "vue",
|
|
367
|
+
checks: ["components", "api", "security"],
|
|
368
|
+
ignorePaths: ["dist/"],
|
|
369
|
+
extra: {},
|
|
370
|
+
},
|
|
371
|
+
fastify: {
|
|
372
|
+
framework: "fastify",
|
|
373
|
+
checks: ["routes", "auth", "api", "security"],
|
|
374
|
+
ignorePaths: [],
|
|
375
|
+
extra: {
|
|
376
|
+
fastify: {
|
|
377
|
+
plugins: detection.frameworks?.fastify?.plugins || [],
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
express: {
|
|
382
|
+
framework: "express",
|
|
383
|
+
checks: ["routes", "auth", "api", "security"],
|
|
384
|
+
ignorePaths: [],
|
|
385
|
+
extra: {},
|
|
386
|
+
},
|
|
387
|
+
nestjs: {
|
|
388
|
+
framework: "nestjs",
|
|
389
|
+
checks: ["routes", "auth", "api", "security", "modules"],
|
|
390
|
+
ignorePaths: ["dist/"],
|
|
391
|
+
extra: {},
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// Get framework-specific config or use detected checks
|
|
396
|
+
const config = frameworkConfigs[frameworkKey] || {
|
|
397
|
+
framework: frameworkKey,
|
|
398
|
+
checks: primary.checks || ["routes", "env", "auth", "security"],
|
|
399
|
+
ignorePaths: [],
|
|
400
|
+
extra: {},
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// Merge detected checks if available
|
|
404
|
+
if (primary.checks && primary.checks.length > 0) {
|
|
405
|
+
config.checks = [...new Set([...config.checks, ...primary.checks])];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return config;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
412
|
+
// INIT STATE TRACKING
|
|
413
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
414
|
+
|
|
415
|
+
function saveInitState(targetDir, state) {
|
|
416
|
+
try {
|
|
417
|
+
const statePath = path.join(targetDir, ".vibecheck", ".init-state.json");
|
|
418
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
419
|
+
} catch (e) {
|
|
420
|
+
// Non-critical - state tracking is optional
|
|
421
|
+
if (process.env.VIBECHECK_DEBUG) {
|
|
422
|
+
console.error(`Failed to save init state: ${e.message}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function loadInitState(targetDir) {
|
|
428
|
+
try {
|
|
429
|
+
const statePath = path.join(targetDir, ".vibecheck", ".init-state.json");
|
|
430
|
+
if (fs.existsSync(statePath)) {
|
|
431
|
+
return JSON.parse(fs.readFileSync(statePath, "utf-8"));
|
|
432
|
+
}
|
|
433
|
+
} catch (e) {
|
|
434
|
+
// Non-critical
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
440
|
+
// POST-INIT VALIDATION
|
|
441
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
442
|
+
|
|
443
|
+
async function verifyInitSetup(targetDir, opts) {
|
|
444
|
+
const issues = [];
|
|
445
|
+
const vibecheckDir = path.join(targetDir, ".vibecheck");
|
|
446
|
+
|
|
447
|
+
// Check directory structure
|
|
448
|
+
const requiredDirs = [
|
|
449
|
+
vibecheckDir,
|
|
450
|
+
path.join(vibecheckDir, "results"),
|
|
451
|
+
path.join(vibecheckDir, "contracts"),
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
for (const dir of requiredDirs) {
|
|
455
|
+
if (!fs.existsSync(dir)) {
|
|
456
|
+
issues.push(`Missing directory: ${path.relative(targetDir, dir)}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Check config.json
|
|
461
|
+
const configPath = path.join(vibecheckDir, "config.json");
|
|
462
|
+
if (!fs.existsSync(configPath)) {
|
|
463
|
+
issues.push("Missing: .vibecheck/config.json");
|
|
464
|
+
} else {
|
|
465
|
+
try {
|
|
466
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
467
|
+
if (!config.version || !config.checks) {
|
|
468
|
+
issues.push("Invalid config.json structure");
|
|
469
|
+
}
|
|
470
|
+
} catch (e) {
|
|
471
|
+
issues.push(`Invalid config.json: ${e.message}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Check truthpack
|
|
476
|
+
const truthpackPath = path.join(vibecheckDir, "truthpack.json");
|
|
477
|
+
if (!fs.existsSync(truthpackPath)) {
|
|
478
|
+
issues.push("Missing: .vibecheck/truthpack.json (run 'vibecheck ctx build')");
|
|
479
|
+
} else {
|
|
480
|
+
try {
|
|
481
|
+
const truthpack = JSON.parse(fs.readFileSync(truthpackPath, "utf-8"));
|
|
482
|
+
if (!truthpack.version || !truthpack.generatedAt) {
|
|
483
|
+
issues.push("Invalid truthpack.json structure");
|
|
484
|
+
}
|
|
485
|
+
} catch (e) {
|
|
486
|
+
issues.push(`Invalid truthpack.json: ${e.message}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Check contracts
|
|
491
|
+
const contractsDir = path.join(vibecheckDir, "contracts");
|
|
492
|
+
const requiredContracts = ["routes.contract.json", "env.contract.json"];
|
|
493
|
+
for (const contract of requiredContracts) {
|
|
494
|
+
const contractPath = path.join(contractsDir, contract);
|
|
495
|
+
if (!fs.existsSync(contractPath)) {
|
|
496
|
+
issues.push(`Missing contract: ${contract}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (issues.length === 0 && !opts.json) {
|
|
501
|
+
console.log();
|
|
502
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} ${c.bold}Setup Verification:${c.reset} All checks passed`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
success: issues.length === 0,
|
|
507
|
+
issues,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
512
|
+
// FRAMEWORK-SPECIFIC RECOMMENDATIONS
|
|
513
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
514
|
+
|
|
515
|
+
function printFrameworkRecommendations(detection, opts) {
|
|
516
|
+
const frameworks = Object.keys(detection.frameworks || {}).filter(k => detection.frameworks[k].detected);
|
|
517
|
+
if (frameworks.length === 0) return;
|
|
518
|
+
|
|
519
|
+
const primaryFramework = frameworks[0];
|
|
520
|
+
const framework = detection.frameworks[primaryFramework];
|
|
521
|
+
|
|
522
|
+
const recommendations = [];
|
|
523
|
+
|
|
524
|
+
// Next.js specific
|
|
525
|
+
if (primaryFramework === 'nextjs') {
|
|
526
|
+
recommendations.push({
|
|
527
|
+
icon: ICONS.framework,
|
|
528
|
+
title: "Next.js Route Verification",
|
|
529
|
+
command: "vibecheck scan --routes",
|
|
530
|
+
description: "Verify all Next.js routes are properly configured",
|
|
531
|
+
});
|
|
532
|
+
if (!opts.connect) {
|
|
533
|
+
recommendations.push({
|
|
534
|
+
icon: ICONS.ci,
|
|
535
|
+
title: "Add Vercel Integration",
|
|
536
|
+
command: "vibecheck init --connect",
|
|
537
|
+
description: "Enable GitHub Actions for automatic PR checks",
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Fastify specific
|
|
543
|
+
if (primaryFramework === 'fastify') {
|
|
544
|
+
recommendations.push({
|
|
545
|
+
icon: ICONS.framework,
|
|
546
|
+
title: "Fastify Route Extraction",
|
|
547
|
+
command: "vibecheck scan --routes",
|
|
548
|
+
description: "Extract and verify all Fastify routes",
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Express specific
|
|
553
|
+
if (primaryFramework === 'express') {
|
|
554
|
+
recommendations.push({
|
|
555
|
+
icon: ICONS.framework,
|
|
556
|
+
title: "Express Route Analysis",
|
|
557
|
+
command: "vibecheck scan --routes",
|
|
558
|
+
description: "Analyze Express route definitions",
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Auth providers
|
|
563
|
+
const authProviders = Object.keys(detection.auth || {}).filter(k => detection.auth[k].detected);
|
|
564
|
+
if (authProviders.length > 0) {
|
|
565
|
+
recommendations.push({
|
|
566
|
+
icon: ICONS.auth,
|
|
567
|
+
title: "Auth Coverage Check",
|
|
568
|
+
command: "vibecheck scan --auth",
|
|
569
|
+
description: "Verify authentication coverage across routes",
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Payment providers
|
|
574
|
+
const paymentProviders = Object.keys(detection.payments || {}).filter(k => detection.payments[k].detected);
|
|
575
|
+
if (paymentProviders.length > 0) {
|
|
576
|
+
recommendations.push({
|
|
577
|
+
icon: ICONS.payment,
|
|
578
|
+
title: "Payment Flow Verification",
|
|
579
|
+
command: "vibecheck scan --billing",
|
|
580
|
+
description: "Verify payment webhook handlers and idempotency",
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (recommendations.length > 0) {
|
|
585
|
+
console.log();
|
|
586
|
+
printSection('RECOMMENDATIONS', ICONS.sparkle);
|
|
587
|
+
console.log();
|
|
588
|
+
for (const rec of recommendations) {
|
|
589
|
+
console.log(` ${rec.icon} ${c.bold}${rec.title}${c.reset}`);
|
|
590
|
+
console.log(` ${c.dim}${rec.description}${c.reset}`);
|
|
591
|
+
console.log(` ${c.cyan}${rec.command}${c.reset}`);
|
|
592
|
+
console.log();
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
598
|
+
// REPAIR MODE - Fix partial init state
|
|
599
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
600
|
+
|
|
601
|
+
async function runRepairMode(targetDir, projectName, opts) {
|
|
602
|
+
printSection('REPAIR MODE', ICONS.repair);
|
|
603
|
+
console.log();
|
|
604
|
+
console.log(` ${colors.info}${ICONS.info}${c.reset} Scanning for partial initialization state...`);
|
|
605
|
+
console.log();
|
|
606
|
+
|
|
607
|
+
// Load previous init state if available
|
|
608
|
+
const previousState = loadInitState(targetDir);
|
|
609
|
+
if (previousState && !opts.json) {
|
|
610
|
+
console.log(` ${c.dim}Previous init: ${previousState.timestamp} (${previousState.mode} mode)${c.reset}`);
|
|
611
|
+
if (previousState.errors && previousState.errors.length > 0) {
|
|
612
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${previousState.errors.length} error(s) from previous attempt`);
|
|
613
|
+
}
|
|
614
|
+
console.log();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const vibecheckDir = path.join(targetDir, ".vibecheck");
|
|
618
|
+
const issues = [];
|
|
619
|
+
const fixes = [];
|
|
620
|
+
|
|
621
|
+
// Check for missing directories
|
|
622
|
+
const requiredDirs = [
|
|
623
|
+
vibecheckDir,
|
|
624
|
+
path.join(vibecheckDir, "results"),
|
|
625
|
+
path.join(vibecheckDir, "reports"),
|
|
626
|
+
path.join(vibecheckDir, "contracts"),
|
|
627
|
+
path.join(vibecheckDir, "missions"),
|
|
628
|
+
path.join(vibecheckDir, "checkpoints"),
|
|
629
|
+
path.join(vibecheckDir, "runs"),
|
|
630
|
+
];
|
|
631
|
+
|
|
632
|
+
for (const dir of requiredDirs) {
|
|
633
|
+
if (!fs.existsSync(dir)) {
|
|
634
|
+
issues.push(`Missing directory: ${path.relative(targetDir, dir)}`);
|
|
635
|
+
if (!opts.dryRun) {
|
|
636
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
637
|
+
fixes.push(`Created: ${path.relative(targetDir, dir)}`);
|
|
638
|
+
} else {
|
|
639
|
+
fixes.push(`Would create: ${path.relative(targetDir, dir)}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Check for missing config.json
|
|
645
|
+
const configPath = path.join(vibecheckDir, "config.json");
|
|
646
|
+
if (!fs.existsSync(configPath)) {
|
|
647
|
+
issues.push("Missing: .vibecheck/config.json");
|
|
648
|
+
if (!opts.dryRun) {
|
|
649
|
+
try {
|
|
650
|
+
const { detectAll } = require("./lib/enterprise-detect");
|
|
651
|
+
const detection = detectAll(targetDir);
|
|
652
|
+
const frameworkConfig = getFrameworkConfig(detection);
|
|
653
|
+
const config = {
|
|
654
|
+
version: "2.0.0",
|
|
655
|
+
project: projectName,
|
|
656
|
+
createdAt: new Date().toISOString(),
|
|
657
|
+
framework: frameworkConfig.framework || "node",
|
|
658
|
+
checks: frameworkConfig.checks || ["routes", "env", "auth", "security"],
|
|
659
|
+
output: ".vibecheck",
|
|
660
|
+
policy: {
|
|
661
|
+
allowlist: { domains: [], packages: [] },
|
|
662
|
+
ignore: {
|
|
663
|
+
paths: [
|
|
664
|
+
"node_modules",
|
|
665
|
+
"__tests__",
|
|
666
|
+
"*.test.*",
|
|
667
|
+
"*.spec.*",
|
|
668
|
+
...(frameworkConfig.ignorePaths || []),
|
|
669
|
+
],
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
thresholds: {
|
|
673
|
+
minScore: 70,
|
|
674
|
+
maxBlockers: 0,
|
|
675
|
+
maxWarnings: 10,
|
|
676
|
+
},
|
|
677
|
+
...(frameworkConfig.extra || {}),
|
|
678
|
+
};
|
|
679
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
680
|
+
fixes.push("Created: .vibecheck/config.json");
|
|
681
|
+
} catch (e) {
|
|
682
|
+
issues.push(`Failed to create config.json: ${e.message}`);
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
fixes.push("Would create: .vibecheck/config.json");
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Check for missing truthpack
|
|
690
|
+
const truthpackPath = path.join(vibecheckDir, "truthpack.json");
|
|
691
|
+
if (!fs.existsSync(truthpackPath)) {
|
|
692
|
+
issues.push("Missing: .vibecheck/truthpack.json");
|
|
693
|
+
if (!opts.dryRun) {
|
|
694
|
+
startSpinner('Generating truthpack...', colors.accent);
|
|
695
|
+
try {
|
|
696
|
+
const { runCtx } = require("./runCtx");
|
|
697
|
+
await runCtx(["build", "--path", targetDir, "--quiet"]);
|
|
698
|
+
stopSpinner('Truthpack generated', true);
|
|
699
|
+
fixes.push("Generated: .vibecheck/truthpack.json");
|
|
700
|
+
} catch (e) {
|
|
701
|
+
stopSpinner('Truthpack generation failed', false);
|
|
702
|
+
issues.push(`Failed to generate truthpack: ${e.message}`);
|
|
703
|
+
}
|
|
704
|
+
} else {
|
|
705
|
+
fixes.push("Would generate: .vibecheck/truthpack.json");
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Check for missing contracts
|
|
710
|
+
const contractsDir = path.join(vibecheckDir, "contracts");
|
|
711
|
+
const routeContractPath = path.join(contractsDir, "routes.contract.json");
|
|
712
|
+
const envContractPath = path.join(contractsDir, "env.contract.json");
|
|
713
|
+
|
|
714
|
+
if (!fs.existsSync(routeContractPath)) {
|
|
715
|
+
issues.push("Missing: .vibecheck/contracts/routes.contract.json");
|
|
716
|
+
if (!opts.dryRun) {
|
|
717
|
+
const routeContract = {
|
|
718
|
+
version: "1.0.0",
|
|
719
|
+
generatedAt: new Date().toISOString(),
|
|
720
|
+
routes: [],
|
|
721
|
+
description: "Route contract - populated by scan",
|
|
722
|
+
};
|
|
723
|
+
fs.writeFileSync(routeContractPath, JSON.stringify(routeContract, null, 2));
|
|
724
|
+
fixes.push("Created: .vibecheck/contracts/routes.contract.json");
|
|
725
|
+
} else {
|
|
726
|
+
fixes.push("Would create: .vibecheck/contracts/routes.contract.json");
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (!fs.existsSync(envContractPath)) {
|
|
731
|
+
issues.push("Missing: .vibecheck/contracts/env.contract.json");
|
|
732
|
+
if (!opts.dryRun) {
|
|
733
|
+
const envContract = {
|
|
734
|
+
version: "1.0.0",
|
|
735
|
+
generatedAt: new Date().toISOString(),
|
|
736
|
+
required: [],
|
|
737
|
+
optional: [],
|
|
738
|
+
description: "Environment contract - populated by scan",
|
|
739
|
+
};
|
|
740
|
+
fs.writeFileSync(envContractPath, JSON.stringify(envContract, null, 2));
|
|
741
|
+
fixes.push("Created: .vibecheck/contracts/env.contract.json");
|
|
742
|
+
} else {
|
|
743
|
+
fixes.push("Would create: .vibecheck/contracts/env.contract.json");
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Use previous state to prioritize fixes
|
|
748
|
+
if (previousState && previousState.errors && previousState.errors.length > 0) {
|
|
749
|
+
const previousErrors = previousState.errors.map(e => e.step);
|
|
750
|
+
// Prioritize fixing errors from previous attempt
|
|
751
|
+
const prioritizedIssues = [];
|
|
752
|
+
const otherIssues = [];
|
|
753
|
+
|
|
754
|
+
for (const issue of issues) {
|
|
755
|
+
const isPreviousError = previousErrors.some(step => issue.toLowerCase().includes(step));
|
|
756
|
+
if (isPreviousError) {
|
|
757
|
+
prioritizedIssues.push(issue);
|
|
758
|
+
} else {
|
|
759
|
+
otherIssues.push(issue);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
issues.length = 0;
|
|
764
|
+
issues.push(...prioritizedIssues, ...otherIssues);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Summary
|
|
768
|
+
console.log();
|
|
769
|
+
if (issues.length === 0) {
|
|
770
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} No issues found - initialization is complete`);
|
|
771
|
+
// Clear previous state on successful repair
|
|
772
|
+
if (!opts.dryRun) {
|
|
773
|
+
const statePath = path.join(targetDir, ".vibecheck", ".init-state.json");
|
|
774
|
+
if (fs.existsSync(statePath)) {
|
|
775
|
+
fs.unlinkSync(statePath);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return 0;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} Found ${issues.length} issue(s)`);
|
|
782
|
+
if (previousState && previousState.errors && previousState.errors.length > 0) {
|
|
783
|
+
console.log(` ${c.dim}(${previousState.errors.length} from previous attempt)${c.reset}`);
|
|
784
|
+
}
|
|
785
|
+
console.log();
|
|
786
|
+
for (const issue of issues) {
|
|
787
|
+
console.log(` ${c.dim}•${c.reset} ${issue}`);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (fixes.length > 0) {
|
|
791
|
+
console.log();
|
|
792
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} Applied ${fixes.length} fix(es)`);
|
|
793
|
+
for (const fix of fixes) {
|
|
794
|
+
console.log(` ${c.dim}•${c.reset} ${fix}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
console.log();
|
|
799
|
+
return issues.length > 0 ? (opts.dryRun ? 0 : 1) : 0;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
803
|
+
// ENHANCED DETECTION DISPLAY
|
|
804
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
805
|
+
|
|
806
|
+
function printEnhancedDetection(detection, verbose = false) {
|
|
807
|
+
const frameworks = Object.keys(detection.frameworks || {}).filter(k => detection.frameworks[k].detected);
|
|
808
|
+
const databases = Object.keys(detection.databases || {}).filter(k => detection.databases[k].detected);
|
|
809
|
+
const authProviders = Object.keys(detection.auth || {}).filter(k => detection.auth[k].detected);
|
|
810
|
+
const paymentProviders = Object.keys(detection.payments || {}).filter(k => detection.payments[k].detected);
|
|
811
|
+
|
|
812
|
+
if (frameworks.length > 0) {
|
|
813
|
+
console.log(` ${colors.info}${ICONS.info}${c.reset} ${c.bold}Detected Stack:${c.reset}`);
|
|
814
|
+
console.log(` ${ICONS.framework} Frameworks: ${frameworks.map(f => detection.frameworks[f].name).join(', ')}`);
|
|
815
|
+
|
|
816
|
+
if (databases.length > 0) {
|
|
817
|
+
console.log(` ${ICONS.database} Databases: ${databases.map(d => detection.databases[d].name).join(', ')}`);
|
|
818
|
+
}
|
|
819
|
+
if (authProviders.length > 0) {
|
|
820
|
+
console.log(` ${ICONS.auth} Auth: ${authProviders.map(a => detection.auth[a].name).join(', ')}`);
|
|
821
|
+
}
|
|
822
|
+
if (paymentProviders.length > 0) {
|
|
823
|
+
console.log(` ${ICONS.payment} Payments: ${paymentProviders.map(p => detection.payments[p].name).join(', ')}`);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (verbose) {
|
|
827
|
+
console.log();
|
|
828
|
+
console.log(` ${c.dim}Detection Details:${c.reset}`);
|
|
829
|
+
if (detection.packageManager) {
|
|
830
|
+
console.log(` Package Manager: ${detection.packageManager}`);
|
|
831
|
+
}
|
|
832
|
+
if (detection.nodeVersion) {
|
|
833
|
+
console.log(` Node Version: ${detection.nodeVersion}`);
|
|
834
|
+
}
|
|
835
|
+
const checks = detection.checks || [];
|
|
836
|
+
if (checks.length > 0) {
|
|
837
|
+
console.log(` Recommended Checks: ${checks.join(', ')}`);
|
|
838
|
+
}
|
|
839
|
+
const invariants = detection.invariants || [];
|
|
840
|
+
if (invariants.length > 0) {
|
|
841
|
+
console.log(` Invariants: ${invariants.join(', ')}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
console.log();
|
|
845
|
+
} else {
|
|
846
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} No framework detected - using default Node.js configuration`);
|
|
847
|
+
console.log();
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
298
851
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
299
852
|
// DETECTION DISPLAY
|
|
300
853
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -314,7 +867,14 @@ function printDetectionResults(detection) {
|
|
|
314
867
|
];
|
|
315
868
|
|
|
316
869
|
for (const cat of categories) {
|
|
317
|
-
const
|
|
870
|
+
const itemsObj = detection[cat.key] || {};
|
|
871
|
+
// Handle both object and array formats
|
|
872
|
+
const items = Array.isArray(itemsObj)
|
|
873
|
+
? itemsObj
|
|
874
|
+
: Object.entries(itemsObj)
|
|
875
|
+
.filter(([key, value]) => value && (value.detected || value === true))
|
|
876
|
+
.map(([key, value]) => typeof value === 'object' ? value.name || key : key);
|
|
877
|
+
|
|
318
878
|
if (items.length === 0) continue;
|
|
319
879
|
|
|
320
880
|
const itemList = items.slice(0, 4).join(', ');
|
|
@@ -343,6 +903,10 @@ function printSetupStep(step, status, detail = null) {
|
|
|
343
903
|
let icon, color;
|
|
344
904
|
|
|
345
905
|
switch (status) {
|
|
906
|
+
case 'dry-run':
|
|
907
|
+
icon = ICONS.info;
|
|
908
|
+
color = colors.warning;
|
|
909
|
+
break;
|
|
346
910
|
case 'creating':
|
|
347
911
|
icon = ICONS.create;
|
|
348
912
|
color = colors.accent;
|
|
@@ -537,8 +1101,10 @@ function printComplianceInfo(complianceType) {
|
|
|
537
1101
|
// HELP DISPLAY
|
|
538
1102
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
539
1103
|
|
|
540
|
-
function printHelp() {
|
|
541
|
-
|
|
1104
|
+
function printHelp(showBanner = true) {
|
|
1105
|
+
if (showBanner && shouldShowBanner({})) {
|
|
1106
|
+
console.log(BANNER_FULL);
|
|
1107
|
+
}
|
|
542
1108
|
console.log(`
|
|
543
1109
|
${c.bold}Usage:${c.reset} vibecheck init [mode] [options]
|
|
544
1110
|
|
|
@@ -550,6 +1116,11 @@ function printHelp() {
|
|
|
550
1116
|
${c.bold}Basic Options:${c.reset}
|
|
551
1117
|
${colors.accent}--path, -p <dir>${c.reset} Project path ${c.dim}(default: current directory)${c.reset}
|
|
552
1118
|
${colors.accent}--quick, -q${c.reset} Skip wizard, use defaults
|
|
1119
|
+
${colors.accent}--wizard, -i${c.reset} Interactive setup wizard
|
|
1120
|
+
${colors.accent}--dry-run${c.reset} Preview changes without applying
|
|
1121
|
+
${colors.accent}--repair${c.reset} Fix partial init state
|
|
1122
|
+
${colors.accent}--verify${c.reset} Verify setup after init
|
|
1123
|
+
${colors.accent}--verbose, -v${c.reset} Show detailed output
|
|
553
1124
|
${colors.accent}--git-hooks${c.reset} Install git pre-push hook
|
|
554
1125
|
${colors.accent}--json${c.reset} Output results as JSON
|
|
555
1126
|
${colors.accent}--help, -h${c.reset} Show this help
|
|
@@ -608,34 +1179,53 @@ function printHelp() {
|
|
|
608
1179
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
609
1180
|
|
|
610
1181
|
function parseArgs(args) {
|
|
1182
|
+
// Parse global flags first
|
|
1183
|
+
const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
|
|
1184
|
+
|
|
611
1185
|
const out = {
|
|
612
|
-
path: ".",
|
|
1186
|
+
path: globalFlags.path || ".",
|
|
613
1187
|
gitHooks: false,
|
|
614
|
-
help: false,
|
|
1188
|
+
help: globalFlags.help || false,
|
|
615
1189
|
quick: false,
|
|
616
|
-
json: false,
|
|
1190
|
+
json: globalFlags.json || false,
|
|
1191
|
+
verbose: globalFlags.verbose || false,
|
|
1192
|
+
noBanner: globalFlags.noBanner || false,
|
|
1193
|
+
ci: globalFlags.ci || false,
|
|
1194
|
+
quiet: globalFlags.quiet || false,
|
|
617
1195
|
// Mode flags (--local is default, --connect requires STARTER)
|
|
618
1196
|
local: false,
|
|
619
1197
|
connect: false,
|
|
1198
|
+
// Enhancement flags
|
|
1199
|
+
wizard: false,
|
|
1200
|
+
interactive: false,
|
|
1201
|
+
dryRun: false,
|
|
1202
|
+
repair: false,
|
|
1203
|
+
verify: false,
|
|
620
1204
|
// Enterprise options
|
|
621
1205
|
enterprise: false,
|
|
622
|
-
ci: false,
|
|
623
1206
|
compliance: false,
|
|
624
1207
|
team: false,
|
|
625
1208
|
mcp: false,
|
|
626
|
-
strict: false,
|
|
1209
|
+
strict: globalFlags.strict || false,
|
|
627
1210
|
failOnWarn: false,
|
|
628
1211
|
detect: false,
|
|
629
1212
|
};
|
|
630
1213
|
|
|
631
|
-
for (let i = 0; i <
|
|
632
|
-
const a =
|
|
1214
|
+
for (let i = 0; i < cleanArgs.length; i++) {
|
|
1215
|
+
const a = cleanArgs[i];
|
|
633
1216
|
if (a.startsWith("--path=")) out.path = a.split("=")[1];
|
|
634
|
-
if (a === "--path" || a === "-p") out.path =
|
|
1217
|
+
if (a === "--path" || a === "-p") out.path = cleanArgs[++i];
|
|
635
1218
|
if (a === "--git-hooks") out.gitHooks = true;
|
|
636
|
-
if (a === "--help" || a === "-h") out.help = true;
|
|
637
1219
|
if (a === "--quick" || a === "-q") out.quick = true;
|
|
638
|
-
if (a === "--
|
|
1220
|
+
if (a === "--verify") out.verify = true;
|
|
1221
|
+
|
|
1222
|
+
// Enhancement flags
|
|
1223
|
+
if (a === "--wizard" || a === "--interactive" || a === "-i") {
|
|
1224
|
+
out.wizard = true;
|
|
1225
|
+
out.interactive = true;
|
|
1226
|
+
}
|
|
1227
|
+
if (a === "--dry-run" || a === "--dryrun") out.dryRun = true;
|
|
1228
|
+
if (a === "--repair") out.repair = true;
|
|
639
1229
|
|
|
640
1230
|
// Mode flags
|
|
641
1231
|
if (a === "--local" || a === "-l") out.local = true;
|
|
@@ -699,9 +1289,41 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
699
1289
|
printSection('LOCAL SETUP', ICONS.setup);
|
|
700
1290
|
console.log();
|
|
701
1291
|
|
|
1292
|
+
const totalSteps = 7; // Approximate number of major steps
|
|
1293
|
+
let currentStep = 0;
|
|
1294
|
+
|
|
1295
|
+
function logStep(stepName) {
|
|
1296
|
+
currentStep++;
|
|
1297
|
+
if (opts.verbose && !opts.json) {
|
|
1298
|
+
console.log(` ${c.dim}[${currentStep}/${totalSteps}]${c.reset} ${stepName}`);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Detect framework for framework-specific templates
|
|
1303
|
+
let detection = null;
|
|
1304
|
+
if (!opts.dryRun) {
|
|
1305
|
+
try {
|
|
1306
|
+
const { detectAll } = require("./lib/enterprise-detect");
|
|
1307
|
+
detection = detectAll(targetDir);
|
|
1308
|
+
if (!opts.json) {
|
|
1309
|
+
printEnhancedDetection(detection, opts.verbose);
|
|
1310
|
+
}
|
|
1311
|
+
} catch (e) {
|
|
1312
|
+
if (!opts.json) {
|
|
1313
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} Detection failed: ${e.message}`);
|
|
1314
|
+
if (opts.verbose) {
|
|
1315
|
+
console.log(` ${c.dim}Stack: ${e.stack}${c.reset}`);
|
|
1316
|
+
}
|
|
1317
|
+
console.log(` ${c.dim}Using default configuration${c.reset}`);
|
|
1318
|
+
console.log();
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
702
1323
|
const vibecheckDir = path.join(targetDir, ".vibecheck");
|
|
703
1324
|
|
|
704
1325
|
// 1. Create .vibecheck/ directory structure
|
|
1326
|
+
logStep('Creating directory structure');
|
|
705
1327
|
const dirs = [
|
|
706
1328
|
vibecheckDir,
|
|
707
1329
|
path.join(vibecheckDir, "results"),
|
|
@@ -714,50 +1336,82 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
714
1336
|
|
|
715
1337
|
for (const dir of dirs) {
|
|
716
1338
|
if (!fs.existsSync(dir)) {
|
|
717
|
-
|
|
1339
|
+
if (opts.dryRun) {
|
|
1340
|
+
printSetupStep(path.relative(targetDir, dir), 'dry-run', 'would create');
|
|
1341
|
+
} else {
|
|
1342
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1343
|
+
}
|
|
718
1344
|
}
|
|
719
1345
|
}
|
|
720
|
-
|
|
721
|
-
|
|
1346
|
+
if (!opts.dryRun) {
|
|
1347
|
+
printSetupStep('.vibecheck/', 'success', 'directory structure created');
|
|
1348
|
+
filesCreated.push('.vibecheck/');
|
|
1349
|
+
}
|
|
722
1350
|
|
|
723
|
-
// 2. Create config.json
|
|
1351
|
+
// 2. Create config.json with framework-specific settings
|
|
1352
|
+
logStep('Creating configuration');
|
|
724
1353
|
const configPath = path.join(vibecheckDir, "config.json");
|
|
725
|
-
if (!fs.existsSync(configPath)) {
|
|
1354
|
+
if (!fs.existsSync(configPath) || opts.dryRun) {
|
|
1355
|
+
const frameworkConfig = getFrameworkConfig(detection);
|
|
726
1356
|
const config = {
|
|
727
1357
|
version: "2.0.0",
|
|
728
1358
|
project: projectName,
|
|
729
1359
|
createdAt: new Date().toISOString(),
|
|
730
|
-
|
|
1360
|
+
framework: frameworkConfig.framework || "node",
|
|
1361
|
+
checks: frameworkConfig.checks || ["routes", "env", "auth", "security"],
|
|
731
1362
|
output: ".vibecheck",
|
|
732
1363
|
policy: {
|
|
733
1364
|
allowlist: { domains: [], packages: [] },
|
|
734
|
-
ignore: {
|
|
1365
|
+
ignore: {
|
|
1366
|
+
paths: [
|
|
1367
|
+
"node_modules",
|
|
1368
|
+
"__tests__",
|
|
1369
|
+
"*.test.*",
|
|
1370
|
+
"*.spec.*",
|
|
1371
|
+
...(frameworkConfig.ignorePaths || [])
|
|
1372
|
+
]
|
|
1373
|
+
},
|
|
735
1374
|
},
|
|
736
1375
|
thresholds: {
|
|
737
1376
|
minScore: 70,
|
|
738
1377
|
maxBlockers: 0,
|
|
739
1378
|
maxWarnings: 10,
|
|
740
1379
|
},
|
|
1380
|
+
...(frameworkConfig.extra || {}),
|
|
741
1381
|
};
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1382
|
+
|
|
1383
|
+
if (opts.dryRun) {
|
|
1384
|
+
printSetupStep('config.json', 'dry-run', `would create (framework: ${config.framework})`);
|
|
1385
|
+
console.log(` ${c.dim}Config preview:${c.reset}`);
|
|
1386
|
+
console.log(JSON.stringify(config, null, 2));
|
|
1387
|
+
} else {
|
|
1388
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1389
|
+
printSetupStep('config.json', 'success', `configuration created (framework: ${config.framework})`);
|
|
1390
|
+
filesCreated.push('.vibecheck/config.json');
|
|
1391
|
+
}
|
|
745
1392
|
} else {
|
|
746
1393
|
printSetupStep('config.json', 'exists', 'already exists');
|
|
747
1394
|
}
|
|
748
1395
|
|
|
749
1396
|
// 3. Generate truthpack
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1397
|
+
logStep('Generating truthpack');
|
|
1398
|
+
if (!opts.dryRun) {
|
|
1399
|
+
startSpinner('Building truthpack...', colors.accent);
|
|
1400
|
+
try {
|
|
1401
|
+
const { runCtx } = require("./runCtx");
|
|
1402
|
+
await runCtx(["build", "--path", targetDir, "--quiet"]);
|
|
1403
|
+
stopSpinner('Truthpack generated', true);
|
|
1404
|
+
filesCreated.push('.vibecheck/truthpack.json');
|
|
1405
|
+
} catch (e) {
|
|
1406
|
+
stopSpinner('Truthpack generation failed', false);
|
|
1407
|
+
throw new Error(`Truthpack generation failed: ${e.message}. Run 'vibecheck ctx build' manually or 'vibecheck init --repair' to retry.`);
|
|
1408
|
+
}
|
|
1409
|
+
} else {
|
|
1410
|
+
printSetupStep('truthpack.json', 'dry-run', 'would generate via vibecheck ctx build');
|
|
758
1411
|
}
|
|
759
1412
|
|
|
760
1413
|
// 4. Generate contracts
|
|
1414
|
+
logStep('Creating contracts');
|
|
761
1415
|
const contractsDir = path.join(vibecheckDir, "contracts");
|
|
762
1416
|
|
|
763
1417
|
// Routes contract
|
|
@@ -769,9 +1423,13 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
769
1423
|
routes: [],
|
|
770
1424
|
description: "Route contract - populated by scan",
|
|
771
1425
|
};
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1426
|
+
if (opts.dryRun) {
|
|
1427
|
+
printSetupStep('routes.contract.json', 'dry-run', 'would create');
|
|
1428
|
+
} else {
|
|
1429
|
+
fs.writeFileSync(routeContractPath, JSON.stringify(routeContract, null, 2));
|
|
1430
|
+
printSetupStep('routes.contract.json', 'success', 'route contract created');
|
|
1431
|
+
filesCreated.push('.vibecheck/contracts/routes.contract.json');
|
|
1432
|
+
}
|
|
775
1433
|
}
|
|
776
1434
|
|
|
777
1435
|
// Env contract
|
|
@@ -784,35 +1442,55 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
784
1442
|
optional: [],
|
|
785
1443
|
description: "Environment contract - populated by scan",
|
|
786
1444
|
};
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1445
|
+
if (opts.dryRun) {
|
|
1446
|
+
printSetupStep('env.contract.json', 'dry-run', 'would create');
|
|
1447
|
+
} else {
|
|
1448
|
+
fs.writeFileSync(envContractPath, JSON.stringify(envContract, null, 2));
|
|
1449
|
+
printSetupStep('env.contract.json', 'success', 'env contract created');
|
|
1450
|
+
filesCreated.push('.vibecheck/contracts/env.contract.json');
|
|
1451
|
+
}
|
|
790
1452
|
}
|
|
791
1453
|
|
|
792
1454
|
// 5. Generate IDE rules (MDC for Cursor, rules for others)
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1455
|
+
logStep('Generating IDE rules');
|
|
1456
|
+
if (!opts.dryRun) {
|
|
1457
|
+
startSpinner('Generating IDE rules...', colors.accent);
|
|
1458
|
+
try {
|
|
1459
|
+
const { runContext } = require("./runContext");
|
|
1460
|
+
await runContext(["--path", targetDir, "--quiet"]);
|
|
1461
|
+
stopSpinner('IDE rules generated', true);
|
|
1462
|
+
filesCreated.push('.cursor/rules/', '.windsurf/', '.github/copilot-instructions.md');
|
|
1463
|
+
} catch (e) {
|
|
1464
|
+
stopSpinner('IDE rules generation failed', false);
|
|
1465
|
+
if (!opts.json) {
|
|
1466
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${e.message}`);
|
|
1467
|
+
console.log(` ${c.dim}Run 'vibecheck context' manually to generate IDE rules${c.reset}`);
|
|
1468
|
+
}
|
|
1469
|
+
// Don't throw - IDE rules are optional
|
|
1470
|
+
}
|
|
1471
|
+
} else {
|
|
1472
|
+
printSetupStep('IDE rules', 'dry-run', 'would generate via vibecheck context');
|
|
801
1473
|
}
|
|
802
1474
|
|
|
803
1475
|
// 6. Create .vibecheckrc at project root
|
|
1476
|
+
logStep('Creating root config');
|
|
804
1477
|
const rcPath = path.join(targetDir, ".vibecheckrc.json");
|
|
805
1478
|
if (!fs.existsSync(rcPath)) {
|
|
806
1479
|
const rc = {
|
|
807
1480
|
extends: ".vibecheck/config.json",
|
|
808
1481
|
tier: "free",
|
|
809
1482
|
};
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1483
|
+
if (opts.dryRun) {
|
|
1484
|
+
printSetupStep('.vibecheckrc.json', 'dry-run', 'would create');
|
|
1485
|
+
} else {
|
|
1486
|
+
fs.writeFileSync(rcPath, JSON.stringify(rc, null, 2));
|
|
1487
|
+
printSetupStep('.vibecheckrc.json', 'success', 'root config created');
|
|
1488
|
+
filesCreated.push('.vibecheckrc.json');
|
|
1489
|
+
}
|
|
813
1490
|
}
|
|
814
1491
|
|
|
815
1492
|
// 7. Update .gitignore
|
|
1493
|
+
logStep('Updating .gitignore');
|
|
816
1494
|
const gitignorePath = path.join(targetDir, ".gitignore");
|
|
817
1495
|
if (fs.existsSync(gitignorePath)) {
|
|
818
1496
|
let gitignore = fs.readFileSync(gitignorePath, "utf-8");
|
|
@@ -822,9 +1500,13 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
822
1500
|
if (!gitignore.includes(".vibecheck/checkpoints/")) additions.push(".vibecheck/checkpoints/");
|
|
823
1501
|
|
|
824
1502
|
if (additions.length > 0) {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1503
|
+
if (opts.dryRun) {
|
|
1504
|
+
printSetupStep('.gitignore', 'dry-run', `would add: ${additions.join(', ')}`);
|
|
1505
|
+
} else {
|
|
1506
|
+
gitignore += `\n# vibecheck (generated outputs)\n${additions.join("\n")}\n`;
|
|
1507
|
+
fs.writeFileSync(gitignorePath, gitignore);
|
|
1508
|
+
printSetupStep('.gitignore', 'success', 'updated with vibecheck paths');
|
|
1509
|
+
}
|
|
828
1510
|
}
|
|
829
1511
|
}
|
|
830
1512
|
|
|
@@ -925,7 +1607,7 @@ async function runInit(args) {
|
|
|
925
1607
|
const opts = parseArgs(args);
|
|
926
1608
|
|
|
927
1609
|
if (opts.help) {
|
|
928
|
-
printHelp();
|
|
1610
|
+
printHelp(shouldShowBanner(opts));
|
|
929
1611
|
return 0;
|
|
930
1612
|
}
|
|
931
1613
|
|
|
@@ -937,18 +1619,22 @@ async function runInit(args) {
|
|
|
937
1619
|
|
|
938
1620
|
const targetDir = path.resolve(opts.path);
|
|
939
1621
|
const projectName = path.basename(targetDir);
|
|
1622
|
+
let bannerPrinted = false;
|
|
940
1623
|
|
|
941
1624
|
// Enterprise mode - comprehensive setup
|
|
942
1625
|
const useEnterprise = opts.enterprise || opts.ci || opts.compliance ||
|
|
943
1626
|
opts.team || opts.mcp || opts.detect;
|
|
944
1627
|
|
|
945
1628
|
if (EnterpriseInit && useEnterprise && !opts.local && !opts.connect) {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1629
|
+
if (shouldShowBanner(opts)) {
|
|
1630
|
+
printBanner();
|
|
1631
|
+
bannerPrinted = true;
|
|
1632
|
+
printEnterpriseHeader(opts);
|
|
1633
|
+
|
|
1634
|
+
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
1635
|
+
console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
|
|
1636
|
+
console.log();
|
|
1637
|
+
}
|
|
952
1638
|
|
|
953
1639
|
const enterprise = new EnterpriseInit(targetDir, opts);
|
|
954
1640
|
return await enterprise.run();
|
|
@@ -956,10 +1642,12 @@ async function runInit(args) {
|
|
|
956
1642
|
|
|
957
1643
|
// Detect-only mode (quick detection report)
|
|
958
1644
|
if (opts.detect) {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1645
|
+
if (shouldShowBanner(opts)) {
|
|
1646
|
+
printBanner();
|
|
1647
|
+
bannerPrinted = true;
|
|
1648
|
+
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
1649
|
+
console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
|
|
1650
|
+
}
|
|
963
1651
|
|
|
964
1652
|
startSpinner('Detecting project configuration...', colors.accent);
|
|
965
1653
|
|
|
@@ -967,9 +1655,11 @@ async function runInit(args) {
|
|
|
967
1655
|
const { detectAll } = require("./lib/enterprise-detect");
|
|
968
1656
|
const detection = detectAll(targetDir);
|
|
969
1657
|
stopSpinner('Detection complete', true);
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1658
|
+
if (!opts.json) {
|
|
1659
|
+
printDetectionResults(detection);
|
|
1660
|
+
console.log();
|
|
1661
|
+
console.log(` ${c.dim}Full JSON output:${c.reset}`);
|
|
1662
|
+
}
|
|
973
1663
|
console.log(JSON.stringify(detection, null, 2));
|
|
974
1664
|
} catch (e) {
|
|
975
1665
|
stopSpinner('Detection failed', false);
|
|
@@ -982,8 +1672,33 @@ async function runInit(args) {
|
|
|
982
1672
|
// NEW: --local and --connect modes
|
|
983
1673
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
984
1674
|
|
|
985
|
-
|
|
1675
|
+
// Interactive wizard mode
|
|
1676
|
+
if (opts.wizard && InitWizard) {
|
|
1677
|
+
if (shouldShowBanner(opts) && !bannerPrinted) {
|
|
1678
|
+
printBanner();
|
|
1679
|
+
bannerPrinted = true;
|
|
1680
|
+
}
|
|
1681
|
+
const wizard = new InitWizard(targetDir, opts);
|
|
1682
|
+
return await wizard.run();
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// Repair mode - fix partial state
|
|
1686
|
+
if (opts.repair) {
|
|
1687
|
+
if (shouldShowBanner(opts) && !bannerPrinted) {
|
|
1688
|
+
printBanner();
|
|
1689
|
+
bannerPrinted = true;
|
|
1690
|
+
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
1691
|
+
console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
|
|
1692
|
+
console.log(` ${c.dim}Mode:${c.reset} ${colors.accent}Repair${c.reset}`);
|
|
1693
|
+
console.log();
|
|
1694
|
+
}
|
|
1695
|
+
return await runRepairMode(targetDir, projectName, opts);
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// Print banner only once (not already printed in enterprise/detect modes)
|
|
1699
|
+
if (shouldShowBanner(opts) && !bannerPrinted) {
|
|
986
1700
|
printBanner();
|
|
1701
|
+
bannerPrinted = true;
|
|
987
1702
|
|
|
988
1703
|
const mode = opts.connect ? 'Local + Connect' : 'Local';
|
|
989
1704
|
const tierBadge = opts.connect ? `${c.cyan}[STARTER]${c.reset}` : `${c.green}[FREE]${c.reset}`;
|
|
@@ -991,6 +1706,9 @@ async function runInit(args) {
|
|
|
991
1706
|
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
992
1707
|
console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
|
|
993
1708
|
console.log(` ${c.dim}Mode:${c.reset} ${colors.accent}${mode}${c.reset} ${tierBadge}`);
|
|
1709
|
+
if (opts.dryRun) {
|
|
1710
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${c.bold}DRY-RUN MODE${c.reset} - No files will be created`);
|
|
1711
|
+
}
|
|
994
1712
|
}
|
|
995
1713
|
|
|
996
1714
|
let filesCreated = [];
|
|
@@ -1001,6 +1719,7 @@ async function runInit(args) {
|
|
|
1001
1719
|
mode: opts.connect ? 'connect' : 'local',
|
|
1002
1720
|
filesCreated: [],
|
|
1003
1721
|
errors: [],
|
|
1722
|
+
dryRun: opts.dryRun,
|
|
1004
1723
|
};
|
|
1005
1724
|
|
|
1006
1725
|
// Always run local setup first
|
|
@@ -1008,9 +1727,16 @@ async function runInit(args) {
|
|
|
1008
1727
|
try {
|
|
1009
1728
|
filesCreated = await runLocalSetup(targetDir, projectName, opts, filesCreated);
|
|
1010
1729
|
} catch (e) {
|
|
1011
|
-
result.errors.push({ step: 'local', error: e.message });
|
|
1730
|
+
result.errors.push({ step: 'local', error: e.message, stack: e.stack });
|
|
1012
1731
|
if (!opts.json) {
|
|
1013
1732
|
console.log(` ${colors.error}${ICONS.cross}${c.reset} Local setup error: ${e.message}`);
|
|
1733
|
+
if (opts.verbose) {
|
|
1734
|
+
console.log(` ${c.dim}Stack: ${e.stack}${c.reset}`);
|
|
1735
|
+
}
|
|
1736
|
+
console.log(` ${colors.info}${ICONS.info}${c.reset} Run ${c.cyan}vibecheck init --repair${c.reset} to fix partial state`);
|
|
1737
|
+
}
|
|
1738
|
+
if (!opts.dryRun) {
|
|
1739
|
+
return 1; // Exit on error unless dry-run
|
|
1014
1740
|
}
|
|
1015
1741
|
}
|
|
1016
1742
|
}
|
|
@@ -1020,9 +1746,15 @@ async function runInit(args) {
|
|
|
1020
1746
|
try {
|
|
1021
1747
|
filesCreated = await runConnectSetup(targetDir, projectName, opts, filesCreated);
|
|
1022
1748
|
} catch (e) {
|
|
1023
|
-
result.errors.push({ step: 'connect', error: e.message });
|
|
1749
|
+
result.errors.push({ step: 'connect', error: e.message, stack: e.stack });
|
|
1024
1750
|
if (!opts.json) {
|
|
1025
1751
|
console.log(` ${colors.error}${ICONS.cross}${c.reset} Connect setup error: ${e.message}`);
|
|
1752
|
+
if (opts.verbose) {
|
|
1753
|
+
console.log(` ${c.dim}Stack: ${e.stack}${c.reset}`);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
if (!opts.dryRun) {
|
|
1757
|
+
return 1;
|
|
1026
1758
|
}
|
|
1027
1759
|
}
|
|
1028
1760
|
}
|
|
@@ -1063,6 +1795,31 @@ fi
|
|
|
1063
1795
|
const mode = opts.connect ? 'Local + Connect' : 'Local Setup';
|
|
1064
1796
|
printSuccessCard(projectName, mode, filesCreated.length);
|
|
1065
1797
|
|
|
1798
|
+
// Generate dependency graph
|
|
1799
|
+
if (!opts.dryRun) {
|
|
1800
|
+
if (!opts.json) {
|
|
1801
|
+
startSpinner('Generating dependency graph...', colors.accent);
|
|
1802
|
+
}
|
|
1803
|
+
try {
|
|
1804
|
+
const { buildDependencyGraph, generateHtmlVisualization } = require("./context/dependency-graph");
|
|
1805
|
+
const depGraph = buildDependencyGraph(targetDir);
|
|
1806
|
+
const htmlViz = generateHtmlVisualization(depGraph, { maxNodes: 200 });
|
|
1807
|
+
const graphPath = path.join(targetDir, ".vibecheck", "dependency-graph.html");
|
|
1808
|
+
fs.writeFileSync(graphPath, htmlViz);
|
|
1809
|
+
filesCreated.push('.vibecheck/dependency-graph.html');
|
|
1810
|
+
if (!opts.json) {
|
|
1811
|
+
stopSpinner('Dependency graph generated', true);
|
|
1812
|
+
}
|
|
1813
|
+
} catch (e) {
|
|
1814
|
+
if (!opts.json) {
|
|
1815
|
+
stopSpinner('Dependency graph skipped', false);
|
|
1816
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${e.message}`);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
} else {
|
|
1820
|
+
printSetupStep('dependency-graph.html', 'dry-run', 'would generate');
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1066
1823
|
// Init summary
|
|
1067
1824
|
printSection('INIT SUMMARY', ICONS.check);
|
|
1068
1825
|
console.log();
|
|
@@ -1070,11 +1827,52 @@ fi
|
|
|
1070
1827
|
console.log(` ${colors.success}${ICONS.check}${c.reset} Truthpack generated`);
|
|
1071
1828
|
console.log(` ${colors.success}${ICONS.check}${c.reset} Contracts initialized`);
|
|
1072
1829
|
console.log(` ${colors.success}${ICONS.check}${c.reset} IDE rules generated`);
|
|
1830
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} Dependency graph generated`);
|
|
1073
1831
|
if (opts.connect) {
|
|
1074
1832
|
console.log(` ${colors.success}${ICONS.check}${c.reset} GitHub Actions workflows installed`);
|
|
1075
1833
|
}
|
|
1076
1834
|
console.log();
|
|
1077
1835
|
|
|
1836
|
+
// Show dependency graph location
|
|
1837
|
+
const graphPath = path.join(targetDir, ".vibecheck", "dependency-graph.html");
|
|
1838
|
+
if (fs.existsSync(graphPath)) {
|
|
1839
|
+
console.log(` ${colors.info}${ICONS.info}${c.reset} ${c.bold}Dependency Graph:${c.reset}`);
|
|
1840
|
+
console.log(` ${c.cyan}file://${graphPath.replace(/\\/g, '/')}${c.reset}`);
|
|
1841
|
+
console.log();
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// Save init state for repair mode
|
|
1845
|
+
if (!opts.dryRun) {
|
|
1846
|
+
saveInitState(targetDir, {
|
|
1847
|
+
timestamp: new Date().toISOString(),
|
|
1848
|
+
mode: opts.connect ? 'connect' : 'local',
|
|
1849
|
+
filesCreated,
|
|
1850
|
+
errors: result.errors,
|
|
1851
|
+
detection: detection ? {
|
|
1852
|
+
frameworks: Object.keys(detection.frameworks || {}).filter(k => detection.frameworks[k].detected),
|
|
1853
|
+
checks: detection.checks || [],
|
|
1854
|
+
} : null,
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// Post-init validation
|
|
1859
|
+
if (opts.verify && !opts.dryRun) {
|
|
1860
|
+
const validationResult = await verifyInitSetup(targetDir, opts);
|
|
1861
|
+
if (!validationResult.success && !opts.json) {
|
|
1862
|
+
console.log();
|
|
1863
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${c.bold}Validation Issues Found:${c.reset}`);
|
|
1864
|
+
for (const issue of validationResult.issues) {
|
|
1865
|
+
console.log(` ${c.dim}•${c.reset} ${issue}`);
|
|
1866
|
+
}
|
|
1867
|
+
console.log();
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
// Framework-specific recommendations
|
|
1872
|
+
if (detection && !opts.json && !opts.dryRun) {
|
|
1873
|
+
printFrameworkRecommendations(detection, opts);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1078
1876
|
// Next steps
|
|
1079
1877
|
printNextSteps({ hasCI: opts.connect, mode: opts.connect ? 'connect' : 'local' });
|
|
1080
1878
|
|