@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.
- package/bin/registry.js +243 -152
- package/bin/runners/cli-utils.js +2 -33
- package/bin/runners/context/generators/cursor.js +49 -2
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
- package/bin/runners/lib/analyzers.js +544 -19
- package/bin/runners/lib/audit-logger.js +532 -0
- package/bin/runners/lib/authority/authorities/architecture.js +364 -0
- package/bin/runners/lib/authority/authorities/compliance.js +341 -0
- package/bin/runners/lib/authority/authorities/human.js +343 -0
- package/bin/runners/lib/authority/authorities/quality.js +420 -0
- package/bin/runners/lib/authority/authorities/security.js +228 -0
- package/bin/runners/lib/authority/index.js +293 -0
- package/bin/runners/lib/authority-badge.js +425 -425
- package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
- package/bin/runners/lib/cli-charts.js +368 -0
- package/bin/runners/lib/cli-config-display.js +405 -0
- package/bin/runners/lib/cli-demo.js +275 -0
- package/bin/runners/lib/cli-errors.js +438 -0
- package/bin/runners/lib/cli-help-formatter.js +439 -0
- package/bin/runners/lib/cli-interactive-menu.js +509 -0
- package/bin/runners/lib/cli-prompts.js +441 -0
- package/bin/runners/lib/cli-scan-cards.js +362 -0
- package/bin/runners/lib/compliance-reporter.js +710 -0
- package/bin/runners/lib/conductor/index.js +671 -0
- package/bin/runners/lib/easy/README.md +123 -0
- package/bin/runners/lib/easy/index.js +140 -0
- package/bin/runners/lib/easy/interactive-wizard.js +788 -0
- package/bin/runners/lib/easy/one-click-firewall.js +564 -0
- package/bin/runners/lib/easy/zero-config-reality.js +714 -0
- package/bin/runners/lib/engines/accessibility-engine.js +218 -18
- package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
- package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
- package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
- package/bin/runners/lib/engines/confidence-scoring.js +276 -0
- package/bin/runners/lib/engines/context-detection.js +264 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
- package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
- package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
- package/bin/runners/lib/engines/env-variables-engine.js +458 -0
- package/bin/runners/lib/engines/error-handling-engine.js +437 -0
- package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
- package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
- package/bin/runners/lib/engines/framework-detection.js +508 -0
- package/bin/runners/lib/engines/import-order-engine.js +429 -0
- package/bin/runners/lib/engines/mock-data-engine.js +53 -10
- package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
- package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
- package/bin/runners/lib/engines/orchestrator.js +334 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
- package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
- package/bin/runners/lib/engines/type-aware-engine.js +263 -39
- package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
- package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
- package/bin/runners/lib/enhanced-features/index.js +305 -0
- package/bin/runners/lib/enhanced-output.js +631 -0
- package/bin/runners/lib/enterprise.js +300 -0
- package/bin/runners/lib/entitlements-v2.js +103 -11
- package/bin/runners/lib/firewall/command-validator.js +351 -0
- package/bin/runners/lib/firewall/config.js +341 -0
- package/bin/runners/lib/firewall/content-validator.js +519 -0
- package/bin/runners/lib/firewall/index.js +101 -0
- package/bin/runners/lib/firewall/path-validator.js +256 -0
- package/bin/runners/lib/html-proof-report.js +350 -700
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
- package/bin/runners/lib/mcp-utils.js +425 -0
- package/bin/runners/lib/missions/plan.js +46 -6
- package/bin/runners/lib/missions/templates.js +232 -0
- package/bin/runners/lib/output/index.js +1022 -0
- package/bin/runners/lib/policy-engine.js +652 -0
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
- package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
- package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
- package/bin/runners/lib/polish/autofix/index.js +200 -0
- package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
- package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
- package/bin/runners/lib/polish/backend-checks.js +148 -0
- package/bin/runners/lib/polish/documentation-checks.js +111 -0
- package/bin/runners/lib/polish/frontend-checks.js +168 -0
- package/bin/runners/lib/polish/index.js +71 -0
- package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
- package/bin/runners/lib/polish/library-detection.js +175 -0
- package/bin/runners/lib/polish/performance-checks.js +100 -0
- package/bin/runners/lib/polish/security-checks.js +148 -0
- package/bin/runners/lib/polish/utils.js +203 -0
- package/bin/runners/lib/prompt-builder.js +540 -0
- package/bin/runners/lib/proof-certificate.js +634 -0
- package/bin/runners/lib/reality/accessibility-audit.js +946 -0
- package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
- package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
- package/bin/runners/lib/reality/performance-tracker.js +1077 -0
- package/bin/runners/lib/reality/scenario-generator.js +1404 -0
- package/bin/runners/lib/reality/visual-regression.js +852 -0
- package/bin/runners/lib/reality-profiler.js +717 -0
- package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
- package/bin/runners/lib/review/ai-code-review.js +832 -0
- package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
- package/bin/runners/lib/sbom-generator.js +641 -0
- package/bin/runners/lib/scan-output-enhanced.js +512 -0
- package/bin/runners/lib/scan-output.js +47 -0
- package/bin/runners/lib/security/owasp-scanner.js +939 -0
- package/bin/runners/lib/terminal-ui.js +113 -1
- package/bin/runners/lib/unified-cli-output.js +603 -430
- package/bin/runners/lib/validators/contract-validator.js +283 -0
- package/bin/runners/lib/validators/dead-export-detector.js +279 -0
- package/bin/runners/lib/validators/dep-audit.js +245 -0
- package/bin/runners/lib/validators/env-validator.js +319 -0
- package/bin/runners/lib/validators/index.js +120 -0
- package/bin/runners/lib/validators/license-checker.js +252 -0
- package/bin/runners/lib/validators/route-validator.js +290 -0
- package/bin/runners/runAIAgent.js +5 -10
- package/bin/runners/runAgent.js +3 -0
- package/bin/runners/runApprove.js +1233 -1200
- package/bin/runners/runAuth.js +22 -1
- package/bin/runners/runAuthority.js +528 -0
- package/bin/runners/runCheckpoint.js +4 -24
- package/bin/runners/runClassify.js +862 -859
- package/bin/runners/runConductor.js +772 -0
- package/bin/runners/runContainer.js +366 -0
- package/bin/runners/runContext.js +3 -0
- package/bin/runners/runDoctor.js +28 -41
- package/bin/runners/runEasy.js +410 -0
- package/bin/runners/runFirewall.js +3 -0
- package/bin/runners/runFirewallHook.js +3 -0
- package/bin/runners/runFix.js +76 -66
- package/bin/runners/runGuard.js +411 -18
- package/bin/runners/runIaC.js +372 -0
- package/bin/runners/runInit.js +10 -60
- package/bin/runners/runMcp.js +11 -12
- package/bin/runners/runPolish.js +240 -64
- package/bin/runners/runPromptFirewall.js +5 -12
- package/bin/runners/runProve.js +20 -55
- package/bin/runners/runReality.js +68 -59
- package/bin/runners/runReport.js +31 -5
- package/bin/runners/runRuntime.js +5 -8
- package/bin/runners/runScan.js +194 -1286
- package/bin/runners/runShip.js +695 -47
- package/bin/runners/runTruth.js +3 -0
- package/bin/runners/runValidate.js +7 -11
- package/bin/runners/runVibe.js +791 -0
- package/bin/runners/runWatch.js +14 -23
- package/bin/vibecheck.js +175 -56
- package/mcp-server/index.js +190 -14
- package/mcp-server/package.json +1 -1
- package/mcp-server/tools-v3.js +397 -64
- package/mcp-server/tools.js +495 -0
- package/package.json +1 -1
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
- package/mcp-server/index-v1.js +0 -698
package/bin/runners/runShip.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vibecheck ship -
|
|
3
|
-
*
|
|
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
|
-
//
|
|
869
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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(
|
|
1290
|
+
if (spinner) spinner.succeed(`Validators complete (${validatorResult.findings.length} findings from ${Object.keys(validatorResult.stats.findingsPerValidator).length} validators)`);
|
|
886
1291
|
|
|
887
|
-
//
|
|
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
|
|
896
|
-
const
|
|
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
|
-
|
|
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
|
-
//
|
|
928
|
-
|
|
929
|
-
|
|
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
|
-
//
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
}
|
|
1376
|
+
// Calculate Production Readiness Score (0-100)
|
|
1377
|
+
// Start at 100 and deduct based on findings
|
|
1378
|
+
let score = 100;
|
|
938
1379
|
|
|
939
|
-
|
|
940
|
-
|
|
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
|
-
|
|
1394
|
+
categoryDeductions[category] += 15;
|
|
947
1395
|
} else if (finding.severity === 'WARN') {
|
|
948
|
-
|
|
1396
|
+
categoryDeductions[category] += 5;
|
|
1397
|
+
} else {
|
|
1398
|
+
categoryDeductions[category] += 1;
|
|
949
1399
|
}
|
|
950
1400
|
}
|
|
951
1401
|
|
|
952
|
-
|
|
953
|
-
const
|
|
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
|
-
//
|
|
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 &&
|
|
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 (
|
|
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
|
-
|
|
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
|
|