vibecheck-ai 2.0.1 → 5.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/bin/.generated +25 -0
- package/bin/_deprecations.js +463 -0
- package/bin/_router.js +46 -0
- package/bin/cli-hygiene.js +241 -0
- package/bin/dev/run-v2-torture.js +30 -0
- package/bin/registry.js +656 -0
- package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
- package/bin/runners/REPORT_AUDIT.md +64 -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 +513 -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-enhanced.js +2525 -0
- package/bin/runners/context/generators/cursor.js +514 -0
- package/bin/runners/context/generators/mcp.js +169 -0
- package/bin/runners/context/generators/windsurf.js +180 -0
- package/bin/runners/context/git-context.js +304 -0
- package/bin/runners/context/index.js +1110 -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 +1264 -0
- package/bin/runners/context/security-scanner.js +541 -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 +336 -0
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -0
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +488 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +303 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
- package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
- package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
- package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
- package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
- package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +127 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +213 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/index.js +200 -0
- package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
- package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
- package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
- package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
- package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
- package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
- package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
- package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
- package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +308 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +90 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +103 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +451 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +79 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +227 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +191 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
- package/bin/runners/lib/agent-firewall/session/index.js +26 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +137 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/ai-bridge.js +416 -0
- package/bin/runners/lib/analysis-core.js +309 -0
- package/bin/runners/lib/analyzers.js +2500 -0
- package/bin/runners/lib/api-client.js +269 -0
- package/bin/runners/lib/approve-output.js +235 -0
- package/bin/runners/lib/artifact-envelope.js +540 -0
- package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
- package/bin/runners/lib/audit-bridge.js +391 -0
- package/bin/runners/lib/auth-shared.js +977 -0
- package/bin/runners/lib/auth-truth.js +193 -0
- package/bin/runners/lib/auth.js +215 -0
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/backup.js +62 -0
- package/bin/runners/lib/billing.js +107 -0
- package/bin/runners/lib/checkpoint.js +941 -0
- package/bin/runners/lib/claims.js +118 -0
- package/bin/runners/lib/classify-output.js +204 -0
- package/bin/runners/lib/cleanup/engine.js +571 -0
- package/bin/runners/lib/cleanup/index.js +53 -0
- package/bin/runners/lib/cleanup/output.js +375 -0
- package/bin/runners/lib/cleanup/rules.js +1060 -0
- package/bin/runners/lib/cli-output.js +400 -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 +202 -0
- package/bin/runners/lib/contracts/env-contract.js +181 -0
- package/bin/runners/lib/contracts/external-contract.js +206 -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 +199 -0
- package/bin/runners/lib/contracts.js +804 -0
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/detect.js +89 -0
- package/bin/runners/lib/detectors-v2.js +622 -0
- package/bin/runners/lib/doctor/autofix.js +254 -0
- package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
- package/bin/runners/lib/doctor/failure-signatures.js +526 -0
- package/bin/runners/lib/doctor/fix-script.js +336 -0
- package/bin/runners/lib/doctor/index.js +37 -0
- package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
- package/bin/runners/lib/doctor/modules/index.js +105 -0
- package/bin/runners/lib/doctor/modules/network.js +250 -0
- package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
- package/bin/runners/lib/doctor/modules/project.js +312 -0
- package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
- package/bin/runners/lib/doctor/modules/runtime.js +224 -0
- package/bin/runners/lib/doctor/modules/security.js +350 -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/safe-repair.js +384 -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-output.js +226 -0
- package/bin/runners/lib/doctor-v2.js +608 -0
- package/bin/runners/lib/drift.js +425 -0
- package/bin/runners/lib/enforcement.js +72 -0
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/attack-detector.js +1192 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -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 +265 -0
- package/bin/runners/lib/entitlements.generated.js +0 -0
- package/bin/runners/lib/entitlements.js +340 -0
- package/bin/runners/lib/env-resolver.js +417 -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 +368 -0
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +684 -0
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/extractors/client-calls.js +990 -0
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
- package/bin/runners/lib/extractors/fastify-routes.js +426 -0
- package/bin/runners/lib/extractors/index.js +363 -0
- package/bin/runners/lib/extractors/next-routes.js +524 -0
- package/bin/runners/lib/extractors/proof-graph.js +431 -0
- package/bin/runners/lib/extractors/route-matcher.js +451 -0
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
- package/bin/runners/lib/extractors/ui-bindings.js +547 -0
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/findings-schema.js +281 -0
- package/bin/runners/lib/fingerprint.js +377 -0
- package/bin/runners/lib/firewall-prompt.js +50 -0
- package/bin/runners/lib/fix-output.js +228 -0
- package/bin/runners/lib/global-flags.js +250 -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/help-formatter.js +413 -0
- package/bin/runners/lib/html-proof-report.js +913 -0
- package/bin/runners/lib/html-report.js +650 -0
- package/bin/runners/lib/init-wizard.js +601 -0
- package/bin/runners/lib/interactive-menu.js +1496 -0
- package/bin/runners/lib/json-output.js +76 -0
- package/bin/runners/lib/llm.js +75 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/meter.js +61 -0
- package/bin/runners/lib/missions/briefing.js +427 -0
- package/bin/runners/lib/missions/checkpoint.js +753 -0
- package/bin/runners/lib/missions/evidence.js +126 -0
- package/bin/runners/lib/missions/hardening.js +851 -0
- package/bin/runners/lib/missions/plan.js +648 -0
- package/bin/runners/lib/missions/safety-gates.js +645 -0
- package/bin/runners/lib/missions/schema.js +478 -0
- package/bin/runners/lib/missions/templates.js +317 -0
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/packs/bundle.js +675 -0
- package/bin/runners/lib/packs/evidence-pack.js +671 -0
- package/bin/runners/lib/packs/pack-factory.js +837 -0
- package/bin/runners/lib/packs/permissions-pack.js +686 -0
- package/bin/runners/lib/packs/proof-graph-pack.js +779 -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/policy.js +295 -0
- package/bin/runners/lib/polish/accessibility.js +62 -0
- package/bin/runners/lib/polish/analyzer.js +93 -0
- package/bin/runners/lib/polish/backend.js +87 -0
- package/bin/runners/lib/polish/configuration.js +83 -0
- package/bin/runners/lib/polish/documentation.js +83 -0
- package/bin/runners/lib/polish/frontend.js +817 -0
- package/bin/runners/lib/polish/index.js +27 -0
- package/bin/runners/lib/polish/infrastructure.js +80 -0
- package/bin/runners/lib/polish/internationalization.js +85 -0
- package/bin/runners/lib/polish/libraries.js +180 -0
- package/bin/runners/lib/polish/observability.js +75 -0
- package/bin/runners/lib/polish/performance.js +64 -0
- package/bin/runners/lib/polish/privacy.js +110 -0
- package/bin/runners/lib/polish/resilience.js +92 -0
- package/bin/runners/lib/polish/security.js +78 -0
- package/bin/runners/lib/polish/seo.js +71 -0
- package/bin/runners/lib/polish/styles.js +62 -0
- package/bin/runners/lib/polish/utils.js +104 -0
- package/bin/runners/lib/preflight.js +142 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/prove-output.js +220 -0
- package/bin/runners/lib/reality/correlation-detectors.js +359 -0
- package/bin/runners/lib/reality/index.js +318 -0
- package/bin/runners/lib/reality/request-hashing.js +416 -0
- package/bin/runners/lib/reality/request-mapper.js +453 -0
- package/bin/runners/lib/reality/safety-rails.js +463 -0
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
- package/bin/runners/lib/reality/toast-detector.js +393 -0
- package/bin/runners/lib/reality-findings.js +84 -0
- package/bin/runners/lib/reality-output.js +231 -0
- package/bin/runners/lib/receipts.js +179 -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 +626 -0
- package/bin/runners/lib/report-html.js +1233 -0
- package/bin/runners/lib/report-output.js +366 -0
- package/bin/runners/lib/report-templates.js +967 -0
- package/bin/runners/lib/report.js +135 -0
- package/bin/runners/lib/route-detection.js +1209 -0
- package/bin/runners/lib/route-truth.js +1322 -0
- package/bin/runners/lib/safelist/index.js +96 -0
- package/bin/runners/lib/safelist/integration.js +334 -0
- package/bin/runners/lib/safelist/matcher.js +696 -0
- package/bin/runners/lib/safelist/schema.js +948 -0
- package/bin/runners/lib/safelist/store.js +438 -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/scan-output.js +631 -0
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schema-validator.js +350 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/contracts.schema.json +160 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/finding.schema.json +100 -0
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
- package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
- package/bin/runners/lib/schemas/validator.js +465 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -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/ship-gate.js +832 -0
- package/bin/runners/lib/ship-manifest.js +1153 -0
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/ship-output.js +1128 -0
- package/bin/runners/lib/snippets.js +67 -0
- package/bin/runners/lib/status-output.js +340 -0
- package/bin/runners/lib/terminal-ui.js +356 -0
- package/bin/runners/lib/truth.js +1691 -0
- package/bin/runners/lib/ui.js +562 -0
- package/bin/runners/lib/unified-cli-output.js +947 -0
- package/bin/runners/lib/unified-output.js +197 -0
- package/bin/runners/lib/upsell.js +410 -0
- package/bin/runners/lib/usage.js +153 -0
- package/bin/runners/lib/validate-patch.js +156 -0
- package/bin/runners/lib/verdict-engine.js +628 -0
- package/bin/runners/lib/verification.js +345 -0
- package/bin/runners/lib/why-tree.js +650 -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 +229 -0
- package/bin/runners/runAgent.d.ts +5 -0
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runAllowlist.js +418 -0
- package/bin/runners/runApprove.js +320 -0
- package/bin/runners/runAudit.js +692 -0
- package/bin/runners/runAuth.js +731 -0
- package/bin/runners/runCI.js +353 -0
- package/bin/runners/runCheckpoint.js +530 -0
- package/bin/runners/runClassify.js +928 -0
- package/bin/runners/runCleanup.js +343 -0
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runContext.js +175 -0
- package/bin/runners/runDoctor.js +877 -0
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.d.ts +5 -0
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.d.ts +5 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runFix.js +1355 -0
- package/bin/runners/runForge.js +451 -0
- package/bin/runners/runGuard.js +262 -0
- package/bin/runners/runInit.js +1927 -0
- package/bin/runners/runIntent.js +906 -0
- package/bin/runners/runKickoff.js +878 -0
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runLaunch.js +2000 -0
- package/bin/runners/runLink.js +785 -0
- package/bin/runners/runMcp.js +1875 -0
- package/bin/runners/runPacks.js +2089 -0
- package/bin/runners/runPolish.d.ts +4 -0
- package/bin/runners/runPolish.js +390 -0
- package/bin/runners/runPromptFirewall.js +211 -0
- package/bin/runners/runProve.js +1411 -0
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +2260 -0
- package/bin/runners/runReport.js +726 -0
- package/bin/runners/runRuntime.js +110 -0
- package/bin/runners/runSafelist.js +1190 -0
- package/bin/runners/runScan.js +688 -0
- package/bin/runners/runShield.js +1282 -0
- package/bin/runners/runShip.js +1660 -0
- package/bin/runners/runTruth.d.ts +5 -0
- package/bin/runners/runTruth.js +101 -0
- package/bin/runners/runValidate.js +179 -0
- package/bin/runners/runWatch.js +478 -0
- package/bin/runners/utils.js +360 -0
- package/bin/scan.js +617 -0
- package/bin/vibecheck.js +1617 -0
- package/dist/guardrail/index.d.ts +2405 -0
- package/dist/guardrail/index.js +9747 -0
- package/dist/guardrail/index.js.map +1 -0
- package/dist/scanner/index.d.ts +282 -0
- package/dist/scanner/index.js +3395 -0
- package/dist/scanner/index.js.map +1 -0
- package/package.json +123 -104
- package/README.md +0 -491
- package/dist/index.js +0 -99711
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,1660 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck ship - The Vibe Coder's Best Friend
|
|
3
|
+
* Zero config. Plain English. One command to ship with confidence.
|
|
4
|
+
*
|
|
5
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* ENTERPRISE EDITION - World-Class Terminal Experience
|
|
7
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const { withErrorHandling } = require("./lib/error-handler");
|
|
13
|
+
const { ensureOutputDir, detectProjectFeatures } = require("./utils");
|
|
14
|
+
const { enforceLimit, enforceFeature, trackUsage, getCurrentTier } = require("./lib/entitlements");
|
|
15
|
+
const { emitShipCheck } = require("./lib/audit-bridge");
|
|
16
|
+
const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
|
|
17
|
+
const {
|
|
18
|
+
generateRunId,
|
|
19
|
+
createJsonOutput,
|
|
20
|
+
writeJsonOutput,
|
|
21
|
+
saveArtifact
|
|
22
|
+
} = require("./lib/cli-output");
|
|
23
|
+
const { EXIT, verdictToExitCode, exitCodeToVerdict } = require("./lib/exit-codes");
|
|
24
|
+
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
26
|
+
// NEW: Ship Gate System (v2) - Deterministic, Evidence-backed
|
|
27
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
+
let _shipGate = null;
|
|
29
|
+
let _shipManifest = null;
|
|
30
|
+
let _whyTree = null;
|
|
31
|
+
|
|
32
|
+
function getShipGate() {
|
|
33
|
+
if (!_shipGate) {
|
|
34
|
+
try {
|
|
35
|
+
_shipGate = require("./lib/ship-gate");
|
|
36
|
+
} catch {
|
|
37
|
+
_shipGate = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return _shipGate;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getShipManifest() {
|
|
44
|
+
if (!_shipManifest) {
|
|
45
|
+
try {
|
|
46
|
+
_shipManifest = require("./lib/ship-manifest");
|
|
47
|
+
} catch {
|
|
48
|
+
_shipManifest = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return _shipManifest;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getWhyTree() {
|
|
55
|
+
if (!_whyTree) {
|
|
56
|
+
try {
|
|
57
|
+
_whyTree = require("./lib/why-tree");
|
|
58
|
+
} catch {
|
|
59
|
+
_whyTree = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return _whyTree;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Route Truth - Fake endpoint detection (V2 engine-aware)
|
|
66
|
+
const { buildTruthpackSmart, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
|
|
67
|
+
const {
|
|
68
|
+
findMissingRoutes,
|
|
69
|
+
findEnvGaps,
|
|
70
|
+
findFakeSuccess,
|
|
71
|
+
findGhostAuth,
|
|
72
|
+
findStripeWebhookViolations,
|
|
73
|
+
findPaidSurfaceNotEnforced,
|
|
74
|
+
findOwnerModeBypass,
|
|
75
|
+
// NEW: AI Hallucination Detectors
|
|
76
|
+
findOptimisticNoRollback,
|
|
77
|
+
findSilentCatch,
|
|
78
|
+
findMethodMismatch,
|
|
79
|
+
findDeadUI,
|
|
80
|
+
} = require("./lib/analyzers");
|
|
81
|
+
const { findingsFromReality } = require("./lib/reality-findings");
|
|
82
|
+
const { findContractDrift, loadContracts, hasContracts, getDriftSummary } = require("./lib/drift");
|
|
83
|
+
const upsell = require("./lib/upsell");
|
|
84
|
+
const entitlements = require("./lib/entitlements-v2");
|
|
85
|
+
|
|
86
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
87
|
+
// ENHANCED TERMINAL UI & OUTPUT MODULES
|
|
88
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
89
|
+
|
|
90
|
+
const {
|
|
91
|
+
ansi,
|
|
92
|
+
colors,
|
|
93
|
+
icons,
|
|
94
|
+
Spinner,
|
|
95
|
+
renderBanner,
|
|
96
|
+
renderSection,
|
|
97
|
+
formatDuration,
|
|
98
|
+
} = require("./lib/terminal-ui");
|
|
99
|
+
|
|
100
|
+
const {
|
|
101
|
+
formatShipOutput: formatShipOutputLegacy,
|
|
102
|
+
renderVerdictCard,
|
|
103
|
+
renderFixModeHeader,
|
|
104
|
+
renderFixResults,
|
|
105
|
+
renderBadgeOutput,
|
|
106
|
+
getExitCode,
|
|
107
|
+
EXIT_CODES,
|
|
108
|
+
shipIcons,
|
|
109
|
+
} = require("./lib/ship-output");
|
|
110
|
+
|
|
111
|
+
const {
|
|
112
|
+
formatShipOutput,
|
|
113
|
+
} = require("./lib/ship-output-enterprise");
|
|
114
|
+
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
116
|
+
// UNIFIED CLI OUTPUT
|
|
117
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
118
|
+
|
|
119
|
+
let _cliOutput = null;
|
|
120
|
+
function getCliOutput() {
|
|
121
|
+
if (!_cliOutput) _cliOutput = require("./lib/unified-cli-output");
|
|
122
|
+
return _cliOutput;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Fallback banner if unified not available
|
|
126
|
+
const BANNER_FALLBACK = `
|
|
127
|
+
${ansi.rgb(0, 255, 200)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${ansi.reset}
|
|
128
|
+
${ansi.rgb(0, 230, 220)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${ansi.reset}
|
|
129
|
+
${ansi.rgb(0, 200, 255)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${ansi.reset}
|
|
130
|
+
${ansi.rgb(50, 150, 255)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${ansi.reset}
|
|
131
|
+
${ansi.rgb(100, 100, 255)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${ansi.reset}
|
|
132
|
+
${ansi.rgb(150, 50, 255)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${ansi.reset}
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
136
|
+
// TERMINAL UTILITIES
|
|
137
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
138
|
+
|
|
139
|
+
const BOX = {
|
|
140
|
+
topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯',
|
|
141
|
+
horizontal: '─', vertical: '│',
|
|
142
|
+
teeRight: '├', teeLeft: '┤', teeDown: '┬', teeUp: '┴',
|
|
143
|
+
cross: '┼',
|
|
144
|
+
// Double line variants
|
|
145
|
+
dTopLeft: '╔', dTopRight: '╗', dBottomLeft: '╚', dBottomRight: '╝',
|
|
146
|
+
dHorizontal: '═', dVertical: '║',
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const ICONS = {
|
|
150
|
+
ship: '🚀',
|
|
151
|
+
check: '✓',
|
|
152
|
+
cross: '✗',
|
|
153
|
+
warning: '⚠',
|
|
154
|
+
error: '✗',
|
|
155
|
+
info: 'ℹ',
|
|
156
|
+
arrow: '→',
|
|
157
|
+
bullet: '•',
|
|
158
|
+
star: '★',
|
|
159
|
+
sparkle: '✨',
|
|
160
|
+
fire: '🔥',
|
|
161
|
+
lock: '🔐',
|
|
162
|
+
key: '🔑',
|
|
163
|
+
link: '🔗',
|
|
164
|
+
graph: '📊',
|
|
165
|
+
map: '🗺️',
|
|
166
|
+
doc: '📄',
|
|
167
|
+
folder: '📁',
|
|
168
|
+
clock: '⏱',
|
|
169
|
+
target: '🎯',
|
|
170
|
+
shield: '🛡️',
|
|
171
|
+
bug: '🐛',
|
|
172
|
+
wrench: '🔧',
|
|
173
|
+
lightning: '⚡',
|
|
174
|
+
package: '📦',
|
|
175
|
+
route: '🛤️',
|
|
176
|
+
env: '🌍',
|
|
177
|
+
auth: '🔒',
|
|
178
|
+
money: '💰',
|
|
179
|
+
ghost: '👻',
|
|
180
|
+
dead: '💀',
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
184
|
+
const SPINNER_DOTS = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
|
|
185
|
+
const SPINNER_ARROWS = ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'];
|
|
186
|
+
|
|
187
|
+
let spinnerIndex = 0;
|
|
188
|
+
let spinnerInterval = null;
|
|
189
|
+
let spinnerStartTime = null;
|
|
190
|
+
|
|
191
|
+
// formatDuration is imported from terminal-ui.js
|
|
192
|
+
|
|
193
|
+
function formatNumber(num) {
|
|
194
|
+
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function truncate(str, len) {
|
|
198
|
+
if (!str) return '';
|
|
199
|
+
if (str.length <= len) return str;
|
|
200
|
+
return str.slice(0, len - 3) + '...';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function padCenter(str, width) {
|
|
204
|
+
const padding = Math.max(0, width - str.length);
|
|
205
|
+
const left = Math.floor(padding / 2);
|
|
206
|
+
const right = padding - left;
|
|
207
|
+
return ' '.repeat(left) + str + ' '.repeat(right);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function progressBar(percent, width = 30, opts = {}) {
|
|
211
|
+
const filled = Math.round((percent / 100) * width);
|
|
212
|
+
const empty = width - filled;
|
|
213
|
+
|
|
214
|
+
let filledColor;
|
|
215
|
+
if (opts.color) {
|
|
216
|
+
filledColor = opts.color;
|
|
217
|
+
} else if (percent >= 80) {
|
|
218
|
+
filledColor = colors.shipGreen;
|
|
219
|
+
} else if (percent >= 50) {
|
|
220
|
+
filledColor = colors.warnAmber;
|
|
221
|
+
} else {
|
|
222
|
+
filledColor = colors.blockRed;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const filledChar = opts.filled || '█';
|
|
226
|
+
const emptyChar = opts.empty || '░';
|
|
227
|
+
|
|
228
|
+
return `${filledColor}${filledChar.repeat(filled)}${c.dim}${emptyChar.repeat(empty)}${c.reset}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function startSpinner(message, style = 'dots') {
|
|
232
|
+
const frames = style === 'arrows' ? SPINNER_ARROWS :
|
|
233
|
+
style === 'dots' ? SPINNER_DOTS : SPINNER_FRAMES;
|
|
234
|
+
spinnerStartTime = Date.now();
|
|
235
|
+
process.stdout.write(c.hideCursor);
|
|
236
|
+
|
|
237
|
+
spinnerInterval = setInterval(() => {
|
|
238
|
+
const elapsed = formatDuration(Date.now() - spinnerStartTime);
|
|
239
|
+
process.stdout.write(`\r${c.clearLine} ${colors.accent}${frames[spinnerIndex]}${c.reset} ${message} ${c.dim}${elapsed}${c.reset}`);
|
|
240
|
+
spinnerIndex = (spinnerIndex + 1) % frames.length;
|
|
241
|
+
}, 80);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function stopSpinner(message, success = true) {
|
|
245
|
+
if (spinnerInterval) {
|
|
246
|
+
clearInterval(spinnerInterval);
|
|
247
|
+
spinnerInterval = null;
|
|
248
|
+
}
|
|
249
|
+
const elapsed = spinnerStartTime ? formatDuration(Date.now() - spinnerStartTime) : '';
|
|
250
|
+
const icon = success ? `${colors.shipGreen}${ICONS.check}${c.reset}` : `${colors.blockRed}${ICONS.cross}${c.reset}`;
|
|
251
|
+
process.stdout.write(`\r${c.clearLine} ${icon} ${message} ${c.dim}${elapsed}${c.reset}\n`);
|
|
252
|
+
process.stdout.write(c.showCursor);
|
|
253
|
+
spinnerStartTime = null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Compact banner for fix mode (fallback)
|
|
257
|
+
const SHIP_BANNER_FALLBACK = `
|
|
258
|
+
${ansi.rgb(0, 255, 200)} ███████╗██╗ ██╗██╗██████╗ ${ansi.reset}
|
|
259
|
+
${ansi.rgb(0, 230, 220)} ██╔════╝██║ ██║██║██╔══██╗${ansi.reset}
|
|
260
|
+
${ansi.rgb(0, 200, 255)} ███████╗███████║██║██████╔╝${ansi.reset}
|
|
261
|
+
${ansi.rgb(50, 150, 255)} ╚════██║██╔══██║██║██╔═══╝ ${ansi.reset}
|
|
262
|
+
${ansi.rgb(100, 100, 255)} ███████║██║ ██║██║██║ ${ansi.reset}
|
|
263
|
+
${ansi.rgb(150, 50, 255)} ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ${ansi.reset}
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
function printBanner(compact = false, projectRoot = process.cwd()) {
|
|
267
|
+
if (compact) {
|
|
268
|
+
console.log(SHIP_BANNER_FALLBACK);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const cli = getCliOutput();
|
|
274
|
+
console.log(cli.renderFullHeader("ship", {
|
|
275
|
+
version: "4.0.0",
|
|
276
|
+
tier: "FREE",
|
|
277
|
+
target: projectRoot,
|
|
278
|
+
sessionId: cli.generateSessionId(),
|
|
279
|
+
}));
|
|
280
|
+
} catch {
|
|
281
|
+
console.log(BANNER_FALLBACK);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function printDivider(char = '─', width = 69, color = c.dim) {
|
|
286
|
+
console.log(`${color} ${char.repeat(width)}${c.reset}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function printSection(title, icon = '◆') {
|
|
290
|
+
console.log();
|
|
291
|
+
console.log(` ${colors.accent}${icon}${c.reset} ${c.bold}${title}${c.reset}`);
|
|
292
|
+
printDivider();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function printSubSection(title) {
|
|
296
|
+
console.log();
|
|
297
|
+
console.log(` ${c.dim}${BOX.teeRight}${BOX.horizontal}${c.reset} ${c.bold}${title}${c.reset}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
301
|
+
// VERDICT DISPLAY - THE HERO MOMENT
|
|
302
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
303
|
+
|
|
304
|
+
function getVerdictConfig(verdict, score, blockers, warnings) {
|
|
305
|
+
if (verdict === 'SHIP' || (score >= 90 && blockers === 0)) {
|
|
306
|
+
return {
|
|
307
|
+
verdict: 'SHIP',
|
|
308
|
+
icon: '🚀',
|
|
309
|
+
headline: 'CLEAR TO SHIP',
|
|
310
|
+
tagline: 'Your app is production ready!',
|
|
311
|
+
color: colors.shipGreen,
|
|
312
|
+
bgColor: bgRgb(0, 80, 50),
|
|
313
|
+
borderColor: rgb(0, 200, 120),
|
|
314
|
+
glow: rgb(0, 255, 150),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (verdict === 'WARN' || (score >= 50 && blockers <= 2)) {
|
|
319
|
+
return {
|
|
320
|
+
verdict: 'WARN',
|
|
321
|
+
icon: '⚠️',
|
|
322
|
+
headline: 'REVIEW BEFORE SHIP',
|
|
323
|
+
tagline: `${warnings} warning${warnings !== 1 ? 's' : ''} to address`,
|
|
324
|
+
color: colors.warnAmber,
|
|
325
|
+
bgColor: bgRgb(80, 60, 0),
|
|
326
|
+
borderColor: rgb(200, 160, 0),
|
|
327
|
+
glow: rgb(255, 200, 0),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
verdict: 'BLOCK',
|
|
333
|
+
icon: '🛑',
|
|
334
|
+
headline: 'NOT SHIP READY',
|
|
335
|
+
tagline: `${blockers} blocker${blockers !== 1 ? 's' : ''} must be fixed`,
|
|
336
|
+
color: colors.blockRed,
|
|
337
|
+
bgColor: bgRgb(80, 20, 20),
|
|
338
|
+
borderColor: rgb(200, 60, 60),
|
|
339
|
+
glow: rgb(255, 80, 80),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function printVerdictCard(verdict, score, blockers, warnings, duration) {
|
|
344
|
+
const config = getVerdictConfig(verdict, score, blockers, warnings);
|
|
345
|
+
const w = 68; // Inner width
|
|
346
|
+
|
|
347
|
+
console.log();
|
|
348
|
+
console.log();
|
|
349
|
+
|
|
350
|
+
// Top border with glow effect
|
|
351
|
+
console.log(` ${config.borderColor}${BOX.dTopLeft}${BOX.dHorizontal.repeat(w)}${BOX.dTopRight}${c.reset}`);
|
|
352
|
+
|
|
353
|
+
// Empty line
|
|
354
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
355
|
+
|
|
356
|
+
// Verdict icon and headline
|
|
357
|
+
const headlineText = `${config.icon} ${config.headline}`;
|
|
358
|
+
const headlinePadded = padCenter(headlineText, w);
|
|
359
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${config.color}${c.bold}${headlinePadded}${c.reset}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
360
|
+
|
|
361
|
+
// Tagline
|
|
362
|
+
const taglinePadded = padCenter(config.tagline, w);
|
|
363
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${c.dim}${taglinePadded}${c.reset}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
364
|
+
|
|
365
|
+
// Empty line
|
|
366
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
367
|
+
|
|
368
|
+
// Score bar
|
|
369
|
+
const scoreLabel = `VIBE SCORE`;
|
|
370
|
+
const scoreValue = `${score}/100`;
|
|
371
|
+
const scoreBar = progressBar(score, 35, { color: config.color });
|
|
372
|
+
const scoreLine = ` ${scoreLabel} ${scoreBar} ${config.color}${c.bold}${scoreValue}${c.reset}`;
|
|
373
|
+
const scoreLinePadded = scoreLine + ' '.repeat(Math.max(0, w - 60));
|
|
374
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${scoreLinePadded}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
375
|
+
|
|
376
|
+
// Empty line
|
|
377
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
378
|
+
|
|
379
|
+
// Stats row
|
|
380
|
+
const stats = [
|
|
381
|
+
{ label: 'Blockers', value: blockers, color: blockers > 0 ? colors.blockRed : colors.shipGreen },
|
|
382
|
+
{ label: 'Warnings', value: warnings, color: warnings > 0 ? colors.warnAmber : colors.shipGreen },
|
|
383
|
+
{ label: 'Duration', value: formatDuration(duration), color: colors.accent },
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
let statsLine = ' ';
|
|
387
|
+
for (const stat of stats) {
|
|
388
|
+
statsLine += `${c.dim}${stat.label}:${c.reset} ${stat.color}${c.bold}${stat.value}${c.reset} `;
|
|
389
|
+
}
|
|
390
|
+
const statsLinePadded = statsLine + ' '.repeat(Math.max(0, w - statsLine.length + 20));
|
|
391
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${statsLinePadded}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
392
|
+
|
|
393
|
+
// Empty line
|
|
394
|
+
console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
|
|
395
|
+
|
|
396
|
+
// Bottom border
|
|
397
|
+
console.log(` ${config.borderColor}${BOX.dBottomLeft}${BOX.dHorizontal.repeat(w)}${BOX.dBottomRight}${c.reset}`);
|
|
398
|
+
|
|
399
|
+
console.log();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
403
|
+
// FINDINGS DISPLAY
|
|
404
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
405
|
+
|
|
406
|
+
function getSeverityStyle(severity) {
|
|
407
|
+
const styles = {
|
|
408
|
+
BLOCK: { color: colors.critical, bg: bgRgb(80, 20, 20), icon: '●', label: 'BLOCKER' },
|
|
409
|
+
critical: { color: colors.critical, bg: bgRgb(80, 20, 20), icon: '●', label: 'CRITICAL' },
|
|
410
|
+
WARN: { color: colors.warnAmber, bg: bgRgb(80, 60, 0), icon: '◐', label: 'WARNING' },
|
|
411
|
+
warning: { color: colors.warnAmber, bg: bgRgb(80, 60, 0), icon: '◐', label: 'WARNING' },
|
|
412
|
+
high: { color: colors.high, bg: bgRgb(80, 40, 20), icon: '○', label: 'HIGH' },
|
|
413
|
+
medium: { color: colors.medium, bg: bgRgb(60, 50, 0), icon: '○', label: 'MEDIUM' },
|
|
414
|
+
low: { color: colors.low, bg: bgRgb(20, 40, 60), icon: '○', label: 'LOW' },
|
|
415
|
+
info: { color: colors.info, bg: bgRgb(40, 40, 50), icon: '○', label: 'INFO' },
|
|
416
|
+
};
|
|
417
|
+
return styles[severity] || styles.info;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function getCategoryIcon(category) {
|
|
421
|
+
const icons = {
|
|
422
|
+
'MissingRoute': ICONS.route,
|
|
423
|
+
'EnvContract': ICONS.env,
|
|
424
|
+
'EnvGap': ICONS.env,
|
|
425
|
+
'FakeSuccess': ICONS.ghost,
|
|
426
|
+
'GhostAuth': ICONS.auth,
|
|
427
|
+
'StripeWebhook': ICONS.money,
|
|
428
|
+
'PaidSurface': ICONS.money,
|
|
429
|
+
'OwnerModeBypass': ICONS.lock,
|
|
430
|
+
'DeadUI': ICONS.dead,
|
|
431
|
+
'ContractDrift': ICONS.warning,
|
|
432
|
+
'Security': ICONS.shield,
|
|
433
|
+
'Auth': ICONS.lock,
|
|
434
|
+
'Fake Code': ICONS.ghost,
|
|
435
|
+
};
|
|
436
|
+
return icons[category] || ICONS.bug;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function printFindingsBreakdown(findings) {
|
|
440
|
+
if (!findings || findings.length === 0) {
|
|
441
|
+
printSection('FINDINGS', ICONS.check);
|
|
442
|
+
console.log();
|
|
443
|
+
console.log(` ${colors.shipGreen}${c.bold}${ICONS.sparkle} No issues found! Your code is clean.${c.reset}`);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Group by category
|
|
448
|
+
const byCategory = {};
|
|
449
|
+
for (const f of findings) {
|
|
450
|
+
const cat = f.category || 'Other';
|
|
451
|
+
if (!byCategory[cat]) byCategory[cat] = [];
|
|
452
|
+
byCategory[cat].push(f);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const blockers = findings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical');
|
|
456
|
+
const warnings = findings.filter(f => f.severity === 'WARN' || f.severity === 'warning');
|
|
457
|
+
|
|
458
|
+
printSection(`FINDINGS (${blockers.length} blockers, ${warnings.length} warnings)`, ICONS.graph);
|
|
459
|
+
console.log();
|
|
460
|
+
|
|
461
|
+
// Summary by category
|
|
462
|
+
const categories = Object.entries(byCategory).sort((a, b) => {
|
|
463
|
+
const aBlockers = a[1].filter(f => f.severity === 'BLOCK').length;
|
|
464
|
+
const bBlockers = b[1].filter(f => f.severity === 'BLOCK').length;
|
|
465
|
+
return bBlockers - aBlockers;
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
for (const [category, catFindings] of categories) {
|
|
469
|
+
const catBlockers = catFindings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical').length;
|
|
470
|
+
const catWarnings = catFindings.filter(f => f.severity === 'WARN' || f.severity === 'warning').length;
|
|
471
|
+
const icon = getCategoryIcon(category);
|
|
472
|
+
|
|
473
|
+
const statusColor = catBlockers > 0 ? colors.blockRed : catWarnings > 0 ? colors.warnAmber : colors.shipGreen;
|
|
474
|
+
const statusIcon = catBlockers > 0 ? ICONS.cross : catWarnings > 0 ? ICONS.warning : ICONS.check;
|
|
475
|
+
|
|
476
|
+
console.log(` ${statusColor}${statusIcon}${c.reset} ${icon} ${c.bold}${category.padEnd(20)}${c.reset} ${catBlockers > 0 ? `${colors.blockRed}${catBlockers} blockers${c.reset}` : ''}${catWarnings > 0 ? ` ${colors.warnAmber}${catWarnings} warnings${c.reset}` : ''}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function printBlockerDetails(findings, maxShow = 8) {
|
|
481
|
+
const blockers = findings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical');
|
|
482
|
+
|
|
483
|
+
if (blockers.length === 0) return;
|
|
484
|
+
|
|
485
|
+
printSection(`BLOCKERS (${blockers.length})`, '🚨');
|
|
486
|
+
console.log();
|
|
487
|
+
|
|
488
|
+
for (const blocker of blockers.slice(0, maxShow)) {
|
|
489
|
+
const style = getSeverityStyle(blocker.severity);
|
|
490
|
+
const icon = getCategoryIcon(blocker.category);
|
|
491
|
+
|
|
492
|
+
// Severity badge
|
|
493
|
+
console.log(` ${style.bg}${c.bold} ${style.label} ${c.reset} ${icon} ${c.bold}${truncate(blocker.title, 50)}${c.reset}`);
|
|
494
|
+
|
|
495
|
+
// Details
|
|
496
|
+
if (blocker.why) {
|
|
497
|
+
console.log(` ${' '.repeat(10)} ${c.dim}${truncate(blocker.why, 55)}${c.reset}`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// File location
|
|
501
|
+
if (blocker.evidence && blocker.evidence.length > 0) {
|
|
502
|
+
const ev = blocker.evidence[0];
|
|
503
|
+
const fileDisplay = `${path.basename(ev.file || '')}${ev.lines ? `:${ev.lines}` : ''}`;
|
|
504
|
+
console.log(` ${' '.repeat(10)} ${colors.accent}${ICONS.doc} ${fileDisplay}${c.reset}`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Fix hint
|
|
508
|
+
if (blocker.fixHints && blocker.fixHints.length > 0) {
|
|
509
|
+
console.log(` ${' '.repeat(10)} ${colors.shipGreen}${ICONS.arrow} ${truncate(blocker.fixHints[0], 50)}${c.reset}`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
console.log();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (blockers.length > maxShow) {
|
|
516
|
+
console.log(` ${c.dim}... and ${blockers.length - maxShow} more blockers (see full report)${c.reset}`);
|
|
517
|
+
console.log();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
522
|
+
// ROUTE TRUTH VISUALIZATION
|
|
523
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
524
|
+
|
|
525
|
+
function printRouteTruthMap(truthpack) {
|
|
526
|
+
if (!truthpack) return;
|
|
527
|
+
|
|
528
|
+
printSection('ROUTE TRUTH MAP', ICONS.map);
|
|
529
|
+
console.log();
|
|
530
|
+
|
|
531
|
+
const serverRoutes = truthpack.routes?.server?.length || 0;
|
|
532
|
+
const clientRefs = truthpack.routes?.clientRefs?.length || 0;
|
|
533
|
+
const envVars = truthpack.env?.vars?.length || 0;
|
|
534
|
+
const envDeclared = truthpack.env?.declared?.length || 0;
|
|
535
|
+
|
|
536
|
+
// Routes coverage
|
|
537
|
+
const routeCoverage = serverRoutes > 0 ? Math.round((clientRefs / serverRoutes) * 100) : 100;
|
|
538
|
+
const routeColor = routeCoverage >= 80 ? colors.shipGreen : routeCoverage >= 50 ? colors.warnAmber : colors.blockRed;
|
|
539
|
+
|
|
540
|
+
console.log(` ${ICONS.route} ${c.bold}Routes${c.reset}`);
|
|
541
|
+
console.log(` Server: ${colors.accent}${serverRoutes}${c.reset} defined`);
|
|
542
|
+
console.log(` Client: ${colors.accent}${clientRefs}${c.reset} references`);
|
|
543
|
+
console.log(` Coverage: ${progressBar(routeCoverage, 20)} ${routeColor}${routeCoverage}%${c.reset}`);
|
|
544
|
+
console.log();
|
|
545
|
+
|
|
546
|
+
// Env coverage
|
|
547
|
+
const envCoverage = envVars > 0 ? Math.round((envDeclared / envVars) * 100) : 100;
|
|
548
|
+
const envColor = envCoverage >= 80 ? colors.shipGreen : envCoverage >= 50 ? colors.warnAmber : colors.blockRed;
|
|
549
|
+
|
|
550
|
+
console.log(` ${ICONS.env} ${c.bold}Environment${c.reset}`);
|
|
551
|
+
console.log(` Used: ${colors.accent}${envVars}${c.reset} variables`);
|
|
552
|
+
console.log(` Declared: ${colors.accent}${envDeclared}${c.reset} in .env`);
|
|
553
|
+
console.log(` Coverage: ${progressBar(envCoverage, 20)} ${envColor}${envCoverage}%${c.reset}`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
557
|
+
// PROOF GRAPH VISUALIZATION
|
|
558
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
559
|
+
|
|
560
|
+
function printProofGraph(proofGraph) {
|
|
561
|
+
if (!proofGraph || !proofGraph.summary) return;
|
|
562
|
+
|
|
563
|
+
printSection('PROOF GRAPH', ICONS.graph);
|
|
564
|
+
console.log();
|
|
565
|
+
|
|
566
|
+
const { summary } = proofGraph;
|
|
567
|
+
|
|
568
|
+
// Confidence gauge
|
|
569
|
+
const confidence = Math.round((summary.confidence || 0) * 100);
|
|
570
|
+
const confColor = confidence >= 80 ? colors.shipGreen : confidence >= 50 ? colors.warnAmber : colors.blockRed;
|
|
571
|
+
|
|
572
|
+
console.log(` ${c.bold}Analysis Confidence${c.reset}`);
|
|
573
|
+
console.log(` ${progressBar(confidence, 40)} ${confColor}${c.bold}${confidence}%${c.reset}`);
|
|
574
|
+
console.log();
|
|
575
|
+
|
|
576
|
+
// Claims summary
|
|
577
|
+
console.log(` ${c.dim}Claims:${c.reset} ${colors.shipGreen}${summary.verifiedClaims || 0}${c.reset} verified ${c.dim}/${c.reset} ${colors.blockRed}${summary.failedClaims || 0}${c.reset} failed ${c.dim}of${c.reset} ${summary.totalClaims || 0} total`);
|
|
578
|
+
console.log(` ${c.dim}Gaps:${c.reset} ${summary.gaps || 0} identified`);
|
|
579
|
+
console.log(` ${c.dim}Risk Score:${c.reset} ${summary.riskScore || 0}/100`);
|
|
580
|
+
|
|
581
|
+
// Top blockers from proof graph
|
|
582
|
+
if (proofGraph.topBlockers && proofGraph.topBlockers.length > 0) {
|
|
583
|
+
console.log();
|
|
584
|
+
console.log(` ${c.bold}Top Unverified Claims:${c.reset}`);
|
|
585
|
+
for (const blocker of proofGraph.topBlockers.slice(0, 3)) {
|
|
586
|
+
console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} ${truncate(blocker.assertion, 50)}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
592
|
+
// BADGE DISPLAY
|
|
593
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
594
|
+
|
|
595
|
+
function printBadgeOutput(projectPath, verdict, score) {
|
|
596
|
+
const projectName = path.basename(projectPath);
|
|
597
|
+
const projectId = projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
598
|
+
|
|
599
|
+
const config = getVerdictConfig(verdict, score, verdict === 'BLOCK' ? 1 : 0, verdict === 'WARN' ? 1 : 0);
|
|
600
|
+
|
|
601
|
+
printSection('SHIP BADGE', '📛');
|
|
602
|
+
console.log();
|
|
603
|
+
|
|
604
|
+
// Badge preview box
|
|
605
|
+
const badgeText = `vibecheck | ${verdict} | ${score}`;
|
|
606
|
+
console.log(` ${config.bgColor}${c.bold} ${badgeText} ${c.reset}`);
|
|
607
|
+
console.log();
|
|
608
|
+
|
|
609
|
+
const badgeUrl = `https://vibecheck.dev/badge/${projectId}.svg`;
|
|
610
|
+
const reportUrl = `https://vibecheck.dev/report/${projectId}`;
|
|
611
|
+
const markdown = `[](${reportUrl})`;
|
|
612
|
+
|
|
613
|
+
console.log(` ${c.dim}Badge URL:${c.reset}`);
|
|
614
|
+
console.log(` ${colors.accent}${badgeUrl}${c.reset}`);
|
|
615
|
+
console.log();
|
|
616
|
+
console.log(` ${c.dim}Report URL:${c.reset}`);
|
|
617
|
+
console.log(` ${colors.accent}${reportUrl}${c.reset}`);
|
|
618
|
+
console.log();
|
|
619
|
+
console.log(` ${c.dim}Add to README.md:${c.reset}`);
|
|
620
|
+
console.log(` ${colors.shipGreen}${markdown}${c.reset}`);
|
|
621
|
+
|
|
622
|
+
return { projectId, badgeUrl, reportUrl, markdown };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
626
|
+
// FIX MODE DISPLAY
|
|
627
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
628
|
+
|
|
629
|
+
function printFixModeHeader() {
|
|
630
|
+
console.log();
|
|
631
|
+
console.log(` ${bgRgb(40, 80, 120)}${c.bold} ${ICONS.wrench} AUTO-FIX MODE ${c.reset}`);
|
|
632
|
+
console.log();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function printFixResults(fixResults) {
|
|
636
|
+
if (!fixResults) return;
|
|
637
|
+
|
|
638
|
+
printSection('FIX RESULTS', ICONS.wrench);
|
|
639
|
+
console.log();
|
|
640
|
+
|
|
641
|
+
if (fixResults.errors && fixResults.errors.length > 0) {
|
|
642
|
+
for (const err of fixResults.errors) {
|
|
643
|
+
console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} ${err}`);
|
|
644
|
+
}
|
|
645
|
+
console.log();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const actions = [
|
|
649
|
+
{ done: fixResults.envExampleCreated, label: 'Created .env.example', icon: ICONS.env },
|
|
650
|
+
{ done: fixResults.gitignoreUpdated, label: 'Updated .gitignore', icon: ICONS.shield },
|
|
651
|
+
{ done: fixResults.fixesMdCreated, label: 'Generated fixes.md', icon: ICONS.doc },
|
|
652
|
+
];
|
|
653
|
+
|
|
654
|
+
for (const action of actions) {
|
|
655
|
+
const icon = action.done ? `${colors.shipGreen}${ICONS.check}` : `${c.dim}${ICONS.bullet}`;
|
|
656
|
+
const label = action.done ? c.reset + action.label : c.dim + action.label + c.reset;
|
|
657
|
+
console.log(` ${icon}${c.reset} ${action.icon} ${label}`);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (fixResults.secretsFound && fixResults.secretsFound.length > 0) {
|
|
661
|
+
console.log();
|
|
662
|
+
console.log(` ${c.bold}Secrets to migrate:${c.reset}`);
|
|
663
|
+
for (const secret of fixResults.secretsFound.slice(0, 5)) {
|
|
664
|
+
console.log(` ${ICONS.key} ${secret.varName} ${c.dim}(${secret.type})${c.reset}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
console.log();
|
|
669
|
+
console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} ${c.bold}Safe fixes applied!${c.reset}`);
|
|
670
|
+
console.log(` ${c.dim}Review changes and follow instructions in ${colors.accent}.vibecheck/fixes.md${c.reset}`);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
674
|
+
// HELP DISPLAY
|
|
675
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
676
|
+
|
|
677
|
+
function printHelp(showBanner = true) {
|
|
678
|
+
if (showBanner && shouldShowBanner({})) {
|
|
679
|
+
console.log(BANNER);
|
|
680
|
+
}
|
|
681
|
+
console.log(`
|
|
682
|
+
${ansi.bold}Usage:${ansi.reset} vibecheck ship ${ansi.dim}(go)${ansi.reset} [options]
|
|
683
|
+
|
|
684
|
+
${ansi.bold}Aliases:${ansi.reset} ${ansi.dim}go${ansi.reset}
|
|
685
|
+
|
|
686
|
+
${ansi.bold}The One Command${ansi.reset} — Get a ship verdict: ${colors.success}SHIP${ansi.reset} | ${colors.warning}WARN${ansi.reset} | ${colors.error}BLOCK${ansi.reset}
|
|
687
|
+
|
|
688
|
+
${ansi.bold}Verdict Options:${ansi.reset}
|
|
689
|
+
${colors.accent}--strict${ansi.reset} Treat warnings as blockers
|
|
690
|
+
${colors.accent}--why${ansi.reset} Show detailed "why tree" (top blockers + evidence)
|
|
691
|
+
${colors.accent}--cached${ansi.reset} Use cached verdict if repo unchanged
|
|
692
|
+
|
|
693
|
+
${ansi.bold}Output Options:${ansi.reset}
|
|
694
|
+
${colors.accent}--json${ansi.reset} Output full manifest as JSON
|
|
695
|
+
${colors.accent}--ci${ansi.reset} Machine output (KEY=VALUE format for CI/CD)
|
|
696
|
+
${colors.accent}--receipt${ansi.reset} Single-line receipt (parseable)
|
|
697
|
+
${colors.accent}--manifest${ansi.reset} Output full signed manifest
|
|
698
|
+
${colors.accent}--badge, -b${ansi.reset} Generate embeddable badge for README
|
|
699
|
+
|
|
700
|
+
${ansi.bold}Fix Options:${ansi.reset}
|
|
701
|
+
${colors.accent}--fix, -f${ansi.reset} Try safe mechanical fixes ${ansi.dim}(shows plan first)${ansi.reset}
|
|
702
|
+
${colors.accent}--assist${ansi.reset} Generate AI mission prompts for complex issues
|
|
703
|
+
|
|
704
|
+
${ansi.bold}General Options:${ansi.reset}
|
|
705
|
+
${colors.accent}--path, -p${ansi.reset} Project path ${ansi.dim}(default: current directory)${ansi.reset}
|
|
706
|
+
${colors.accent}--verbose, -v${ansi.reset} Show detailed progress
|
|
707
|
+
${colors.accent}--legacy${ansi.reset} Use legacy (non-gate) evaluation
|
|
708
|
+
${colors.accent}--help, -h${ansi.reset} Show this help
|
|
709
|
+
|
|
710
|
+
${ansi.bold}Exit Codes:${ansi.reset}
|
|
711
|
+
${colors.success}0${ansi.reset} SHIP — Ready to ship
|
|
712
|
+
${colors.warning}1${ansi.reset} WARN — Warnings found, review recommended
|
|
713
|
+
${colors.error}2${ansi.reset} BLOCK — Blockers found, must fix before shipping
|
|
714
|
+
|
|
715
|
+
${ansi.bold}CI Integration:${ansi.reset}
|
|
716
|
+
${ansi.dim}# GitHub Actions - set output variables${ansi.reset}
|
|
717
|
+
vibecheck ship --ci >> $$GITHUB_OUTPUT
|
|
718
|
+
|
|
719
|
+
${ansi.dim}# Get receipt for artifact storage${ansi.reset}
|
|
720
|
+
vibecheck ship --receipt > .vibecheck/receipt.txt
|
|
721
|
+
|
|
722
|
+
${ansi.dim}# Cache-aware evaluation (fast re-runs)${ansi.reset}
|
|
723
|
+
vibecheck ship --cached
|
|
724
|
+
|
|
725
|
+
${ansi.bold}Examples:${ansi.reset}
|
|
726
|
+
${ansi.dim}# Quick ship check${ansi.reset}
|
|
727
|
+
vibecheck ship
|
|
728
|
+
|
|
729
|
+
${ansi.dim}# Show why a verdict was reached${ansi.reset}
|
|
730
|
+
vibecheck ship --why
|
|
731
|
+
|
|
732
|
+
${ansi.dim}# Auto-fix what can be fixed${ansi.reset}
|
|
733
|
+
vibecheck ship --fix
|
|
734
|
+
|
|
735
|
+
${ansi.dim}# Generate README badge${ansi.reset}
|
|
736
|
+
vibecheck ship --badge
|
|
737
|
+
|
|
738
|
+
${ansi.dim}# Strict CI mode (warnings = failure)${ansi.reset}
|
|
739
|
+
vibecheck ship --strict --ci
|
|
740
|
+
|
|
741
|
+
${ansi.bold}Artifacts Generated:${ansi.reset}
|
|
742
|
+
${ansi.dim}.vibecheck/ship/manifest.json${ansi.reset} Full signed manifest
|
|
743
|
+
${ansi.dim}.vibecheck/ship/receipt.txt${ansi.reset} CI-friendly receipt
|
|
744
|
+
${ansi.dim}.vibecheck/ship/github-output.txt${ansi.reset} GitHub Actions output
|
|
745
|
+
`);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
749
|
+
// PROOF GRAPH BUILDER
|
|
750
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
751
|
+
|
|
752
|
+
function buildProofGraph(findings, truthpack, root) {
|
|
753
|
+
const claims = [];
|
|
754
|
+
let claimId = 0;
|
|
755
|
+
|
|
756
|
+
for (const finding of findings) {
|
|
757
|
+
const claim = {
|
|
758
|
+
id: `claim-${++claimId}`,
|
|
759
|
+
type: getClaimType(finding.category),
|
|
760
|
+
assertion: finding.title || finding.message,
|
|
761
|
+
verified: finding.severity !== 'BLOCK',
|
|
762
|
+
confidence: finding.confidence === 'high' ? 0.9 : finding.confidence === 'medium' ? 0.7 : 0.5,
|
|
763
|
+
evidence: (finding.evidence || []).map((e, i) => ({
|
|
764
|
+
id: `evidence-${claimId}-${i}`,
|
|
765
|
+
type: 'file_citation',
|
|
766
|
+
file: e.file,
|
|
767
|
+
line: parseInt(e.lines?.split('-')[0]) || e.line || 1,
|
|
768
|
+
snippet: e.snippetHash || '',
|
|
769
|
+
strength: 0.8,
|
|
770
|
+
verifiedAt: new Date().toISOString(),
|
|
771
|
+
method: 'static'
|
|
772
|
+
})),
|
|
773
|
+
gaps: [{
|
|
774
|
+
id: `gap-${claimId}`,
|
|
775
|
+
type: getGapType(finding.category),
|
|
776
|
+
description: finding.why || finding.title,
|
|
777
|
+
severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
|
|
778
|
+
suggestion: (finding.fixHints || [])[0] || ''
|
|
779
|
+
}],
|
|
780
|
+
severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
|
|
781
|
+
file: finding.evidence?.[0]?.file || '',
|
|
782
|
+
line: parseInt(finding.evidence?.[0]?.lines?.split('-')[0]) || 1
|
|
783
|
+
};
|
|
784
|
+
claims.push(claim);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const verifiedClaims = claims.filter(c => c.verified);
|
|
788
|
+
const failedClaims = claims.filter(c => !c.verified);
|
|
789
|
+
const allGaps = claims.flatMap(c => c.gaps);
|
|
790
|
+
|
|
791
|
+
const riskScore = Math.min(100, failedClaims.reduce((sum, c) => {
|
|
792
|
+
if (c.severity === 'critical') return sum + 30;
|
|
793
|
+
if (c.severity === 'high') return sum + 20;
|
|
794
|
+
if (c.severity === 'medium') return sum + 10;
|
|
795
|
+
return sum + 5;
|
|
796
|
+
}, 0));
|
|
797
|
+
|
|
798
|
+
const confidence = claims.length > 0
|
|
799
|
+
? claims.reduce((sum, c) => sum + c.confidence, 0) / claims.length
|
|
800
|
+
: 1.0;
|
|
801
|
+
|
|
802
|
+
return {
|
|
803
|
+
version: '1.0.0',
|
|
804
|
+
generatedAt: new Date().toISOString(),
|
|
805
|
+
projectPath: root,
|
|
806
|
+
claims,
|
|
807
|
+
summary: {
|
|
808
|
+
totalClaims: claims.length,
|
|
809
|
+
verifiedClaims: verifiedClaims.length,
|
|
810
|
+
failedClaims: failedClaims.length,
|
|
811
|
+
gaps: allGaps.length,
|
|
812
|
+
riskScore,
|
|
813
|
+
confidence
|
|
814
|
+
},
|
|
815
|
+
verdict: findings.some(f => f.severity === 'BLOCK') ? 'BLOCK' :
|
|
816
|
+
findings.some(f => f.severity === 'WARN') ? 'WARN' : 'SHIP',
|
|
817
|
+
topBlockers: failedClaims.filter(c => c.severity === 'critical' || c.severity === 'high').slice(0, 5),
|
|
818
|
+
topGaps: allGaps.filter(g => g.severity === 'critical' || g.severity === 'high').slice(0, 5)
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function getClaimType(category) {
|
|
823
|
+
const map = {
|
|
824
|
+
'MissingRoute': 'route_exists',
|
|
825
|
+
'EnvContract': 'env_declared',
|
|
826
|
+
'FakeSuccess': 'success_verified',
|
|
827
|
+
'GhostAuth': 'auth_protected',
|
|
828
|
+
'StripeWebhook': 'billing_enforced',
|
|
829
|
+
'PaidSurface': 'billing_enforced',
|
|
830
|
+
'OwnerModeBypass': 'billing_enforced',
|
|
831
|
+
'DeadUI': 'ui_wired',
|
|
832
|
+
'ContractDrift': 'contract_satisfied'
|
|
833
|
+
};
|
|
834
|
+
return map[category] || 'ui_wired';
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function getGapType(category) {
|
|
838
|
+
const map = {
|
|
839
|
+
'MissingRoute': 'missing_handler',
|
|
840
|
+
'EnvContract': 'missing_verification',
|
|
841
|
+
'FakeSuccess': 'missing_verification',
|
|
842
|
+
'GhostAuth': 'missing_gate',
|
|
843
|
+
'StripeWebhook': 'missing_verification',
|
|
844
|
+
'PaidSurface': 'missing_gate',
|
|
845
|
+
'OwnerModeBypass': 'missing_gate',
|
|
846
|
+
'DeadUI': 'missing_handler',
|
|
847
|
+
'ContractDrift': 'contract_drift'
|
|
848
|
+
};
|
|
849
|
+
return map[category] || 'untested_path';
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
853
|
+
// ARGS PARSER
|
|
854
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
855
|
+
|
|
856
|
+
function parseArgs(args) {
|
|
857
|
+
// Parse global flags first
|
|
858
|
+
const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
|
|
859
|
+
|
|
860
|
+
const opts = {
|
|
861
|
+
fix: false,
|
|
862
|
+
path: globalFlags.path || ".",
|
|
863
|
+
verbose: globalFlags.verbose || false,
|
|
864
|
+
json: globalFlags.json || false,
|
|
865
|
+
badge: false,
|
|
866
|
+
assist: false,
|
|
867
|
+
strict: globalFlags.strict || false,
|
|
868
|
+
ci: globalFlags.ci || false,
|
|
869
|
+
mode: null, // "scan" for scan mode
|
|
870
|
+
withRuntime: false, // merge runtime results
|
|
871
|
+
help: globalFlags.help || false,
|
|
872
|
+
noBanner: globalFlags.noBanner || false,
|
|
873
|
+
quiet: globalFlags.quiet || false,
|
|
874
|
+
// NEW: Gate mode options
|
|
875
|
+
useGate: true, // Use new gate system by default
|
|
876
|
+
cached: false, // Use cached verdict if valid
|
|
877
|
+
manifest: false, // Output full manifest
|
|
878
|
+
receipt: false, // Output CI receipt
|
|
879
|
+
whyTree: false, // Show detailed why tree
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// Parse command-specific args
|
|
883
|
+
for (let i = 0; i < cleanArgs.length; i++) {
|
|
884
|
+
const a = cleanArgs[i];
|
|
885
|
+
if (a === "--fix" || a === "-f") opts.fix = true;
|
|
886
|
+
else if (a === "--badge" || a === "-b") opts.badge = true;
|
|
887
|
+
else if (a === "--assist") opts.assist = true;
|
|
888
|
+
else if (a === "--mode") opts.mode = cleanArgs[++i];
|
|
889
|
+
else if (a === "--with") {
|
|
890
|
+
const next = cleanArgs[++i];
|
|
891
|
+
if (next === "runtime") opts.withRuntime = true;
|
|
892
|
+
}
|
|
893
|
+
else if (a.startsWith("--path=")) opts.path = a.split("=")[1];
|
|
894
|
+
else if (a === "--path" || a === "-p") opts.path = args[++i];
|
|
895
|
+
// NEW: Gate options
|
|
896
|
+
else if (a === "--legacy") opts.useGate = false;
|
|
897
|
+
else if (a === "--cached") opts.cached = true;
|
|
898
|
+
else if (a === "--manifest") opts.manifest = true;
|
|
899
|
+
else if (a === "--receipt") opts.receipt = true;
|
|
900
|
+
else if (a === "--why") opts.whyTree = true;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return opts;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
907
|
+
// MAIN SHIP FUNCTION
|
|
908
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
909
|
+
|
|
910
|
+
async function runShip(args, context = {}) {
|
|
911
|
+
// Extract runId from context or generate new one
|
|
912
|
+
const runId = context.runId || generateRunId();
|
|
913
|
+
const startTime = context.startTime || new Date().toISOString();
|
|
914
|
+
|
|
915
|
+
const opts = parseArgs(args);
|
|
916
|
+
const executionStart = Date.now();
|
|
917
|
+
|
|
918
|
+
// Show help if requested
|
|
919
|
+
if (opts.help) {
|
|
920
|
+
printHelp(shouldShowBanner(opts));
|
|
921
|
+
return 0;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
925
|
+
// NEW: Use Ship Gate (v2) system for deterministic, evidence-backed verdicts
|
|
926
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
927
|
+
if (opts.useGate && !opts.fix && !opts.assist) {
|
|
928
|
+
try {
|
|
929
|
+
const gateResult = await runShipGate(opts, context);
|
|
930
|
+
if (gateResult !== null) {
|
|
931
|
+
return gateResult;
|
|
932
|
+
}
|
|
933
|
+
// If null returned, fall through to legacy mode
|
|
934
|
+
} catch (err) {
|
|
935
|
+
if (opts.verbose) {
|
|
936
|
+
console.error(` Gate evaluation failed: ${err.message}, using legacy mode`);
|
|
937
|
+
}
|
|
938
|
+
// Fall through to legacy mode
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Entitlement check
|
|
943
|
+
try {
|
|
944
|
+
await enforceLimit('scans');
|
|
945
|
+
await enforceFeature('ship');
|
|
946
|
+
if (opts.fix) {
|
|
947
|
+
await enforceFeature('fix');
|
|
948
|
+
}
|
|
949
|
+
} catch (err) {
|
|
950
|
+
if (err.code === 'LIMIT_EXCEEDED' || err.code === 'FEATURE_NOT_AVAILABLE') {
|
|
951
|
+
console.error(`\n ${colors.error}${icons.error}${ansi.reset} ${err.upgradePrompt || err.message}\n`);
|
|
952
|
+
return EXIT.TIER_REQUIRED;
|
|
953
|
+
}
|
|
954
|
+
throw err;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
await trackUsage('scans');
|
|
958
|
+
|
|
959
|
+
const projectPath = path.resolve(opts.path);
|
|
960
|
+
const outputDir = path.join(projectPath, ".vibecheck");
|
|
961
|
+
const projectName = path.basename(projectPath);
|
|
962
|
+
|
|
963
|
+
// Print banner (respects --no-banner, VIBECHECK_NO_BANNER, --ci, --quiet, --json)
|
|
964
|
+
if (shouldShowBanner(opts)) {
|
|
965
|
+
printBanner();
|
|
966
|
+
console.log(` ${ansi.dim}Project:${ansi.reset} ${ansi.bold}${projectName}${ansi.reset}`);
|
|
967
|
+
console.log(` ${ansi.dim}Path:${ansi.reset} ${projectPath}`);
|
|
968
|
+
console.log();
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
let results = {
|
|
972
|
+
score: 100,
|
|
973
|
+
grade: "A",
|
|
974
|
+
canShip: true,
|
|
975
|
+
deductions: [],
|
|
976
|
+
blockers: [],
|
|
977
|
+
warnings: [],
|
|
978
|
+
findings: [],
|
|
979
|
+
truthpack: null,
|
|
980
|
+
proofGraph: null,
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
// Initialize spinner outside try block so it's in scope for catch
|
|
984
|
+
let spinner;
|
|
985
|
+
if (!opts.json && !opts.ci) {
|
|
986
|
+
spinner = new Spinner({ color: colors.accent });
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
try {
|
|
990
|
+
|
|
991
|
+
// Phase 1: Production Integrity Check
|
|
992
|
+
if (spinner) spinner.start('Checking production integrity...');
|
|
993
|
+
|
|
994
|
+
try {
|
|
995
|
+
const { auditProductionIntegrity } = require(
|
|
996
|
+
path.join(__dirname, "../../scripts/audit-production-integrity.js"),
|
|
997
|
+
);
|
|
998
|
+
const { results: integrityResults, integrity } = await auditProductionIntegrity(projectPath);
|
|
999
|
+
results.score = integrity.score;
|
|
1000
|
+
results.grade = integrity.grade;
|
|
1001
|
+
results.canShip = integrity.canShip;
|
|
1002
|
+
results.deductions = integrity.deductions;
|
|
1003
|
+
results.integrity = integrityResults;
|
|
1004
|
+
} catch (err) {
|
|
1005
|
+
if (opts.verbose) console.warn(` ${ansi.dim}Integrity check skipped: ${err.message}${ansi.reset}`);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (spinner) spinner.succeed('Production integrity checked');
|
|
1009
|
+
|
|
1010
|
+
// Phase 2: Route Truth Analysis
|
|
1011
|
+
if (spinner) spinner.start('Building route truth map...');
|
|
1012
|
+
|
|
1013
|
+
const fastifyEntry = detectFastifyEntry(projectPath);
|
|
1014
|
+
const truthpack = await buildTruthpackSmart({ repoRoot: projectPath, fastifyEntry });
|
|
1015
|
+
writeTruthpack(projectPath, truthpack);
|
|
1016
|
+
results.truthpack = truthpack;
|
|
1017
|
+
|
|
1018
|
+
// Run all analyzers in parallel for performance (independent analyzers)
|
|
1019
|
+
// Group 1: File-based analyzers (can run fully in parallel)
|
|
1020
|
+
const fileAnalyzerPromises = [
|
|
1021
|
+
Promise.resolve(findFakeSuccess(projectPath)),
|
|
1022
|
+
Promise.resolve(findOptimisticNoRollback(projectPath)),
|
|
1023
|
+
Promise.resolve(findSilentCatch(projectPath)),
|
|
1024
|
+
Promise.resolve(findDeadUI(projectPath)),
|
|
1025
|
+
Promise.resolve(findOwnerModeBypass(projectPath)),
|
|
1026
|
+
];
|
|
1027
|
+
|
|
1028
|
+
// Group 2: Truthpack-based analyzers (depend on truthpack but not each other)
|
|
1029
|
+
const truthpackAnalyzerPromises = [
|
|
1030
|
+
Promise.resolve(findMissingRoutes(truthpack)),
|
|
1031
|
+
Promise.resolve(findMethodMismatch(truthpack)),
|
|
1032
|
+
Promise.resolve(findEnvGaps(truthpack)),
|
|
1033
|
+
Promise.resolve(findGhostAuth(truthpack, projectPath)),
|
|
1034
|
+
Promise.resolve(findStripeWebhookViolations(truthpack)),
|
|
1035
|
+
Promise.resolve(findPaidSurfaceNotEnforced(truthpack)),
|
|
1036
|
+
];
|
|
1037
|
+
|
|
1038
|
+
// Run both groups in parallel
|
|
1039
|
+
const [fileResults, truthpackResults] = await Promise.all([
|
|
1040
|
+
Promise.all(fileAnalyzerPromises),
|
|
1041
|
+
Promise.all(truthpackAnalyzerPromises),
|
|
1042
|
+
]);
|
|
1043
|
+
|
|
1044
|
+
// Flatten results
|
|
1045
|
+
const allFindings = [
|
|
1046
|
+
...fileResults.flat(),
|
|
1047
|
+
...truthpackResults.flat(),
|
|
1048
|
+
// Runtime findings (if requested)
|
|
1049
|
+
...(opts.withRuntime ? findingsFromReality(projectPath) : [])
|
|
1050
|
+
];
|
|
1051
|
+
|
|
1052
|
+
// Contract drift detection
|
|
1053
|
+
if (hasContracts(projectPath)) {
|
|
1054
|
+
const contracts = loadContracts(projectPath);
|
|
1055
|
+
const driftFindings = findContractDrift(contracts, truthpack);
|
|
1056
|
+
allFindings.push(...driftFindings);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
results.findings = allFindings;
|
|
1060
|
+
|
|
1061
|
+
if (spinner) spinner.succeed(`Route truth mapped (${truthpack.routes?.server?.length || 0} routes)`);
|
|
1062
|
+
|
|
1063
|
+
// Phase 3: Build Proof Graph
|
|
1064
|
+
if (spinner) spinner.start('Building proof graph...');
|
|
1065
|
+
|
|
1066
|
+
const proofGraph = buildProofGraph(allFindings, truthpack, projectPath);
|
|
1067
|
+
results.proofGraph = proofGraph;
|
|
1068
|
+
|
|
1069
|
+
if (spinner) spinner.succeed(`Proof graph built (${proofGraph.summary.totalClaims} claims)`);
|
|
1070
|
+
|
|
1071
|
+
// Calculate final verdict
|
|
1072
|
+
const blockers = allFindings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical');
|
|
1073
|
+
const warnings = allFindings.filter(f => f.severity === 'WARN' || f.severity === 'warning');
|
|
1074
|
+
|
|
1075
|
+
results.blockers = blockers;
|
|
1076
|
+
results.warnings = warnings;
|
|
1077
|
+
|
|
1078
|
+
// Apply strict mode
|
|
1079
|
+
if (opts.strict && warnings.length > 0) {
|
|
1080
|
+
results.canShip = false;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if (blockers.length > 0) {
|
|
1084
|
+
results.canShip = false;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Deduct score for findings
|
|
1088
|
+
for (const finding of allFindings) {
|
|
1089
|
+
if (finding.severity === 'BLOCK') {
|
|
1090
|
+
results.score = Math.max(0, results.score - 15);
|
|
1091
|
+
} else if (finding.severity === 'WARN') {
|
|
1092
|
+
results.score = Math.max(0, results.score - 5);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const verdict = results.canShip ? 'SHIP' : blockers.length > 0 ? 'BLOCK' : 'WARN';
|
|
1097
|
+
const duration = Date.now() - startTime;
|
|
1098
|
+
|
|
1099
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1100
|
+
// OUTPUT
|
|
1101
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1102
|
+
|
|
1103
|
+
// JSON output mode
|
|
1104
|
+
if (opts.json) {
|
|
1105
|
+
const output = createJsonOutput({
|
|
1106
|
+
runId,
|
|
1107
|
+
command: "ship",
|
|
1108
|
+
startTime,
|
|
1109
|
+
exitCode: getExitCode(verdict),
|
|
1110
|
+
verdict,
|
|
1111
|
+
result: {
|
|
1112
|
+
verdict,
|
|
1113
|
+
score: results.score,
|
|
1114
|
+
grade: results.grade,
|
|
1115
|
+
canShip: results.canShip,
|
|
1116
|
+
summary: {
|
|
1117
|
+
blockers: blockers.length,
|
|
1118
|
+
warnings: warnings.length,
|
|
1119
|
+
total: allFindings.length,
|
|
1120
|
+
},
|
|
1121
|
+
findings: allFindings,
|
|
1122
|
+
proofGraph: proofGraph.summary,
|
|
1123
|
+
duration: Date.now() - executionStart,
|
|
1124
|
+
},
|
|
1125
|
+
tier: getCurrentTier(),
|
|
1126
|
+
version: require("../../package.json").version,
|
|
1127
|
+
artifacts: [
|
|
1128
|
+
{
|
|
1129
|
+
type: "report",
|
|
1130
|
+
path: path.join(outputDir, "report.json"),
|
|
1131
|
+
description: "Ship report with findings"
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
type: "proof",
|
|
1135
|
+
path: path.join(outputDir, "proof-graph.json"),
|
|
1136
|
+
description: "Proof graph analysis"
|
|
1137
|
+
}
|
|
1138
|
+
]
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
writeJsonOutput(output, opts.output);
|
|
1142
|
+
|
|
1143
|
+
// Save artifacts
|
|
1144
|
+
const reportPath = saveArtifact(runId, "report", {
|
|
1145
|
+
...output.result,
|
|
1146
|
+
truthpack,
|
|
1147
|
+
integrity: results.integrity
|
|
1148
|
+
});
|
|
1149
|
+
const proofPath = saveArtifact(runId, "proof-graph", proofGraph);
|
|
1150
|
+
|
|
1151
|
+
return getExitCode(verdict);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// CI output mode (minimal)
|
|
1155
|
+
if (opts.ci) {
|
|
1156
|
+
console.log(`VERDICT=${verdict}`);
|
|
1157
|
+
console.log(`SCORE=${results.score}`);
|
|
1158
|
+
console.log(`BLOCKERS=${blockers.length}`);
|
|
1159
|
+
console.log(`WARNINGS=${warnings.length}`);
|
|
1160
|
+
|
|
1161
|
+
// Save CI artifacts
|
|
1162
|
+
saveArtifact(runId, "ci-summary", {
|
|
1163
|
+
verdict,
|
|
1164
|
+
score: results.score,
|
|
1165
|
+
blockers: blockers.length,
|
|
1166
|
+
warnings: warnings.length,
|
|
1167
|
+
timestamp: new Date().toISOString()
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
return getExitCode(verdict);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// Fix mode
|
|
1174
|
+
let fixResults = null;
|
|
1175
|
+
if (opts.fix) {
|
|
1176
|
+
if (spinner) spinner.start('Applying safe fixes...');
|
|
1177
|
+
fixResults = await runAutoFix(projectPath, results, outputDir, allFindings);
|
|
1178
|
+
if (spinner) spinner.succeed('Safe fixes applied');
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Human-readable output using enterprise ship-output module
|
|
1182
|
+
const result = {
|
|
1183
|
+
verdict,
|
|
1184
|
+
score: results.score,
|
|
1185
|
+
findings: allFindings,
|
|
1186
|
+
blockers,
|
|
1187
|
+
warnings,
|
|
1188
|
+
truthpack,
|
|
1189
|
+
proofGraph,
|
|
1190
|
+
fixResults,
|
|
1191
|
+
duration: Date.now() - executionStart,
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
// Get current tier for output formatting
|
|
1195
|
+
const currentTier = context?.authInfo?.access?.tier || getCurrentTier() || "free";
|
|
1196
|
+
|
|
1197
|
+
// Use enterprise format
|
|
1198
|
+
console.log(formatShipOutput(result, {
|
|
1199
|
+
tier: currentTier,
|
|
1200
|
+
}));
|
|
1201
|
+
|
|
1202
|
+
// Badge file generation (STARTER+ only)
|
|
1203
|
+
if (opts.badge) {
|
|
1204
|
+
const isVerified = opts.withRuntime && (currentTier === 'pro' || currentTier === 'compliance');
|
|
1205
|
+
const { data: badgeData } = renderBadgeOutput(projectPath, verdict, results.score, {
|
|
1206
|
+
tier: currentTier,
|
|
1207
|
+
isVerified
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
// Save badge info
|
|
1211
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1212
|
+
fs.writeFileSync(
|
|
1213
|
+
path.join(outputDir, 'badge.json'),
|
|
1214
|
+
JSON.stringify({
|
|
1215
|
+
...badgeData,
|
|
1216
|
+
verdict,
|
|
1217
|
+
score: results.score,
|
|
1218
|
+
tier: currentTier,
|
|
1219
|
+
isVerified,
|
|
1220
|
+
generatedAt: new Date().toISOString()
|
|
1221
|
+
}, null, 2)
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Earned upsell: Badge withheld when verdict != SHIP
|
|
1226
|
+
if (!results.canShip) {
|
|
1227
|
+
const currentTier = context?.authInfo?.access?.tier || "free";
|
|
1228
|
+
if (entitlements.tierMeetsMinimum(currentTier, "starter")) {
|
|
1229
|
+
// User has badge access but verdict prevents it
|
|
1230
|
+
console.log(upsell.formatEarnedUpsell({
|
|
1231
|
+
cmd: "ship",
|
|
1232
|
+
verdict,
|
|
1233
|
+
topIssues: blockers.slice(0, 3),
|
|
1234
|
+
withheldArtifact: "badge",
|
|
1235
|
+
currentTier,
|
|
1236
|
+
suggestedCmd: currentTier === "free" ? "vibecheck fix --plan-only" : "vibecheck fix",
|
|
1237
|
+
}));
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Emit audit event
|
|
1242
|
+
emitShipCheck(projectPath, results.canShip ? 'success' : 'failure', {
|
|
1243
|
+
score: results.score,
|
|
1244
|
+
grade: results.grade,
|
|
1245
|
+
canShip: results.canShip,
|
|
1246
|
+
issueCount: allFindings.length,
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
// Write artifacts (no HTML report - use 'vibecheck report' command instead)
|
|
1250
|
+
try {
|
|
1251
|
+
const { writeArtifacts } = require("./utils");
|
|
1252
|
+
writeArtifacts(outputDir, results, { skipHtmlReport: true });
|
|
1253
|
+
} catch {}
|
|
1254
|
+
|
|
1255
|
+
// Exit code: 0=SHIP, 1=WARN, 2=BLOCK
|
|
1256
|
+
const exitCode = getExitCode(verdict);
|
|
1257
|
+
|
|
1258
|
+
// Save final results
|
|
1259
|
+
saveArtifact(runId, "summary", {
|
|
1260
|
+
verdict,
|
|
1261
|
+
score: results.score,
|
|
1262
|
+
canShip: results.canShip,
|
|
1263
|
+
exitCode,
|
|
1264
|
+
timestamp: new Date().toISOString()
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
return exitCode;
|
|
1268
|
+
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
if (spinner) spinner.fail(`Ship check failed: ${error.message}`);
|
|
1271
|
+
|
|
1272
|
+
console.error(`\n ${colors.error}${icons.error}${ansi.reset} ${ansi.bold}Error:${ansi.reset} ${error.message}`);
|
|
1273
|
+
if (opts.verbose) {
|
|
1274
|
+
console.error(` ${ansi.dim}${error.stack}${ansi.reset}`);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
return EXIT.INTERNAL_ERROR;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1282
|
+
// AUTO-FIX (Placeholder - would import from original)
|
|
1283
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1284
|
+
|
|
1285
|
+
async function runAutoFix(projectPath, results, outputDir, findings) {
|
|
1286
|
+
// This would be the full auto-fix implementation from the original
|
|
1287
|
+
// Keeping it as a placeholder for now
|
|
1288
|
+
const fixResults = {
|
|
1289
|
+
envExampleCreated: false,
|
|
1290
|
+
gitignoreUpdated: false,
|
|
1291
|
+
fixesMdCreated: false,
|
|
1292
|
+
secretsFound: [],
|
|
1293
|
+
errors: [],
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1297
|
+
|
|
1298
|
+
// Generate fixes.md with modern findings
|
|
1299
|
+
const fixesMdPath = path.join(outputDir, "fixes.md");
|
|
1300
|
+
let md = "# 🔧 vibecheck Fix Guide\n\n";
|
|
1301
|
+
md += `Generated: ${new Date().toISOString()}\n\n`;
|
|
1302
|
+
md += `**${findings.length} issues found**\n\n---\n\n`;
|
|
1303
|
+
|
|
1304
|
+
for (const finding of findings) {
|
|
1305
|
+
md += `## ${finding.category}: ${finding.title}\n\n`;
|
|
1306
|
+
md += `**Severity:** ${finding.severity}\n\n`;
|
|
1307
|
+
if (finding.why) md += `**Why:** ${finding.why}\n\n`;
|
|
1308
|
+
if (finding.fixHints?.length) {
|
|
1309
|
+
md += "**Fix:**\n";
|
|
1310
|
+
for (const hint of finding.fixHints) {
|
|
1311
|
+
md += `- ${hint}\n`;
|
|
1312
|
+
}
|
|
1313
|
+
md += "\n";
|
|
1314
|
+
}
|
|
1315
|
+
md += "---\n\n";
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
fs.writeFileSync(fixesMdPath, md);
|
|
1319
|
+
fixResults.fixesMdCreated = true;
|
|
1320
|
+
|
|
1321
|
+
return fixResults;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1325
|
+
// SHIP GATE (v2) - Deterministic, Evidence-backed Verdict
|
|
1326
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1327
|
+
|
|
1328
|
+
/**
|
|
1329
|
+
* Run ship using the new gate system.
|
|
1330
|
+
*
|
|
1331
|
+
* The gate system provides:
|
|
1332
|
+
* - Deterministic verdicts (same repo state → same verdict)
|
|
1333
|
+
* - Evidence-backed decisions (why tree)
|
|
1334
|
+
* - CI-ready artifacts (manifest, receipt, badge)
|
|
1335
|
+
*
|
|
1336
|
+
* @param {Object} opts - Options from parseArgs
|
|
1337
|
+
* @param {Object} context - Execution context
|
|
1338
|
+
* @returns {number} Exit code
|
|
1339
|
+
*/
|
|
1340
|
+
async function runShipGate(opts, context = {}) {
|
|
1341
|
+
const shipGate = getShipGate();
|
|
1342
|
+
const shipManifest = getShipManifest();
|
|
1343
|
+
const whyTreeModule = getWhyTree();
|
|
1344
|
+
|
|
1345
|
+
if (!shipGate || !shipManifest) {
|
|
1346
|
+
// Fall back to legacy mode if gate modules not available
|
|
1347
|
+
console.error(" Ship gate modules not available, using legacy mode");
|
|
1348
|
+
return null; // Signal to use legacy
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const projectPath = path.resolve(opts.path);
|
|
1352
|
+
const startTime = Date.now();
|
|
1353
|
+
|
|
1354
|
+
// Check cache first if requested
|
|
1355
|
+
if (opts.cached) {
|
|
1356
|
+
const cacheResult = shipGate.checkVerdictCache(projectPath);
|
|
1357
|
+
if (cacheResult.valid) {
|
|
1358
|
+
const manifest = cacheResult.manifest;
|
|
1359
|
+
|
|
1360
|
+
if (opts.json || opts.manifest) {
|
|
1361
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
1362
|
+
} else if (opts.receipt) {
|
|
1363
|
+
console.log(shipManifest.generateCIReceipt(manifest));
|
|
1364
|
+
} else if (!opts.quiet) {
|
|
1365
|
+
console.log(` Using cached verdict: ${manifest.verdict.status} (${cacheResult.age}ms old)`);
|
|
1366
|
+
console.log(` Fingerprint: ${manifest.repo.fingerprint}`);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
return manifest.verdict.exitCode;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if (!opts.quiet && !opts.json) {
|
|
1373
|
+
console.log(` Cache invalid: ${cacheResult.reason}`);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// Run full gate evaluation
|
|
1378
|
+
const gate = new shipGate.ShipGate(projectPath, {
|
|
1379
|
+
strict: opts.strict,
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
const result = await gate.evaluate({
|
|
1383
|
+
skipReality: !opts.withRuntime,
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
// Output based on mode
|
|
1387
|
+
if (opts.json || opts.manifest) {
|
|
1388
|
+
console.log(JSON.stringify(result.manifest, null, 2));
|
|
1389
|
+
return result.exitCode;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
if (opts.receipt) {
|
|
1393
|
+
console.log(shipManifest.generateCIReceipt(result.manifest));
|
|
1394
|
+
return result.exitCode;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
if (opts.ci) {
|
|
1398
|
+
// CI mode: minimal key=value output
|
|
1399
|
+
const envVars = shipManifest.generateCIEnvVars(result.manifest);
|
|
1400
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
1401
|
+
console.log(`${key}=${value}`);
|
|
1402
|
+
}
|
|
1403
|
+
return result.exitCode;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// Human-readable output
|
|
1407
|
+
if (!opts.quiet) {
|
|
1408
|
+
// Print verdict card
|
|
1409
|
+
printVerdictCard(
|
|
1410
|
+
result.verdict,
|
|
1411
|
+
result.score,
|
|
1412
|
+
result.evidence.audit.blockers,
|
|
1413
|
+
result.evidence.audit.warnings,
|
|
1414
|
+
result.durationMs
|
|
1415
|
+
);
|
|
1416
|
+
|
|
1417
|
+
// Print why tree if requested or if blocked
|
|
1418
|
+
if (opts.whyTree || result.verdict !== "SHIP") {
|
|
1419
|
+
if (whyTreeModule) {
|
|
1420
|
+
const whyTree = whyTreeModule.buildWhyTree(result.findings);
|
|
1421
|
+
console.log(whyTreeModule.renderWhyTreeTerminal(whyTree));
|
|
1422
|
+
} else {
|
|
1423
|
+
// Fallback: simple why tree from manifest
|
|
1424
|
+
const why = result.manifest.evidence.whyTree;
|
|
1425
|
+
console.log(`\n ${ansi.bold}WHY: ${result.verdict}${ansi.reset}`);
|
|
1426
|
+
console.log(` ${ansi.dim}${why.summary}${ansi.reset}\n`);
|
|
1427
|
+
|
|
1428
|
+
for (const issue of why.topIssues || []) {
|
|
1429
|
+
console.log(` ${colors.blockRed}•${ansi.reset} ${issue.title}`);
|
|
1430
|
+
if (issue.evidence?.file) {
|
|
1431
|
+
console.log(` ${ansi.dim}→ ${issue.evidence.file}:${issue.evidence.lines || ""}${ansi.reset}`);
|
|
1432
|
+
}
|
|
1433
|
+
if (issue.fixHint) {
|
|
1434
|
+
console.log(` ${colors.shipGreen}Fix: ${issue.fixHint}${ansi.reset}`);
|
|
1435
|
+
}
|
|
1436
|
+
console.log();
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// Print artifact paths
|
|
1442
|
+
console.log(` ${ansi.dim}Artifacts:${ansi.reset}`);
|
|
1443
|
+
console.log(` ${ansi.dim}Manifest: ${result.artifactPaths.manifest}${ansi.reset}`);
|
|
1444
|
+
console.log(` ${ansi.dim}Receipt: ${result.artifactPaths.receipt}${ansi.reset}`);
|
|
1445
|
+
console.log();
|
|
1446
|
+
|
|
1447
|
+
// Badge info if requested
|
|
1448
|
+
if (opts.badge) {
|
|
1449
|
+
const badgeInfo = shipManifest.generateBadgeInfo(result.manifest);
|
|
1450
|
+
console.log(` ${ansi.bold}Badge:${ansi.reset}`);
|
|
1451
|
+
console.log(` ${badgeInfo.markdown}`);
|
|
1452
|
+
console.log();
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
return result.exitCode;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1460
|
+
// SHIP CORE (for programmatic use)
|
|
1461
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1462
|
+
|
|
1463
|
+
async function shipCore({ repoRoot, fastifyEntry, jsonOut, noWrite } = {}) {
|
|
1464
|
+
const root = repoRoot || process.cwd();
|
|
1465
|
+
const fastEntry = fastifyEntry || detectFastifyEntry(root);
|
|
1466
|
+
|
|
1467
|
+
const truthpack = await buildTruthpackSmart({ repoRoot: root, fastifyEntry: fastEntry });
|
|
1468
|
+
if (!noWrite) writeTruthpack(root, truthpack);
|
|
1469
|
+
|
|
1470
|
+
// Run analyzers in parallel for performance
|
|
1471
|
+
const [fileResults, truthpackResults] = await Promise.all([
|
|
1472
|
+
Promise.all([
|
|
1473
|
+
Promise.resolve(findFakeSuccess(root)),
|
|
1474
|
+
Promise.resolve(findOptimisticNoRollback(root)),
|
|
1475
|
+
Promise.resolve(findSilentCatch(root)),
|
|
1476
|
+
Promise.resolve(findDeadUI(root)),
|
|
1477
|
+
Promise.resolve(findOwnerModeBypass(root)),
|
|
1478
|
+
]),
|
|
1479
|
+
Promise.all([
|
|
1480
|
+
Promise.resolve(findMissingRoutes(truthpack)),
|
|
1481
|
+
Promise.resolve(findMethodMismatch(truthpack)),
|
|
1482
|
+
Promise.resolve(findEnvGaps(truthpack)),
|
|
1483
|
+
Promise.resolve(findGhostAuth(truthpack, root)),
|
|
1484
|
+
Promise.resolve(findStripeWebhookViolations(truthpack)),
|
|
1485
|
+
Promise.resolve(findPaidSurfaceNotEnforced(truthpack)),
|
|
1486
|
+
]),
|
|
1487
|
+
]);
|
|
1488
|
+
|
|
1489
|
+
const allFindings = [
|
|
1490
|
+
...fileResults.flat(),
|
|
1491
|
+
...truthpackResults.flat(),
|
|
1492
|
+
// Runtime findings
|
|
1493
|
+
...findingsFromReality(root)
|
|
1494
|
+
];
|
|
1495
|
+
|
|
1496
|
+
const verdict = allFindings.some(f => f.severity === "BLOCK") ? "BLOCK" :
|
|
1497
|
+
allFindings.some(f => f.severity === "WARN") ? "WARN" : "SHIP";
|
|
1498
|
+
|
|
1499
|
+
const proofGraph = buildProofGraph(allFindings, truthpack, root);
|
|
1500
|
+
|
|
1501
|
+
const report = {
|
|
1502
|
+
meta: { generatedAt: new Date().toISOString(), verdict },
|
|
1503
|
+
truthpackHash: truthpack.index?.hashes?.truthpackHash,
|
|
1504
|
+
findings: allFindings,
|
|
1505
|
+
proofGraph: {
|
|
1506
|
+
summary: proofGraph.summary,
|
|
1507
|
+
topBlockers: proofGraph.topBlockers.slice(0, 5),
|
|
1508
|
+
topGaps: proofGraph.topGaps.slice(0, 5)
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
const outDir = path.join(root, ".vibecheck");
|
|
1513
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
1514
|
+
fs.writeFileSync(path.join(outDir, "last_ship.json"), JSON.stringify(report, null, 2));
|
|
1515
|
+
fs.writeFileSync(path.join(outDir, "proof-graph.json"), JSON.stringify(proofGraph, null, 2));
|
|
1516
|
+
|
|
1517
|
+
// Note: HTML reports are generated by 'vibecheck report' command, not here
|
|
1518
|
+
|
|
1519
|
+
if (jsonOut) {
|
|
1520
|
+
fs.writeFileSync(path.isAbsolute(jsonOut) ? jsonOut : path.join(root, jsonOut), JSON.stringify(report, null, 2));
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
return { report, truthpack, verdict };
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1527
|
+
// EXPORTS
|
|
1528
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1529
|
+
|
|
1530
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1531
|
+
// SEAL COMMAND - Badge and Attestation Generator
|
|
1532
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* Generate seal (badge) and attestation from the latest ship manifest.
|
|
1536
|
+
*
|
|
1537
|
+
* @param {string[]} args - Command arguments
|
|
1538
|
+
* @param {Object} context - Execution context
|
|
1539
|
+
* @returns {number} Exit code
|
|
1540
|
+
*/
|
|
1541
|
+
async function runSeal(args = [], context = {}) {
|
|
1542
|
+
const { parseGlobalFlags } = require("./lib/global-flags");
|
|
1543
|
+
const { EXIT } = require("./lib/exit-codes");
|
|
1544
|
+
|
|
1545
|
+
const { flags: globalFlags } = parseGlobalFlags(args);
|
|
1546
|
+
const quiet = globalFlags.quiet || args.includes("--quiet") || args.includes("-q");
|
|
1547
|
+
const json = globalFlags.json || args.includes("--json");
|
|
1548
|
+
const projectRoot = context.repoRoot || globalFlags.path || process.cwd();
|
|
1549
|
+
|
|
1550
|
+
const shipManifest = getShipManifest();
|
|
1551
|
+
|
|
1552
|
+
if (!shipManifest) {
|
|
1553
|
+
if (!quiet) {
|
|
1554
|
+
console.error(" Seal module not available");
|
|
1555
|
+
}
|
|
1556
|
+
return EXIT.INTERNAL_ERROR;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// Load latest manifest
|
|
1560
|
+
const manifest = shipManifest.loadShipManifest(projectRoot);
|
|
1561
|
+
|
|
1562
|
+
if (!manifest) {
|
|
1563
|
+
if (json) {
|
|
1564
|
+
console.log(JSON.stringify({
|
|
1565
|
+
success: false,
|
|
1566
|
+
error: "No ship manifest found. Run 'vibecheck ship' first."
|
|
1567
|
+
}));
|
|
1568
|
+
} else if (!quiet) {
|
|
1569
|
+
console.error(" No ship manifest found. Run 'vibecheck ship' first.");
|
|
1570
|
+
}
|
|
1571
|
+
return EXIT.NOT_FOUND;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// Verify signature
|
|
1575
|
+
const verification = shipManifest.verifyManifestSignature(manifest);
|
|
1576
|
+
|
|
1577
|
+
// Generate badge info
|
|
1578
|
+
const badge = shipManifest.generateBadgeInfo(manifest);
|
|
1579
|
+
|
|
1580
|
+
// Build seal output
|
|
1581
|
+
const seal = {
|
|
1582
|
+
verdict: manifest.verdict.status,
|
|
1583
|
+
score: manifest.verdict.score,
|
|
1584
|
+
canShip: manifest.verdict.canShip,
|
|
1585
|
+
|
|
1586
|
+
// Repo info
|
|
1587
|
+
repo: {
|
|
1588
|
+
name: manifest.repo.name,
|
|
1589
|
+
commit: manifest.repo.commit,
|
|
1590
|
+
branch: manifest.repo.branch,
|
|
1591
|
+
fingerprint: manifest.repo.fingerprint,
|
|
1592
|
+
},
|
|
1593
|
+
|
|
1594
|
+
// Badge URLs
|
|
1595
|
+
badge: {
|
|
1596
|
+
shields: badge.url.shields,
|
|
1597
|
+
badgen: badge.url.badgen,
|
|
1598
|
+
markdown: badge.markdown,
|
|
1599
|
+
},
|
|
1600
|
+
|
|
1601
|
+
// Attestation
|
|
1602
|
+
attestation: {
|
|
1603
|
+
signatureValid: verification.valid,
|
|
1604
|
+
digest: manifest.signature.digest,
|
|
1605
|
+
algorithm: manifest.signature.algorithm,
|
|
1606
|
+
generatedAt: manifest.meta.generatedAt,
|
|
1607
|
+
},
|
|
1608
|
+
|
|
1609
|
+
// Evidence summary
|
|
1610
|
+
evidence: {
|
|
1611
|
+
blockers: manifest.evidence.summary.blockers,
|
|
1612
|
+
warnings: manifest.evidence.summary.warnings,
|
|
1613
|
+
totalFindings: manifest.evidence.summary.total,
|
|
1614
|
+
},
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
if (json) {
|
|
1618
|
+
console.log(JSON.stringify(seal, null, 2));
|
|
1619
|
+
} else if (!quiet) {
|
|
1620
|
+
console.log(`
|
|
1621
|
+
${ansi.bold}${ansi.cyan}╔═══════════════════════════════════════════════════════════════════╗
|
|
1622
|
+
║ ║
|
|
1623
|
+
║ SHIP SEAL ║
|
|
1624
|
+
║ ║
|
|
1625
|
+
╚═══════════════════════════════════════════════════════════════════╝${ansi.reset}
|
|
1626
|
+
|
|
1627
|
+
${ansi.bold}Verdict:${ansi.reset} ${seal.verdict === "SHIP" ? colors.shipGreen : seal.verdict === "WARN" ? colors.warnAmber : colors.blockRed}${seal.verdict}${ansi.reset}
|
|
1628
|
+
${ansi.bold}Score:${ansi.reset} ${seal.score}/100
|
|
1629
|
+
|
|
1630
|
+
${ansi.bold}Repository${ansi.reset}
|
|
1631
|
+
${ansi.dim}Name:${ansi.reset} ${seal.repo.name}
|
|
1632
|
+
${ansi.dim}Commit:${ansi.reset} ${seal.repo.commit}
|
|
1633
|
+
${ansi.dim}Branch:${ansi.reset} ${seal.repo.branch}
|
|
1634
|
+
${ansi.dim}Fingerprint:${ansi.reset} ${seal.repo.fingerprint}
|
|
1635
|
+
|
|
1636
|
+
${ansi.bold}Attestation${ansi.reset}
|
|
1637
|
+
${ansi.dim}Signature:${ansi.reset} ${verification.valid ? colors.shipGreen + "✓ Valid" : colors.blockRed + "✗ Invalid"}${ansi.reset}
|
|
1638
|
+
${ansi.dim}Digest:${ansi.reset} ${seal.attestation.digest}
|
|
1639
|
+
${ansi.dim}Generated:${ansi.reset} ${seal.attestation.generatedAt}
|
|
1640
|
+
|
|
1641
|
+
${ansi.bold}Badge (copy to README.md):${ansi.reset}
|
|
1642
|
+
${colors.shipGreen}${seal.badge.markdown}${ansi.reset}
|
|
1643
|
+
|
|
1644
|
+
${ansi.dim}Evidence: ${seal.evidence.blockers} blockers, ${seal.evidence.warnings} warnings${ansi.reset}
|
|
1645
|
+
`);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
return manifest.verdict.exitCode;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1652
|
+
// EXPORTS
|
|
1653
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1654
|
+
|
|
1655
|
+
module.exports = {
|
|
1656
|
+
runShip: withErrorHandling(runShip, "Ship check failed"),
|
|
1657
|
+
runSeal: withErrorHandling(runSeal, "Seal generation failed"),
|
|
1658
|
+
runShipGate,
|
|
1659
|
+
shipCore,
|
|
1660
|
+
};
|