@vibecheckai/cli 3.4.0 → 3.5.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 (166) hide show
  1. package/bin/registry.js +243 -152
  2. package/bin/runners/cli-utils.js +2 -33
  3. package/bin/runners/context/generators/cursor.js +49 -2
  4. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
  5. package/bin/runners/lib/analyzers.js +544 -19
  6. package/bin/runners/lib/audit-logger.js +532 -0
  7. package/bin/runners/lib/authority/authorities/architecture.js +364 -0
  8. package/bin/runners/lib/authority/authorities/compliance.js +341 -0
  9. package/bin/runners/lib/authority/authorities/human.js +343 -0
  10. package/bin/runners/lib/authority/authorities/quality.js +420 -0
  11. package/bin/runners/lib/authority/authorities/security.js +228 -0
  12. package/bin/runners/lib/authority/index.js +293 -0
  13. package/bin/runners/lib/authority-badge.js +425 -425
  14. package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
  15. package/bin/runners/lib/cli-charts.js +368 -0
  16. package/bin/runners/lib/cli-config-display.js +405 -0
  17. package/bin/runners/lib/cli-demo.js +275 -0
  18. package/bin/runners/lib/cli-errors.js +438 -0
  19. package/bin/runners/lib/cli-help-formatter.js +439 -0
  20. package/bin/runners/lib/cli-interactive-menu.js +509 -0
  21. package/bin/runners/lib/cli-prompts.js +441 -0
  22. package/bin/runners/lib/cli-scan-cards.js +362 -0
  23. package/bin/runners/lib/compliance-reporter.js +710 -0
  24. package/bin/runners/lib/conductor/index.js +671 -0
  25. package/bin/runners/lib/easy/README.md +123 -0
  26. package/bin/runners/lib/easy/index.js +140 -0
  27. package/bin/runners/lib/easy/interactive-wizard.js +788 -0
  28. package/bin/runners/lib/easy/one-click-firewall.js +564 -0
  29. package/bin/runners/lib/easy/zero-config-reality.js +714 -0
  30. package/bin/runners/lib/engines/accessibility-engine.js +218 -18
  31. package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
  32. package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
  33. package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
  34. package/bin/runners/lib/engines/confidence-scoring.js +276 -0
  35. package/bin/runners/lib/engines/context-detection.js +264 -0
  36. package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
  37. package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
  38. package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
  39. package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
  40. package/bin/runners/lib/engines/env-variables-engine.js +458 -0
  41. package/bin/runners/lib/engines/error-handling-engine.js +437 -0
  42. package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
  43. package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
  44. package/bin/runners/lib/engines/framework-detection.js +508 -0
  45. package/bin/runners/lib/engines/import-order-engine.js +429 -0
  46. package/bin/runners/lib/engines/mock-data-engine.js +53 -10
  47. package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
  48. package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
  49. package/bin/runners/lib/engines/orchestrator.js +334 -0
  50. package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
  51. package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
  52. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
  53. package/bin/runners/lib/engines/type-aware-engine.js +263 -39
  54. package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
  55. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
  56. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
  57. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
  58. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
  59. package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
  60. package/bin/runners/lib/enhanced-features/index.js +305 -0
  61. package/bin/runners/lib/enhanced-output.js +631 -0
  62. package/bin/runners/lib/enterprise.js +300 -0
  63. package/bin/runners/lib/entitlements-v2.js +103 -11
  64. package/bin/runners/lib/firewall/command-validator.js +351 -0
  65. package/bin/runners/lib/firewall/config.js +341 -0
  66. package/bin/runners/lib/firewall/content-validator.js +519 -0
  67. package/bin/runners/lib/firewall/index.js +101 -0
  68. package/bin/runners/lib/firewall/path-validator.js +256 -0
  69. package/bin/runners/lib/html-proof-report.js +350 -700
  70. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
  71. package/bin/runners/lib/mcp-utils.js +425 -0
  72. package/bin/runners/lib/missions/plan.js +46 -6
  73. package/bin/runners/lib/missions/templates.js +232 -0
  74. package/bin/runners/lib/output/index.js +1022 -0
  75. package/bin/runners/lib/policy-engine.js +652 -0
  76. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
  77. package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
  78. package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
  79. package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
  80. package/bin/runners/lib/polish/autofix/index.js +200 -0
  81. package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
  82. package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
  83. package/bin/runners/lib/polish/backend-checks.js +148 -0
  84. package/bin/runners/lib/polish/documentation-checks.js +111 -0
  85. package/bin/runners/lib/polish/frontend-checks.js +168 -0
  86. package/bin/runners/lib/polish/index.js +71 -0
  87. package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
  88. package/bin/runners/lib/polish/library-detection.js +175 -0
  89. package/bin/runners/lib/polish/performance-checks.js +100 -0
  90. package/bin/runners/lib/polish/security-checks.js +148 -0
  91. package/bin/runners/lib/polish/utils.js +203 -0
  92. package/bin/runners/lib/prompt-builder.js +540 -0
  93. package/bin/runners/lib/proof-certificate.js +634 -0
  94. package/bin/runners/lib/reality/accessibility-audit.js +946 -0
  95. package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
  96. package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
  97. package/bin/runners/lib/reality/performance-tracker.js +1077 -0
  98. package/bin/runners/lib/reality/scenario-generator.js +1404 -0
  99. package/bin/runners/lib/reality/visual-regression.js +852 -0
  100. package/bin/runners/lib/reality-profiler.js +717 -0
  101. package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
  102. package/bin/runners/lib/review/ai-code-review.js +832 -0
  103. package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
  104. package/bin/runners/lib/sbom-generator.js +641 -0
  105. package/bin/runners/lib/scan-output-enhanced.js +512 -0
  106. package/bin/runners/lib/scan-output.js +47 -0
  107. package/bin/runners/lib/security/owasp-scanner.js +939 -0
  108. package/bin/runners/lib/terminal-ui.js +113 -1
  109. package/bin/runners/lib/unified-cli-output.js +603 -430
  110. package/bin/runners/lib/validators/contract-validator.js +283 -0
  111. package/bin/runners/lib/validators/dead-export-detector.js +279 -0
  112. package/bin/runners/lib/validators/dep-audit.js +245 -0
  113. package/bin/runners/lib/validators/env-validator.js +319 -0
  114. package/bin/runners/lib/validators/index.js +120 -0
  115. package/bin/runners/lib/validators/license-checker.js +252 -0
  116. package/bin/runners/lib/validators/route-validator.js +290 -0
  117. package/bin/runners/runAIAgent.js +5 -10
  118. package/bin/runners/runAgent.js +3 -0
  119. package/bin/runners/runApprove.js +1233 -1200
  120. package/bin/runners/runAuth.js +22 -1
  121. package/bin/runners/runAuthority.js +528 -0
  122. package/bin/runners/runCheckpoint.js +4 -24
  123. package/bin/runners/runClassify.js +862 -859
  124. package/bin/runners/runConductor.js +772 -0
  125. package/bin/runners/runContainer.js +366 -0
  126. package/bin/runners/runContext.js +3 -0
  127. package/bin/runners/runDoctor.js +28 -41
  128. package/bin/runners/runEasy.js +410 -0
  129. package/bin/runners/runFirewall.js +3 -0
  130. package/bin/runners/runFirewallHook.js +3 -0
  131. package/bin/runners/runFix.js +76 -66
  132. package/bin/runners/runGuard.js +411 -18
  133. package/bin/runners/runIaC.js +372 -0
  134. package/bin/runners/runInit.js +10 -60
  135. package/bin/runners/runMcp.js +11 -12
  136. package/bin/runners/runPolish.js +240 -64
  137. package/bin/runners/runPromptFirewall.js +5 -12
  138. package/bin/runners/runProve.js +20 -55
  139. package/bin/runners/runReality.js +68 -59
  140. package/bin/runners/runReport.js +31 -5
  141. package/bin/runners/runRuntime.js +5 -8
  142. package/bin/runners/runScan.js +194 -1286
  143. package/bin/runners/runShip.js +695 -47
  144. package/bin/runners/runTruth.js +3 -0
  145. package/bin/runners/runValidate.js +7 -11
  146. package/bin/runners/runVibe.js +791 -0
  147. package/bin/runners/runWatch.js +14 -23
  148. package/bin/vibecheck.js +175 -56
  149. package/mcp-server/index.js +190 -14
  150. package/mcp-server/package.json +1 -1
  151. package/mcp-server/tools-v3.js +397 -64
  152. package/mcp-server/tools.js +495 -0
  153. package/package.json +1 -1
  154. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
  155. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
  156. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
  157. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
  158. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
  159. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
  160. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
  161. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
  162. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
  163. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
  166. package/mcp-server/index-v1.js +0 -698
@@ -1,6 +1,11 @@
1
1
  /**
2
- * vibecheck ship - The Vibe Coder's Best Friend
3
- * Zero config. Plain English. One command to ship with confidence.
2
+ * vibecheck ship - Comprehensive Production Readiness Analysis [PRO]
3
+ *
4
+ * The comprehensive analysis command running:
5
+ * - ALL 17+ scan engines (vs 5 in quick scan)
6
+ * - Ship-only validators (route integrity, contracts, deps, licenses, env, dead exports)
7
+ * - Production Readiness Score calculation
8
+ * - SHIP/WARN/BLOCK verdict with proof certificate
4
9
  *
5
10
  * ═══════════════════════════════════════════════════════════════════════════════
6
11
  * ENTERPRISE EDITION - World-Class Terminal Experience
@@ -11,7 +16,7 @@ const path = require("path");
11
16
  const fs = require("fs");
12
17
  const { withErrorHandling } = require("./lib/error-handler");
13
18
  const { ensureOutputDir, detectProjectFeatures } = require("./utils");
14
- const { enforceLimit, enforceFeature, trackUsage, getCurrentTier } = require("./lib/entitlements");
19
+ const { enforceLimit, enforceFeature, trackUsage, getCurrentTier } = require("./lib/entitlements-v2");
15
20
  const { emitShipCheck } = require("./lib/audit-bridge");
16
21
  const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
17
22
  const {
@@ -22,8 +27,62 @@ const {
22
27
  } = require("./lib/cli-output");
23
28
  const { EXIT, verdictToExitCode, exitCodeToVerdict } = require("./lib/exit-codes");
24
29
 
30
+ // NEW: Import orchestrator and validators
31
+ const { runShipEngines, ALL_ENGINES } = require("./lib/engines/orchestrator");
32
+ const { runShipValidators } = require("./lib/validators");
33
+
25
34
  // Route Truth v1 - Fake endpoint detection
26
35
  const { buildTruthpack, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
36
+
37
+ // Helper to normalize severity for ship verdict
38
+ function normalizeSeverityForShip(sev) {
39
+ if (!sev) return 'WARN';
40
+ const s = String(sev).toLowerCase();
41
+ if (s === 'block' || s === 'critical' || s === 'high') return 'BLOCK';
42
+ if (s === 'warn' || s === 'warning' || s === 'medium') return 'WARN';
43
+ return 'INFO';
44
+ }
45
+
46
+ // Helper to categorize findings for score breakdown
47
+ function categorizeFindings(finding) {
48
+ const category = (finding.category || '').toLowerCase();
49
+ const engine = (finding.engine || '').toLowerCase();
50
+ const validator = (finding.validator || '').toLowerCase();
51
+
52
+ // Security
53
+ if (category.includes('secret') || category.includes('security') ||
54
+ category.includes('auth') || category.includes('billing') ||
55
+ engine.includes('secret') || engine.includes('security')) {
56
+ return 'security';
57
+ }
58
+
59
+ // Hallucinations
60
+ if (category.includes('hallucination') || category.includes('fake') ||
61
+ category.includes('mock') || engine.includes('hallucination')) {
62
+ return 'hallucination';
63
+ }
64
+
65
+ // Routes
66
+ if (category.includes('route') || category.includes('missing') ||
67
+ validator.includes('route')) {
68
+ return 'routes';
69
+ }
70
+
71
+ // Contracts
72
+ if (category.includes('contract') || category.includes('drift') ||
73
+ validator.includes('contract')) {
74
+ return 'contracts';
75
+ }
76
+
77
+ // Dependencies
78
+ if (category.includes('dependency') || category.includes('license') ||
79
+ validator.includes('dep') || validator.includes('license')) {
80
+ return 'dependencies';
81
+ }
82
+
83
+ // Default to quality
84
+ return 'quality';
85
+ }
27
86
  const {
28
87
  findMissingRoutes,
29
88
  findEnvGaps,
@@ -38,6 +97,213 @@ const { findContractDrift, loadContracts, hasContracts, getDriftSummary } = requ
38
97
  const upsell = require("./lib/upsell");
39
98
  const entitlements = require("./lib/entitlements-v2");
40
99
 
100
+ // V7: World-class proof certificate and risk radar
101
+ let proofCertificate;
102
+ try {
103
+ proofCertificate = require("./lib/proof-certificate");
104
+ } catch (e) {
105
+ proofCertificate = null;
106
+ }
107
+
108
+ // Import vibecheck engines for enhanced analysis
109
+ let vibeEngines;
110
+ try {
111
+ vibeEngines = require("./lib/engines/vibecheck-engines");
112
+ } catch (e) {
113
+ vibeEngines = null;
114
+ }
115
+
116
+ // ═══════════════════════════════════════════════════════════════════════════════
117
+ // ENHANCED ANALYSIS WITH VIBECHECK ENGINES
118
+ // ═══════════════════════════════════════════════════════════════════════════════
119
+
120
+ const CODE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
121
+ const IGNORE_PATTERNS = ['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage', '__pycache__', '.vibecheck'];
122
+
123
+ function* walkFilesForShip(dir, depth = 0, maxDepth = 8) {
124
+ if (depth > maxDepth) return;
125
+
126
+ let entries;
127
+ try {
128
+ entries = fs.readdirSync(dir, { withFileTypes: true });
129
+ } catch {
130
+ return;
131
+ }
132
+
133
+ for (const entry of entries) {
134
+ const fullPath = path.join(dir, entry.name);
135
+
136
+ if (entry.isDirectory()) {
137
+ if (IGNORE_PATTERNS.some(p => entry.name.includes(p))) continue;
138
+ yield* walkFilesForShip(fullPath, depth + 1, maxDepth);
139
+ } else if (entry.isFile()) {
140
+ const ext = path.extname(entry.name);
141
+ if (CODE_EXTENSIONS.includes(ext)) {
142
+ yield fullPath;
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ async function runVibeEngineAnalysis(projectPath, opts = {}) {
149
+ if (!vibeEngines) return [];
150
+
151
+ const findings = [];
152
+ const files = Array.from(walkFilesForShip(projectPath));
153
+
154
+ // Limit files to analyze in ship mode (performance)
155
+ const filesToAnalyze = files.slice(0, 500);
156
+
157
+ for (const filePath of filesToAnalyze) {
158
+ try {
159
+ const content = fs.readFileSync(filePath, 'utf-8');
160
+
161
+ // Skip test files unless explicitly included
162
+ if (!opts.includeTests && (
163
+ filePath.includes('.test.') ||
164
+ filePath.includes('.spec.') ||
165
+ filePath.includes('__tests__')
166
+ )) {
167
+ continue;
168
+ }
169
+
170
+ // Run async patterns analysis - critical for production
171
+ if (vibeEngines.analyzeAsyncPatterns) {
172
+ try {
173
+ const asyncFindings = vibeEngines.analyzeAsyncPatterns(content, filePath);
174
+ findings.push(...asyncFindings.filter(f =>
175
+ f.severity === 'BLOCK' || f.severity === 'WARN'
176
+ ).map(f => ({
177
+ ...f,
178
+ category: f.category || 'AsyncPatterns',
179
+ // Map to ship finding format
180
+ title: f.title,
181
+ why: f.message,
182
+ severity: f.severity,
183
+ evidence: [{ file: f.file, lines: String(f.line) }],
184
+ fixHints: [f.fixHint].filter(Boolean),
185
+ })));
186
+ } catch {}
187
+ }
188
+
189
+ // Run env variable analysis - security critical
190
+ if (vibeEngines.analyzeEnvVariables) {
191
+ try {
192
+ const envFindings = vibeEngines.analyzeEnvVariables(content, filePath);
193
+ findings.push(...envFindings.filter(f =>
194
+ f.severity === 'BLOCK' || f.severity === 'WARN'
195
+ ).map(f => ({
196
+ ...f,
197
+ category: f.category || 'EnvVariable',
198
+ title: f.title,
199
+ why: f.message,
200
+ severity: f.severity,
201
+ evidence: [{ file: f.file, lines: String(f.line) }],
202
+ fixHints: [f.fixHint].filter(Boolean),
203
+ })));
204
+ } catch {}
205
+ }
206
+
207
+ // Run AI hallucination detection - core vibecheck functionality
208
+ if (vibeEngines.analyzeAIHallucinations) {
209
+ try {
210
+ const aiFindings = vibeEngines.analyzeAIHallucinations(content, filePath, {
211
+ includeTests: opts.includeTests || false,
212
+ });
213
+ findings.push(...aiFindings.filter(f =>
214
+ f.severity === 'BLOCK' || f.severity === 'WARN'
215
+ ).map(f => ({
216
+ ...f,
217
+ category: f.category || 'AIHallucination',
218
+ title: f.title || f.type,
219
+ why: f.message,
220
+ severity: f.severity,
221
+ evidence: [{ file: f.file, lines: String(f.line) }],
222
+ fixHints: [f.fixHint].filter(Boolean),
223
+ })));
224
+ } catch {}
225
+ }
226
+
227
+ // Run React patterns analysis (critical for React apps)
228
+ if (vibeEngines.analyzeReactPatterns) {
229
+ try {
230
+ const reactFindings = vibeEngines.analyzeReactPatterns(content, filePath);
231
+ findings.push(...reactFindings.filter(f =>
232
+ f.severity === 'BLOCK' || f.severity === 'WARN'
233
+ ).map(f => ({
234
+ ...f,
235
+ category: f.category || 'ReactPatterns',
236
+ title: f.title,
237
+ why: f.message,
238
+ severity: f.severity,
239
+ evidence: [{ file: f.file, lines: String(f.line) }],
240
+ fixHints: [f.fixHint].filter(Boolean),
241
+ })));
242
+ } catch {}
243
+ }
244
+
245
+ // Run database patterns analysis (critical for production)
246
+ if (vibeEngines.analyzeDatabasePatterns) {
247
+ try {
248
+ const dbFindings = vibeEngines.analyzeDatabasePatterns(content, filePath);
249
+ findings.push(...dbFindings.filter(f =>
250
+ f.severity === 'BLOCK' || f.severity === 'WARN'
251
+ ).map(f => ({
252
+ ...f,
253
+ category: f.category || 'DatabasePatterns',
254
+ title: f.title,
255
+ why: f.message,
256
+ severity: f.severity,
257
+ evidence: [{ file: f.file, lines: String(f.line) }],
258
+ fixHints: [f.fixHint].filter(Boolean),
259
+ })));
260
+ } catch {}
261
+ }
262
+
263
+ // Run error handling analysis
264
+ if (vibeEngines.analyzeErrorHandling) {
265
+ try {
266
+ const errorFindings = vibeEngines.analyzeErrorHandling(content, filePath);
267
+ findings.push(...errorFindings.filter(f =>
268
+ f.severity === 'BLOCK' || f.severity === 'WARN'
269
+ ).map(f => ({
270
+ ...f,
271
+ category: f.category || 'ErrorHandling',
272
+ title: f.title,
273
+ why: f.message,
274
+ severity: f.severity,
275
+ evidence: [{ file: f.file, lines: String(f.line) }],
276
+ fixHints: [f.fixHint].filter(Boolean),
277
+ })));
278
+ } catch {}
279
+ }
280
+
281
+ } catch (e) {
282
+ // Skip files that can't be read
283
+ }
284
+ }
285
+
286
+ // Run env setup analysis at project level
287
+ if (vibeEngines.analyzeEnvSetup) {
288
+ try {
289
+ const setupFindings = vibeEngines.analyzeEnvSetup(projectPath);
290
+ findings.push(...setupFindings.filter(f =>
291
+ f.severity === 'BLOCK' || f.severity === 'WARN'
292
+ ).map(f => ({
293
+ ...f,
294
+ category: f.category || 'EnvSetup',
295
+ title: f.title,
296
+ why: f.message,
297
+ severity: f.severity,
298
+ evidence: [{ file: f.file, lines: String(f.line) }],
299
+ fixHints: [f.fixHint].filter(Boolean),
300
+ })));
301
+ } catch {}
302
+ }
303
+
304
+ return findings;
305
+ }
306
+
41
307
  // ═══════════════════════════════════════════════════════════════════════════════
42
308
  // ENHANCED TERMINAL UI & OUTPUT MODULES
43
309
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -63,6 +329,9 @@ const {
63
329
  shipIcons,
64
330
  } = require("./lib/ship-output");
65
331
 
332
+ // Unified Output System
333
+ const { output } = require("./lib/output/index.js");
334
+
66
335
  const {
67
336
  formatShipOutput,
68
337
  } = require("./lib/ship-output-enterprise");
@@ -359,6 +628,8 @@ function getCategoryIcon(category) {
359
628
  'MissingRoute': ICONS.route,
360
629
  'EnvContract': ICONS.env,
361
630
  'EnvGap': ICONS.env,
631
+ 'EnvVariable': ICONS.env,
632
+ 'EnvSetup': ICONS.env,
362
633
  'FakeSuccess': ICONS.ghost,
363
634
  'GhostAuth': ICONS.auth,
364
635
  'StripeWebhook': ICONS.money,
@@ -369,6 +640,33 @@ function getCategoryIcon(category) {
369
640
  'Security': ICONS.shield,
370
641
  'Auth': ICONS.lock,
371
642
  'Fake Code': ICONS.ghost,
643
+ // New engine categories
644
+ 'AsyncPatterns': ICONS.lightning,
645
+ 'AIHallucination': ICONS.ghost,
646
+ 'NamingConventions': '📝',
647
+ 'floating_promise': ICONS.lightning,
648
+ 'empty_async_catch': ICONS.bug,
649
+ 'exposed_secret': ICONS.key,
650
+ 'insecure_default': ICONS.warning,
651
+ // React patterns
652
+ 'ReactPatterns': '⚛️',
653
+ 'missing_key': '⚛️',
654
+ 'conditional_hook': '⚛️',
655
+ 'direct_state_mutation': '⚛️',
656
+ 'stale_closure': '⚛️',
657
+ // Database patterns
658
+ 'DatabasePatterns': '🗃️',
659
+ 'n_plus_1_query': '🗃️',
660
+ 'query_in_loop': '🗃️',
661
+ 'unbounded_query': '🗃️',
662
+ 'missing_transaction': '🗃️',
663
+ 'raw_query_interpolation': ICONS.key,
664
+ // Error handling
665
+ 'ErrorHandling': ICONS.bug,
666
+ 'empty_catch': ICONS.bug,
667
+ 'generic_error_message': ICONS.bug,
668
+ // Import order
669
+ 'ImportOrder': '📦',
372
670
  };
373
671
  return icons[category] || ICONS.bug;
374
672
  }
@@ -633,6 +931,11 @@ function printHelp(showBanner = true) {
633
931
  ${colors.accent}--verbose, -v${ansi.reset} Show detailed progress
634
932
  ${colors.accent}--help, -h${ansi.reset} Show this help
635
933
 
934
+ ${ansi.bold}${colors.accent}Deploy Gate (CI/CD Integration):${ansi.reset}
935
+ ${colors.accent}--gate, -g${ansi.reset} Enable deploy gate mode ${ansi.dim}(blocks deploys on failures)${ansi.reset}
936
+ ${colors.accent}--fail-on${ansi.reset} What triggers failure: fake-features, warnings, any, blockers
937
+ ${ansi.dim}(default: fake-features)${ansi.reset}
938
+
636
939
  ${ansi.bold}Exit Codes:${ansi.reset}
637
940
  ${colors.success}0${ansi.reset} SHIP — Ready to ship
638
941
  ${colors.warning}1${ansi.reset} WARN — Warnings found, review recommended
@@ -650,6 +953,20 @@ function printHelp(showBanner = true) {
650
953
 
651
954
  ${ansi.dim}# Strict CI mode (warnings = failure)${ansi.reset}
652
955
  vibecheck ship --strict --ci
956
+
957
+ ${ansi.bold}${colors.accent}Deploy Gate Examples:${ansi.reset}
958
+
959
+ ${ansi.dim}# Block deploy if fake features detected (default)${ansi.reset}
960
+ vibecheck ship --gate
961
+
962
+ ${ansi.dim}# Block deploy on any warning${ansi.reset}
963
+ vibecheck ship --gate --fail-on=warnings
964
+
965
+ ${ansi.dim}# Block deploy only on critical blockers${ansi.reset}
966
+ vibecheck ship --gate --fail-on=blockers
967
+
968
+ ${ansi.dim}# Use in GitHub Actions / Vercel / Netlify${ansi.reset}
969
+ vibecheck ship --gate && npm run build
653
970
  `);
654
971
  }
655
972
 
@@ -731,13 +1048,40 @@ function getClaimType(category) {
731
1048
  const map = {
732
1049
  'MissingRoute': 'route_exists',
733
1050
  'EnvContract': 'env_declared',
1051
+ 'EnvVariable': 'env_validated',
1052
+ 'EnvSetup': 'env_secured',
734
1053
  'FakeSuccess': 'success_verified',
735
1054
  'GhostAuth': 'auth_protected',
736
1055
  'StripeWebhook': 'billing_enforced',
737
1056
  'PaidSurface': 'billing_enforced',
738
1057
  'OwnerModeBypass': 'billing_enforced',
739
1058
  'DeadUI': 'ui_wired',
740
- 'ContractDrift': 'contract_satisfied'
1059
+ 'ContractDrift': 'contract_satisfied',
1060
+ // New engine claim types
1061
+ 'AsyncPatterns': 'promises_handled',
1062
+ 'AIHallucination': 'implementation_real',
1063
+ 'NamingConventions': 'code_quality',
1064
+ 'floating_promise': 'promises_handled',
1065
+ 'empty_async_catch': 'errors_handled',
1066
+ 'exposed_secret': 'secrets_secured',
1067
+ 'insecure_default': 'config_secured',
1068
+ // React patterns
1069
+ 'ReactPatterns': 'react_patterns_valid',
1070
+ 'missing_key': 'list_keys_present',
1071
+ 'conditional_hook': 'hooks_order_valid',
1072
+ 'direct_state_mutation': 'state_immutable',
1073
+ 'stale_closure': 'deps_correct',
1074
+ // Database patterns
1075
+ 'DatabasePatterns': 'db_patterns_optimal',
1076
+ 'n_plus_1_query': 'queries_optimized',
1077
+ 'query_in_loop': 'queries_batched',
1078
+ 'unbounded_query': 'queries_limited',
1079
+ 'missing_transaction': 'transactions_used',
1080
+ 'raw_query_interpolation': 'queries_parameterized',
1081
+ // Error handling
1082
+ 'ErrorHandling': 'errors_handled',
1083
+ 'empty_catch': 'errors_logged',
1084
+ 'generic_error_message': 'errors_descriptive',
741
1085
  };
742
1086
  return map[category] || 'ui_wired';
743
1087
  }
@@ -746,13 +1090,40 @@ function getGapType(category) {
746
1090
  const map = {
747
1091
  'MissingRoute': 'missing_handler',
748
1092
  'EnvContract': 'missing_verification',
1093
+ 'EnvVariable': 'missing_validation',
1094
+ 'EnvSetup': 'missing_security',
749
1095
  'FakeSuccess': 'missing_verification',
750
1096
  'GhostAuth': 'missing_gate',
751
1097
  'StripeWebhook': 'missing_verification',
752
1098
  'PaidSurface': 'missing_gate',
753
1099
  'OwnerModeBypass': 'missing_gate',
754
1100
  'DeadUI': 'missing_handler',
755
- 'ContractDrift': 'contract_drift'
1101
+ 'ContractDrift': 'contract_drift',
1102
+ // New engine gap types
1103
+ 'AsyncPatterns': 'unhandled_async',
1104
+ 'AIHallucination': 'stub_implementation',
1105
+ 'NamingConventions': 'naming_issue',
1106
+ 'floating_promise': 'unhandled_promise',
1107
+ 'empty_async_catch': 'swallowed_error',
1108
+ 'exposed_secret': 'exposed_credential',
1109
+ 'insecure_default': 'insecure_config',
1110
+ // React patterns
1111
+ 'ReactPatterns': 'react_antipattern',
1112
+ 'missing_key': 'missing_list_key',
1113
+ 'conditional_hook': 'invalid_hook_call',
1114
+ 'direct_state_mutation': 'state_mutation',
1115
+ 'stale_closure': 'stale_deps',
1116
+ // Database patterns
1117
+ 'DatabasePatterns': 'db_antipattern',
1118
+ 'n_plus_1_query': 'n_plus_1',
1119
+ 'query_in_loop': 'loop_query',
1120
+ 'unbounded_query': 'no_limit',
1121
+ 'missing_transaction': 'no_transaction',
1122
+ 'raw_query_interpolation': 'sql_injection_risk',
1123
+ // Error handling
1124
+ 'ErrorHandling': 'poor_error_handling',
1125
+ 'empty_catch': 'swallowed_error',
1126
+ 'generic_error_message': 'generic_error',
756
1127
  };
757
1128
  return map[category] || 'untested_path';
758
1129
  }
@@ -779,6 +1150,10 @@ function parseArgs(args) {
779
1150
  help: globalFlags.help || false,
780
1151
  noBanner: globalFlags.noBanner || false,
781
1152
  quiet: globalFlags.quiet || false,
1153
+ // Deploy Gate mode - for CI/CD integration (Vercel, Netlify, GitHub Actions)
1154
+ gate: false,
1155
+ // Deploy Gate options
1156
+ failOn: "fake-features", // fake-features, warnings, any
782
1157
  };
783
1158
 
784
1159
  // Parse command-specific args
@@ -794,6 +1169,21 @@ function parseArgs(args) {
794
1169
  }
795
1170
  else if (a.startsWith("--path=")) opts.path = a.split("=")[1];
796
1171
  else if (a === "--path" || a === "-p") opts.path = args[++i];
1172
+ // Deploy Gate flags for CI/CD integration
1173
+ else if (a === "--gate" || a === "-g") {
1174
+ opts.gate = true;
1175
+ opts.ci = true; // Gate mode implies CI mode
1176
+ opts.json = true; // Gate mode outputs JSON for parsing
1177
+ }
1178
+ else if (a === "--fail-on") {
1179
+ const next = cleanArgs[++i];
1180
+ if (["fake-features", "warnings", "any", "blockers"].includes(next)) {
1181
+ opts.failOn = next;
1182
+ }
1183
+ }
1184
+ else if (a.startsWith("--fail-on=")) {
1185
+ opts.failOn = a.split("=")[1];
1186
+ }
797
1187
  }
798
1188
 
799
1189
  return opts;
@@ -811,6 +1201,13 @@ async function runShip(args, context = {}) {
811
1201
  const opts = parseArgs(args);
812
1202
  const executionStart = Date.now();
813
1203
 
1204
+ // Configure unified output mode
1205
+ output.setMode({
1206
+ json: opts.json,
1207
+ quiet: opts.quiet,
1208
+ ci: opts.ci
1209
+ });
1210
+
814
1211
  // Show help if requested
815
1212
  if (opts.help) {
816
1213
  printHelp(shouldShowBanner(opts));
@@ -865,26 +1262,36 @@ async function runShip(args, context = {}) {
865
1262
  spinner = new Spinner({ color: colors.accent });
866
1263
  }
867
1264
 
868
- // Phase 1: Production Integrity Check
869
- if (spinner) spinner.start('Checking production integrity...');
1265
+ // ═══════════════════════════════════════════════════════════════════════════
1266
+ // PHASE 1: Run ALL 17+ Scan Engines (comprehensive analysis)
1267
+ // ═══════════════════════════════════════════════════════════════════════════
1268
+ if (spinner) spinner.start(`Running comprehensive analysis (${ALL_ENGINES.length} engines)...`);
870
1269
 
871
- try {
872
- const { auditProductionIntegrity } = require(
873
- path.join(__dirname, "../../scripts/audit-production-integrity.js"),
874
- );
875
- const { results: integrityResults, integrity } = await auditProductionIntegrity(projectPath);
876
- results.score = integrity.score;
877
- results.grade = integrity.grade;
878
- results.canShip = integrity.canShip;
879
- results.deductions = integrity.deductions;
880
- results.integrity = integrityResults;
881
- } catch (err) {
882
- if (opts.verbose) console.warn(` ${ansi.dim}Integrity check skipped: ${err.message}${ansi.reset}`);
883
- }
1270
+ const engineResult = await runShipEngines(projectPath, {
1271
+ maxFiles: 2000,
1272
+ onProgress: opts.verbose ? (phase, pct) => {
1273
+ if (spinner) spinner.text = `${phase}: ${pct}%`;
1274
+ } : undefined,
1275
+ });
1276
+
1277
+ if (spinner) spinner.succeed(`Engines complete (${engineResult.findings.length} findings from ${ALL_ENGINES.length} engines)`);
1278
+
1279
+ // ═══════════════════════════════════════════════════════════════════════════
1280
+ // PHASE 2: Run Ship-Only Validators
1281
+ // ═══════════════════════════════════════════════════════════════════════════
1282
+ if (spinner) spinner.start('Running ship-only validators...');
1283
+
1284
+ const validatorResult = await runShipValidators(projectPath, {
1285
+ onProgress: opts.verbose ? (name, status) => {
1286
+ if (spinner) spinner.text = `${name}: ${status}`;
1287
+ } : undefined,
1288
+ });
884
1289
 
885
- if (spinner) spinner.succeed('Production integrity checked');
1290
+ if (spinner) spinner.succeed(`Validators complete (${validatorResult.findings.length} findings from ${Object.keys(validatorResult.stats.findingsPerValidator).length} validators)`);
886
1291
 
887
- // Phase 2: Route Truth Analysis
1292
+ // ═══════════════════════════════════════════════════════════════════════════
1293
+ // PHASE 3: Legacy analyzers (route truth, billing, etc.)
1294
+ // ═══════════════════════════════════════════════════════════════════════════
888
1295
  if (spinner) spinner.start('Building route truth map...');
889
1296
 
890
1297
  const fastifyEntry = detectFastifyEntry(projectPath);
@@ -892,8 +1299,8 @@ async function runShip(args, context = {}) {
892
1299
  writeTruthpack(projectPath, truthpack);
893
1300
  results.truthpack = truthpack;
894
1301
 
895
- // Run all analyzers
896
- const allFindings = [
1302
+ // Run legacy analyzers that aren't in the orchestrator
1303
+ const legacyFindings = [
897
1304
  ...findMissingRoutes(truthpack),
898
1305
  ...findEnvGaps(truthpack),
899
1306
  ...findFakeSuccess(projectPath),
@@ -901,7 +1308,6 @@ async function runShip(args, context = {}) {
901
1308
  ...findStripeWebhookViolations(truthpack),
902
1309
  ...findPaidSurfaceNotEnforced(truthpack),
903
1310
  ...findOwnerModeBypass(projectPath),
904
- // Merge runtime findings if --with runtime is specified
905
1311
  ...(opts.withRuntime ? findingsFromReality(projectPath) : [])
906
1312
  ];
907
1313
 
@@ -909,13 +1315,39 @@ async function runShip(args, context = {}) {
909
1315
  if (hasContracts(projectPath)) {
910
1316
  const contracts = loadContracts(projectPath);
911
1317
  const driftFindings = findContractDrift(contracts, truthpack);
912
- allFindings.push(...driftFindings);
1318
+ legacyFindings.push(...driftFindings);
913
1319
  }
914
1320
 
915
- results.findings = allFindings;
916
-
917
1321
  if (spinner) spinner.succeed(`Route truth mapped (${truthpack.routes?.server?.length || 0} routes)`);
918
1322
 
1323
+ // ═══════════════════════════════════════════════════════════════════════════
1324
+ // Combine all findings
1325
+ // ═══════════════════════════════════════════════════════════════════════════
1326
+ const allFindings = [
1327
+ // Engine findings (17+ engines)
1328
+ ...engineResult.findings.map(f => ({
1329
+ ...f,
1330
+ source: 'engine',
1331
+ severity: normalizeSeverityForShip(f.severity),
1332
+ })),
1333
+ // Validator findings (6 ship-only validators)
1334
+ ...validatorResult.findings.map(f => ({
1335
+ ...f,
1336
+ source: 'validator',
1337
+ severity: normalizeSeverityForShip(f.severity),
1338
+ })),
1339
+ // Legacy analyzer findings
1340
+ ...legacyFindings.map(f => ({
1341
+ ...f,
1342
+ source: 'legacy',
1343
+ severity: normalizeSeverityForShip(f.severity),
1344
+ })),
1345
+ ];
1346
+
1347
+ results.findings = allFindings;
1348
+ results.engineStats = engineResult.stats;
1349
+ results.validatorStats = validatorResult.stats;
1350
+
919
1351
  // Phase 3: Build Proof Graph
920
1352
  if (spinner) spinner.start('Building proof graph...');
921
1353
 
@@ -924,33 +1356,94 @@ async function runShip(args, context = {}) {
924
1356
 
925
1357
  if (spinner) spinner.succeed(`Proof graph built (${proofGraph.summary.totalClaims} claims)`);
926
1358
 
927
- // Calculate final verdict
928
- const blockers = allFindings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical');
929
- const warnings = allFindings.filter(f => f.severity === 'WARN' || f.severity === 'warning');
1359
+ // ═══════════════════════════════════════════════════════════════════════════
1360
+ // Calculate Production Readiness Score and Verdict
1361
+ // ═══════════════════════════════════════════════════════════════════════════
1362
+ const blockers = allFindings.filter(f => f.severity === 'BLOCK');
1363
+ const warnings = allFindings.filter(f => f.severity === 'WARN');
1364
+ const infos = allFindings.filter(f => f.severity === 'INFO');
1365
+
1366
+ // Check for hallucinations (critical for vibecheck)
1367
+ const hallucinations = allFindings.filter(f =>
1368
+ f.engine === 'ai-hallucination-engine' ||
1369
+ f.category?.includes('AIHallucination') ||
1370
+ f.category?.includes('Hallucination')
1371
+ );
930
1372
 
931
1373
  results.blockers = blockers;
932
1374
  results.warnings = warnings;
933
1375
 
934
- // Apply strict mode
935
- if (opts.strict && warnings.length > 0) {
936
- results.canShip = false;
937
- }
1376
+ // Calculate Production Readiness Score (0-100)
1377
+ // Start at 100 and deduct based on findings
1378
+ let score = 100;
938
1379
 
939
- if (blockers.length > 0) {
940
- results.canShip = false;
941
- }
1380
+ // Category-based deductions
1381
+ const categoryDeductions = {
1382
+ security: 0,
1383
+ quality: 0,
1384
+ hallucination: 0,
1385
+ routes: 0,
1386
+ contracts: 0,
1387
+ dependencies: 0,
1388
+ };
942
1389
 
943
- // Deduct score for findings
944
1390
  for (const finding of allFindings) {
1391
+ const category = categorizeFindings(finding);
1392
+
945
1393
  if (finding.severity === 'BLOCK') {
946
- results.score = Math.max(0, results.score - 15);
1394
+ categoryDeductions[category] += 15;
947
1395
  } else if (finding.severity === 'WARN') {
948
- results.score = Math.max(0, results.score - 5);
1396
+ categoryDeductions[category] += 5;
1397
+ } else {
1398
+ categoryDeductions[category] += 1;
949
1399
  }
950
1400
  }
951
1401
 
952
- const verdict = results.canShip ? 'SHIP' : blockers.length > 0 ? 'BLOCK' : 'WARN';
953
- const duration = Date.now() - startTime;
1402
+ // Apply weighted deductions (max 100 points deducted)
1403
+ const totalDeduction = Math.min(100,
1404
+ categoryDeductions.security * 1.5 + // Security issues weighted higher
1405
+ categoryDeductions.hallucination * 2.0 + // AI hallucinations weighted highest
1406
+ categoryDeductions.quality * 0.8 +
1407
+ categoryDeductions.routes * 1.0 +
1408
+ categoryDeductions.contracts * 1.0 +
1409
+ categoryDeductions.dependencies * 0.5
1410
+ );
1411
+
1412
+ score = Math.max(0, Math.round(100 - totalDeduction));
1413
+ results.score = score;
1414
+
1415
+ // Determine grade
1416
+ results.grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 50 ? 'D' : 'F';
1417
+
1418
+ // Determine verdict using the specified rules
1419
+ let verdict;
1420
+ const hasCritical = blockers.length > 0;
1421
+ const hasHallucinations = hallucinations.filter(f => f.severity !== 'INFO').length > 0;
1422
+
1423
+ if (hasCritical || score < 50) {
1424
+ verdict = 'BLOCK';
1425
+ results.canShip = false;
1426
+ } else if (hasHallucinations || score < 75 || (opts.strict && warnings.length > 0)) {
1427
+ verdict = 'WARN';
1428
+ results.canShip = false;
1429
+ } else {
1430
+ verdict = 'SHIP';
1431
+ results.canShip = true;
1432
+ }
1433
+
1434
+ // Build breakdown by category
1435
+ const breakdown = {
1436
+ security: allFindings.filter(f => categorizeFindings(f) === 'security'),
1437
+ quality: allFindings.filter(f => categorizeFindings(f) === 'quality'),
1438
+ hallucination: hallucinations,
1439
+ routes: allFindings.filter(f => categorizeFindings(f) === 'routes'),
1440
+ contracts: allFindings.filter(f => categorizeFindings(f) === 'contracts'),
1441
+ dependencies: allFindings.filter(f => categorizeFindings(f) === 'dependencies'),
1442
+ };
1443
+
1444
+ results.breakdown = breakdown;
1445
+
1446
+ const duration = Date.now() - executionStart;
954
1447
 
955
1448
  // ═══════════════════════════════════════════════════════════════════════════
956
1449
  // OUTPUT
@@ -1055,9 +1548,77 @@ async function runShip(args, context = {}) {
1055
1548
  tier: currentTier,
1056
1549
  }));
1057
1550
 
1058
- // Badge file generation (STARTER+ only)
1551
+ // V7: World-class Risk Radar and Pre-flight Checklist
1552
+ if (proofCertificate && !opts.quiet && !opts.json && !opts.ci) {
1553
+ // Risk Radar visualization
1554
+ const riskRadar = proofCertificate.calculateRiskRadar(allFindings);
1555
+ console.log();
1556
+ console.log(proofCertificate.renderRiskRadar(riskRadar));
1557
+
1558
+ // Pre-flight Checklist
1559
+ const preflight = proofCertificate.runPreflightChecklist(allFindings, proofGraph);
1560
+ console.log();
1561
+ console.log(proofCertificate.renderPreflightChecklist(preflight));
1562
+
1563
+ // Generate and save Proof Certificate
1564
+ const certificate = proofCertificate.generateProofCertificate({
1565
+ projectPath,
1566
+ projectName: path.basename(projectPath),
1567
+ verdict,
1568
+ score: results.score,
1569
+ findings: allFindings,
1570
+ truthpack,
1571
+ proofGraph,
1572
+ duration: Date.now() - executionStart,
1573
+ tier: currentTier,
1574
+ version: require('../../package.json').version || '1.0.0',
1575
+ });
1576
+
1577
+ // Save certificate
1578
+ fs.mkdirSync(outputDir, { recursive: true });
1579
+ fs.writeFileSync(
1580
+ path.join(outputDir, 'proof-certificate.json'),
1581
+ JSON.stringify(certificate.certificate, null, 2)
1582
+ );
1583
+
1584
+ // Show certificate ID
1585
+ console.log();
1586
+ console.log(` ${ansi.dim}╭${'─'.repeat(58)}╮${ansi.reset}`);
1587
+ console.log(` ${ansi.dim}│${ansi.reset} ${colors.accent}📜 PROOF CERTIFICATE${ansi.reset} ${ansi.dim}│${ansi.reset}`);
1588
+ console.log(` ${ansi.dim}├${'─'.repeat(58)}┤${ansi.reset}`);
1589
+ console.log(` ${ansi.dim}│${ansi.reset} ID: ${ansi.cyan}${certificate.certificate.certificateId}${ansi.reset} ${ansi.dim}│${ansi.reset}`);
1590
+ console.log(` ${ansi.dim}│${ansi.reset} Short Code: ${ansi.bold}${certificate.shortCode}${ansi.reset} ${ansi.dim}│${ansi.reset}`);
1591
+ console.log(` ${ansi.dim}│${ansi.reset} Confidence: ${ansi.bold}${certificate.certificate.verdict.confidence}%${ansi.reset} ${ansi.dim}│${ansi.reset}`);
1592
+ console.log(` ${ansi.dim}│${ansi.reset} Expires: ${certificate.certificate.expires.slice(0, 10)} ${ansi.dim}│${ansi.reset}`);
1593
+ console.log(` ${ansi.dim}├${'─'.repeat(58)}┤${ansi.reset}`);
1594
+ console.log(` ${ansi.dim}│${ansi.reset} ${ansi.dim}Verify at:${ansi.reset} ${ansi.cyan}${certificate.certificate.verificationUrl.slice(0, 45)}${ansi.reset} ${ansi.dim}│${ansi.reset}`);
1595
+ console.log(` ${ansi.dim}╰${'─'.repeat(58)}╯${ansi.reset}`);
1596
+ }
1597
+
1598
+ // Pro upsell for free users
1599
+ if (currentTier === 'free' && !opts.quiet) {
1600
+ console.log();
1601
+ console.log(` ${ansi.gray}${BOX.dTopLeft}${BOX.dHorizontal.repeat(66)}${BOX.dTopRight}${ansi.reset}`);
1602
+ console.log(` ${ansi.gray}${BOX.dVertical}${ansi.reset}${' '.repeat(66)}${ansi.gray}${BOX.dVertical}${ansi.reset}`);
1603
+
1604
+ if (verdict === 'SHIP') {
1605
+ console.log(` ${ansi.gray}${BOX.dVertical}${ansi.reset} ${ansi.magenta}★ PRO${ansi.reset} Generate a ${ansi.bold}status badge${ansi.reset} to show off your clean code! ${ansi.gray}${BOX.dVertical}${ansi.reset}`);
1606
+ console.log(` ${ansi.gray}${BOX.dVertical}${ansi.reset} Run: ${ansi.cyan}vibecheck ship --badge${ansi.reset} ${ansi.gray}${BOX.dVertical}${ansi.reset}`);
1607
+ } else if (allFindings.length > 0) {
1608
+ console.log(` ${ansi.gray}${BOX.dVertical}${ansi.reset} ${ansi.magenta}★ PRO${ansi.reset} Auto-fix all ${ansi.bold}${allFindings.length} issues${ansi.reset} instantly with AI ${ansi.gray}${BOX.dVertical}${ansi.reset}`);
1609
+ console.log(` ${ansi.gray}${BOX.dVertical}${ansi.reset} Run: ${ansi.cyan}vibecheck fix --apply${ansi.reset} ${ansi.gray}${BOX.dVertical}${ansi.reset}`);
1610
+ }
1611
+
1612
+ console.log(` ${ansi.gray}${BOX.dVertical}${ansi.reset}${' '.repeat(66)}${ansi.gray}${BOX.dVertical}${ansi.reset}`);
1613
+ console.log(` ${ansi.gray}${BOX.dVertical}${ansi.reset} Upgrade: ${ansi.cyan}https://vibecheckai.dev/pricing${ansi.reset} ${ansi.gray}${BOX.dVertical}${ansi.reset}`);
1614
+ console.log(` ${ansi.gray}${BOX.dVertical}${ansi.reset}${' '.repeat(66)}${ansi.gray}${BOX.dVertical}${ansi.reset}`);
1615
+ console.log(` ${ansi.gray}${BOX.dBottomLeft}${BOX.dHorizontal.repeat(66)}${BOX.dBottomRight}${ansi.reset}`);
1616
+ console.log();
1617
+ }
1618
+
1619
+ // Badge file generation (PRO only)
1059
1620
  if (opts.badge) {
1060
- const isVerified = opts.withRuntime && (currentTier === 'pro' || currentTier === 'compliance');
1621
+ const isVerified = opts.withRuntime && currentTier === 'pro';
1061
1622
  const { data: badgeData } = renderBadgeOutput(projectPath, verdict, results.score, {
1062
1623
  tier: currentTier,
1063
1624
  isVerified
@@ -1081,7 +1642,7 @@ async function runShip(args, context = {}) {
1081
1642
  // Earned upsell: Badge withheld when verdict != SHIP
1082
1643
  if (!results.canShip) {
1083
1644
  const currentTier = context?.authInfo?.access?.tier || "free";
1084
- if (entitlements.tierMeetsMinimum(currentTier, "starter")) {
1645
+ if (currentTier === "pro") {
1085
1646
  // User has badge access but verdict prevents it
1086
1647
  console.log(upsell.formatEarnedUpsell({
1087
1648
  cmd: "ship",
@@ -1109,7 +1670,92 @@ async function runShip(args, context = {}) {
1109
1670
  } catch {}
1110
1671
 
1111
1672
  // Exit code: 0=SHIP, 1=WARN, 2=BLOCK
1112
- const exitCode = getExitCode(verdict);
1673
+ let exitCode = getExitCode(verdict);
1674
+
1675
+ // Deploy Gate Mode: Stricter exit code logic for CI/CD integration
1676
+ if (opts.gate) {
1677
+ const hasFakeFeatures = allFindings.some(f =>
1678
+ f.category?.toLowerCase().includes('fake') ||
1679
+ f.category?.toLowerCase().includes('mock') ||
1680
+ f.type?.toLowerCase().includes('fake') ||
1681
+ f.title?.toLowerCase().includes('fake') ||
1682
+ f.title?.toLowerCase().includes('mock data')
1683
+ );
1684
+
1685
+ const hasBlockers = allFindings.some(f =>
1686
+ f.severity === 'BLOCK' || f.severity === 'critical' || f.severity === 'high'
1687
+ );
1688
+
1689
+ const hasWarnings = allFindings.some(f =>
1690
+ f.severity === 'WARN' || f.severity === 'warning' || f.severity === 'medium'
1691
+ );
1692
+
1693
+ // Determine gate failure based on --fail-on option
1694
+ let gateBlocked = false;
1695
+ let gateReason = '';
1696
+
1697
+ switch (opts.failOn) {
1698
+ case 'fake-features':
1699
+ gateBlocked = hasFakeFeatures;
1700
+ gateReason = 'Fake features detected';
1701
+ break;
1702
+ case 'warnings':
1703
+ gateBlocked = hasWarnings || hasBlockers || hasFakeFeatures;
1704
+ gateReason = hasBlockers ? 'Blockers found' : hasFakeFeatures ? 'Fake features detected' : 'Warnings found';
1705
+ break;
1706
+ case 'any':
1707
+ gateBlocked = allFindings.length > 0;
1708
+ gateReason = 'Issues detected';
1709
+ break;
1710
+ case 'blockers':
1711
+ default:
1712
+ gateBlocked = hasBlockers || hasFakeFeatures;
1713
+ gateReason = hasFakeFeatures ? 'Fake features detected' : 'Blockers found';
1714
+ break;
1715
+ }
1716
+
1717
+ if (gateBlocked) {
1718
+ exitCode = EXIT.BLOCKING;
1719
+
1720
+ // Output gate-specific JSON for CI/CD parsing
1721
+ if (opts.json) {
1722
+ console.log(JSON.stringify({
1723
+ gate: {
1724
+ blocked: true,
1725
+ reason: gateReason,
1726
+ failOn: opts.failOn,
1727
+ hasFakeFeatures,
1728
+ hasBlockers,
1729
+ hasWarnings,
1730
+ issueCount: allFindings.length,
1731
+ },
1732
+ verdict,
1733
+ score: results.score,
1734
+ canShip: false,
1735
+ exitCode,
1736
+ findings: allFindings.slice(0, 20), // Top 20 findings
1737
+ timestamp: new Date().toISOString(),
1738
+ }, null, 2));
1739
+ }
1740
+ } else if (opts.json) {
1741
+ console.log(JSON.stringify({
1742
+ gate: {
1743
+ blocked: false,
1744
+ reason: 'All checks passed',
1745
+ failOn: opts.failOn,
1746
+ hasFakeFeatures: false,
1747
+ hasBlockers: false,
1748
+ hasWarnings,
1749
+ issueCount: allFindings.length,
1750
+ },
1751
+ verdict,
1752
+ score: results.score,
1753
+ canShip: true,
1754
+ exitCode,
1755
+ timestamp: new Date().toISOString(),
1756
+ }, null, 2));
1757
+ }
1758
+ }
1113
1759
 
1114
1760
  // Save final results
1115
1761
  saveArtifact(runId, "summary", {
@@ -1117,6 +1763,8 @@ async function runShip(args, context = {}) {
1117
1763
  score: results.score,
1118
1764
  canShip: results.canShip,
1119
1765
  exitCode,
1766
+ gateMode: opts.gate,
1767
+ failOn: opts.failOn,
1120
1768
  timestamp: new Date().toISOString()
1121
1769
  });
1122
1770