@vibecheckai/cli 3.6.1 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +135 -63
  2. package/bin/_deprecations.js +447 -19
  3. package/bin/_router.js +1 -1
  4. package/bin/registry.js +347 -280
  5. package/bin/runners/context/generators/cursor-enhanced.js +2439 -0
  6. package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
  7. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
  8. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
  9. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
  10. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
  11. package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
  12. package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
  13. package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
  14. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
  15. package/bin/runners/lib/agent-firewall/index.js +200 -0
  16. package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
  17. package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
  18. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +622 -0
  19. package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
  20. package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
  21. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
  22. package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
  23. package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
  24. package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
  25. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +31 -38
  26. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +68 -3
  27. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +4 -2
  28. package/bin/runners/lib/agent-firewall/risk/thresholds.js +5 -4
  29. package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
  30. package/bin/runners/lib/agent-firewall/session/index.js +26 -0
  31. package/bin/runners/lib/artifact-envelope.js +540 -0
  32. package/bin/runners/lib/auth-shared.js +977 -0
  33. package/bin/runners/lib/checkpoint.js +941 -0
  34. package/bin/runners/lib/cleanup/engine.js +571 -0
  35. package/bin/runners/lib/cleanup/index.js +53 -0
  36. package/bin/runners/lib/cleanup/output.js +375 -0
  37. package/bin/runners/lib/cleanup/rules.js +1060 -0
  38. package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
  39. package/bin/runners/lib/doctor/failure-signatures.js +526 -0
  40. package/bin/runners/lib/doctor/fix-script.js +336 -0
  41. package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
  42. package/bin/runners/lib/doctor/modules/index.js +62 -3
  43. package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
  44. package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
  45. package/bin/runners/lib/doctor/safe-repair.js +384 -0
  46. package/bin/runners/lib/engines/attack-detector.js +1192 -0
  47. package/bin/runners/lib/entitlements-v2.js +2 -2
  48. package/bin/runners/lib/error-messages.js +1 -1
  49. package/bin/runners/lib/missions/briefing.js +427 -0
  50. package/bin/runners/lib/missions/checkpoint.js +753 -0
  51. package/bin/runners/lib/missions/hardening.js +851 -0
  52. package/bin/runners/lib/missions/plan.js +421 -32
  53. package/bin/runners/lib/missions/safety-gates.js +645 -0
  54. package/bin/runners/lib/missions/schema.js +478 -0
  55. package/bin/runners/lib/packs/bundle.js +675 -0
  56. package/bin/runners/lib/packs/evidence-pack.js +671 -0
  57. package/bin/runners/lib/packs/pack-factory.js +837 -0
  58. package/bin/runners/lib/packs/permissions-pack.js +686 -0
  59. package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
  60. package/bin/runners/lib/report-output.js +6 -6
  61. package/bin/runners/lib/safelist/index.js +96 -0
  62. package/bin/runners/lib/safelist/integration.js +334 -0
  63. package/bin/runners/lib/safelist/matcher.js +696 -0
  64. package/bin/runners/lib/safelist/schema.js +948 -0
  65. package/bin/runners/lib/safelist/store.js +438 -0
  66. package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
  67. package/bin/runners/lib/ship-gate.js +832 -0
  68. package/bin/runners/lib/ship-manifest.js +1153 -0
  69. package/bin/runners/lib/ship-output.js +1 -1
  70. package/bin/runners/lib/unified-cli-output.js +710 -383
  71. package/bin/runners/lib/upsell.js +3 -3
  72. package/bin/runners/lib/why-tree.js +650 -0
  73. package/bin/runners/runAllowlist.js +33 -4
  74. package/bin/runners/runApprove.js +240 -1122
  75. package/bin/runners/runAudit.js +692 -0
  76. package/bin/runners/runAuth.js +325 -29
  77. package/bin/runners/runCheckpoint.js +442 -494
  78. package/bin/runners/runCleanup.js +343 -0
  79. package/bin/runners/runDoctor.js +269 -19
  80. package/bin/runners/runFix.js +411 -32
  81. package/bin/runners/runForge.js +411 -0
  82. package/bin/runners/runIntent.js +906 -0
  83. package/bin/runners/runKickoff.js +878 -0
  84. package/bin/runners/runLaunch.js +2000 -0
  85. package/bin/runners/runLink.js +785 -0
  86. package/bin/runners/runMcp.js +1741 -837
  87. package/bin/runners/runPacks.js +2089 -0
  88. package/bin/runners/runPolish.js +41 -0
  89. package/bin/runners/runSafelist.js +1190 -0
  90. package/bin/runners/runScan.js +21 -9
  91. package/bin/runners/runShield.js +1282 -0
  92. package/bin/runners/runShip.js +395 -16
  93. package/bin/vibecheck.js +34 -6
  94. package/mcp-server/README.md +117 -158
  95. package/mcp-server/handlers/tool-handler.ts +3 -3
  96. package/mcp-server/index.js +16 -0
  97. package/mcp-server/intent-firewall-interceptor.js +529 -0
  98. package/mcp-server/manifest.json +473 -0
  99. package/mcp-server/package.json +1 -1
  100. package/mcp-server/registry/tool-registry.js +315 -523
  101. package/mcp-server/registry/tools.json +442 -428
  102. package/mcp-server/tier-auth.js +164 -16
  103. package/mcp-server/tools-v3.js +70 -16
  104. package/package.json +1 -1
  105. package/bin/runners/runProof.zip +0 -0
@@ -23,7 +23,7 @@ const path = require('path');
23
23
  const fs = require('fs');
24
24
  const { execSync } = require('child_process');
25
25
  const { shipCore } = require('./runShip');
26
- const { planMissions } = require('./lib/missions/plan');
26
+ const { planMissions, getMissionStats } = require('./lib/missions/plan');
27
27
  const { templateForMissionType } = require('./lib/missions/templates');
28
28
  const { expandEvidence } = require('./lib/missions/evidence');
29
29
  const { buildRealityFirewall } = require('./lib/firewall-prompt');
@@ -42,6 +42,45 @@ const upsell = require('./lib/upsell');
42
42
  // Mission Control output formatter
43
43
  const { formatFixOutput } = require('./lib/fix-output');
44
44
 
45
+ // Fix Missions V2 - New components
46
+ const { updateMissionStatus, MISSION_STATUS, isSafeToAutoApply } = require('./lib/missions/schema');
47
+ const {
48
+ createCheckpoint,
49
+ rollbackToCheckpoint,
50
+ rollbackMission,
51
+ markCheckpointApplied,
52
+ markCheckpointFailed,
53
+ listCheckpoints,
54
+ getMissionHistory,
55
+ cleanupOldCheckpoints,
56
+ } = require('./lib/missions/checkpoint');
57
+ const {
58
+ runPreFlightGates,
59
+ runPostFlightGates,
60
+ shouldAutoApply,
61
+ DEFAULT_THRESHOLDS,
62
+ } = require('./lib/missions/safety-gates');
63
+ const {
64
+ formatAllBriefings,
65
+ formatMissionSummary,
66
+ formatMissionResult,
67
+ formatFinalSummary,
68
+ formatMissionBriefing,
69
+ colors: briefingColors,
70
+ ICONS: briefingIcons,
71
+ } = require('./lib/missions/briefing');
72
+ const {
73
+ initAuditTrail,
74
+ getAuditTrail,
75
+ circuitBreakerAllows,
76
+ circuitBreakerSuccess,
77
+ circuitBreakerFailure,
78
+ circuitBreakerReset,
79
+ circuitBreakerStatus,
80
+ safeExecute,
81
+ ExecutionError,
82
+ } = require('./lib/missions/hardening');
83
+
45
84
  // ═══════════════════════════════════════════════════════════════════════════════
46
85
  // ADVANCED TERMINAL - ANSI CODES & UTILITIES
47
86
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -511,11 +550,103 @@ async function runFix(args) {
511
550
  const root = path.resolve(opts.path || process.cwd());
512
551
  const projectName = path.basename(root);
513
552
 
553
+ // ═══════════════════════════════════════════════════════════════════════════════
554
+ // FIX MISSIONS V2 - Initialize hardening components
555
+ // ═══════════════════════════════════════════════════════════════════════════════
556
+
557
+ // Initialize audit trail
558
+ const auditDir = path.join(root, '.vibecheck', 'logs');
559
+ initAuditTrail(auditDir);
560
+ const audit = getAuditTrail();
561
+
562
+ // Generate session ID for tracking
563
+ const sessionId = `fix_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
564
+ process.env.VIBECHECK_SESSION_ID = sessionId;
565
+
566
+ audit.info('fix_session_start', {
567
+ sessionId,
568
+ projectName,
569
+ root,
570
+ args: args.filter(a => !a.includes('key') && !a.includes('token')), // Filter sensitive args
571
+ });
572
+
573
+ // Check circuit breaker
574
+ if (!circuitBreakerAllows()) {
575
+ const status = circuitBreakerStatus();
576
+ console.log(` ${colors.blockRed}${ICONS.stop}${c.reset} Circuit breaker is open - too many recent failures`);
577
+ console.log(` ${c.dim}Failures: ${status.failures}, Last failure: ${new Date(status.lastFailure).toISOString()}${c.reset}`);
578
+ console.log(` ${c.dim}Wait ${Math.ceil((status.resetTimeout - (Date.now() - status.lastFailure)) / 1000)}s or use --force to override${c.reset}`);
579
+
580
+ if (!opts.force) {
581
+ audit.warn('circuit_breaker_blocked', { status });
582
+ return EXIT.BLOCKING;
583
+ }
584
+
585
+ audit.warn('circuit_breaker_override', { status });
586
+ circuitBreakerReset();
587
+ }
588
+
514
589
  // Print banner
515
590
  printBanner();
516
591
  console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
517
592
  console.log(` ${c.dim}Path:${c.reset} ${root}`);
593
+ console.log(` ${c.dim}Session:${c.reset} ${c.dim}${sessionId}${c.reset}`);
518
594
  console.log();
595
+
596
+ // ═══════════════════════════════════════════════════════════════════════════════
597
+ // FIX MISSIONS V2 - Handle special modes first
598
+ // ═══════════════════════════════════════════════════════════════════════════════
599
+
600
+ // Handle --list-checkpoints
601
+ if (opts.listCheckpoints) {
602
+ const checkpoints = listCheckpoints(root);
603
+ if (checkpoints.length === 0) {
604
+ console.log(` ${c.dim}No checkpoints found${c.reset}`);
605
+ } else {
606
+ console.log(` ${colors.accent}${ICONS.mission}${c.reset} ${c.bold}CHECKPOINTS${c.reset} ${c.dim}(${checkpoints.length})${c.reset}`);
607
+ console.log();
608
+ for (const cp of checkpoints.slice(0, 10)) {
609
+ const age = Math.round((Date.now() - new Date(cp.createdAt).getTime()) / 1000 / 60);
610
+ console.log(` ${ICONS.check} ${cp.id}`);
611
+ console.log(` ${c.dim}Mission: ${cp.missionType} • ${cp.files} files • ${age}m ago${c.reset}`);
612
+ }
613
+ if (checkpoints.length > 10) {
614
+ console.log(` ${c.dim}... and ${checkpoints.length - 10} more${c.reset}`);
615
+ }
616
+ }
617
+ console.log();
618
+ return 0;
619
+ }
620
+
621
+ // Handle --cleanup-checkpoints
622
+ if (opts.cleanupCheckpoints) {
623
+ startSpinner('Cleaning up old checkpoints...', 'dots');
624
+ const result = cleanupOldCheckpoints(root);
625
+ stopSpinner(`Cleaned up ${result.count} checkpoints`, true);
626
+ return 0;
627
+ }
628
+
629
+ // Handle --rollback
630
+ if (opts.rollback) {
631
+ const missionId = opts.rollback;
632
+ console.log(` ${colors.rollback}${ICONS.rollback}${c.reset} Rolling back mission: ${c.bold}${missionId}${c.reset}`);
633
+
634
+ const result = rollbackMission(root, missionId);
635
+
636
+ if (result.ok) {
637
+ console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} Rollback successful`);
638
+ if (result.restored.length > 0) {
639
+ console.log(` ${c.dim}Restored: ${result.restored.length} files${c.reset}`);
640
+ }
641
+ if (result.deleted.length > 0) {
642
+ console.log(` ${c.dim}Deleted: ${result.deleted.length} files${c.reset}`);
643
+ }
644
+ return 0;
645
+ } else {
646
+ console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} Rollback failed: ${result.error}`);
647
+ return EXIT.INTERNAL_ERROR;
648
+ }
649
+ }
519
650
 
520
651
  // TIER ENFORCEMENT: Check if --apply is allowed
521
652
  if (opts.apply || opts.autopilot) {
@@ -558,14 +689,65 @@ async function runFix(args) {
558
689
  return 0;
559
690
  }
560
691
 
561
- // Plan missions
692
+ // Plan missions with enhanced options
562
693
  startSpinner('Planning fix missions...', 'gears');
563
- const missions = planMissions(first.report.findings, { maxMissions: opts.maxMissions || 8 });
564
- fs.writeFileSync(path.join(outDir, "missions.json"), JSON.stringify({ missions, fromVerdict: first.verdict }, null, 2));
565
- stopSpinner(`Planned ${missions.length} missions`, true);
694
+ const missions = planMissions(first.report.findings, {
695
+ maxMissions: opts.maxMissions || 8,
696
+ truthpack: first.truthpack,
697
+ maxBlastRadius: opts.maxBlast,
698
+ minConfidence: opts.minConfidence,
699
+ });
700
+
701
+ // Filter to specific mission if requested
702
+ let targetMissions = missions;
703
+ if (opts.mission) {
704
+ targetMissions = missions.filter(m => m.id === opts.mission || m.id.includes(opts.mission));
705
+ if (targetMissions.length === 0) {
706
+ stopSpinner('Mission not found', false);
707
+ console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} No mission found with ID: ${opts.mission}`);
708
+ console.log(` ${c.dim}Available missions:${c.reset}`);
709
+ for (const m of missions.slice(0, 5)) {
710
+ console.log(` ${c.dim}${m.id} - ${m.type}${c.reset}`);
711
+ }
712
+ return EXIT.BLOCKING;
713
+ }
714
+ }
715
+
716
+ fs.writeFileSync(path.join(outDir, "missions.json"), JSON.stringify({ missions: targetMissions, fromVerdict: first.verdict }, null, 2));
717
+ stopSpinner(`Planned ${targetMissions.length} missions`, true);
718
+
719
+ // Get mission stats
720
+ const stats = getMissionStats(targetMissions);
721
+
722
+ // ═══════════════════════════════════════════════════════════════════════════════
723
+ // FIX MISSIONS V2 - Plan-only mode with full briefings
724
+ // ═══════════════════════════════════════════════════════════════════════════════
725
+
726
+ if (opts.planOnly) {
727
+ // Show full mission briefings
728
+ const gateOptions = {
729
+ minConfidence: opts.minConfidence,
730
+ maxBlastRadius: opts.maxBlast,
731
+ force: opts.force,
732
+ allowDirty: opts.allowDirty,
733
+ };
734
+
735
+ console.log(formatAllBriefings(root, targetMissions, gateOptions));
736
+
737
+ // Save briefings to file
738
+ fs.writeFileSync(
739
+ path.join(outDir, "briefings.txt"),
740
+ formatAllBriefings(root, targetMissions, gateOptions).replace(/\x1b\[[0-9;]*m/g, ''),
741
+ "utf8"
742
+ );
743
+ console.log(` ${c.dim}${ICONS.folder} Briefings saved: ${colors.accent}${path.relative(root, path.join(outDir, "briefings.txt"))}${c.reset}`);
744
+ console.log();
745
+
746
+ return 0;
747
+ }
566
748
 
567
749
  // Show mission summary
568
- printMissionSummary(missions);
750
+ printMissionSummary(targetMissions);
569
751
  console.log();
570
752
  console.log(` ${c.dim}${ICONS.folder} Mission pack: ${colors.accent}${path.relative(root, outDir)}${c.reset}`);
571
753
  console.log();
@@ -575,8 +757,11 @@ async function runFix(args) {
575
757
  console.log();
576
758
  console.log(` ${c.bold}Choose a mode:${c.reset}`);
577
759
  console.log();
760
+ console.log(` ${colors.accent}${ICONS.prompt}${c.reset} ${c.bold}vibecheck fix --plan-only${c.reset}`);
761
+ console.log(` ${c.dim}Full mission briefings with safety gates ${c.green}(FREE)${c.reset}${c.dim}${c.reset}`);
762
+ console.log();
578
763
  console.log(` ${colors.accent}${ICONS.prompt}${c.reset} ${c.bold}vibecheck fix --prompt-only${c.reset}`);
579
- console.log(` ${c.dim}Generate perfect prompts, no edits ${c.green}(FREE)${c.reset}${c.dim}${c.reset}`);
764
+ console.log(` ${c.dim}Generate LLM prompts, no edits ${c.green}(FREE)${c.reset}${c.dim}${c.reset}`);
580
765
  console.log();
581
766
 
582
767
  // Check if apply features are available
@@ -615,6 +800,15 @@ async function runFix(args) {
615
800
  printLoopHeader(maxSteps, stagnationLimit);
616
801
  }
617
802
 
803
+ // Track execution summary for V2
804
+ const executionSummary = {
805
+ total: targetMissions.length,
806
+ completed: 0,
807
+ failed: 0,
808
+ rolledBack: 0,
809
+ skipped: 0,
810
+ };
811
+
618
812
  for (let step = 1; step <= maxSteps; step++) {
619
813
  const before = await shipCore({ repoRoot: root, fastifyEntry: opts.fastifyEntry, noWrite: false });
620
814
 
@@ -635,12 +829,12 @@ async function runFix(args) {
635
829
  // Mission Control output
636
830
  if (!opts.json && !opts.quiet) {
637
831
  console.log(formatFixOutput({
638
- missions,
832
+ missions: targetMissions,
639
833
  verdict: before.verdict,
640
834
  beforeVerdict: first.verdict,
641
835
  afterVerdict: before.verdict,
642
836
  appliedMissions: step - 1,
643
- totalMissions: missions.length,
837
+ totalMissions: targetMissions.length,
644
838
  duration,
645
839
  success: true,
646
840
  }, { apply: opts.apply, promptOnly: opts.promptOnly, loop: opts.loop || opts.autopilot }));
@@ -650,7 +844,7 @@ async function runFix(args) {
650
844
  }
651
845
 
652
846
  const ids = new Set(before.report.findings.map(f => f.id));
653
- const mission = missions.find(m => m.targetFindingIds.some(id => ids.has(id)));
847
+ const mission = targetMissions.find(m => m.objective?.targetFindingIds?.some(id => ids.has(id)) || m.targetFindingIds?.some(id => ids.has(id)));
654
848
  if (!mission) {
655
849
  printSection('COMPLETE', ICONS.check);
656
850
  console.log();
@@ -660,6 +854,28 @@ async function runFix(args) {
660
854
  return 0;
661
855
  }
662
856
 
857
+ // ═══════════════════════════════════════════════════════════════════════════════
858
+ // FIX MISSIONS V2 - Pre-flight safety gates
859
+ // ═══════════════════════════════════════════════════════════════════════════════
860
+
861
+ const gateOptions = {
862
+ minConfidence: opts.minConfidence,
863
+ maxBlastRadius: opts.maxBlast,
864
+ force: opts.force,
865
+ allowDirty: opts.allowDirty,
866
+ };
867
+
868
+ const preFlightResults = runPreFlightGates(root, mission, gateOptions);
869
+
870
+ if (!preFlightResults.ok && !opts.force) {
871
+ console.log(` ${colors.warnAmber}${ICONS.warning}${c.reset} Mission ${mission.id} skipped: pre-flight gates failed`);
872
+ for (const result of preFlightResults.results.filter(r => !r.pass)) {
873
+ console.log(` ${c.dim}${ICONS.cross} ${result.gate}: ${result.reason}${c.reset}`);
874
+ }
875
+ executionSummary.skipped++;
876
+ continue;
877
+ }
878
+
663
879
  // Save before proof
664
880
  fs.writeFileSync(
665
881
  path.join(outDir, `step_${String(step).padStart(2,"0")}_${mission.id}_before_ship.json`),
@@ -667,7 +883,8 @@ async function runFix(args) {
667
883
  "utf8"
668
884
  );
669
885
 
670
- const targetFindings = before.report.findings.filter(f => mission.targetFindingIds.includes(f.id));
886
+ const targetFindingIds = mission.objective?.targetFindingIds || mission.targetFindingIds || [];
887
+ const targetFindings = before.report.findings.filter(f => targetFindingIds.includes(f.id));
671
888
 
672
889
  // Show mission card
673
890
  printMissionCard(mission, step, maxSteps, 'running');
@@ -753,28 +970,60 @@ async function runFix(args) {
753
970
  return 0;
754
971
  }
755
972
 
756
- // Apply patch
973
+ // ═══════════════════════════════════════════════════════════════════════════════
974
+ // FIX MISSIONS V2 - Apply patch with checkpoint-based rollback
975
+ // ═══════════════════════════════════════════════════════════════════════════════
976
+
977
+ // Determine files to touch
757
978
  const touchedFiles = new Set();
758
979
  for (const ed of patchJson.edits) {
759
980
  for (const f of parseDiffTouchedFiles(ed.diff)) touchedFiles.add(f);
760
981
  }
761
982
  const touchedList = Array.from(touchedFiles);
762
983
 
763
- const backupRoot = path.join(outDir, `backup_step_${String(step).padStart(2,"0")}`);
764
- backupFiles(root, touchedList, backupRoot);
984
+ // Create checkpoint before applying (V2)
985
+ let checkpoint;
986
+ try {
987
+ checkpoint = createCheckpoint(root, mission, touchedList);
988
+ mission.safety = mission.safety || {};
989
+ mission.safety.checkpointId = checkpoint.id;
990
+ console.log(` ${briefingColors.info}${briefingIcons.checkpoint}${c.reset} Checkpoint: ${c.dim}${checkpoint.id}${c.reset}`);
991
+ } catch (e) {
992
+ // Fall back to simple backup
993
+ const backupRoot = path.join(outDir, `backup_step_${String(step).padStart(2,"0")}`);
994
+ backupFiles(root, touchedList, backupRoot);
995
+ checkpoint = { id: null, backupRoot };
996
+ }
765
997
 
998
+ // Apply patches
999
+ let applyFailed = false;
766
1000
  for (const ed of patchJson.edits) {
767
1001
  const res = applyUnifiedDiff(root, ed.diff);
768
1002
  if (!res.ok) {
769
1003
  console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} Patch apply failed: ${res.error}`);
770
- restoreBackup(root, backupRoot);
771
- console.log(` ${colors.rollback}${ICONS.rollback}${c.reset} Restored from backup`);
772
- return EXIT.INTERNAL_ERROR;
1004
+ applyFailed = true;
1005
+ break;
773
1006
  }
774
1007
  console.log(` ${colors.patch}${ICONS.patch}${c.reset} Applied: ${c.dim}${ed.path}${c.reset}`);
775
1008
  }
776
1009
 
777
- // Verify
1010
+ // Handle apply failure - rollback
1011
+ if (applyFailed) {
1012
+ if (checkpoint.id) {
1013
+ const rollbackResult = rollbackToCheckpoint(root, checkpoint.id);
1014
+ markCheckpointFailed(root, checkpoint.id, 'Patch apply failed');
1015
+ console.log(` ${colors.rollback}${ICONS.rollback}${c.reset} Rolled back via checkpoint`);
1016
+ } else if (checkpoint.backupRoot) {
1017
+ restoreBackup(root, checkpoint.backupRoot);
1018
+ console.log(` ${colors.rollback}${ICONS.rollback}${c.reset} Restored from backup`);
1019
+ }
1020
+ executionSummary.failed++;
1021
+ circuitBreakerFailure();
1022
+ audit.error('mission_apply_failed', { missionId: mission.id, step });
1023
+ return EXIT.INTERNAL_ERROR;
1024
+ }
1025
+
1026
+ // Verify fix
778
1027
  startSpinner('Verifying fix...', 'dots');
779
1028
  const after = await shipCore({ repoRoot: root, fastifyEntry: opts.fastifyEntry, noWrite: false });
780
1029
 
@@ -790,29 +1039,101 @@ async function runFix(args) {
790
1039
  "utf8"
791
1040
  );
792
1041
 
1042
+ // ═══════════════════════════════════════════════════════════════════════════════
1043
+ // FIX MISSIONS V2 - Post-flight safety gates
1044
+ // ═══════════════════════════════════════════════════════════════════════════════
1045
+
1046
+ const postFlightResults = runPostFlightGates(root, mission, before, after, {
1047
+ runTests: !opts.skipTests,
1048
+ testCommand: opts.testCommand,
1049
+ });
1050
+
793
1051
  const beforeScore = score(before.report.findings);
794
1052
  const afterScore = score(after.report.findings);
795
- const targetStillThere = mission.targetFindingIds.some(id => after.report.findings.some(f => f.id === id));
796
- const improved = (afterScore < beforeScore) || (!targetStillThere);
1053
+ const targetStillThere = targetFindingIds.some(id => after.report.findings.some(f => f.id === id));
1054
+ const improved = (afterScore < beforeScore) || (!targetStillThere) || postFlightResults.ok;
797
1055
 
798
- if (!improved) {
799
- console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} No measurable progress`);
800
- restoreBackup(root, backupRoot);
801
- console.log(` ${colors.rollback}${ICONS.rollback}${c.reset} Restored from backup`);
1056
+ if (!improved || postFlightResults.shouldRollback) {
1057
+ console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} ${postFlightResults.shouldRollback ? 'Post-flight gates failed' : 'No measurable progress'}`);
1058
+
1059
+ // Show failed gates
1060
+ for (const result of postFlightResults.results.filter(r => !r.pass)) {
1061
+ console.log(` ${c.dim}${ICONS.cross} ${result.gate}: ${result.reason}${c.reset}`);
1062
+ }
1063
+
1064
+ // Rollback
1065
+ if (checkpoint.id) {
1066
+ const rollbackResult = rollbackToCheckpoint(root, checkpoint.id);
1067
+ console.log(` ${colors.rollback}${ICONS.rollback}${c.reset} Rolled back via checkpoint`);
1068
+ } else if (checkpoint.backupRoot) {
1069
+ restoreBackup(root, checkpoint.backupRoot);
1070
+ console.log(` ${colors.rollback}${ICONS.rollback}${c.reset} Restored from backup`);
1071
+ }
1072
+
1073
+ executionSummary.rolledBack++;
1074
+ circuitBreakerFailure();
1075
+ audit.warn('mission_rolled_back', {
1076
+ missionId: mission.id,
1077
+ step,
1078
+ reason: postFlightResults.shouldRollback ? 'post_flight_failed' : 'no_progress',
1079
+ failedGates: postFlightResults.results.filter(r => !r.pass).map(r => r.gate),
1080
+ });
1081
+
802
1082
  stagnant += 1;
1083
+
803
1084
  if (!opts.autopilot || stagnant >= stagnationLimit) {
804
1085
  printSection('STAGNATION', ICONS.stop);
805
1086
  console.log();
806
1087
  console.log(` ${colors.blockRed}${ICONS.stop}${c.reset} Stopping: stagnation limit reached (${stagnant}/${stagnationLimit})`);
807
1088
  console.log();
1089
+
1090
+ // Show final summary
1091
+ executionSummary.verdict = after.verdict;
1092
+ executionSummary.duration = Date.now() - startTime;
1093
+ console.log(formatFinalSummary(executionSummary));
1094
+
1095
+ audit.error('fix_session_stagnation', {
1096
+ sessionId,
1097
+ stagnant,
1098
+ stagnationLimit,
1099
+ duration: Date.now() - startTime,
1100
+ });
1101
+
808
1102
  return EXIT.BLOCKING;
809
1103
  }
810
1104
  continue;
811
1105
  }
812
1106
 
1107
+ // Success - mark checkpoint as applied
1108
+ if (checkpoint.id) {
1109
+ markCheckpointApplied(root, checkpoint.id);
1110
+ }
1111
+
813
1112
  console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} Progress confirmed`);
1113
+ console.log(formatMissionResult(mission, {
1114
+ success: true,
1115
+ findings: { before: beforeScore, after: afterScore }
1116
+ }));
1117
+
1118
+ executionSummary.completed++;
1119
+ circuitBreakerSuccess();
1120
+ audit.info('mission_completed', {
1121
+ missionId: mission.id,
1122
+ step,
1123
+ beforeScore,
1124
+ afterScore,
1125
+ improvement: beforeScore - afterScore,
1126
+ });
1127
+
814
1128
  stagnant = 0;
815
- if (!opts.autopilot) return 0;
1129
+ if (!opts.autopilot) {
1130
+ audit.info('fix_session_end', {
1131
+ sessionId,
1132
+ duration: Date.now() - startTime,
1133
+ result: 'success_single_mission',
1134
+ });
1135
+ return 0;
1136
+ }
816
1137
  }
817
1138
 
818
1139
  // Max steps reached
@@ -833,17 +1154,32 @@ async function runFix(args) {
833
1154
  // Mission Control output
834
1155
  if (!opts.json && !opts.quiet) {
835
1156
  console.log(formatFixOutput({
836
- missions,
1157
+ missions: targetMissions,
837
1158
  verdict: lastShip.verdict,
838
1159
  beforeVerdict: first.verdict,
839
1160
  afterVerdict: lastShip.verdict,
840
1161
  appliedMissions: maxSteps,
841
- totalMissions: missions.length,
1162
+ totalMissions: targetMissions.length,
842
1163
  duration,
843
1164
  success: false,
844
1165
  }, { apply: opts.apply, promptOnly: opts.promptOnly, loop: opts.loop || opts.autopilot }));
1166
+
1167
+ // V2 Final summary
1168
+ executionSummary.verdict = lastShip.verdict;
1169
+ executionSummary.duration = duration;
1170
+ console.log(formatFinalSummary(executionSummary));
845
1171
  }
846
1172
 
1173
+ // Flush audit trail
1174
+ audit.info('fix_session_end', {
1175
+ sessionId,
1176
+ duration,
1177
+ result: 'max_steps_reached',
1178
+ executionSummary,
1179
+ verdict: lastShip.verdict,
1180
+ });
1181
+ audit.flush();
1182
+
847
1183
  return EXIT.WARNINGS; // Max steps reached, incomplete
848
1184
  }
849
1185
 
@@ -865,12 +1201,23 @@ function parseArgs(args) {
865
1201
  path: globalFlags.path || process.cwd(),
866
1202
  apply: false,
867
1203
  promptOnly: false,
1204
+ planOnly: false,
868
1205
  loop: false,
869
1206
  share: false,
870
1207
  maxMissions: 8,
871
1208
  maxSteps: 10,
872
1209
  stagnationLimit: 2,
873
1210
  fastifyEntry: null,
1211
+ // Fix Missions V2 - New options
1212
+ rollback: null, // Mission ID to rollback
1213
+ mission: null, // Specific mission ID to run
1214
+ force: false, // Override safety gates
1215
+ minConfidence: DEFAULT_THRESHOLDS.minConfidence,
1216
+ maxBlast: DEFAULT_THRESHOLDS.maxBlastRadius,
1217
+ allowDirty: false, // Allow uncommitted changes
1218
+ skipTests: true, // Skip post-flight tests
1219
+ listCheckpoints: false,
1220
+ cleanupCheckpoints: false,
874
1221
  // Note: help comes from globalFlags, don't override it here
875
1222
  };
876
1223
 
@@ -879,6 +1226,7 @@ function parseArgs(args) {
879
1226
  const arg = cleanArgs[i];
880
1227
  if (arg === '--apply') opts.apply = true;
881
1228
  else if (arg === '--prompt-only') opts.promptOnly = true;
1229
+ else if (arg === '--plan-only') opts.planOnly = true;
882
1230
  else if (arg === '--autopilot') opts.autopilot = true;
883
1231
  else if (arg === '--share') opts.share = true;
884
1232
  else if (arg === '--max-missions') opts.maxMissions = Number(cleanArgs[++i]) || 8;
@@ -887,6 +1235,16 @@ function parseArgs(args) {
887
1235
  else if (arg === '--fastify-entry') opts.fastifyEntry = cleanArgs[++i];
888
1236
  else if (arg === '--path' || arg === '-p') opts.path = cleanArgs[++i];
889
1237
  else if (arg === '--help' || arg === '-h') opts.help = true;
1238
+ // Fix Missions V2 - New flags
1239
+ else if (arg === '--rollback') opts.rollback = cleanArgs[++i];
1240
+ else if (arg === '--mission') opts.mission = cleanArgs[++i];
1241
+ else if (arg === '--force') opts.force = true;
1242
+ else if (arg === '--min-confidence') opts.minConfidence = Number(cleanArgs[++i]) || DEFAULT_THRESHOLDS.minConfidence;
1243
+ else if (arg === '--max-blast') opts.maxBlast = Number(cleanArgs[++i]) || DEFAULT_THRESHOLDS.maxBlastRadius;
1244
+ else if (arg === '--allow-dirty') opts.allowDirty = true;
1245
+ else if (arg === '--run-tests') opts.skipTests = false;
1246
+ else if (arg === '--list-checkpoints') opts.listCheckpoints = true;
1247
+ else if (arg === '--cleanup-checkpoints') opts.cleanupCheckpoints = true;
890
1248
  }
891
1249
 
892
1250
  return opts;
@@ -899,22 +1257,43 @@ function printHelp(showBanner = true) {
899
1257
  console.log(`
900
1258
  ${c.bold}Usage:${c.reset} vibecheck fix (f) [options]
901
1259
 
902
- ${c.bold}Mission Control${c.reset} — Generate surgical fix prompts, apply patches, verify with ship.
1260
+ ${c.bold}Mission Control V2${c.reset} — "Missions, not chaos" - Surgical fixes with safety gates.
903
1261
 
904
1262
  ${c.bold}Modes:${c.reset}
905
1263
  ${colors.accent}vibecheck fix${c.reset} ${c.dim}Plan missions (no changes) ${c.green}(FREE)${c.reset}${c.dim}${c.reset}
906
- ${colors.accent}vibecheck fix --prompt-only${c.reset} ${c.dim}Generate perfect prompts ${c.green}(FREE)${c.reset}${c.dim}${c.reset}
1264
+ ${colors.accent}vibecheck fix --plan-only${c.reset} ${c.dim}Full mission briefings ${c.green}(FREE)${c.reset}${c.dim}${c.reset}
1265
+ ${colors.accent}vibecheck fix --prompt-only${c.reset} ${c.dim}Generate LLM prompts ${c.green}(FREE)${c.reset}${c.dim}${c.reset}
907
1266
  ${colors.accent}vibecheck fix --apply${c.reset} ${c.dim}Apply patches from LLM ${c.yellow}(PRO)${c.reset}${c.dim}${c.reset}
908
- ${colors.accent}vibecheck fix --loop --apply${c.reset} ${c.dim}Loop until SHIP or stuck ${c.yellow}(PRO)${c.reset}${c.dim}${c.reset}
1267
+ ${colors.accent}vibecheck fix --autopilot --apply${c.reset} ${c.dim}Loop until SHIP or stuck ${c.yellow}(PRO)${c.reset}${c.dim}${c.reset}
909
1268
 
910
- ${c.bold}Options:${c.reset}
1269
+ ${c.bold}Core Options:${c.reset}
1270
+ ${colors.accent}--plan-only${c.reset} Show full mission briefings with safety gates
911
1271
  ${colors.accent}--prompt-only${c.reset} Generate mission prompts only ${c.dim}(no edits)${c.reset}
912
1272
  ${colors.accent}--apply${c.reset} Apply patches returned by the model ${c.dim}(PRO tier)${c.reset}
913
- ${colors.accent}--loop${c.reset} Loop: fix → verify → fix until SHIP ${c.dim}(PRO tier)${c.reset}
1273
+ ${colors.accent}--autopilot${c.reset} Loop: fix → verify → fix until SHIP ${c.dim}(PRO tier)${c.reset}
914
1274
  ${colors.accent}--share${c.reset} Generate share bundle after fix ${c.dim}(PRO tier)${c.reset}
1275
+
1276
+ ${c.bold}Mission Selection:${c.reset}
1277
+ ${colors.accent}--mission <id>${c.reset} Run specific mission only (e.g., M_abc123)
915
1278
  ${colors.accent}--max-missions <n>${c.reset} Max missions to plan ${c.dim}(default: 8)${c.reset}
1279
+
1280
+ ${c.bold}Safety Gates (V2):${c.reset}
1281
+ ${colors.accent}--force${c.reset} Override safety gates for critical missions
1282
+ ${colors.accent}--min-confidence <n>${c.reset} Min confidence threshold ${c.dim}(default: 0.6)${c.reset}
1283
+ ${colors.accent}--max-blast <n>${c.reset} Max files per mission ${c.dim}(default: 10)${c.reset}
1284
+ ${colors.accent}--allow-dirty${c.reset} Allow uncommitted git changes
1285
+ ${colors.accent}--run-tests${c.reset} Run tests in post-flight gates
1286
+
1287
+ ${c.bold}Rollback & Checkpoints (V2):${c.reset}
1288
+ ${colors.accent}--rollback <id>${c.reset} Rollback a mission by ID
1289
+ ${colors.accent}--list-checkpoints${c.reset} List all checkpoints
1290
+ ${colors.accent}--cleanup-checkpoints${c.reset} Clean up old checkpoints
1291
+
1292
+ ${c.bold}Autopilot Options:${c.reset}
916
1293
  ${colors.accent}--max-steps <n>${c.reset} Max autopilot steps ${c.dim}(default: 10)${c.reset}
917
1294
  ${colors.accent}--stagnation-limit${c.reset} Stop after N non-improving steps ${c.dim}(default: 2)${c.reset}
1295
+
1296
+ ${c.bold}Other Options:${c.reset}
918
1297
  ${colors.accent}--fastify-entry${c.reset} Fastify entry file ${c.dim}(e.g. src/server.ts)${c.reset}
919
1298
  ${colors.accent}--path, -p${c.reset} Project path ${c.dim}(default: current directory)${c.reset}
920
1299
  ${colors.accent}--help, -h${c.reset} Show this help