@vibecheckai/cli 3.1.8 → 3.2.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 (36) hide show
  1. package/bin/registry.js +106 -116
  2. package/bin/runners/context/generators/mcp.js +18 -0
  3. package/bin/runners/context/index.js +72 -4
  4. package/bin/runners/context/proof-context.js +293 -1
  5. package/bin/runners/context/security-scanner.js +311 -73
  6. package/bin/runners/lib/analyzers.js +607 -20
  7. package/bin/runners/lib/detectors-v2.js +172 -15
  8. package/bin/runners/lib/entitlements-v2.js +48 -1
  9. package/bin/runners/lib/evidence-pack.js +678 -0
  10. package/bin/runners/lib/html-proof-report.js +913 -0
  11. package/bin/runners/lib/missions/plan.js +231 -41
  12. package/bin/runners/lib/missions/templates.js +125 -0
  13. package/bin/runners/lib/scan-output.js +492 -253
  14. package/bin/runners/lib/ship-output.js +901 -641
  15. package/bin/runners/runCheckpoint.js +44 -3
  16. package/bin/runners/runContext.d.ts +4 -0
  17. package/bin/runners/runDoctor.js +10 -2
  18. package/bin/runners/runFix.js +51 -341
  19. package/bin/runners/runInit.js +11 -0
  20. package/bin/runners/runPolish.d.ts +4 -0
  21. package/bin/runners/runPolish.js +608 -29
  22. package/bin/runners/runProve.js +210 -25
  23. package/bin/runners/runReality.js +846 -101
  24. package/bin/runners/runScan.js +238 -4
  25. package/bin/runners/runShip.js +19 -3
  26. package/bin/runners/runWatch.js +14 -1
  27. package/bin/vibecheck.js +32 -2
  28. package/mcp-server/consolidated-tools.js +408 -42
  29. package/mcp-server/index.js +152 -15
  30. package/mcp-server/proof-tools.js +571 -0
  31. package/mcp-server/tier-auth.js +22 -19
  32. package/mcp-server/tools-v3.js +744 -0
  33. package/mcp-server/truth-firewall-tools.js +190 -4
  34. package/package.json +3 -1
  35. package/bin/runners/runInstall.js +0 -281
  36. package/bin/runners/runLabs.js +0 -341
@@ -213,6 +213,8 @@ function parseArgs(args) {
213
213
  current: null,
214
214
  hallucination: false,
215
215
  save: false,
216
+ gate: false, // Exit non-zero on regressions (for CI)
217
+ preCommit: false, // Pre-commit mode - fast check with minimal output
216
218
  path: process.cwd(),
217
219
  };
218
220
 
@@ -224,6 +226,8 @@ function parseArgs(args) {
224
226
  else if (arg === '--current' || arg === '-c') opts.current = args[++i];
225
227
  else if (arg === '--hallucination' || arg === '--hall') opts.hallucination = true;
226
228
  else if (arg === '--save' || arg === '-s') opts.save = true;
229
+ else if (arg === '--gate') opts.gate = true;
230
+ else if (arg === '--pre-commit') opts.preCommit = true;
227
231
  else if (arg === '--path' || arg === '-p') opts.path = args[++i];
228
232
  else if (!arg.startsWith('-')) opts.path = arg;
229
233
  }
@@ -245,15 +249,22 @@ ${c.bold}OPTIONS${c.reset}
245
249
  -c, --current <file> Path to current results file (default: latest)
246
250
  --hallucination Include hallucination scoring ${c.magenta}[PRO]${c.reset}
247
251
  -s, --save Save checkpoint to .vibecheck/checkpoints/
252
+ --gate Exit non-zero on regressions (for CI)
253
+ --pre-commit Pre-commit mode - fast check with minimal output
248
254
  --json Output as JSON
249
255
  -p, --path <dir> Project path (default: current directory)
250
256
  -h, --help Show this help
251
257
 
258
+ ${c.bold}CI INTEGRATION${c.reset}
259
+ vibecheck checkpoint --gate # Fail CI on regressions
260
+ vibecheck checkpoint --pre-commit # Fast check for git hooks
261
+
252
262
  ${c.bold}EXAMPLES${c.reset}
253
263
  vibecheck checkpoint # Compare latest vs baseline
254
264
  vibecheck checkpoint --baseline scan-v1.json # Use specific baseline
255
265
  vibecheck checkpoint --hallucination # Include AI hallucination check
256
266
  vibecheck checkpoint --save # Save checkpoint report
267
+ vibecheck checkpoint --gate --json # CI: exit 1 on regressions
257
268
 
258
269
  ${c.bold}OUTPUT${c.reset}
259
270
  • Fixed issues - Issues resolved since baseline
@@ -261,6 +272,10 @@ ${c.bold}OUTPUT${c.reset}
261
272
  • Regressions - New issues introduced
262
273
  • Hallucination score - AI-generated code issues ${c.magenta}[PRO]${c.reset}
263
274
 
275
+ ${c.bold}EXIT CODES${c.reset}
276
+ 0 No regressions (or --gate not specified)
277
+ 1 Regressions detected (with --gate flag)
278
+
264
279
  ${c.bold}TIER${c.reset}
265
280
  ${c.cyan}FREE${c.reset} Basic comparison (fixed, remaining, regressions)
266
281
  ${c.magenta}PRO${c.reset} + Hallucination scoring
@@ -382,10 +397,29 @@ async function runCheckpoint(args) {
382
397
  fs.writeFileSync(checkpointFile, JSON.stringify(checkpoint, null, 2));
383
398
  }
384
399
 
400
+ // Pre-commit mode - minimal output for git hooks
401
+ if (opts.preCommit) {
402
+ const hasRegressions = checkpoint.summary.regressions > 0;
403
+ if (hasRegressions) {
404
+ console.log(`${c.red}${icons.cross}${c.reset} Checkpoint: ${checkpoint.summary.regressions} regression(s) detected`);
405
+ for (const r of comparison.regressions.slice(0, 3)) {
406
+ console.log(` ${c.dim}${r.title || r.id || r.type}${c.reset}`);
407
+ }
408
+ if (comparison.regressions.length > 3) {
409
+ console.log(` ${c.dim}... and ${comparison.regressions.length - 3} more${c.reset}`);
410
+ }
411
+ return 1;
412
+ } else {
413
+ console.log(`${c.green}${icons.check}${c.reset} Checkpoint passed (${checkpoint.summary.fixed} fixed, ${checkpoint.summary.remaining} remaining)`);
414
+ return 0;
415
+ }
416
+ }
417
+
385
418
  // JSON output
386
419
  if (opts.json) {
387
420
  console.log(JSON.stringify(checkpoint, null, 2));
388
- return checkpoint.summary.regressions > 0 ? 1 : 0;
421
+ const exitCode = opts.gate && checkpoint.summary.regressions > 0 ? 1 : 0;
422
+ return exitCode;
389
423
  }
390
424
 
391
425
  // Human-readable output
@@ -496,8 +530,15 @@ ${c.bold}╔══════════════════════
496
530
  }
497
531
  console.log();
498
532
 
499
- // Return code: 1 if regressions, 0 otherwise
500
- return comparison.regressions.length > 0 ? 1 : 0;
533
+ // Return code:
534
+ // - With --gate: 1 if regressions, 0 otherwise
535
+ // - Without --gate: always 0 (informational only)
536
+ if (opts.gate && comparison.regressions.length > 0) {
537
+ console.log(`${c.yellow}${icons.warning} Gate mode: Exiting with code 1 due to regressions${c.reset}\n`);
538
+ return 1;
539
+ }
540
+
541
+ return 0;
501
542
  }
502
543
 
503
544
  module.exports = { runCheckpoint };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Type declarations for runContext.js
3
+ */
4
+ export function runContext(args: string[]): Promise<number>;
@@ -574,10 +574,18 @@ function runDoctorLegacy() {
574
574
 
575
575
  console.log("");
576
576
  if (hasIssues) {
577
- console.log(" ❌ Issues found. Fix them and run doctor again.\n");
577
+ console.log(" ❌ Issues found. Fix them and run doctor again.");
578
+ console.log();
579
+ console.log(` ${c.dim}Need help?${c.reset} ${colors.accent}vibecheck fix${c.reset} ${c.dim}can auto-repair many issues${c.reset}`);
580
+ console.log(` ${c.dim}Requires STARTER plan • vibecheck.dev/pricing${c.reset}`);
581
+ console.log();
578
582
  return 2;
579
583
  } else {
580
- console.log(" ✅ Environment healthy!\n");
584
+ console.log(" ✅ Environment healthy!");
585
+ console.log();
586
+ console.log(` ${c.dim}Ready to scan?${c.reset} ${colors.accent}vibecheck scan${c.reset} ${c.dim}finds AI mistakes before they ship${c.reset}`);
587
+ console.log(` ${c.dim}Want verified proof?${c.reset} ${colors.accent}vibecheck prove${c.reset} ${c.dim}(PRO) records video evidence${c.reset}`);
588
+ console.log();
581
589
  return 0;
582
590
  }
583
591
  }
@@ -330,6 +330,17 @@ function getMissionIcon(missionType) {
330
330
  'FIX_FAKE_SUCCESS': ICONS.ghost,
331
331
  'FIX_ENV_CONTRACT': ICONS.env,
332
332
  'FIX_DEAD_UI': ICONS.dead,
333
+ // New enhanced mission types
334
+ 'FIX_EMPTY_CATCH': ICONS.bug,
335
+ 'FIX_TEST_KEYS': ICONS.key,
336
+ 'FIX_MOCK_DOMAINS': ICONS.link,
337
+ 'FIX_PLACEHOLDER_DATA': ICONS.doc,
338
+ 'FIX_HARDCODED_SECRETS': ICONS.lock,
339
+ 'FIX_SIMULATED_BILLING': ICONS.money,
340
+ 'FIX_SILENT_FALLBACK': ICONS.warning,
341
+ 'SYNC_CONTRACTS': ICONS.graph,
342
+ 'FIX_ROUTE_DRIFT': ICONS.route,
343
+ 'FIX_AUTH_DRIFT': ICONS.shield,
333
344
  };
334
345
  return icons[missionType] || ICONS.mission;
335
346
  }
@@ -344,6 +355,17 @@ function getMissionColor(missionType) {
344
355
  'FIX_FAKE_SUCCESS': colors.medium,
345
356
  'FIX_ENV_CONTRACT': colors.low,
346
357
  'FIX_DEAD_UI': colors.low,
358
+ // New enhanced mission types
359
+ 'FIX_EMPTY_CATCH': colors.high,
360
+ 'FIX_TEST_KEYS': colors.critical,
361
+ 'FIX_MOCK_DOMAINS': colors.critical,
362
+ 'FIX_PLACEHOLDER_DATA': colors.medium,
363
+ 'FIX_HARDCODED_SECRETS': colors.critical,
364
+ 'FIX_SIMULATED_BILLING': colors.critical,
365
+ 'FIX_SILENT_FALLBACK': colors.high,
366
+ 'SYNC_CONTRACTS': colors.medium,
367
+ 'FIX_ROUTE_DRIFT': colors.medium,
368
+ 'FIX_AUTH_DRIFT': colors.critical,
347
369
  };
348
370
  return colorMap[missionType] || colors.accent;
349
371
  }
@@ -579,7 +601,7 @@ async function runFix(args) {
579
601
 
580
602
  // Show autopilot header if in autopilot mode
581
603
  if (opts.autopilot) {
582
- printAutopilotHeader(maxSteps, stagnationLimit);
604
+ printLoopHeader(maxSteps, stagnationLimit);
583
605
  }
584
606
 
585
607
  for (let step = 1; step <= maxSteps; step++) {
@@ -801,349 +823,21 @@ async function runFix(args) {
801
823
  return 1;
802
824
  }
803
825
 
804
- /**
805
- * Show Fix Plan (default behavior - no changes applied)
806
- */
807
- async function showFixPlan(projectPath, opts) {
808
- console.log(`\n${c.cyan}${c.bold}📋 FIX PLAN${c.reset}\n`);
809
- console.log(`${c.dim}This shows what CAN be fixed. No changes will be made.${c.reset}`);
810
- console.log(`${c.dim}Use --apply to apply changes (requires clean git state).${c.reset}\n`);
811
-
812
- const scanResult = await loadLatestScan(projectPath);
813
- if (!scanResult) {
814
- console.error(`${c.red}Error:${c.reset} No scan results found. Run 'vibecheck scan' first.\n`);
815
- return EXIT_CODES.MISCONFIG;
816
- }
817
-
818
- // Filter to fixable findings
819
- const fixable = scanResult.findings.filter(f => {
820
- const ruleId = f.ruleId || '';
821
- return f.autofixAvailable && ALLOWED_FIX_TYPES.includes(ruleId);
822
- });
823
-
824
- if (fixable.length === 0) {
825
- console.log(`${c.green}✓${c.reset} No mechanical fixes available.\n`);
826
- console.log(`${c.dim}All remaining issues require manual intervention.${c.reset}\n`);
827
- return EXIT_CODES.PASS;
828
- }
829
-
830
- console.log(`${c.bold}Fixable Issues (${fixable.length}):${c.reset}\n`);
831
-
832
- for (const finding of fixable) {
833
- const patch = generatePatchPreview(projectPath, finding);
834
-
835
- console.log(`${c.blue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`);
836
- console.log(`${c.bold}${finding.id?.full || finding.id}${c.reset} - ${finding.ruleId}`);
837
- console.log(`${c.dim}File:${c.reset} ${finding.file}:${finding.line}`);
838
- console.log(`${c.dim}Why:${c.reset} ${finding.message || finding.description}`);
839
- console.log(`${c.dim}Evidence:${c.reset} ${finding.evidence?.[0]?.snippet || 'See file'}`);
840
- console.log('');
841
- console.log(`${c.bold}Proposed Change:${c.reset}`);
842
- console.log(patch.preview);
843
- console.log('');
844
- console.log(`${c.dim}Risk:${c.reset} ${patch.risk}`);
845
- console.log(`${c.dim}Verification:${c.reset} Re-run scan to confirm no regression`);
846
- console.log('');
847
- }
848
-
849
- console.log(`${c.blue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`);
850
- console.log(`\n${c.bold}To apply these fixes:${c.reset}`);
851
- console.log(` ${c.cyan}vibecheck fix --apply${c.reset}\n`);
852
- console.log(`${c.dim}This will:${c.reset}`);
853
- console.log(` 1. Check for clean git state`);
854
- console.log(` 2. Create branch: vibecheck/fix-<timestamp>`);
855
- console.log(` 3. Apply patches with structured commits`);
856
- console.log(` 4. Re-run scan to verify no regression\n`);
857
-
858
- return EXIT_CODES.PASS;
859
- }
860
-
861
- /**
862
- * Apply fixes with proof-gating
863
- */
864
- async function applyFixes(projectPath, opts) {
865
- console.log(`\n${c.cyan}${c.bold}🔧 APPLYING FIXES${c.reset}\n`);
866
-
867
- // Step 1: Check git state
868
- if (!isGitClean(projectPath)) {
869
- console.error(`${c.red}Error:${c.reset} Git working directory is not clean.`);
870
- console.error(`${c.dim}Commit or stash your changes before applying fixes.${c.reset}\n`);
871
- return EXIT_CODES.MISCONFIG;
872
- }
873
-
874
- const scanResult = await loadLatestScan(projectPath);
875
- if (!scanResult) {
876
- console.error(`${c.red}Error:${c.reset} No scan results found. Run 'vibecheck scan' first.\n`);
877
- return EXIT_CODES.MISCONFIG;
878
- }
879
-
880
- const fixable = scanResult.findings.filter(f => {
881
- const ruleId = f.ruleId || '';
882
- return f.autofixAvailable && ALLOWED_FIX_TYPES.includes(ruleId);
883
- });
884
-
885
- if (fixable.length === 0) {
886
- console.log(`${c.green}✓${c.reset} No fixes to apply.\n`);
887
- return EXIT_CODES.PASS;
888
- }
889
-
890
- // Step 2: Create branch
891
- const branchName = `vibecheck/fix-${Date.now()}`;
892
- try {
893
- execSync(`git checkout -b ${branchName}`, { cwd: projectPath, stdio: 'pipe' });
894
- console.log(`${c.green}✓${c.reset} Created branch: ${branchName}\n`);
895
- } catch (err) {
896
- console.error(`${c.red}Error:${c.reset} Failed to create branch: ${err.message}\n`);
897
- return EXIT_CODES.INTERNAL;
898
- }
899
-
900
- // Step 3: Apply fixes
901
- let applied = 0;
902
- let failed = 0;
903
-
904
- for (const finding of fixable) {
905
- try {
906
- const result = await applyAutofix(projectPath, finding, opts);
907
- if (result) {
908
- applied++;
909
-
910
- // Commit with structured message
911
- const commitMsg = `fix(${finding.ruleId}): ${finding.id?.full || finding.id}\n\nFile: ${finding.file}:${finding.line}\nEvidence: ${finding.evidence?.[0]?.snippet || 'See scan results'}\n\nApplied by vibecheck fix --apply`;
912
-
913
- try {
914
- execSync(`git add "${finding.file}"`, { cwd: projectPath, stdio: 'pipe' });
915
- execSync(`git commit -m "${commitMsg.replace(/"/g, '\\"')}"`, { cwd: projectPath, stdio: 'pipe' });
916
- } catch {
917
- // File might not have changed, continue
918
- }
919
-
920
- console.log(`${c.green}✓${c.reset} Fixed: ${finding.id?.full || finding.id}`);
921
- } else {
922
- failed++;
923
- console.log(`${c.yellow}⚠${c.reset} Skipped: ${finding.id?.full || finding.id}`);
924
- }
925
- } catch (error) {
926
- failed++;
927
- console.log(`${c.red}✗${c.reset} Error: ${finding.id?.full || finding.id} - ${error.message}`);
928
- }
929
- }
930
-
931
- console.log('');
932
- console.log(`${c.bold}Summary:${c.reset}`);
933
- console.log(` Applied: ${c.green}${applied}${c.reset}`);
934
- console.log(` Skipped: ${c.yellow}${failed}${c.reset}`);
935
- console.log('');
936
-
937
- // Step 4: Re-run scan to verify
938
- console.log(`${c.dim}Verifying fixes...${c.reset}\n`);
939
-
940
- try {
941
- const { runScan } = require('./runScan');
942
- const verifyResult = await runScan(['--json', '--path', projectPath]);
943
-
944
- if (verifyResult === 0 || verifyResult === EXIT_CODES.PASS) {
945
- console.log(`${c.green}✓${c.reset} Verification passed - no regressions\n`);
946
- } else {
947
- console.log(`${c.yellow}⚠${c.reset} Verification found issues - review before merging\n`);
948
- }
949
- } catch {
950
- console.log(`${c.dim}Run 'vibecheck scan' to verify manually.${c.reset}\n`);
951
- }
952
-
953
- console.log(`${c.bold}Rollback:${c.reset}`);
954
- console.log(` ${c.cyan}git checkout main && git branch -D ${branchName}${c.reset}\n`);
955
-
956
- return applied > 0 ? EXIT_CODES.PASS : EXIT_CODES.FAIL;
957
- }
958
-
959
- /**
960
- * Check if git working directory is clean
961
- */
962
- function isGitClean(projectPath) {
963
- try {
964
- const status = execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf8' });
965
- return status.trim() === '';
966
- } catch {
967
- return false;
968
- }
969
- }
970
-
971
- /**
972
- * Generate patch preview for a finding
973
- */
974
- function generatePatchPreview(projectPath, finding) {
975
- const filePath = path.join(projectPath, finding.file);
976
-
977
- if (!fs.existsSync(filePath)) {
978
- return { preview: ` ${c.red}File not found${c.reset}`, risk: 'N/A' };
979
- }
980
-
981
- const content = fs.readFileSync(filePath, 'utf8');
982
- const lines = content.split('\n');
983
- const lineIndex = finding.line - 1;
984
-
985
- if (lineIndex < 0 || lineIndex >= lines.length) {
986
- return { preview: ` ${c.red}Invalid line number${c.reset}`, risk: 'N/A' };
987
- }
988
-
989
- const before = lines[lineIndex];
990
- let after = before;
991
- let risk = 'Low';
992
-
993
- // Generate preview based on rule type
994
- switch (finding.ruleId) {
995
- case 'empty-catch':
996
- after = before.replace(/catch\s*(?:\(([^)]*)\))?\s*\{\s*\}/, (m, v) => {
997
- const varName = v || 'err';
998
- return `catch (${varName}) { console.error('Error:', ${varName}); throw ${varName}; }`;
999
- });
1000
- risk = 'Low - adds proper error handling';
1001
- break;
1002
- case 'dangerous-default':
1003
- after = before.replace(/\s*(\|\||\?\?)\s*['"][^'"]*['"]/, '');
1004
- risk = 'Medium - removes fallback (may require env var)';
1005
- break;
1006
- case 'placeholder-value':
1007
- after = before.replace(/(CHANGEME|REPLACE_ME|YOUR_\w+|INSERT_\w+)/g, "/* TODO: Replace $1 */ ''");
1008
- risk = 'Low - flags for manual replacement';
1009
- break;
1010
- default:
1011
- return { preview: ` ${c.dim}No preview available${c.reset}`, risk: 'Unknown' };
1012
- }
1013
-
1014
- return {
1015
- preview: ` ${c.red}- ${before.trim()}${c.reset}\n ${c.green}+ ${after.trim()}${c.reset}`,
1016
- risk,
1017
- };
1018
- }
1019
-
1020
- async function applyAutofix(projectPath, finding, options) {
1021
- const filePath = path.join(projectPath, finding.file);
1022
-
1023
- if (!fs.existsSync(filePath)) {
1024
- throw new Error(`File not found: ${finding.file}`);
1025
- }
1026
-
1027
- const content = fs.readFileSync(filePath, 'utf8');
1028
- const lines = content.split('\n');
1029
- const lineIndex = finding.line - 1;
1030
-
1031
- if (lineIndex < 0 || lineIndex >= lines.length) {
1032
- throw new Error(`Invalid line number: ${finding.line}`);
1033
- }
1034
-
1035
- let newContent = content;
1036
- const ruleId = finding.ruleId;
1037
-
1038
- // Apply fixes based on rule type
1039
- switch (ruleId) {
1040
- case 'empty-catch':
1041
- newContent = fixEmptyCatch(lines, lineIndex, filePath);
1042
- break;
1043
- case 'dangerous-default':
1044
- newContent = fixDangerousDefault(lines, lineIndex, filePath);
1045
- break;
1046
- case 'placeholder-value':
1047
- newContent = fixPlaceholderValue(lines, lineIndex, filePath);
1048
- break;
1049
- default:
1050
- return false;
1051
- }
1052
-
1053
- if (newContent !== content) {
1054
- if (!options.dryRun) {
1055
- fs.writeFileSync(filePath, newContent, 'utf8');
1056
- }
1057
- return true;
1058
- }
1059
-
1060
- return false;
1061
- }
1062
-
1063
- function fixEmptyCatch(lines, lineIndex, filePath) {
1064
- const line = lines[lineIndex];
1065
- const newLines = [...lines];
1066
-
1067
- // Find the catch block
1068
- if (line.includes('catch') && line.includes('{')) {
1069
- // Simple case: catch {} on one line
1070
- if (line.match(/catch\s*(?:\([^)]*\))?\s*\{\s*\}/)) {
1071
- const indent = line.match(/^(\s*)/)?.[1] || '';
1072
- newLines[lineIndex] = line.replace(
1073
- /catch\s*(?:\(([^)]*)\))?\s*\{\s*\}/,
1074
- (match, errVar) => {
1075
- const varName = errVar || 'err';
1076
- return `catch (${varName}) {\n${indent} console.error('Error:', ${varName});\n${indent} throw ${varName};\n${indent}}`;
1077
- }
1078
- );
1079
- }
1080
- }
1081
-
1082
- return newLines.join('\n');
1083
- }
1084
-
1085
- function fixDangerousDefault(lines, lineIndex, filePath) {
1086
- const line = lines[lineIndex];
1087
- const newLines = [...lines];
1088
-
1089
- // Remove dangerous default
1090
- if (line.includes('process.env.') && (line.includes('||') || line.includes('??'))) {
1091
- newLines[lineIndex] = line.replace(
1092
- /\s*(\|\||\?\?)\s*['"][^'"]*['"]/,
1093
- ''
1094
- );
1095
- }
1096
-
1097
- return newLines.join('\n');
1098
- }
1099
-
1100
- function fixPlaceholderValue(lines, lineIndex, filePath) {
1101
- const line = lines[lineIndex];
1102
- const newLines = [...lines];
1103
-
1104
- // Replace placeholder with TODO comment
1105
- newLines[lineIndex] = line.replace(
1106
- /(CHANGEME|REPLACE_ME|YOUR_[A-Z0-9_]+|INSERT_[A-Z0-9_]+)/g,
1107
- (match) => `/* TODO: Replace ${match} */ ''`
1108
- );
1109
-
1110
- return newLines.join('\n');
1111
- }
1112
-
1113
- async function loadLatestScan(projectPath) {
1114
- const scanDir = path.join(projectPath, '.vibecheck', 'reality-sniff', 'scans');
1115
-
1116
- if (!fs.existsSync(scanDir)) {
1117
- return null;
1118
- }
1119
-
1120
- try {
1121
- const files = fs.readdirSync(scanDir)
1122
- .filter(f => f.endsWith('.json'))
1123
- .map(f => ({
1124
- name: f,
1125
- path: path.join(scanDir, f),
1126
- mtime: fs.statSync(path.join(scanDir, f)).mtime,
1127
- }))
1128
- .sort((a, b) => b.mtime - a.mtime);
1129
-
1130
- if (files.length === 0) {
1131
- return null;
1132
- }
1133
-
1134
- const content = fs.readFileSync(files[0].path, 'utf8');
1135
- return JSON.parse(content);
1136
- } catch {
1137
- return null;
1138
- }
1139
- }
826
+ // ═══════════════════════════════════════════════════════════════════════════════
827
+ // NOTE: Legacy rule-based fix functions (showFixPlan, applyFixes, etc.) were removed.
828
+ // The fix command now uses the mission-based system exclusively, which provides:
829
+ // - LLM-generated patches with Reality Firewall prompt
830
+ // - Automatic backup/restore on failure
831
+ // - Progress detection and stagnation handling
832
+ // - Integration with ship verification
833
+ // ═══════════════════════════════════════════════════════════════════════════════
1140
834
 
1141
835
  function parseArgs(args) {
1142
836
  // Parse global flags first
1143
837
  const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
1144
838
 
1145
839
  const opts = {
1146
- ...globalFlags, // Merge global flags (json, verbose, noBanner, quiet, ci, etc.)
840
+ ...globalFlags, // Merge global flags (json, verbose, noBanner, quiet, ci, help, etc.)
1147
841
  path: globalFlags.path || process.cwd(),
1148
842
  apply: false,
1149
843
  promptOnly: false,
@@ -1153,7 +847,7 @@ function parseArgs(args) {
1153
847
  maxSteps: 10,
1154
848
  stagnationLimit: 2,
1155
849
  fastifyEntry: null,
1156
- help: false,
850
+ // Note: help comes from globalFlags, don't override it here
1157
851
  };
1158
852
 
1159
853
  // Parse command-specific args from cleanArgs
@@ -1208,13 +902,29 @@ function printHelp(showBanner = true) {
1208
902
  ${colors.shipGreen}${ICONS.stop}${c.reset} Progress detector stops on stagnation
1209
903
 
1210
904
  ${c.bold}Mission Types:${c.reset}
905
+ ${c.dim}Security & Auth:${c.reset}
1211
906
  ${colors.critical}${ICONS.lock}${c.reset} REMOVE_OWNER_MODE ${c.dim}Delete backdoor env bypass${c.reset}
907
+ ${colors.critical}${ICONS.lock}${c.reset} FIX_HARDCODED_SECRETS ${c.dim}Move secrets to env vars${c.reset}
908
+ ${colors.critical}${ICONS.key}${c.reset} FIX_TEST_KEYS ${c.dim}Replace test API keys${c.reset}
909
+ ${colors.critical}${ICONS.shield}${c.reset} FIX_AUTH_DRIFT ${c.dim}Restore removed auth patterns${c.reset}
910
+ ${colors.medium}${ICONS.auth}${c.reset} ADD_SERVER_AUTH ${c.dim}Add auth to sensitive endpoints${c.reset}
911
+
912
+ ${c.dim}Billing & Payments:${c.reset}
1212
913
  ${colors.high}${ICONS.money}${c.reset} FIX_STRIPE_WEBHOOKS ${c.dim}Add signature verification + idempotency${c.reset}
1213
914
  ${colors.high}${ICONS.shield}${c.reset} ENFORCE_PAID_SURFACE ${c.dim}Add server-side entitlement checks${c.reset}
1214
- ${colors.medium}${ICONS.auth}${c.reset} ADD_SERVER_AUTH ${c.dim}Add auth to sensitive endpoints${c.reset}
1215
- ${colors.medium}${ICONS.route}${c.reset} FIX_MISSING_ROUTE ${c.dim}Wire client refs to server routes${c.reset}
915
+ ${colors.critical}${ICONS.money}${c.reset} FIX_SIMULATED_BILLING ${c.dim}Replace fake billing with real${c.reset}
916
+
917
+ ${c.dim}Reality & Data:${c.reset}
918
+ ${colors.critical}${ICONS.link}${c.reset} FIX_MOCK_DOMAINS ${c.dim}Replace localhost/mock URLs${c.reset}
1216
919
  ${colors.medium}${ICONS.ghost}${c.reset} FIX_FAKE_SUCCESS ${c.dim}Gate success UI on network result${c.reset}
920
+ ${colors.medium}${ICONS.doc}${c.reset} FIX_PLACEHOLDER_DATA ${c.dim}Replace lorem ipsum with real data${c.reset}
921
+ ${colors.high}${ICONS.warning}${c.reset} FIX_SILENT_FALLBACK ${c.dim}Make failures visible${c.reset}
922
+
923
+ ${c.dim}Code Quality:${c.reset}
924
+ ${colors.high}${ICONS.bug}${c.reset} FIX_EMPTY_CATCH ${c.dim}Add error handling to catch blocks${c.reset}
925
+ ${colors.medium}${ICONS.route}${c.reset} FIX_MISSING_ROUTE ${c.dim}Wire client refs to server routes${c.reset}
1217
926
  ${colors.low}${ICONS.env}${c.reset} FIX_ENV_CONTRACT ${c.dim}Add missing env vars to templates${c.reset}
927
+ ${colors.low}${ICONS.dead}${c.reset} FIX_DEAD_UI ${c.dim}Make UI actions functional${c.reset}
1218
928
 
1219
929
  ${c.bold}LLM Configuration:${c.reset}
1220
930
  ${c.dim}Set these environment variables:${c.reset}
@@ -1054,6 +1054,17 @@ function printNextSteps(options = {}) {
1054
1054
  console.log();
1055
1055
  console.log(` ${c.dim}Full docs:${c.reset} ${colors.info}https://docs.vibecheckai.dev${c.reset}`);
1056
1056
  console.log();
1057
+
1058
+ // Upsell box
1059
+ console.log(` ${c.dim}╭────────────────────────────────────────────────────────────╮${c.reset}`);
1060
+ console.log(` ${c.dim}│${c.reset} ${c.dim}│${c.reset}`);
1061
+ console.log(` ${c.dim}│${c.reset} ${colors.accent}⚡ STARTER${c.reset} ${c.dim}•${c.reset} AI-powered fixes, GitHub CI, MCP tools ${c.dim}│${c.reset}`);
1062
+ console.log(` ${c.dim}│${c.reset} ${colors.accent}🏆 PRO${c.reset} ${c.dim}•${c.reset} Runtime proof, verified badges, AI testing ${c.dim}│${c.reset}`);
1063
+ console.log(` ${c.dim}│${c.reset} ${c.dim}│${c.reset}`);
1064
+ console.log(` ${c.dim}│${c.reset} ${colors.info}vibecheck login${c.reset} ${c.dim}to upgrade • vibecheck.dev/pricing${c.reset} ${c.dim}│${c.reset}`);
1065
+ console.log(` ${c.dim}│${c.reset} ${c.dim}│${c.reset}`);
1066
+ console.log(` ${c.dim}╰────────────────────────────────────────────────────────────╯${c.reset}`);
1067
+ console.log();
1057
1068
  }
1058
1069
 
1059
1070
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Type declarations for runPolish.js
3
+ */
4
+ export function runPolish(args: string[]): Promise<number>;