@vibecheckai/cli 3.1.5 → 3.1.6

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