@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.
Files changed (38) hide show
  1. package/README.md +27 -32
  2. package/bin/registry.js +216 -341
  3. package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
  4. package/bin/runners/lib/global-flags.js +213 -0
  5. package/bin/runners/lib/init-wizard.js +566 -273
  6. package/bin/runners/lib/interactive-menu.js +1496 -0
  7. package/bin/runners/runCheckpoint.js +503 -502
  8. package/bin/runners/runContext.js +16 -9
  9. package/bin/runners/runDoctor.js +20 -33
  10. package/bin/runners/runFix.js +19 -11
  11. package/bin/runners/runInit.js +866 -68
  12. package/bin/runners/runInstall.js +14 -6
  13. package/bin/runners/runProve.js +18 -12
  14. package/bin/runners/runReality.js +15 -6
  15. package/bin/runners/runReport.js +31 -18
  16. package/bin/runners/runScan.js +28 -17
  17. package/bin/runners/runShip.js +27 -21
  18. package/bin/runners/runWatch.js +11 -4
  19. package/bin/vibecheck.js +851 -212
  20. package/mcp-server/package.json +1 -1
  21. package/package.json +1 -1
  22. package/bin/runners/runBadge.js +0 -916
  23. package/bin/runners/runContracts.js +0 -105
  24. package/bin/runners/runCtx.js +0 -675
  25. package/bin/runners/runCtxDiff.js +0 -301
  26. package/bin/runners/runCtxGuard.js +0 -176
  27. package/bin/runners/runCtxSync.js +0 -116
  28. package/bin/runners/runExport.js +0 -93
  29. package/bin/runners/runGraph.js +0 -454
  30. package/bin/runners/runLaunch.js +0 -181
  31. package/bin/runners/runPR.js +0 -255
  32. package/bin/runners/runPermissions.js +0 -304
  33. package/bin/runners/runPreflight.js +0 -580
  34. package/bin/runners/runReplay.js +0 -499
  35. package/bin/runners/runSecurity.js +0 -92
  36. package/bin/runners/runShare.js +0 -212
  37. package/bin/runners/runStatus.js +0 -86
  38. package/bin/runners/runVerify.js +0 -272
@@ -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 items = detection[cat.key] || [];
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
- console.log(BANNER_FULL);
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 < args.length; i++) {
632
- const a = args[i];
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 = args[++i];
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 === "--json") out.json = true;
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
- fs.mkdirSync(dir, { recursive: true });
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
- printSetupStep('.vibecheck/', 'success', 'directory structure created');
721
- filesCreated.push('.vibecheck/');
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
- checks: ["routes", "env", "auth", "security"],
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: { paths: ["node_modules", "__tests__", "*.test.*", "*.spec.*"] },
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
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
743
- printSetupStep('config.json', 'success', 'configuration created');
744
- filesCreated.push('.vibecheck/config.json');
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
- startSpinner('Building truthpack...', colors.accent);
751
- try {
752
- const { runCtx } = require("./runCtx");
753
- await runCtx(["build", "--path", targetDir, "--quiet"]);
754
- stopSpinner('Truthpack generated', true);
755
- filesCreated.push('.vibecheck/truthpack.json');
756
- } catch (e) {
757
- stopSpinner('Truthpack generation failed (will retry on scan)', false);
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
- fs.writeFileSync(routeContractPath, JSON.stringify(routeContract, null, 2));
773
- printSetupStep('routes.contract.json', 'success', 'route contract created');
774
- filesCreated.push('.vibecheck/contracts/routes.contract.json');
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
- fs.writeFileSync(envContractPath, JSON.stringify(envContract, null, 2));
788
- printSetupStep('env.contract.json', 'success', 'env contract created');
789
- filesCreated.push('.vibecheck/contracts/env.contract.json');
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
- startSpinner('Generating IDE rules...', colors.accent);
794
- try {
795
- const { runContext } = require("./runContext");
796
- await runContext(["--path", targetDir, "--quiet"]);
797
- stopSpinner('IDE rules generated', true);
798
- filesCreated.push('.cursor/rules/', '.windsurf/', '.github/copilot-instructions.md');
799
- } catch (e) {
800
- stopSpinner('IDE rules skipped (run vibecheck context later)', false);
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
- fs.writeFileSync(rcPath, JSON.stringify(rc, null, 2));
811
- printSetupStep('.vibecheckrc.json', 'success', 'root config created');
812
- filesCreated.push('.vibecheckrc.json');
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
- gitignore += `\n# vibecheck (generated outputs)\n${additions.join("\n")}\n`;
826
- fs.writeFileSync(gitignorePath, gitignore);
827
- printSetupStep('.gitignore', 'success', 'updated with vibecheck paths');
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
- printBanner();
947
- printEnterpriseHeader(opts);
948
-
949
- console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
950
- console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
951
- console.log();
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
- printBanner();
960
-
961
- console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
962
- console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
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
- printDetectionResults(detection);
971
- console.log();
972
- console.log(` ${c.dim}Full JSON output:${c.reset}`);
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
- if (!opts.json) {
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