@vibecheckai/cli 2.8.2 → 3.0.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/README.md +8 -8
- package/bin/_deprecations.js +35 -0
- package/bin/_router.js +46 -0
- package/bin/cli-hygiene.js +241 -0
- package/bin/guardrail.js +834 -0
- package/bin/runners/cli-utils.js +1070 -0
- package/bin/runners/context/ai-task-decomposer.js +337 -0
- package/bin/runners/context/analyzer.js +462 -0
- package/bin/runners/context/api-contracts.js +427 -0
- package/bin/runners/context/context-diff.js +342 -0
- package/bin/runners/context/context-pruner.js +291 -0
- package/bin/runners/context/dependency-graph.js +414 -0
- package/bin/runners/context/generators/claude.js +107 -0
- package/bin/runners/context/generators/codex.js +108 -0
- package/bin/runners/context/generators/copilot.js +119 -0
- package/bin/runners/context/generators/cursor.js +514 -0
- package/bin/runners/context/generators/mcp.js +151 -0
- package/bin/runners/context/generators/windsurf.js +180 -0
- package/bin/runners/context/git-context.js +302 -0
- package/bin/runners/context/index.js +1042 -0
- package/bin/runners/context/insights.js +173 -0
- package/bin/runners/context/mcp-server/generate-rules.js +337 -0
- package/bin/runners/context/mcp-server/index.js +1176 -0
- package/bin/runners/context/mcp-server/package.json +24 -0
- package/bin/runners/context/memory.js +200 -0
- package/bin/runners/context/monorepo.js +215 -0
- package/bin/runners/context/multi-repo-federation.js +404 -0
- package/bin/runners/context/patterns.js +253 -0
- package/bin/runners/context/proof-context.js +972 -0
- package/bin/runners/context/security-scanner.js +303 -0
- package/bin/runners/context/semantic-search.js +350 -0
- package/bin/runners/context/shared.js +264 -0
- package/bin/runners/context/team-conventions.js +310 -0
- package/bin/runners/lib/ai-bridge.js +416 -0
- package/bin/runners/lib/analysis-core.js +271 -0
- package/bin/runners/lib/analyzers.js +541 -0
- package/bin/runners/lib/audit-bridge.js +391 -0
- package/bin/runners/lib/auth-truth.js +193 -0
- package/bin/runners/lib/auth.js +215 -0
- package/bin/runners/lib/backup.js +62 -0
- package/bin/runners/lib/billing.js +107 -0
- package/bin/runners/lib/claims.js +118 -0
- package/bin/runners/lib/cli-ui.js +540 -0
- package/bin/runners/lib/compliance-bridge-new.js +0 -0
- package/bin/runners/lib/compliance-bridge.js +165 -0
- package/bin/runners/lib/contracts/auth-contract.js +194 -0
- package/bin/runners/lib/contracts/env-contract.js +178 -0
- package/bin/runners/lib/contracts/external-contract.js +198 -0
- package/bin/runners/lib/contracts/guard.js +168 -0
- package/bin/runners/lib/contracts/index.js +89 -0
- package/bin/runners/lib/contracts/plan-validator.js +311 -0
- package/bin/runners/lib/contracts/route-contract.js +192 -0
- package/bin/runners/lib/detect.js +89 -0
- package/bin/runners/lib/doctor/autofix.js +254 -0
- package/bin/runners/lib/doctor/index.js +37 -0
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
- package/bin/runners/lib/doctor/modules/index.js +46 -0
- package/bin/runners/lib/doctor/modules/network.js +250 -0
- package/bin/runners/lib/doctor/modules/project.js +312 -0
- package/bin/runners/lib/doctor/modules/runtime.js +224 -0
- package/bin/runners/lib/doctor/modules/security.js +348 -0
- package/bin/runners/lib/doctor/modules/system.js +213 -0
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -0
- package/bin/runners/lib/doctor/reporter.js +262 -0
- package/bin/runners/lib/doctor/service.js +262 -0
- package/bin/runners/lib/doctor/types.js +113 -0
- package/bin/runners/lib/doctor/ui.js +263 -0
- package/bin/runners/lib/doctor-enhanced.js +233 -0
- package/bin/runners/lib/doctor-v2.js +608 -0
- package/bin/runners/lib/enforcement.js +72 -0
- package/bin/runners/lib/enterprise-detect.js +603 -0
- package/bin/runners/lib/enterprise-init.js +942 -0
- package/bin/runners/lib/entitlements-v2.js +381 -0
- package/bin/runners/lib/entitlements.generated.js +0 -0
- package/bin/runners/lib/entitlements.js +332 -0
- package/bin/runners/lib/env-template.js +66 -0
- package/bin/runners/lib/env.js +189 -0
- package/bin/runners/lib/error-handler.js +320 -0
- package/bin/runners/lib/firewall-prompt.js +50 -0
- package/bin/runners/lib/graph/graph-builder.js +265 -0
- package/bin/runners/lib/graph/html-renderer.js +413 -0
- package/bin/runners/lib/graph/index.js +32 -0
- package/bin/runners/lib/graph/runtime-collector.js +215 -0
- package/bin/runners/lib/graph/static-extractor.js +518 -0
- package/bin/runners/lib/init-wizard.js +308 -0
- package/bin/runners/lib/json-output.js +76 -0
- package/bin/runners/lib/llm.js +75 -0
- package/bin/runners/lib/meter.js +61 -0
- package/bin/runners/lib/missions/evidence.js +126 -0
- package/bin/runners/lib/missions/plan.js +69 -0
- package/bin/runners/lib/missions/templates.js +147 -0
- package/bin/runners/lib/patch.js +40 -0
- package/bin/runners/lib/permissions/auth-model.js +213 -0
- package/bin/runners/lib/permissions/idor-prover.js +205 -0
- package/bin/runners/lib/permissions/index.js +45 -0
- package/bin/runners/lib/permissions/matrix-builder.js +198 -0
- package/bin/runners/lib/pkgjson.js +28 -0
- package/bin/runners/lib/preflight.js +142 -0
- package/bin/runners/lib/reality-findings.js +84 -0
- package/bin/runners/lib/redact.js +29 -0
- package/bin/runners/lib/replay/capsule-manager.js +154 -0
- package/bin/runners/lib/replay/index.js +263 -0
- package/bin/runners/lib/replay/player.js +348 -0
- package/bin/runners/lib/replay/recorder.js +331 -0
- package/bin/runners/lib/report-engine.js +447 -0
- package/bin/runners/lib/report-html.js +1117 -0
- package/bin/runners/lib/report-templates.js +964 -0
- package/bin/runners/lib/route-detection.js +1140 -0
- package/bin/runners/lib/route-truth.js +477 -0
- package/bin/runners/lib/sandbox/index.js +59 -0
- package/bin/runners/lib/sandbox/proof-chain.js +399 -0
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -0
- package/bin/runners/lib/sandbox/worktree.js +174 -0
- package/bin/runners/lib/scan-cache.js +330 -0
- package/bin/runners/lib/scan-output-schema.js +344 -0
- package/bin/runners/lib/score-history.js +282 -0
- package/bin/runners/lib/security-bridge.js +249 -0
- package/bin/runners/lib/server-usage.js +513 -0
- package/bin/runners/lib/share-pack.js +239 -0
- package/bin/runners/lib/snippets.js +67 -0
- package/bin/runners/lib/truth.js +667 -0
- package/bin/runners/lib/unified-output.js +189 -0
- package/bin/runners/lib/validate-patch.js +156 -0
- package/bin/runners/lib/verification.js +345 -0
- package/bin/runners/reality/engine.js +917 -0
- package/bin/runners/reality/flows.js +122 -0
- package/bin/runners/reality/report.js +378 -0
- package/bin/runners/reality/session.js +193 -0
- package/bin/runners/runAIAgent.js +2 -0
- package/bin/runners/runAudit.js +2 -0
- package/bin/runners/runAuth.js +106 -0
- package/bin/runners/runAutopilot.js +2 -0
- package/bin/runners/runBadge.js +2 -0
- package/bin/runners/runCertify.js +2 -0
- package/bin/runners/runClaimVerifier.js +483 -0
- package/bin/runners/runContext.js +56 -0
- package/bin/runners/runContextCompiler.js +385 -0
- package/bin/runners/runCtx.js +187 -0
- package/bin/runners/runCtxGuard.js +176 -0
- package/bin/runners/runCtxSync.js +116 -0
- package/bin/runners/runDashboard.js +10 -0
- package/bin/runners/runDoctor.js +245 -0
- package/bin/runners/runEnhancedShip.js +2 -0
- package/bin/runners/runFix.js +735 -0
- package/bin/runners/runFixPacks.js +2 -0
- package/bin/runners/runGate.js +17 -0
- package/bin/runners/runGraph.js +283 -0
- package/bin/runners/runInit.js +260 -0
- package/bin/runners/runInitGha.js +101 -0
- package/bin/runners/runInstall.js +76 -0
- package/bin/runners/runInteractive.js +388 -0
- package/bin/runners/runLaunch.js +2 -0
- package/bin/runners/runMcp.js +19 -0
- package/bin/runners/runMdc.js +2 -0
- package/bin/runners/runMissionGenerator.js +282 -0
- package/bin/runners/runNaturalLanguage.js +3 -0
- package/bin/runners/runPR.js +96 -0
- package/bin/runners/runPermissions.js +290 -0
- package/bin/runners/runPromptFirewall.js +211 -0
- package/bin/runners/runProof.js +2 -0
- package/bin/runners/runProve.js +392 -0
- package/bin/runners/runReality.js +489 -0
- package/bin/runners/runRealitySniff.js +2 -0
- package/bin/runners/runReplay.js +469 -0
- package/bin/runners/runReport.js +478 -0
- package/bin/runners/runScan.js +835 -0
- package/bin/runners/runShare.js +34 -0
- package/bin/runners/runShip.js +1062 -0
- package/bin/runners/runStatus.js +136 -0
- package/bin/runners/runTruthpack.js +634 -0
- package/bin/runners/runUpgrade.js +2 -0
- package/bin/runners/runValidate.js +2 -0
- package/bin/runners/runVerifyAgentOutput.js +2 -0
- package/bin/runners/runWatch.js +230 -0
- package/bin/runners/utils.js +360 -0
- package/bin/scan.js +612 -0
- package/bin/vibecheck.js +834 -0
- package/package.json +11 -11
- package/dist/autopatch/verified-autopatch.d.ts +0 -111
- package/dist/autopatch/verified-autopatch.d.ts.map +0 -1
- package/dist/autopatch/verified-autopatch.js +0 -503
- package/dist/autopatch/verified-autopatch.js.map +0 -1
- package/dist/bundles/index.js +0 -8
- package/dist/bundles/vibecheck-core.js +0 -25799
- package/dist/bundles/vibecheck-security.js +0 -208693
- package/dist/bundles/vibecheck-ship.js +0 -2318
- package/dist/commands/baseline.d.ts +0 -7
- package/dist/commands/baseline.d.ts.map +0 -1
- package/dist/commands/baseline.js +0 -79
- package/dist/commands/baseline.js.map +0 -1
- package/dist/commands/cache.d.ts +0 -13
- package/dist/commands/cache.d.ts.map +0 -1
- package/dist/commands/cache.js +0 -165
- package/dist/commands/cache.js.map +0 -1
- package/dist/commands/checkpoint.d.ts +0 -8
- package/dist/commands/checkpoint.d.ts.map +0 -1
- package/dist/commands/checkpoint.js +0 -35
- package/dist/commands/checkpoint.js.map +0 -1
- package/dist/commands/doctor.d.ts +0 -17
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -226
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/evidence.d.ts +0 -45
- package/dist/commands/evidence.d.ts.map +0 -1
- package/dist/commands/evidence.js +0 -197
- package/dist/commands/evidence.js.map +0 -1
- package/dist/commands/explain.d.ts +0 -8
- package/dist/commands/explain.d.ts.map +0 -1
- package/dist/commands/explain.js +0 -52
- package/dist/commands/explain.js.map +0 -1
- package/dist/commands/fix-consolidated.d.ts +0 -19
- package/dist/commands/fix-consolidated.d.ts.map +0 -1
- package/dist/commands/fix-consolidated.js +0 -165
- package/dist/commands/fix-consolidated.js.map +0 -1
- package/dist/commands/index.d.ts +0 -8
- package/dist/commands/index.d.ts.map +0 -1
- package/dist/commands/index.js +0 -15
- package/dist/commands/index.js.map +0 -1
- package/dist/commands/init.d.ts +0 -8
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -125
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/launcher.d.ts +0 -10
- package/dist/commands/launcher.d.ts.map +0 -1
- package/dist/commands/launcher.js +0 -174
- package/dist/commands/launcher.js.map +0 -1
- package/dist/commands/on.d.ts +0 -8
- package/dist/commands/on.d.ts.map +0 -1
- package/dist/commands/on.js +0 -123
- package/dist/commands/on.js.map +0 -1
- package/dist/commands/replay.d.ts +0 -8
- package/dist/commands/replay.d.ts.map +0 -1
- package/dist/commands/replay.js +0 -52
- package/dist/commands/replay.js.map +0 -1
- package/dist/commands/scan-consolidated.d.ts +0 -61
- package/dist/commands/scan-consolidated.d.ts.map +0 -1
- package/dist/commands/scan-consolidated.js +0 -243
- package/dist/commands/scan-consolidated.js.map +0 -1
- package/dist/commands/scan-secrets.d.ts +0 -47
- package/dist/commands/scan-secrets.d.ts.map +0 -1
- package/dist/commands/scan-secrets.js +0 -225
- package/dist/commands/scan-secrets.js.map +0 -1
- package/dist/commands/scan-vulnerabilities-enhanced.d.ts +0 -41
- package/dist/commands/scan-vulnerabilities-enhanced.d.ts.map +0 -1
- package/dist/commands/scan-vulnerabilities-enhanced.js +0 -368
- package/dist/commands/scan-vulnerabilities-enhanced.js.map +0 -1
- package/dist/commands/scan-vulnerabilities-osv.d.ts +0 -58
- package/dist/commands/scan-vulnerabilities-osv.d.ts.map +0 -1
- package/dist/commands/scan-vulnerabilities-osv.js +0 -722
- package/dist/commands/scan-vulnerabilities-osv.js.map +0 -1
- package/dist/commands/scan-vulnerabilities.d.ts +0 -32
- package/dist/commands/scan-vulnerabilities.d.ts.map +0 -1
- package/dist/commands/scan-vulnerabilities.js +0 -283
- package/dist/commands/scan-vulnerabilities.js.map +0 -1
- package/dist/commands/secrets-allowlist.d.ts +0 -7
- package/dist/commands/secrets-allowlist.d.ts.map +0 -1
- package/dist/commands/secrets-allowlist.js +0 -85
- package/dist/commands/secrets-allowlist.js.map +0 -1
- package/dist/commands/ship-consolidated.d.ts +0 -58
- package/dist/commands/ship-consolidated.d.ts.map +0 -1
- package/dist/commands/ship-consolidated.js +0 -515
- package/dist/commands/ship-consolidated.js.map +0 -1
- package/dist/commands/stats.d.ts +0 -8
- package/dist/commands/stats.d.ts.map +0 -1
- package/dist/commands/stats.js +0 -134
- package/dist/commands/stats.js.map +0 -1
- package/dist/commands/upgrade.d.ts +0 -8
- package/dist/commands/upgrade.d.ts.map +0 -1
- package/dist/commands/upgrade.js +0 -30
- package/dist/commands/upgrade.js.map +0 -1
- package/dist/fix/applicator.d.ts +0 -44
- package/dist/fix/applicator.d.ts.map +0 -1
- package/dist/fix/applicator.js +0 -144
- package/dist/fix/applicator.js.map +0 -1
- package/dist/fix/backup.d.ts +0 -38
- package/dist/fix/backup.d.ts.map +0 -1
- package/dist/fix/backup.js +0 -154
- package/dist/fix/backup.js.map +0 -1
- package/dist/fix/engine.d.ts +0 -55
- package/dist/fix/engine.d.ts.map +0 -1
- package/dist/fix/engine.js +0 -285
- package/dist/fix/engine.js.map +0 -1
- package/dist/fix/index.d.ts +0 -5
- package/dist/fix/index.d.ts.map +0 -1
- package/dist/fix/index.js +0 -12
- package/dist/fix/index.js.map +0 -1
- package/dist/fix/interactive.d.ts +0 -22
- package/dist/fix/interactive.d.ts.map +0 -1
- package/dist/fix/interactive.js +0 -172
- package/dist/fix/interactive.js.map +0 -1
- package/dist/formatters/index.d.ts +0 -6
- package/dist/formatters/index.d.ts.map +0 -1
- package/dist/formatters/index.js +0 -11
- package/dist/formatters/index.js.map +0 -1
- package/dist/formatters/sarif-enhanced.d.ts +0 -78
- package/dist/formatters/sarif-enhanced.d.ts.map +0 -1
- package/dist/formatters/sarif-enhanced.js +0 -144
- package/dist/formatters/sarif-enhanced.js.map +0 -1
- package/dist/formatters/sarif-v2.d.ts +0 -121
- package/dist/formatters/sarif-v2.d.ts.map +0 -1
- package/dist/formatters/sarif-v2.js +0 -356
- package/dist/formatters/sarif-v2.js.map +0 -1
- package/dist/formatters/sarif.d.ts +0 -72
- package/dist/formatters/sarif.d.ts.map +0 -1
- package/dist/formatters/sarif.js +0 -146
- package/dist/formatters/sarif.js.map +0 -1
- package/dist/index.d.ts +0 -61
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -4388
- package/dist/index.js.map +0 -1
- package/dist/init/ci-generator.d.ts +0 -18
- package/dist/init/ci-generator.d.ts.map +0 -1
- package/dist/init/ci-generator.js +0 -317
- package/dist/init/ci-generator.js.map +0 -1
- package/dist/init/detect-framework.d.ts +0 -15
- package/dist/init/detect-framework.d.ts.map +0 -1
- package/dist/init/detect-framework.js +0 -301
- package/dist/init/detect-framework.js.map +0 -1
- package/dist/init/hooks-installer.d.ts +0 -22
- package/dist/init/hooks-installer.d.ts.map +0 -1
- package/dist/init/hooks-installer.js +0 -310
- package/dist/init/hooks-installer.js.map +0 -1
- package/dist/init/index.d.ts +0 -8
- package/dist/init/index.d.ts.map +0 -1
- package/dist/init/index.js +0 -22
- package/dist/init/index.js.map +0 -1
- package/dist/init/templates.d.ts +0 -402
- package/dist/init/templates.d.ts.map +0 -1
- package/dist/init/templates.js +0 -240
- package/dist/init/templates.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -12
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -42
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/telemetry.d.ts +0 -40
- package/dist/mcp/telemetry.d.ts.map +0 -1
- package/dist/mcp/telemetry.js +0 -98
- package/dist/mcp/telemetry.js.map +0 -1
- package/dist/reality/no-dead-buttons/button-sweep-generator.d.ts +0 -32
- package/dist/reality/no-dead-buttons/button-sweep-generator.d.ts.map +0 -1
- package/dist/reality/no-dead-buttons/button-sweep-generator.js +0 -236
- package/dist/reality/no-dead-buttons/button-sweep-generator.js.map +0 -1
- package/dist/reality/no-dead-buttons/index.d.ts +0 -11
- package/dist/reality/no-dead-buttons/index.d.ts.map +0 -1
- package/dist/reality/no-dead-buttons/index.js +0 -18
- package/dist/reality/no-dead-buttons/index.js.map +0 -1
- package/dist/reality/no-dead-buttons/static-scanner.d.ts +0 -34
- package/dist/reality/no-dead-buttons/static-scanner.d.ts.map +0 -1
- package/dist/reality/no-dead-buttons/static-scanner.js +0 -230
- package/dist/reality/no-dead-buttons/static-scanner.js.map +0 -1
- package/dist/reality/reality-graph.d.ts +0 -192
- package/dist/reality/reality-graph.d.ts.map +0 -1
- package/dist/reality/reality-graph.js +0 -600
- package/dist/reality/reality-graph.js.map +0 -1
- package/dist/reality/reality-runner.d.ts +0 -89
- package/dist/reality/reality-runner.d.ts.map +0 -1
- package/dist/reality/reality-runner.js +0 -540
- package/dist/reality/reality-runner.js.map +0 -1
- package/dist/reality/receipt-generator.d.ts +0 -152
- package/dist/reality/receipt-generator.d.ts.map +0 -1
- package/dist/reality/receipt-generator.js +0 -495
- package/dist/reality/receipt-generator.js.map +0 -1
- package/dist/reality/runtime-tracer.d.ts +0 -75
- package/dist/reality/runtime-tracer.d.ts.map +0 -1
- package/dist/reality/runtime-tracer.js +0 -109
- package/dist/reality/runtime-tracer.js.map +0 -1
- package/dist/runtime/auth-utils.d.ts +0 -43
- package/dist/runtime/auth-utils.d.ts.map +0 -1
- package/dist/runtime/auth-utils.js +0 -130
- package/dist/runtime/auth-utils.js.map +0 -1
- package/dist/runtime/client.d.ts +0 -74
- package/dist/runtime/client.d.ts.map +0 -1
- package/dist/runtime/client.js +0 -222
- package/dist/runtime/client.js.map +0 -1
- package/dist/runtime/creds.d.ts +0 -48
- package/dist/runtime/creds.d.ts.map +0 -1
- package/dist/runtime/creds.js +0 -245
- package/dist/runtime/creds.js.map +0 -1
- package/dist/runtime/exit-codes.d.ts +0 -49
- package/dist/runtime/exit-codes.d.ts.map +0 -1
- package/dist/runtime/exit-codes.js +0 -93
- package/dist/runtime/exit-codes.js.map +0 -1
- package/dist/runtime/index.d.ts +0 -9
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js +0 -25
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/json-output.d.ts +0 -42
- package/dist/runtime/json-output.d.ts.map +0 -1
- package/dist/runtime/json-output.js +0 -59
- package/dist/runtime/json-output.js.map +0 -1
- package/dist/runtime/semver.d.ts +0 -37
- package/dist/runtime/semver.d.ts.map +0 -1
- package/dist/runtime/semver.js +0 -110
- package/dist/runtime/semver.js.map +0 -1
- package/dist/scan/dead-ui-detector.d.ts +0 -48
- package/dist/scan/dead-ui-detector.d.ts.map +0 -1
- package/dist/scan/dead-ui-detector.js +0 -170
- package/dist/scan/dead-ui-detector.js.map +0 -1
- package/dist/scan/playwright-sweep.d.ts +0 -40
- package/dist/scan/playwright-sweep.d.ts.map +0 -1
- package/dist/scan/playwright-sweep.js +0 -216
- package/dist/scan/playwright-sweep.js.map +0 -1
- package/dist/scan/proof-bundle.d.ts +0 -25
- package/dist/scan/proof-bundle.d.ts.map +0 -1
- package/dist/scan/proof-bundle.js +0 -203
- package/dist/scan/proof-bundle.js.map +0 -1
- package/dist/scan/proof-graph.d.ts +0 -59
- package/dist/scan/proof-graph.d.ts.map +0 -1
- package/dist/scan/proof-graph.js +0 -64
- package/dist/scan/proof-graph.js.map +0 -1
- package/dist/scan/reality-sniff.d.ts +0 -56
- package/dist/scan/reality-sniff.d.ts.map +0 -1
- package/dist/scan/reality-sniff.js +0 -200
- package/dist/scan/reality-sniff.js.map +0 -1
- package/dist/scan/structural-verifier.d.ts +0 -20
- package/dist/scan/structural-verifier.d.ts.map +0 -1
- package/dist/scan/structural-verifier.js +0 -112
- package/dist/scan/structural-verifier.js.map +0 -1
- package/dist/scan/verification-engine.d.ts +0 -47
- package/dist/scan/verification-engine.d.ts.map +0 -1
- package/dist/scan/verification-engine.js +0 -141
- package/dist/scan/verification-engine.js.map +0 -1
- package/dist/scanner/baseline.d.ts +0 -52
- package/dist/scanner/baseline.d.ts.map +0 -1
- package/dist/scanner/baseline.js +0 -85
- package/dist/scanner/baseline.js.map +0 -1
- package/dist/scanner/incremental.d.ts +0 -30
- package/dist/scanner/incremental.d.ts.map +0 -1
- package/dist/scanner/incremental.js +0 -82
- package/dist/scanner/incremental.js.map +0 -1
- package/dist/scanner/parallel.d.ts +0 -43
- package/dist/scanner/parallel.d.ts.map +0 -1
- package/dist/scanner/parallel.js +0 -99
- package/dist/scanner/parallel.js.map +0 -1
- package/dist/standalone.d.ts +0 -1
- package/dist/standalone.d.ts.map +0 -1
- package/dist/standalone.js +0 -1
- package/dist/standalone.js.map +0 -1
- package/dist/truth-pack/index.d.ts +0 -102
- package/dist/truth-pack/index.d.ts.map +0 -1
- package/dist/truth-pack/index.js +0 -694
- package/dist/truth-pack/index.js.map +0 -1
- package/dist/ui/frame.d.ts +0 -68
- package/dist/ui/frame.d.ts.map +0 -1
- package/dist/ui/frame.js +0 -165
- package/dist/ui/frame.js.map +0 -1
- package/dist/ui/index.d.ts +0 -5
- package/dist/ui/index.d.ts.map +0 -1
- package/dist/ui/index.js +0 -16
- package/dist/ui/index.js.map +0 -1
- package/dist/ui.d.ts +0 -36
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js +0 -45
- package/dist/ui.js.map +0 -1
|
@@ -0,0 +1,1062 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck ship - The Vibe Coder's Best Friend
|
|
3
|
+
* Zero config. Plain English. One command to ship with confidence.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const { withErrorHandling } = require("./lib/error-handler");
|
|
9
|
+
const { ensureOutputDir, detectProjectFeatures } = require("./utils");
|
|
10
|
+
const { enforceLimit, enforceFeature, trackUsage, getCurrentTier } = require("./lib/entitlements");
|
|
11
|
+
const { emitShipCheck } = require("./lib/audit-bridge");
|
|
12
|
+
|
|
13
|
+
// Route Truth v1 - Fake endpoint detection
|
|
14
|
+
const { buildTruthpack, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
|
|
15
|
+
const {
|
|
16
|
+
findMissingRoutes,
|
|
17
|
+
findEnvGaps,
|
|
18
|
+
findFakeSuccess,
|
|
19
|
+
findGhostAuth,
|
|
20
|
+
findStripeWebhookViolations,
|
|
21
|
+
findPaidSurfaceNotEnforced,
|
|
22
|
+
findOwnerModeBypass
|
|
23
|
+
} = require("./lib/analyzers");
|
|
24
|
+
const { findingsFromReality } = require("./lib/reality-findings");
|
|
25
|
+
|
|
26
|
+
// Build proof graph from findings for evidence-backed verdicts
|
|
27
|
+
function buildProofGraph(findings, truthpack, root) {
|
|
28
|
+
const claims = [];
|
|
29
|
+
let claimId = 0;
|
|
30
|
+
|
|
31
|
+
for (const finding of findings) {
|
|
32
|
+
const claim = {
|
|
33
|
+
id: `claim-${++claimId}`,
|
|
34
|
+
type: getClaimType(finding.category),
|
|
35
|
+
assertion: finding.title || finding.message,
|
|
36
|
+
verified: finding.severity !== 'BLOCK',
|
|
37
|
+
confidence: finding.confidence === 'high' ? 0.9 : finding.confidence === 'medium' ? 0.7 : 0.5,
|
|
38
|
+
evidence: (finding.evidence || []).map((e, i) => ({
|
|
39
|
+
id: `evidence-${claimId}-${i}`,
|
|
40
|
+
type: 'file_citation',
|
|
41
|
+
file: e.file,
|
|
42
|
+
line: parseInt(e.lines?.split('-')[0]) || e.line || 1,
|
|
43
|
+
snippet: e.snippetHash || '',
|
|
44
|
+
strength: 0.8,
|
|
45
|
+
verifiedAt: new Date().toISOString(),
|
|
46
|
+
method: 'static'
|
|
47
|
+
})),
|
|
48
|
+
gaps: [{
|
|
49
|
+
id: `gap-${claimId}`,
|
|
50
|
+
type: getGapType(finding.category),
|
|
51
|
+
description: finding.why || finding.title,
|
|
52
|
+
severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
|
|
53
|
+
suggestion: (finding.fixHints || [])[0] || ''
|
|
54
|
+
}],
|
|
55
|
+
severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
|
|
56
|
+
file: finding.evidence?.[0]?.file || '',
|
|
57
|
+
line: parseInt(finding.evidence?.[0]?.lines?.split('-')[0]) || 1
|
|
58
|
+
};
|
|
59
|
+
claims.push(claim);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const verifiedClaims = claims.filter(c => c.verified);
|
|
63
|
+
const failedClaims = claims.filter(c => !c.verified);
|
|
64
|
+
const allGaps = claims.flatMap(c => c.gaps);
|
|
65
|
+
|
|
66
|
+
const riskScore = Math.min(100, failedClaims.reduce((sum, c) => {
|
|
67
|
+
if (c.severity === 'critical') return sum + 30;
|
|
68
|
+
if (c.severity === 'high') return sum + 20;
|
|
69
|
+
if (c.severity === 'medium') return sum + 10;
|
|
70
|
+
return sum + 5;
|
|
71
|
+
}, 0));
|
|
72
|
+
|
|
73
|
+
const confidence = claims.length > 0
|
|
74
|
+
? claims.reduce((sum, c) => sum + c.confidence, 0) / claims.length
|
|
75
|
+
: 1.0;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
generatedAt: new Date().toISOString(),
|
|
80
|
+
projectPath: root,
|
|
81
|
+
claims,
|
|
82
|
+
summary: {
|
|
83
|
+
totalClaims: claims.length,
|
|
84
|
+
verifiedClaims: verifiedClaims.length,
|
|
85
|
+
failedClaims: failedClaims.length,
|
|
86
|
+
gaps: allGaps.length,
|
|
87
|
+
riskScore,
|
|
88
|
+
confidence
|
|
89
|
+
},
|
|
90
|
+
verdict: findings.some(f => f.severity === 'BLOCK') ? 'BLOCK' :
|
|
91
|
+
findings.some(f => f.severity === 'WARN') ? 'WARN' : 'SHIP',
|
|
92
|
+
topBlockers: failedClaims.filter(c => c.severity === 'critical' || c.severity === 'high').slice(0, 5),
|
|
93
|
+
topGaps: allGaps.filter(g => g.severity === 'critical' || g.severity === 'high').slice(0, 5)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getClaimType(category) {
|
|
98
|
+
const map = {
|
|
99
|
+
'MissingRoute': 'route_exists',
|
|
100
|
+
'EnvContract': 'env_declared',
|
|
101
|
+
'FakeSuccess': 'success_verified',
|
|
102
|
+
'GhostAuth': 'auth_protected',
|
|
103
|
+
'StripeWebhook': 'billing_enforced',
|
|
104
|
+
'PaidSurface': 'billing_enforced',
|
|
105
|
+
'OwnerModeBypass': 'billing_enforced',
|
|
106
|
+
'DeadUI': 'ui_wired'
|
|
107
|
+
};
|
|
108
|
+
return map[category] || 'ui_wired';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getGapType(category) {
|
|
112
|
+
const map = {
|
|
113
|
+
'MissingRoute': 'missing_handler',
|
|
114
|
+
'EnvContract': 'missing_verification',
|
|
115
|
+
'FakeSuccess': 'missing_verification',
|
|
116
|
+
'GhostAuth': 'missing_gate',
|
|
117
|
+
'StripeWebhook': 'missing_verification',
|
|
118
|
+
'PaidSurface': 'missing_gate',
|
|
119
|
+
'OwnerModeBypass': 'missing_gate',
|
|
120
|
+
'DeadUI': 'missing_handler'
|
|
121
|
+
};
|
|
122
|
+
return map[category] || 'untested_path';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ANSI color codes
|
|
126
|
+
const c = {
|
|
127
|
+
reset: "\x1b[0m",
|
|
128
|
+
bold: "\x1b[1m",
|
|
129
|
+
dim: "\x1b[2m",
|
|
130
|
+
red: "\x1b[31m",
|
|
131
|
+
green: "\x1b[32m",
|
|
132
|
+
yellow: "\x1b[33m",
|
|
133
|
+
blue: "\x1b[34m",
|
|
134
|
+
magenta: "\x1b[35m",
|
|
135
|
+
cyan: "\x1b[36m",
|
|
136
|
+
white: "\x1b[37m",
|
|
137
|
+
bgRed: "\x1b[41m",
|
|
138
|
+
bgGreen: "\x1b[42m",
|
|
139
|
+
bgYellow: "\x1b[43m",
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const PLAIN_ENGLISH = {
|
|
143
|
+
secretExposed: (type) => ({
|
|
144
|
+
message: `🔑 Your ${type} is visible in the code - hackers can steal it`,
|
|
145
|
+
why: `If this code is pushed to GitHub or deployed, anyone can see your ${type} and use it maliciously.`,
|
|
146
|
+
fix: `Move this to a .env file and use process.env.${type.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`,
|
|
147
|
+
}),
|
|
148
|
+
adminExposed: (route) => ({
|
|
149
|
+
message: `🔐 Anyone can access ${route} without logging in`,
|
|
150
|
+
why: `This endpoint has no authentication. Attackers can access admin features directly.`,
|
|
151
|
+
fix: `Add authentication middleware to protect this route.`,
|
|
152
|
+
}),
|
|
153
|
+
mockInProd: (detail) => ({
|
|
154
|
+
message: `🎭 Your code uses fake data instead of real data`,
|
|
155
|
+
why: `Mock/test code in production means users see fake data or features don't work.`,
|
|
156
|
+
fix: `Remove or conditionally disable this mock code for production builds.`,
|
|
157
|
+
}),
|
|
158
|
+
endpointMissing: (route) => ({
|
|
159
|
+
message: `🔗 Button calls ${route} but that endpoint doesn't exist`,
|
|
160
|
+
why: `Your frontend calls an API that doesn't exist - this will cause errors for users.`,
|
|
161
|
+
fix: `Either create the missing endpoint or update the frontend to use the correct URL.`,
|
|
162
|
+
}),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
function getTrafficLight(score) {
|
|
166
|
+
if (score >= 80) return "🟢";
|
|
167
|
+
if (score >= 50) return "🟡";
|
|
168
|
+
return "🔴";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getVerdict(score, blockers) {
|
|
172
|
+
if (score >= 90 && blockers.length === 0) {
|
|
173
|
+
return {
|
|
174
|
+
emoji: "🚀",
|
|
175
|
+
headline: "Ready to ship!",
|
|
176
|
+
detail: "Your app looks solid. Ship it!",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (score >= 70 && blockers.length <= 2) {
|
|
180
|
+
return {
|
|
181
|
+
emoji: "⚠️",
|
|
182
|
+
headline: "Almost ready",
|
|
183
|
+
detail: "A few things to fix, but you're close.",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (score >= 50) {
|
|
187
|
+
return {
|
|
188
|
+
emoji: "🛑",
|
|
189
|
+
headline: "Not ready yet",
|
|
190
|
+
detail: "Some important issues need your attention.",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
emoji: "🚨",
|
|
195
|
+
headline: "Don't ship this!",
|
|
196
|
+
detail: "Critical problems found. Fix these first.",
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function translateToPlainEnglish(results) {
|
|
201
|
+
const problems = [],
|
|
202
|
+
warnings = [],
|
|
203
|
+
passes = [];
|
|
204
|
+
|
|
205
|
+
if (results.integrity?.env?.secrets) {
|
|
206
|
+
for (const secret of results.integrity.env.secrets) {
|
|
207
|
+
if (secret.severity === "critical") {
|
|
208
|
+
const info = PLAIN_ENGLISH.secretExposed(secret.type);
|
|
209
|
+
problems.push({
|
|
210
|
+
category: "Security",
|
|
211
|
+
type: secret.type,
|
|
212
|
+
message: info.message,
|
|
213
|
+
why: info.why,
|
|
214
|
+
fix: info.fix,
|
|
215
|
+
file: `${secret.file}:${secret.line}`,
|
|
216
|
+
line: secret.line,
|
|
217
|
+
rawFile: secret.file,
|
|
218
|
+
fixable: true,
|
|
219
|
+
fixAction: "move-to-env",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (results.integrity?.auth?.analysis?.adminExposed?.length > 0) {
|
|
226
|
+
for (const route of results.integrity.auth.analysis.adminExposed) {
|
|
227
|
+
const info = PLAIN_ENGLISH.adminExposed(`${route.method} ${route.path}`);
|
|
228
|
+
problems.push({
|
|
229
|
+
category: "Auth",
|
|
230
|
+
message: info.message,
|
|
231
|
+
why: info.why,
|
|
232
|
+
fix: info.fix,
|
|
233
|
+
file: route.file,
|
|
234
|
+
fixable: false,
|
|
235
|
+
fixAction: "add-auth-middleware",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (results.integrity?.mocks?.issues) {
|
|
241
|
+
for (const issue of results.integrity.mocks.issues) {
|
|
242
|
+
if (issue.severity === "critical" || issue.severity === "high") {
|
|
243
|
+
const info = PLAIN_ENGLISH.mockInProd(issue.type);
|
|
244
|
+
problems.push({
|
|
245
|
+
category: "Fake Code",
|
|
246
|
+
message: info.message,
|
|
247
|
+
why: info.why,
|
|
248
|
+
fix: info.fix,
|
|
249
|
+
detail: issue.evidence || issue.type,
|
|
250
|
+
file: `${issue.file}:${issue.line}`,
|
|
251
|
+
fixable: false,
|
|
252
|
+
fixAction: "remove-mock",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!results.integrity?.env?.secrets?.length)
|
|
259
|
+
passes.push({
|
|
260
|
+
category: "Secrets",
|
|
261
|
+
message: "✅ No exposed secrets found",
|
|
262
|
+
});
|
|
263
|
+
if (!results.integrity?.mocks?.issues?.length)
|
|
264
|
+
passes.push({ category: "Code", message: "✅ No fake/mock code found" });
|
|
265
|
+
|
|
266
|
+
return { problems, warnings, passes };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function printVibeCoderResults(results, translated, outputDir) {
|
|
270
|
+
const { problems, warnings, passes } = translated;
|
|
271
|
+
const score = results.score || 0;
|
|
272
|
+
const light = getTrafficLight(score);
|
|
273
|
+
const verdict = getVerdict(score, problems);
|
|
274
|
+
|
|
275
|
+
// Get colors based on score
|
|
276
|
+
const boxColor = score >= 80 ? c.green : score >= 50 ? c.yellow : c.red;
|
|
277
|
+
const scoreColor = score >= 80 ? c.green : score >= 50 ? c.yellow : c.red;
|
|
278
|
+
|
|
279
|
+
console.log("");
|
|
280
|
+
console.log(
|
|
281
|
+
` ${boxColor}╔═════════════════════════════════════════════════════════════════╗${c.reset}`,
|
|
282
|
+
);
|
|
283
|
+
console.log(
|
|
284
|
+
` ${boxColor}║${c.reset} ${boxColor}║${c.reset}`,
|
|
285
|
+
);
|
|
286
|
+
console.log(
|
|
287
|
+
` ${boxColor}║${c.reset} ${light} ${c.bold}${verdict.headline}${c.reset} ${boxColor}║${c.reset}`,
|
|
288
|
+
);
|
|
289
|
+
console.log(
|
|
290
|
+
` ${boxColor}║${c.reset} ${c.dim}${verdict.detail}${c.reset} ${boxColor}║${c.reset}`,
|
|
291
|
+
);
|
|
292
|
+
console.log(
|
|
293
|
+
` ${boxColor}║${c.reset} ${boxColor}║${c.reset}`,
|
|
294
|
+
);
|
|
295
|
+
console.log(
|
|
296
|
+
` ${boxColor}╚═════════════════════════════════════════════════════════════════╝${c.reset}`,
|
|
297
|
+
);
|
|
298
|
+
console.log("");
|
|
299
|
+
|
|
300
|
+
if (problems.length > 0) {
|
|
301
|
+
console.log(
|
|
302
|
+
` ${c.bold}${c.red}🚨 PROBLEMS${c.reset} ${c.dim}(${problems.length} found)${c.reset}\n`,
|
|
303
|
+
);
|
|
304
|
+
for (const p of problems.slice(0, 8)) {
|
|
305
|
+
console.log(` ${c.red}❌${c.reset} ${c.bold}${p.message}${c.reset}`);
|
|
306
|
+
if (p.why) console.log(` ${c.dim}Why: ${p.why}${c.reset}`);
|
|
307
|
+
if (p.file) console.log(` ${c.cyan}📍 ${p.file}${c.reset}`);
|
|
308
|
+
if (p.fix) console.log(` ${c.green}💡 Fix: ${p.fix}${c.reset}`);
|
|
309
|
+
console.log("");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (passes.length > 0) {
|
|
314
|
+
console.log(` ${c.bold}${c.green}✅ WHAT'S WORKING${c.reset}\n`);
|
|
315
|
+
for (const p of passes) console.log(` ${c.green}${p.message}${c.reset}`);
|
|
316
|
+
console.log("");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Score summary box
|
|
320
|
+
console.log(
|
|
321
|
+
` ${c.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`,
|
|
322
|
+
);
|
|
323
|
+
console.log(
|
|
324
|
+
` ${c.bold}Score:${c.reset} ${scoreColor}${c.bold}${score}${c.reset}/100 ${light}`,
|
|
325
|
+
);
|
|
326
|
+
console.log("");
|
|
327
|
+
if (problems.length > 0) {
|
|
328
|
+
const fixable = problems.filter((p) => p.fixable).length;
|
|
329
|
+
console.log(
|
|
330
|
+
` ${c.dim}${problems.length} problems found (${fixable} auto-fixable)${c.reset}`,
|
|
331
|
+
);
|
|
332
|
+
console.log(
|
|
333
|
+
` ${c.dim}Run:${c.reset} ${c.cyan}${c.bold}vibecheck ship --fix${c.reset}`,
|
|
334
|
+
);
|
|
335
|
+
} else {
|
|
336
|
+
console.log(` ${c.green}${c.bold}No critical problems! 🎉${c.reset}`);
|
|
337
|
+
}
|
|
338
|
+
console.log(
|
|
339
|
+
` ${c.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`,
|
|
340
|
+
);
|
|
341
|
+
console.log("");
|
|
342
|
+
console.log(
|
|
343
|
+
` ${c.dim}📄 Full report:${c.reset} ${c.cyan}${outputDir}/report.html${c.reset}\n`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function parseArgs(args) {
|
|
348
|
+
const opts = {
|
|
349
|
+
fix: false,
|
|
350
|
+
path: ".",
|
|
351
|
+
verbose: false,
|
|
352
|
+
json: false,
|
|
353
|
+
badge: false,
|
|
354
|
+
assist: false,
|
|
355
|
+
strict: false,
|
|
356
|
+
ci: false,
|
|
357
|
+
};
|
|
358
|
+
for (let i = 0; i < args.length; i++) {
|
|
359
|
+
const a = args[i];
|
|
360
|
+
if (a === "--fix" || a === "-f") opts.fix = true;
|
|
361
|
+
if (a === "--verbose" || a === "-v") opts.verbose = true;
|
|
362
|
+
if (a === "--json") opts.json = true;
|
|
363
|
+
if (a === "--badge" || a === "-b") opts.badge = true;
|
|
364
|
+
if (a === "--assist") opts.assist = true;
|
|
365
|
+
if (a === "--strict") opts.strict = true;
|
|
366
|
+
if (a === "--ci") opts.ci = true;
|
|
367
|
+
if (a.startsWith("--path=")) opts.path = a.split("=")[1];
|
|
368
|
+
if (a === "--path" || a === "-p") opts.path = args[++i];
|
|
369
|
+
if (a === "--help" || a === "-h") opts.help = true;
|
|
370
|
+
}
|
|
371
|
+
return opts;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function runShip(args) {
|
|
375
|
+
const opts = parseArgs(args);
|
|
376
|
+
|
|
377
|
+
if (opts.help) {
|
|
378
|
+
console.log(`
|
|
379
|
+
${c.bold}${c.cyan}vibecheck ship${c.reset} — The One Command
|
|
380
|
+
|
|
381
|
+
${c.dim}Get a ship verdict: SHIP | WARN | BLOCK${c.reset}
|
|
382
|
+
|
|
383
|
+
${c.bold}USAGE${c.reset}
|
|
384
|
+
vibecheck ship Get ship verdict + Vibe Score
|
|
385
|
+
vibecheck ship --fix Try safe mechanical fixes
|
|
386
|
+
vibecheck ship --assist Generate AI mission packs
|
|
387
|
+
vibecheck ship --badge Generate embeddable badge
|
|
388
|
+
|
|
389
|
+
${c.bold}OPTIONS${c.reset}
|
|
390
|
+
--fix, -f Try safe mechanical fixes (shows plan first)
|
|
391
|
+
--assist Generate AI mission prompts for complex issues
|
|
392
|
+
--badge, -b Generate embeddable badge for README
|
|
393
|
+
--strict Treat warnings as blockers
|
|
394
|
+
--ci Machine output for CI/CD
|
|
395
|
+
--json Output as JSON
|
|
396
|
+
--path, -p Project path (default: current directory)
|
|
397
|
+
--help, -h Show this help
|
|
398
|
+
|
|
399
|
+
${c.bold}EXIT CODES${c.reset}
|
|
400
|
+
${c.green}0${c.reset} SHIP — Ready to ship
|
|
401
|
+
${c.yellow}1${c.reset} WARN — Warnings found
|
|
402
|
+
${c.red}2${c.reset} BLOCK — Blockers found
|
|
403
|
+
|
|
404
|
+
${c.bold}EXAMPLES${c.reset}
|
|
405
|
+
vibecheck ship # Get verdict
|
|
406
|
+
vibecheck ship --fix # Fix what can be fixed
|
|
407
|
+
vibecheck ship --badge # Get README badge
|
|
408
|
+
vibecheck ship --strict --ci # Strict CI mode
|
|
409
|
+
`);
|
|
410
|
+
return 0;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
414
|
+
// ENTITLEMENT CHECK
|
|
415
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
416
|
+
try {
|
|
417
|
+
await enforceLimit('scans');
|
|
418
|
+
await enforceFeature('ship');
|
|
419
|
+
|
|
420
|
+
// Check for fix feature (premium)
|
|
421
|
+
if (opts.fix) {
|
|
422
|
+
await enforceFeature('fix');
|
|
423
|
+
}
|
|
424
|
+
} catch (err) {
|
|
425
|
+
if (err.code === 'LIMIT_EXCEEDED' || err.code === 'FEATURE_NOT_AVAILABLE') {
|
|
426
|
+
console.error(err.upgradePrompt || err.message);
|
|
427
|
+
const { EXIT_CODES } = require('./lib/error-handler');
|
|
428
|
+
process.exit(EXIT_CODES.AUTH_FAILURE);
|
|
429
|
+
}
|
|
430
|
+
throw err;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Track usage
|
|
434
|
+
await trackUsage('scans');
|
|
435
|
+
|
|
436
|
+
const projectPath = path.resolve(opts.path);
|
|
437
|
+
const outputDir = path.join(projectPath, ".vibecheck");
|
|
438
|
+
|
|
439
|
+
console.log(`\n ${c.bold}${c.magenta}🚀 vibecheck SHIP${c.reset}\n`);
|
|
440
|
+
console.log(
|
|
441
|
+
` ${c.dim}Scanning your app for production readiness...${c.reset}\n`,
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
let results = {
|
|
445
|
+
score: 100,
|
|
446
|
+
grade: "A",
|
|
447
|
+
canShip: true,
|
|
448
|
+
deductions: [],
|
|
449
|
+
blockers: [],
|
|
450
|
+
counts: {},
|
|
451
|
+
checks: {},
|
|
452
|
+
outputDir,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
console.log(" 🔍 Checking for problems...");
|
|
457
|
+
const { auditProductionIntegrity } = require(
|
|
458
|
+
path.join(__dirname, "../../scripts/audit-production-integrity.js"),
|
|
459
|
+
);
|
|
460
|
+
const { results: integrityResults, integrity } =
|
|
461
|
+
await auditProductionIntegrity(projectPath);
|
|
462
|
+
results.score = integrity.score;
|
|
463
|
+
results.grade = integrity.grade;
|
|
464
|
+
results.canShip = integrity.canShip;
|
|
465
|
+
results.deductions = integrity.deductions;
|
|
466
|
+
results.integrity = integrityResults;
|
|
467
|
+
} catch (err) {
|
|
468
|
+
if (opts.verbose) console.error(" ⚠️ Integrity check error:", err.message);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
472
|
+
// ROUTE TRUTH v1 - Fake Endpoint Detection
|
|
473
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
474
|
+
try {
|
|
475
|
+
console.log(" 🗺️ Building route truth map...");
|
|
476
|
+
const fastifyEntry = detectFastifyEntry(projectPath);
|
|
477
|
+
const truthpack = await buildTruthpack({ repoRoot: projectPath, fastifyEntry });
|
|
478
|
+
writeTruthpack(projectPath, truthpack);
|
|
479
|
+
|
|
480
|
+
// Run all v1 analyzers
|
|
481
|
+
const allFindings = [
|
|
482
|
+
...findMissingRoutes(truthpack),
|
|
483
|
+
...findEnvGaps(truthpack),
|
|
484
|
+
...findFakeSuccess(projectPath),
|
|
485
|
+
...findGhostAuth(truthpack, projectPath),
|
|
486
|
+
...findStripeWebhookViolations(truthpack),
|
|
487
|
+
...findPaidSurfaceNotEnforced(truthpack),
|
|
488
|
+
...findOwnerModeBypass(projectPath),
|
|
489
|
+
// Runtime UI reality (Dead UI detection)
|
|
490
|
+
...findingsFromReality(projectPath)
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
results.routeTruth = {
|
|
494
|
+
serverRoutes: truthpack.routes.server.length,
|
|
495
|
+
clientRefs: truthpack.routes.clientRefs.length,
|
|
496
|
+
envVars: truthpack.env?.vars?.length || 0,
|
|
497
|
+
envDeclared: truthpack.env?.declared?.length || 0,
|
|
498
|
+
findings: allFindings,
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// Add blocking findings to results
|
|
502
|
+
for (const finding of allFindings) {
|
|
503
|
+
if (finding.severity === "BLOCK") {
|
|
504
|
+
results.canShip = false;
|
|
505
|
+
results.blockers.push({
|
|
506
|
+
type: finding.category,
|
|
507
|
+
message: finding.title,
|
|
508
|
+
evidence: finding.evidence,
|
|
509
|
+
fix: finding.fixHints?.join(" ") || "",
|
|
510
|
+
});
|
|
511
|
+
results.deductions.push({
|
|
512
|
+
reason: finding.title,
|
|
513
|
+
points: 15,
|
|
514
|
+
severity: "critical",
|
|
515
|
+
});
|
|
516
|
+
results.score = Math.max(0, results.score - 15);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Print summary
|
|
521
|
+
console.log(` Routes: ${truthpack.routes.server.length} server, ${truthpack.routes.clientRefs.length} client refs`);
|
|
522
|
+
console.log(` Env: ${truthpack.env?.vars?.length || 0} used, ${truthpack.env?.declared?.length || 0} declared`);
|
|
523
|
+
|
|
524
|
+
const blockers = allFindings.filter(f => f.severity === "BLOCK");
|
|
525
|
+
const warns = allFindings.filter(f => f.severity === "WARN");
|
|
526
|
+
|
|
527
|
+
if (blockers.length) {
|
|
528
|
+
console.log(` ${c.red}🛑 ${blockers.length} BLOCKERS detected${c.reset}`);
|
|
529
|
+
for (const b of blockers.slice(0, 5)) {
|
|
530
|
+
console.log(` - ${b.title}`);
|
|
531
|
+
}
|
|
532
|
+
if (blockers.length > 5) console.log(` ...and ${blockers.length - 5} more`);
|
|
533
|
+
}
|
|
534
|
+
if (warns.length) {
|
|
535
|
+
console.log(` ${c.yellow}⚠️ ${warns.length} WARNINGS${c.reset}`);
|
|
536
|
+
}
|
|
537
|
+
} catch (err) {
|
|
538
|
+
if (opts.verbose) console.error(" ⚠️ Route truth check error:", err.message);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
console.log(" ✅ Scan complete!");
|
|
542
|
+
|
|
543
|
+
const translated = translateToPlainEnglish(results);
|
|
544
|
+
|
|
545
|
+
// Run auto-fix if requested
|
|
546
|
+
if (opts.fix) {
|
|
547
|
+
console.log(`\n ${c.bold}${c.cyan}🔧 AUTO-FIX MODE${c.reset}\n`);
|
|
548
|
+
const fixResults = await runAutoFix(
|
|
549
|
+
projectPath,
|
|
550
|
+
translated,
|
|
551
|
+
results,
|
|
552
|
+
outputDir,
|
|
553
|
+
);
|
|
554
|
+
printFixResults(fixResults);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
printVibeCoderResults(results, translated, outputDir);
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
const { writeArtifacts } = require("./utils");
|
|
561
|
+
writeArtifacts(outputDir, results);
|
|
562
|
+
} catch (err) {
|
|
563
|
+
// Log but don't fail - artifact writing is non-critical
|
|
564
|
+
console.warn(`${c.yellow}⚠${c.reset} Failed to write artifacts: ${err.message}`);
|
|
565
|
+
if (process.env.DEBUG || process.env.VIBECHECK_DEBUG) {
|
|
566
|
+
console.error(err.stack);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Emit audit event for ship check
|
|
571
|
+
emitShipCheck(projectPath, results.canShip ? 'success' : 'failure', {
|
|
572
|
+
score: results.score,
|
|
573
|
+
grade: results.grade,
|
|
574
|
+
canShip: results.canShip,
|
|
575
|
+
issueCount: translated.problems?.length || 0,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// Badge generation mode
|
|
579
|
+
if (opts.badge) {
|
|
580
|
+
const projectName = path.basename(projectPath);
|
|
581
|
+
const projectId = projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
582
|
+
const verdict = results.canShip ? 'SHIP' : (results.score >= 50 ? 'WARN' : 'BLOCK');
|
|
583
|
+
const score = results.score || 0;
|
|
584
|
+
|
|
585
|
+
console.log(`\n${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`);
|
|
586
|
+
console.log(`${c.bold}📛 Ship Badge Generated${c.reset}\n`);
|
|
587
|
+
|
|
588
|
+
const badgeUrl = `https://vibecheck.dev/badge/${projectId}.svg`;
|
|
589
|
+
const reportUrl = `https://vibecheck.dev/report/${projectId}`;
|
|
590
|
+
const markdown = `[](${reportUrl})`;
|
|
591
|
+
|
|
592
|
+
const verdictColor = verdict === 'SHIP' ? c.green : verdict === 'WARN' ? c.yellow : c.red;
|
|
593
|
+
const verdictIcon = verdict === 'SHIP' ? '✅' : verdict === 'WARN' ? '⚠️' : '🚫';
|
|
594
|
+
|
|
595
|
+
console.log(` ${verdictIcon} ${verdictColor}${c.bold}${verdict}${c.reset} · Score: ${c.bold}${score}${c.reset}`);
|
|
596
|
+
console.log(`\n ${c.dim}Badge URL:${c.reset} ${c.cyan}${badgeUrl}${c.reset}`);
|
|
597
|
+
console.log(` ${c.dim}Report URL:${c.reset} ${c.cyan}${reportUrl}${c.reset}`);
|
|
598
|
+
console.log(`\n ${c.dim}Add to README.md:${c.reset}`);
|
|
599
|
+
console.log(` ${c.green}${markdown}${c.reset}`);
|
|
600
|
+
console.log(`\n${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}\n`);
|
|
601
|
+
|
|
602
|
+
// Save badge info to .vibecheck directory
|
|
603
|
+
const badgeInfo = {
|
|
604
|
+
projectId,
|
|
605
|
+
score,
|
|
606
|
+
verdict,
|
|
607
|
+
badgeUrl,
|
|
608
|
+
reportUrl,
|
|
609
|
+
markdown,
|
|
610
|
+
generatedAt: new Date().toISOString(),
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
fs.writeFileSync(
|
|
615
|
+
path.join(outputDir, 'badge.json'),
|
|
616
|
+
JSON.stringify(badgeInfo, null, 2)
|
|
617
|
+
);
|
|
618
|
+
} catch {}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// JSON output mode - use standardized schema
|
|
622
|
+
if (opts.json) {
|
|
623
|
+
const { createScanResult, validateScanResult } = require('./lib/scan-output-schema');
|
|
624
|
+
|
|
625
|
+
// Convert results to standardized format
|
|
626
|
+
const findings = (translated.problems || []).map((p, idx) => ({
|
|
627
|
+
id: `finding_${idx}`,
|
|
628
|
+
type: p.type || 'ship_blocker',
|
|
629
|
+
severity: p.severity || 'high',
|
|
630
|
+
message: p.message || p.title || '',
|
|
631
|
+
file: p.file || null,
|
|
632
|
+
line: p.line || null,
|
|
633
|
+
confidence: 0.9,
|
|
634
|
+
blocksShip: !results.canShip,
|
|
635
|
+
suggestedFix: p.fix || null,
|
|
636
|
+
}));
|
|
637
|
+
|
|
638
|
+
const standardizedResult = createScanResult({
|
|
639
|
+
findings,
|
|
640
|
+
projectPath,
|
|
641
|
+
scanId: `ship_${Date.now()}`,
|
|
642
|
+
startTime: Date.now(),
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Validate before output
|
|
646
|
+
const validation = validateScanResult(standardizedResult);
|
|
647
|
+
if (!validation.valid) {
|
|
648
|
+
console.error(JSON.stringify({
|
|
649
|
+
schemaVersion: "1.0.0",
|
|
650
|
+
success: false,
|
|
651
|
+
error: {
|
|
652
|
+
code: "SCHEMA_VALIDATION_FAILED",
|
|
653
|
+
message: "JSON output validation failed",
|
|
654
|
+
nextSteps: validation.errors,
|
|
655
|
+
},
|
|
656
|
+
}, null, 2));
|
|
657
|
+
const { EXIT_CODES } = require('./lib/error-handler');
|
|
658
|
+
return EXIT_CODES.SYSTEM_ERROR;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
console.log(JSON.stringify(standardizedResult, null, 2));
|
|
662
|
+
// Exit codes per spec: 0=SHIP, 1=WARN, 2=BLOCK
|
|
663
|
+
if (results.canShip) return 0; // SHIP
|
|
664
|
+
if (results.hasWarnings && !results.hasBlockers) return 1; // WARN
|
|
665
|
+
return 2; // BLOCK
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Exit codes per spec: 0=SHIP, 1=WARN, 2=BLOCK
|
|
669
|
+
if (results.canShip) return 0; // SHIP
|
|
670
|
+
if (results.hasWarnings && !results.hasBlockers) return 1; // WARN
|
|
671
|
+
return 2; // BLOCK
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Safe auto-fix implementation
|
|
676
|
+
* Only performs non-destructive fixes:
|
|
677
|
+
* 1. Creates .env.example with detected secrets as placeholders
|
|
678
|
+
* 2. Updates .gitignore to protect sensitive files
|
|
679
|
+
* 3. Generates fixes.md with detailed manual fix instructions
|
|
680
|
+
*/
|
|
681
|
+
async function runAutoFix(projectPath, translated, results, outputDir) {
|
|
682
|
+
const fixResults = {
|
|
683
|
+
envExampleCreated: false,
|
|
684
|
+
gitignoreUpdated: false,
|
|
685
|
+
fixesMdCreated: false,
|
|
686
|
+
secretsFound: [],
|
|
687
|
+
errors: [],
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const { ensureOutputDir } = require("./utils");
|
|
691
|
+
ensureOutputDir(outputDir);
|
|
692
|
+
|
|
693
|
+
// 1. Create .env.example with detected secrets
|
|
694
|
+
const secretProblems = translated.problems.filter(
|
|
695
|
+
(p) => p.fixAction === "move-to-env",
|
|
696
|
+
);
|
|
697
|
+
if (secretProblems.length > 0) {
|
|
698
|
+
try {
|
|
699
|
+
const envExamplePath = path.join(projectPath, ".env.example");
|
|
700
|
+
const envVars = new Set();
|
|
701
|
+
|
|
702
|
+
for (const problem of secretProblems) {
|
|
703
|
+
const varName = problem.type.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
704
|
+
envVars.add(varName);
|
|
705
|
+
fixResults.secretsFound.push({
|
|
706
|
+
type: problem.type,
|
|
707
|
+
varName,
|
|
708
|
+
file: problem.rawFile,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
let envContent = "# Environment Variables Template\n";
|
|
713
|
+
envContent += "# Copy this file to .env and fill in your actual values\n";
|
|
714
|
+
envContent += "# NEVER commit .env to version control!\n\n";
|
|
715
|
+
|
|
716
|
+
for (const varName of envVars) {
|
|
717
|
+
envContent += `${varName}=your_${varName.toLowerCase()}_here\n`;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Append to existing .env.example or create new
|
|
721
|
+
if (fs.existsSync(envExamplePath)) {
|
|
722
|
+
const existing = fs.readFileSync(envExamplePath, "utf8");
|
|
723
|
+
for (const varName of envVars) {
|
|
724
|
+
if (!existing.includes(varName)) {
|
|
725
|
+
fs.appendFileSync(
|
|
726
|
+
envExamplePath,
|
|
727
|
+
`${varName}=your_${varName.toLowerCase()}_here\n`,
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
console.log(
|
|
732
|
+
` ${c.green}✓${c.reset} Updated ${c.cyan}.env.example${c.reset} with ${envVars.size} variables`,
|
|
733
|
+
);
|
|
734
|
+
} else {
|
|
735
|
+
fs.writeFileSync(envExamplePath, envContent);
|
|
736
|
+
console.log(
|
|
737
|
+
` ${c.green}✓${c.reset} Created ${c.cyan}.env.example${c.reset} with ${envVars.size} variables`,
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
fixResults.envExampleCreated = true;
|
|
741
|
+
} catch (err) {
|
|
742
|
+
fixResults.errors.push(`Failed to create .env.example: ${err.message}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// 2. Update .gitignore to protect sensitive files
|
|
747
|
+
try {
|
|
748
|
+
const gitignorePath = path.join(projectPath, ".gitignore");
|
|
749
|
+
const sensitivePatterns = [
|
|
750
|
+
".env",
|
|
751
|
+
".env.local",
|
|
752
|
+
".env.*.local",
|
|
753
|
+
"*.pem",
|
|
754
|
+
"*.key",
|
|
755
|
+
".vibecheck/artifacts/",
|
|
756
|
+
];
|
|
757
|
+
|
|
758
|
+
let gitignoreContent = "";
|
|
759
|
+
if (fs.existsSync(gitignorePath)) {
|
|
760
|
+
gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const patternsToAdd = sensitivePatterns.filter(
|
|
764
|
+
(p) => !gitignoreContent.includes(p),
|
|
765
|
+
);
|
|
766
|
+
|
|
767
|
+
if (patternsToAdd.length > 0) {
|
|
768
|
+
const addition =
|
|
769
|
+
"\n# vibecheck: Protect sensitive files\n" +
|
|
770
|
+
patternsToAdd.join("\n") +
|
|
771
|
+
"\n";
|
|
772
|
+
fs.appendFileSync(gitignorePath, addition);
|
|
773
|
+
console.log(
|
|
774
|
+
` ${c.green}✓${c.reset} Updated ${c.cyan}.gitignore${c.reset} with ${patternsToAdd.length} patterns`,
|
|
775
|
+
);
|
|
776
|
+
fixResults.gitignoreUpdated = true;
|
|
777
|
+
} else {
|
|
778
|
+
console.log(
|
|
779
|
+
` ${c.dim}✓ .gitignore already protects sensitive files${c.reset}`,
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
} catch (err) {
|
|
783
|
+
fixResults.errors.push(`Failed to update .gitignore: ${err.message}`);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// 3. Generate detailed fixes.md with manual instructions AND AI agent prompt
|
|
787
|
+
try {
|
|
788
|
+
const fixesMdPath = path.join(outputDir, "fixes.md");
|
|
789
|
+
const aiPromptPath = path.join(outputDir, "ai-fix-prompt.md");
|
|
790
|
+
|
|
791
|
+
// Group problems by category and dedupe by file
|
|
792
|
+
const byCategory = {};
|
|
793
|
+
const seenFiles = new Set();
|
|
794
|
+
for (const p of translated.problems) {
|
|
795
|
+
// Dedupe: only show first issue per file for same category
|
|
796
|
+
const key = `${p.category}:${p.file}`;
|
|
797
|
+
if (seenFiles.has(key)) continue;
|
|
798
|
+
seenFiles.add(key);
|
|
799
|
+
|
|
800
|
+
if (!byCategory[p.category]) byCategory[p.category] = [];
|
|
801
|
+
byCategory[p.category].push(p);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Read actual file content for context (keyed by file:line to handle multiple issues per file)
|
|
805
|
+
const fileContexts = {};
|
|
806
|
+
const fileContents = {}; // Cache file contents
|
|
807
|
+
for (const problems of Object.values(byCategory)) {
|
|
808
|
+
for (const p of problems) {
|
|
809
|
+
if (p.rawFile) {
|
|
810
|
+
const contextKey = `${p.rawFile}:${p.line || 1}`;
|
|
811
|
+
if (!fileContexts[contextKey]) {
|
|
812
|
+
try {
|
|
813
|
+
// Cache file content
|
|
814
|
+
if (!fileContents[p.rawFile]) {
|
|
815
|
+
const fullPath = path.join(projectPath, p.rawFile);
|
|
816
|
+
if (fs.existsSync(fullPath)) {
|
|
817
|
+
fileContents[p.rawFile] = fs
|
|
818
|
+
.readFileSync(fullPath, "utf8")
|
|
819
|
+
.split("\n");
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (fileContents[p.rawFile]) {
|
|
824
|
+
const lines = fileContents[p.rawFile];
|
|
825
|
+
const lineNum = p.line || 1;
|
|
826
|
+
const start = Math.max(0, lineNum - 3);
|
|
827
|
+
const end = Math.min(lines.length, lineNum + 3);
|
|
828
|
+
fileContexts[contextKey] = {
|
|
829
|
+
snippet: lines
|
|
830
|
+
.slice(start, end)
|
|
831
|
+
.map((l, i) => `${start + i + 1}: ${l}`)
|
|
832
|
+
.join("\n"),
|
|
833
|
+
line: lineNum,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
} catch (e) {
|
|
837
|
+
/* ignore read errors */
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
// Store the context key on the problem for later lookup
|
|
841
|
+
p._contextKey = contextKey;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Generate human-readable fixes.md
|
|
847
|
+
let md = "# 🔧 vibecheck Fix Guide\n\n";
|
|
848
|
+
md += `Generated: ${new Date().toISOString()}\n\n`;
|
|
849
|
+
|
|
850
|
+
const totalIssues = Object.values(byCategory).flat().length;
|
|
851
|
+
md += `**${totalIssues} unique issues found across ${Object.keys(byCategory).length} categories**\n\n`;
|
|
852
|
+
md += "---\n\n";
|
|
853
|
+
|
|
854
|
+
for (const [category, problems] of Object.entries(byCategory)) {
|
|
855
|
+
md += `## ${category} (${problems.length} files)\n\n`;
|
|
856
|
+
|
|
857
|
+
for (const p of problems) {
|
|
858
|
+
md += `### \`${p.file}\`\n\n`;
|
|
859
|
+
md += `**Problem:** ${p.message}\n\n`;
|
|
860
|
+
md += `**Risk:** ${p.why}\n\n`;
|
|
861
|
+
|
|
862
|
+
// Show actual code context if available
|
|
863
|
+
if (p._contextKey && fileContexts[p._contextKey]) {
|
|
864
|
+
md += "**Current code:**\n```\n";
|
|
865
|
+
md += fileContexts[p._contextKey].snippet;
|
|
866
|
+
md += "\n```\n\n";
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
md += `**Fix:** ${p.fix}\n\n`;
|
|
870
|
+
|
|
871
|
+
// Add specific code example for secrets
|
|
872
|
+
if (p.fixAction === "move-to-env" && p.type) {
|
|
873
|
+
const varName = p.type.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
874
|
+
md += "**Replace with:**\n```javascript\n";
|
|
875
|
+
md += `const ${p.type.toLowerCase().replace(/[^a-z0-9]/g, "")} = process.env.${varName};\n`;
|
|
876
|
+
md += "```\n\n";
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
md += "---\n\n";
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
md += "## Next Steps\n\n";
|
|
884
|
+
md += "1. Copy `.env.example` to `.env` and fill in real values\n";
|
|
885
|
+
md += "2. Apply the fixes above to each file\n";
|
|
886
|
+
md += "3. Run `vibecheck ship` again to verify\n\n";
|
|
887
|
+
md += "---\n\n";
|
|
888
|
+
md +=
|
|
889
|
+
"📋 **AI Agent prompt available at:** `.vibecheck/ai-fix-prompt.md`\n";
|
|
890
|
+
|
|
891
|
+
fs.writeFileSync(fixesMdPath, md);
|
|
892
|
+
console.log(
|
|
893
|
+
` ${c.green}✓${c.reset} Created ${c.cyan}.vibecheck/fixes.md${c.reset} with detailed instructions`,
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
// 4. Generate AI agent prompt
|
|
897
|
+
let aiPrompt = "# AI Agent Fix Prompt\n\n";
|
|
898
|
+
aiPrompt +=
|
|
899
|
+
"> Copy this entire prompt to an AI coding assistant to fix these issues safely.\n\n";
|
|
900
|
+
aiPrompt += "---\n\n";
|
|
901
|
+
aiPrompt += "## Task\n\n";
|
|
902
|
+
aiPrompt +=
|
|
903
|
+
"Fix the following production security and code quality issues. ";
|
|
904
|
+
aiPrompt +=
|
|
905
|
+
"Follow the exact instructions for each fix. Do NOT break existing functionality.\n\n";
|
|
906
|
+
|
|
907
|
+
aiPrompt += "## Critical Rules\n\n";
|
|
908
|
+
aiPrompt += "1. **Never delete code** - only modify or comment out\n";
|
|
909
|
+
aiPrompt += "2. **Never change function signatures** - keep APIs stable\n";
|
|
910
|
+
aiPrompt += "3. **Test after each fix** - ensure the app still runs\n";
|
|
911
|
+
aiPrompt +=
|
|
912
|
+
"4. **Preserve comments** - don't remove existing documentation\n";
|
|
913
|
+
aiPrompt += "5. **Use environment variables** - never hardcode secrets\n\n";
|
|
914
|
+
|
|
915
|
+
aiPrompt += "## Fixes Required\n\n";
|
|
916
|
+
|
|
917
|
+
let fixNum = 1;
|
|
918
|
+
for (const [category, problems] of Object.entries(byCategory)) {
|
|
919
|
+
for (const p of problems) {
|
|
920
|
+
aiPrompt += `### Fix ${fixNum}: ${category}\n\n`;
|
|
921
|
+
aiPrompt += `**File:** \`${p.file}\`\n\n`;
|
|
922
|
+
aiPrompt += `**Problem:** ${p.message}\n\n`;
|
|
923
|
+
|
|
924
|
+
if (p._contextKey && fileContexts[p._contextKey]) {
|
|
925
|
+
aiPrompt +=
|
|
926
|
+
"**Current code (around line " +
|
|
927
|
+
fileContexts[p._contextKey].line +
|
|
928
|
+
"):**\n```\n";
|
|
929
|
+
aiPrompt += fileContexts[p._contextKey].snippet;
|
|
930
|
+
aiPrompt += "\n```\n\n";
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
aiPrompt += "**Action:**\n";
|
|
934
|
+
|
|
935
|
+
if (p.fixAction === "move-to-env") {
|
|
936
|
+
const varName = p.type.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
937
|
+
aiPrompt += `1. Find the hardcoded ${p.type} in this file\n`;
|
|
938
|
+
aiPrompt += `2. Replace the hardcoded value with \`process.env.${varName}\`\n`;
|
|
939
|
+
aiPrompt += `3. Add \`${varName}\` to \`.env.example\` if not present\n`;
|
|
940
|
+
aiPrompt += `4. Ensure the code handles undefined env var gracefully\n\n`;
|
|
941
|
+
aiPrompt += "**Example transformation:**\n```diff\n";
|
|
942
|
+
aiPrompt += `- const secret = "sk_live_xxxxx";\n`;
|
|
943
|
+
aiPrompt += `+ const secret = process.env.${varName};\n`;
|
|
944
|
+
aiPrompt += `+ if (!secret) throw new Error('${varName} is required');\n`;
|
|
945
|
+
aiPrompt += "```\n\n";
|
|
946
|
+
} else if (p.fixAction === "remove-mock") {
|
|
947
|
+
aiPrompt +=
|
|
948
|
+
"1. Check if this file is a test file (should be in __tests__, *.test.*, *.spec.*)\n";
|
|
949
|
+
aiPrompt +=
|
|
950
|
+
"2. If it's a test file, this is a FALSE POSITIVE - skip it\n";
|
|
951
|
+
aiPrompt += "3. If it's production code, either:\n";
|
|
952
|
+
aiPrompt += " - Remove the mock import/code entirely, OR\n";
|
|
953
|
+
aiPrompt +=
|
|
954
|
+
' - Wrap it in `if (process.env.NODE_ENV !== "production")`\n\n';
|
|
955
|
+
} else if (p.fixAction === "add-auth-middleware") {
|
|
956
|
+
aiPrompt += "1. Identify the route handler for this endpoint\n";
|
|
957
|
+
aiPrompt += "2. Add authentication middleware before the handler\n";
|
|
958
|
+
aiPrompt +=
|
|
959
|
+
'3. Example: `router.get("/admin", authMiddleware, adminHandler)`\n\n';
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
fixNum++;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
aiPrompt += "## Verification\n\n";
|
|
967
|
+
aiPrompt += "After applying fixes:\n";
|
|
968
|
+
aiPrompt += "1. Run `npm run build` or `pnpm build` to check for errors\n";
|
|
969
|
+
aiPrompt += "2. Run `vibecheck ship` to verify all issues are resolved\n";
|
|
970
|
+
aiPrompt += "3. Test the application manually to ensure it works\n";
|
|
971
|
+
|
|
972
|
+
fs.writeFileSync(aiPromptPath, aiPrompt);
|
|
973
|
+
console.log(
|
|
974
|
+
` ${c.green}✓${c.reset} Created ${c.cyan}.vibecheck/ai-fix-prompt.md${c.reset} for AI agents`,
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
fixResults.fixesMdCreated = true;
|
|
978
|
+
} catch (err) {
|
|
979
|
+
fixResults.errors.push(`Failed to create fixes.md: ${err.message}`);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
return fixResults;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function printFixResults(fixResults) {
|
|
986
|
+
console.log("");
|
|
987
|
+
if (fixResults.errors.length > 0) {
|
|
988
|
+
for (const err of fixResults.errors) {
|
|
989
|
+
console.log(` ${c.red}⚠️ ${err}${c.reset}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if (
|
|
994
|
+
fixResults.envExampleCreated ||
|
|
995
|
+
fixResults.gitignoreUpdated ||
|
|
996
|
+
fixResults.fixesMdCreated
|
|
997
|
+
) {
|
|
998
|
+
console.log(`\n ${c.bold}${c.green}✅ Safe fixes applied!${c.reset}`);
|
|
999
|
+
console.log(
|
|
1000
|
+
` ${c.dim}Review the changes and follow instructions in .vibecheck/fixes.md${c.reset}\n`,
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* shipCore: returns report, never consumes meter, never exits.
|
|
1007
|
+
* Used by fix/autopilot verification loops.
|
|
1008
|
+
*/
|
|
1009
|
+
async function shipCore({ repoRoot, fastifyEntry, jsonOut, noWrite } = {}) {
|
|
1010
|
+
const root = repoRoot || process.cwd();
|
|
1011
|
+
const fastEntry = fastifyEntry || detectFastifyEntry(root);
|
|
1012
|
+
|
|
1013
|
+
const truthpack = await buildTruthpack({ repoRoot: root, fastifyEntry: fastEntry });
|
|
1014
|
+
if (!noWrite) writeTruthpack(root, truthpack);
|
|
1015
|
+
|
|
1016
|
+
const allFindings = [
|
|
1017
|
+
...findMissingRoutes(truthpack),
|
|
1018
|
+
...findEnvGaps(truthpack),
|
|
1019
|
+
...findFakeSuccess(root),
|
|
1020
|
+
...findGhostAuth(truthpack, root),
|
|
1021
|
+
...findStripeWebhookViolations(truthpack),
|
|
1022
|
+
...findPaidSurfaceNotEnforced(truthpack),
|
|
1023
|
+
...findOwnerModeBypass(root),
|
|
1024
|
+
// Runtime UI reality (Dead UI detection)
|
|
1025
|
+
...findingsFromReality(root)
|
|
1026
|
+
];
|
|
1027
|
+
|
|
1028
|
+
const verdict = allFindings.some(f => f.severity === "BLOCK") ? "BLOCK" :
|
|
1029
|
+
allFindings.some(f => f.severity === "WARN") ? "WARN" : "SHIP";
|
|
1030
|
+
|
|
1031
|
+
// Build proof graph from findings
|
|
1032
|
+
const proofGraph = buildProofGraph(allFindings, truthpack, root);
|
|
1033
|
+
|
|
1034
|
+
const report = {
|
|
1035
|
+
meta: { generatedAt: new Date().toISOString(), verdict },
|
|
1036
|
+
truthpackHash: truthpack.index?.hashes?.truthpackHash,
|
|
1037
|
+
findings: allFindings,
|
|
1038
|
+
proofGraph: {
|
|
1039
|
+
summary: proofGraph.summary,
|
|
1040
|
+
topBlockers: proofGraph.topBlockers.slice(0, 5),
|
|
1041
|
+
topGaps: proofGraph.topGaps.slice(0, 5)
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1045
|
+
const outDir = path.join(root, ".vibecheck");
|
|
1046
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
1047
|
+
fs.writeFileSync(path.join(outDir, "last_ship.json"), JSON.stringify(report, null, 2));
|
|
1048
|
+
|
|
1049
|
+
// Write full proof graph separately
|
|
1050
|
+
fs.writeFileSync(path.join(outDir, "proof-graph.json"), JSON.stringify(proofGraph, null, 2));
|
|
1051
|
+
|
|
1052
|
+
if (jsonOut) {
|
|
1053
|
+
fs.writeFileSync(path.isAbsolute(jsonOut) ? jsonOut : path.join(root, jsonOut), JSON.stringify(report, null, 2));
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
return { report, truthpack, verdict };
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
module.exports = {
|
|
1060
|
+
runShip: withErrorHandling(runShip, "Ship check failed"),
|
|
1061
|
+
shipCore
|
|
1062
|
+
};
|