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,1153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ship Manifest - Deterministic Artifact System
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* THE SHIP GATE ARTIFACT - TRUSTWORTHY, FINAL, DETERMINISTIC
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Creates a cryptographically signed manifest that guarantees:
|
|
9
|
+
* - Same repo state → Same verdict (deterministic)
|
|
10
|
+
* - Evidence-backed decisions (traceable)
|
|
11
|
+
* - CI-friendly outputs (badges, receipts)
|
|
12
|
+
*
|
|
13
|
+
* Used by `vibecheck ship` and `vibecheck seal` commands.
|
|
14
|
+
*
|
|
15
|
+
* @module ship-manifest
|
|
16
|
+
* @version 1.1.0 - Hardened
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
const fs = require("fs");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
const crypto = require("crypto");
|
|
24
|
+
const { execSync } = require("child_process");
|
|
25
|
+
const os = require("os");
|
|
26
|
+
|
|
27
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
+
// ERROR TYPES
|
|
29
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
30
|
+
|
|
31
|
+
class ManifestError extends Error {
|
|
32
|
+
constructor(message, code, details = {}) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "ManifestError";
|
|
35
|
+
this.code = code;
|
|
36
|
+
this.details = details;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class ValidationError extends ManifestError {
|
|
41
|
+
constructor(message, field, value) {
|
|
42
|
+
super(message, "VALIDATION_ERROR", { field, value });
|
|
43
|
+
this.name = "ValidationError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class FileSystemError extends ManifestError {
|
|
48
|
+
constructor(message, operation, path) {
|
|
49
|
+
super(message, "FS_ERROR", { operation, path });
|
|
50
|
+
this.name = "FileSystemError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
55
|
+
// CONSTANTS
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
57
|
+
|
|
58
|
+
const MANIFEST_VERSION = "1.1.0";
|
|
59
|
+
const MANIFEST_SCHEMA = "vibecheck/ship-manifest/v1";
|
|
60
|
+
|
|
61
|
+
// Maximum file size to read (prevent DoS on large files)
|
|
62
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
63
|
+
|
|
64
|
+
// Maximum findings to include in manifest
|
|
65
|
+
const MAX_FINDINGS = 1000;
|
|
66
|
+
|
|
67
|
+
// Git command timeout
|
|
68
|
+
const GIT_TIMEOUT = 10000; // 10 seconds
|
|
69
|
+
|
|
70
|
+
const VERDICTS = Object.freeze({
|
|
71
|
+
SHIP: Object.freeze({ code: 0, badge: "passing", color: "#22c55e", ci: "success" }),
|
|
72
|
+
WARN: Object.freeze({ code: 1, badge: "warnings", color: "#eab308", ci: "warning" }),
|
|
73
|
+
BLOCK: Object.freeze({ code: 2, badge: "blocked", color: "#ef4444", ci: "failure" }),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const VALID_VERDICTS = new Set(["SHIP", "WARN", "BLOCK"]);
|
|
77
|
+
|
|
78
|
+
// Files used for repo fingerprint (critical config)
|
|
79
|
+
const FINGERPRINT_FILES = Object.freeze([
|
|
80
|
+
"package.json",
|
|
81
|
+
"pnpm-lock.yaml",
|
|
82
|
+
"package-lock.json",
|
|
83
|
+
"yarn.lock",
|
|
84
|
+
"prisma/schema.prisma",
|
|
85
|
+
".vibecheckrc",
|
|
86
|
+
".vibecheck/contracts/routes.json",
|
|
87
|
+
".vibecheck/contracts/env.json",
|
|
88
|
+
".vibecheck/contracts/auth.json",
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
92
|
+
// INPUT VALIDATION
|
|
93
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Validate that a path is safe (no traversal, exists)
|
|
97
|
+
* @param {string} inputPath - Path to validate
|
|
98
|
+
* @param {string} context - Context for error messages
|
|
99
|
+
* @returns {string} Validated absolute path
|
|
100
|
+
*/
|
|
101
|
+
function validatePath(inputPath, context = "path") {
|
|
102
|
+
if (typeof inputPath !== "string" || !inputPath.trim()) {
|
|
103
|
+
throw new ValidationError(`${context} must be a non-empty string`, context, inputPath);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Prevent null byte injection
|
|
107
|
+
if (inputPath.includes("\0")) {
|
|
108
|
+
throw new ValidationError(`${context} contains invalid characters`, context, "[contains null byte]");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const resolved = path.resolve(inputPath);
|
|
112
|
+
|
|
113
|
+
// Prevent traversal outside workspace (basic check)
|
|
114
|
+
const normalized = path.normalize(resolved);
|
|
115
|
+
if (normalized !== resolved) {
|
|
116
|
+
// Path contained .. or other traversal
|
|
117
|
+
// This is OK as long as it resolves to a valid absolute path
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return resolved;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Validate verdict string
|
|
125
|
+
* @param {string} verdict - Verdict to validate
|
|
126
|
+
* @returns {string} Validated verdict
|
|
127
|
+
*/
|
|
128
|
+
function validateVerdict(verdict) {
|
|
129
|
+
const v = String(verdict || "").toUpperCase();
|
|
130
|
+
|
|
131
|
+
if (!VALID_VERDICTS.has(v)) {
|
|
132
|
+
throw new ValidationError(
|
|
133
|
+
`Invalid verdict: ${verdict}. Must be one of: SHIP, WARN, BLOCK`,
|
|
134
|
+
"verdict",
|
|
135
|
+
verdict
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return v;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Validate score is in range
|
|
144
|
+
* @param {number} score - Score to validate
|
|
145
|
+
* @returns {number} Validated score (clamped to 0-100)
|
|
146
|
+
*/
|
|
147
|
+
function validateScore(score) {
|
|
148
|
+
const num = Number(score);
|
|
149
|
+
|
|
150
|
+
if (isNaN(num)) {
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return Math.max(0, Math.min(100, Math.round(num)));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Validate and sanitize findings array
|
|
159
|
+
* @param {Array} findings - Findings to validate
|
|
160
|
+
* @returns {Array} Sanitized findings
|
|
161
|
+
*/
|
|
162
|
+
function validateFindings(findings) {
|
|
163
|
+
if (!Array.isArray(findings)) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Limit number of findings
|
|
168
|
+
const limited = findings.slice(0, MAX_FINDINGS);
|
|
169
|
+
|
|
170
|
+
return limited.map((f, index) => {
|
|
171
|
+
if (!f || typeof f !== "object") {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
id: sanitizeString(f.id || f.fingerprint || `finding-${index}`),
|
|
177
|
+
category: sanitizeString(f.category || "Unknown"),
|
|
178
|
+
severity: sanitizeSeverity(f.severity),
|
|
179
|
+
title: sanitizeString(f.title || f.message || "Untitled finding", 500),
|
|
180
|
+
why: sanitizeString(f.why || f.explanation, 1000),
|
|
181
|
+
confidence: sanitizeConfidence(f.confidence),
|
|
182
|
+
evidence: sanitizeEvidence(f.evidence),
|
|
183
|
+
fixHints: sanitizeFixHints(f.fixHints),
|
|
184
|
+
fingerprint: sanitizeString(f.fingerprint, 64),
|
|
185
|
+
};
|
|
186
|
+
}).filter(Boolean);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Sanitize a string (prevent injection, limit length)
|
|
191
|
+
*/
|
|
192
|
+
function sanitizeString(str, maxLength = 200) {
|
|
193
|
+
if (str === null || str === undefined) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const s = String(str)
|
|
198
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "") // Remove control chars
|
|
199
|
+
.trim();
|
|
200
|
+
|
|
201
|
+
return s.slice(0, maxLength) || null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Sanitize severity
|
|
206
|
+
*/
|
|
207
|
+
function sanitizeSeverity(severity) {
|
|
208
|
+
const valid = ["BLOCK", "WARN", "INFO"];
|
|
209
|
+
const s = String(severity || "").toUpperCase();
|
|
210
|
+
return valid.includes(s) ? s : "INFO";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Sanitize confidence
|
|
215
|
+
*/
|
|
216
|
+
function sanitizeConfidence(confidence) {
|
|
217
|
+
const valid = ["high", "medium", "low"];
|
|
218
|
+
const c = String(confidence || "").toLowerCase();
|
|
219
|
+
return valid.includes(c) ? c : "medium";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Sanitize evidence array
|
|
224
|
+
*/
|
|
225
|
+
function sanitizeEvidence(evidence) {
|
|
226
|
+
if (!Array.isArray(evidence)) {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return evidence.slice(0, 5).map(e => {
|
|
231
|
+
if (!e || typeof e !== "object") return null;
|
|
232
|
+
return {
|
|
233
|
+
file: sanitizeString(e.file || e.path, 500),
|
|
234
|
+
lines: sanitizeString(e.lines, 20),
|
|
235
|
+
snippet: sanitizeString(e.snippet, 200),
|
|
236
|
+
kind: sanitizeString(e.kind, 20) || "file",
|
|
237
|
+
};
|
|
238
|
+
}).filter(Boolean);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Sanitize fix hints
|
|
243
|
+
*/
|
|
244
|
+
function sanitizeFixHints(hints) {
|
|
245
|
+
if (!Array.isArray(hints)) {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
return hints.slice(0, 5).map(h => sanitizeString(h, 500)).filter(Boolean);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
252
|
+
// REPO FINGERPRINT - DETERMINISTIC STATE HASH
|
|
253
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Generate a deterministic fingerprint for the current repo state.
|
|
257
|
+
* Same repo state always produces the same fingerprint.
|
|
258
|
+
*
|
|
259
|
+
* @param {string} repoRoot - Repository root path
|
|
260
|
+
* @returns {Object} Fingerprint with components
|
|
261
|
+
* @throws {ValidationError} If repoRoot is invalid
|
|
262
|
+
*/
|
|
263
|
+
function generateRepoFingerprint(repoRoot) {
|
|
264
|
+
// Validate input
|
|
265
|
+
const validatedRoot = validatePath(repoRoot, "repoRoot");
|
|
266
|
+
|
|
267
|
+
// Verify directory exists
|
|
268
|
+
if (!fs.existsSync(validatedRoot)) {
|
|
269
|
+
throw new FileSystemError(
|
|
270
|
+
`Repository root does not exist: ${validatedRoot}`,
|
|
271
|
+
"stat",
|
|
272
|
+
validatedRoot
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const stats = safeStatSync(validatedRoot);
|
|
277
|
+
if (!stats || !stats.isDirectory()) {
|
|
278
|
+
throw new FileSystemError(
|
|
279
|
+
`Repository root is not a directory: ${validatedRoot}`,
|
|
280
|
+
"stat",
|
|
281
|
+
validatedRoot
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const components = {
|
|
286
|
+
commitHash: getGitCommitHash(validatedRoot),
|
|
287
|
+
headRef: getGitHeadRef(validatedRoot),
|
|
288
|
+
treeHash: getGitTreeHash(validatedRoot),
|
|
289
|
+
configHashes: {},
|
|
290
|
+
timestamp: null, // Not included in hash for determinism
|
|
291
|
+
gitAvailable: false,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// Check if git is available
|
|
295
|
+
components.gitAvailable = components.commitHash !== "unknown" ||
|
|
296
|
+
fs.existsSync(path.join(validatedRoot, ".git"));
|
|
297
|
+
|
|
298
|
+
// Hash critical config files
|
|
299
|
+
for (const file of FINGERPRINT_FILES) {
|
|
300
|
+
const filePath = path.join(validatedRoot, file);
|
|
301
|
+
const content = safeReadFile(filePath, MAX_FILE_SIZE);
|
|
302
|
+
|
|
303
|
+
if (content !== null) {
|
|
304
|
+
components.configHashes[file] = sha256(content).slice(0, 16);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Compute combined fingerprint
|
|
309
|
+
// Use deterministic sorting to ensure same output
|
|
310
|
+
const sortedHashes = Object.entries(components.configHashes)
|
|
311
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
312
|
+
.map(([k, v]) => `${k}:${v}`);
|
|
313
|
+
|
|
314
|
+
const fingerprintInput = [
|
|
315
|
+
components.commitHash || "no-commit",
|
|
316
|
+
components.treeHash || "no-tree",
|
|
317
|
+
...sortedHashes,
|
|
318
|
+
].join("|");
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
...components,
|
|
322
|
+
fingerprint: sha256(fingerprintInput).slice(0, 32),
|
|
323
|
+
isDirty: isGitDirty(validatedRoot),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Safe stat that returns null on error
|
|
329
|
+
*/
|
|
330
|
+
function safeStatSync(filePath) {
|
|
331
|
+
try {
|
|
332
|
+
return fs.statSync(filePath);
|
|
333
|
+
} catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Safe file read with size limit
|
|
340
|
+
*/
|
|
341
|
+
function safeReadFile(filePath, maxSize = MAX_FILE_SIZE) {
|
|
342
|
+
try {
|
|
343
|
+
// Check if file exists and get size
|
|
344
|
+
const stats = fs.statSync(filePath);
|
|
345
|
+
|
|
346
|
+
if (!stats.isFile()) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Prevent reading huge files
|
|
351
|
+
if (stats.size > maxSize) {
|
|
352
|
+
// For large files, hash first 1MB + size
|
|
353
|
+
const fd = fs.openSync(filePath, "r");
|
|
354
|
+
try {
|
|
355
|
+
const buffer = Buffer.alloc(1024 * 1024);
|
|
356
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
357
|
+
return buffer.slice(0, bytesRead).toString("utf8") + `|size:${stats.size}`;
|
|
358
|
+
} finally {
|
|
359
|
+
fs.closeSync(fd);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return fs.readFileSync(filePath, "utf8");
|
|
364
|
+
} catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Execute a git command safely with timeout
|
|
371
|
+
* @param {string} command - Git command (without "git" prefix)
|
|
372
|
+
* @param {string} cwd - Working directory
|
|
373
|
+
* @returns {string|null} Command output or null on failure
|
|
374
|
+
*/
|
|
375
|
+
function safeGitExec(command, cwd) {
|
|
376
|
+
try {
|
|
377
|
+
return execSync(`git ${command}`, {
|
|
378
|
+
cwd,
|
|
379
|
+
encoding: "utf8",
|
|
380
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
381
|
+
timeout: GIT_TIMEOUT,
|
|
382
|
+
maxBuffer: 1024 * 1024, // 1MB max output
|
|
383
|
+
windowsHide: true,
|
|
384
|
+
}).trim();
|
|
385
|
+
} catch (err) {
|
|
386
|
+
// Log in debug mode
|
|
387
|
+
if (process.env.DEBUG || process.env.VIBECHECK_DEBUG) {
|
|
388
|
+
console.error(`Git command failed: git ${command}`, err.message);
|
|
389
|
+
}
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get Git commit hash
|
|
396
|
+
* @param {string} repoRoot - Repository root
|
|
397
|
+
* @returns {string} Commit hash (12 chars) or "unknown"
|
|
398
|
+
*/
|
|
399
|
+
function getGitCommitHash(repoRoot) {
|
|
400
|
+
const result = safeGitExec("rev-parse HEAD", repoRoot);
|
|
401
|
+
|
|
402
|
+
if (result && /^[a-f0-9]{40}$/i.test(result)) {
|
|
403
|
+
return result.slice(0, 12);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return "unknown";
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get Git HEAD ref (branch name or detached HEAD)
|
|
411
|
+
* @param {string} repoRoot - Repository root
|
|
412
|
+
* @returns {string} Branch name or "HEAD" or "unknown"
|
|
413
|
+
*/
|
|
414
|
+
function getGitHeadRef(repoRoot) {
|
|
415
|
+
// Try reading HEAD file directly (faster, no subprocess)
|
|
416
|
+
const headPath = path.join(repoRoot, ".git", "HEAD");
|
|
417
|
+
const content = safeReadFile(headPath, 1024);
|
|
418
|
+
|
|
419
|
+
if (content) {
|
|
420
|
+
const head = content.trim();
|
|
421
|
+
if (head.startsWith("ref: refs/heads/")) {
|
|
422
|
+
const branch = head.slice("ref: refs/heads/".length);
|
|
423
|
+
// Sanitize branch name
|
|
424
|
+
if (/^[\w\-./]+$/.test(branch)) {
|
|
425
|
+
return branch;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// Detached HEAD
|
|
429
|
+
if (/^[a-f0-9]{40}$/i.test(head)) {
|
|
430
|
+
return "HEAD";
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Fallback to git command
|
|
435
|
+
const branch = safeGitExec("rev-parse --abbrev-ref HEAD", repoRoot);
|
|
436
|
+
if (branch && branch !== "HEAD" && /^[\w\-./]+$/.test(branch)) {
|
|
437
|
+
return branch;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return "unknown";
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Get Git tree hash (content-addressable hash of working tree)
|
|
445
|
+
* @param {string} repoRoot - Repository root
|
|
446
|
+
* @returns {string} Tree hash (12 chars) or "unknown"
|
|
447
|
+
*/
|
|
448
|
+
function getGitTreeHash(repoRoot) {
|
|
449
|
+
const result = safeGitExec("write-tree", repoRoot);
|
|
450
|
+
|
|
451
|
+
if (result && /^[a-f0-9]{40}$/i.test(result)) {
|
|
452
|
+
return result.slice(0, 12);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Fallback: hash of HEAD tree
|
|
456
|
+
const treeRef = safeGitExec("rev-parse HEAD^{tree}", repoRoot);
|
|
457
|
+
if (treeRef && /^[a-f0-9]{40}$/i.test(treeRef)) {
|
|
458
|
+
return treeRef.slice(0, 12);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return "unknown";
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Check if Git working tree is dirty
|
|
466
|
+
* @param {string} repoRoot - Repository root
|
|
467
|
+
* @returns {boolean} True if dirty, false if clean or unknown
|
|
468
|
+
*/
|
|
469
|
+
function isGitDirty(repoRoot) {
|
|
470
|
+
const status = safeGitExec("status --porcelain", repoRoot);
|
|
471
|
+
|
|
472
|
+
// null means git failed (not a repo, etc.) - treat as not dirty
|
|
473
|
+
if (status === null) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return status.length > 0;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
481
|
+
// SHIP MANIFEST GENERATION
|
|
482
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Generate a ship manifest from verdict data.
|
|
486
|
+
*
|
|
487
|
+
* The manifest is the authoritative artifact that proves:
|
|
488
|
+
* - What was checked
|
|
489
|
+
* - What verdict was reached
|
|
490
|
+
* - Why (evidence chain)
|
|
491
|
+
* - Who/when (audit trail)
|
|
492
|
+
*
|
|
493
|
+
* @param {Object} options - Manifest options
|
|
494
|
+
* @returns {Object} Ship manifest
|
|
495
|
+
* @throws {ValidationError} If required options are invalid
|
|
496
|
+
*/
|
|
497
|
+
function generateShipManifest(options = {}) {
|
|
498
|
+
// Validate and extract options with defaults
|
|
499
|
+
const {
|
|
500
|
+
repoRoot = process.cwd(),
|
|
501
|
+
verdict: rawVerdict = "BLOCK",
|
|
502
|
+
score: rawScore = 0,
|
|
503
|
+
findings: rawFindings = [],
|
|
504
|
+
truthpack = null,
|
|
505
|
+
realityReport = null,
|
|
506
|
+
shieldStatus = null,
|
|
507
|
+
coverage = null,
|
|
508
|
+
durationMs = 0,
|
|
509
|
+
} = options || {};
|
|
510
|
+
|
|
511
|
+
// Validate inputs
|
|
512
|
+
const validatedRoot = validatePath(repoRoot, "repoRoot");
|
|
513
|
+
const verdict = validateVerdict(rawVerdict);
|
|
514
|
+
const score = validateScore(rawScore);
|
|
515
|
+
const findings = validateFindings(rawFindings);
|
|
516
|
+
const duration = Math.max(0, Math.round(Number(durationMs) || 0));
|
|
517
|
+
|
|
518
|
+
// Generate repo fingerprint (may throw on invalid path)
|
|
519
|
+
let repoFP;
|
|
520
|
+
try {
|
|
521
|
+
repoFP = generateRepoFingerprint(validatedRoot);
|
|
522
|
+
} catch (err) {
|
|
523
|
+
// If fingerprint fails, create a degraded one
|
|
524
|
+
repoFP = {
|
|
525
|
+
fingerprint: sha256(validatedRoot + Date.now()).slice(0, 32),
|
|
526
|
+
commitHash: "unknown",
|
|
527
|
+
headRef: "unknown",
|
|
528
|
+
treeHash: "unknown",
|
|
529
|
+
isDirty: false,
|
|
530
|
+
gitAvailable: false,
|
|
531
|
+
configHashes: {},
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
if (process.env.DEBUG || process.env.VIBECHECK_DEBUG) {
|
|
535
|
+
console.error("Fingerprint generation failed:", err.message);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const now = new Date().toISOString();
|
|
540
|
+
|
|
541
|
+
// Build findings summary with safe counting
|
|
542
|
+
const findingsSummary = {
|
|
543
|
+
total: findings.length,
|
|
544
|
+
blockers: findings.filter(f => f.severity === "BLOCK").length,
|
|
545
|
+
warnings: findings.filter(f => f.severity === "WARN").length,
|
|
546
|
+
info: findings.filter(f => f.severity === "INFO").length,
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// Build evidence map (top 3 blockers with evidence)
|
|
550
|
+
const whyTree = buildWhyTree(findings);
|
|
551
|
+
|
|
552
|
+
// Build coverage metrics with null safety
|
|
553
|
+
const coverageMetrics = sanitizeCoverage(coverage, truthpack, realityReport);
|
|
554
|
+
|
|
555
|
+
// Build manifest with all validated data
|
|
556
|
+
const manifest = {
|
|
557
|
+
version: MANIFEST_VERSION,
|
|
558
|
+
schema: MANIFEST_SCHEMA,
|
|
559
|
+
|
|
560
|
+
// Verdict (the final word)
|
|
561
|
+
verdict: {
|
|
562
|
+
status: verdict,
|
|
563
|
+
exitCode: VERDICTS[verdict]?.code ?? 2,
|
|
564
|
+
score,
|
|
565
|
+
ciStatus: VERDICTS[verdict]?.ci ?? "failure",
|
|
566
|
+
canShip: verdict === "SHIP",
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
// Repo state (for determinism)
|
|
570
|
+
repo: {
|
|
571
|
+
root: validatedRoot,
|
|
572
|
+
name: sanitizeString(path.basename(validatedRoot), 100) || "unknown",
|
|
573
|
+
fingerprint: repoFP.fingerprint,
|
|
574
|
+
commit: repoFP.commitHash,
|
|
575
|
+
branch: repoFP.headRef,
|
|
576
|
+
treeHash: repoFP.treeHash,
|
|
577
|
+
isDirty: Boolean(repoFP.isDirty),
|
|
578
|
+
gitAvailable: Boolean(repoFP.gitAvailable),
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
// Evidence (why this verdict)
|
|
582
|
+
evidence: {
|
|
583
|
+
summary: findingsSummary,
|
|
584
|
+
whyTree,
|
|
585
|
+
coverage: coverageMetrics,
|
|
586
|
+
},
|
|
587
|
+
|
|
588
|
+
// Source checks performed
|
|
589
|
+
checks: {
|
|
590
|
+
audit: {
|
|
591
|
+
ran: findings.length > 0 || truthpack !== null,
|
|
592
|
+
findingsCount: findings.length,
|
|
593
|
+
truthpackHash: sanitizeString(truthpack?.index?.hashes?.truthpackHash, 64),
|
|
594
|
+
},
|
|
595
|
+
reality: {
|
|
596
|
+
ran: realityReport !== null,
|
|
597
|
+
actionsCount: Math.max(0, Number(realityReport?.actions?.length) || 0),
|
|
598
|
+
requestsCount: Math.max(0, Number(realityReport?.requests?.length) || 0),
|
|
599
|
+
},
|
|
600
|
+
shield: {
|
|
601
|
+
ran: shieldStatus !== null,
|
|
602
|
+
mode: sanitizeString(shieldStatus?.mode, 20) || "unknown",
|
|
603
|
+
enforcing: shieldStatus?.mode === "enforce",
|
|
604
|
+
locked: Boolean(shieldStatus?.locked),
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
// Metadata
|
|
609
|
+
meta: {
|
|
610
|
+
generatedAt: now,
|
|
611
|
+
durationMs: duration,
|
|
612
|
+
tool: "vibecheck",
|
|
613
|
+
toolVersion: getVibeCheckVersion(),
|
|
614
|
+
platform: `${os.platform()}-${os.arch()}`,
|
|
615
|
+
nodeVersion: process.version,
|
|
616
|
+
},
|
|
617
|
+
|
|
618
|
+
// Signature (for verification)
|
|
619
|
+
signature: null, // Will be computed below
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// Compute manifest signature
|
|
623
|
+
manifest.signature = computeManifestSignature(manifest);
|
|
624
|
+
|
|
625
|
+
// Freeze the manifest to prevent accidental mutation
|
|
626
|
+
return deepFreeze(manifest);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Sanitize coverage metrics
|
|
631
|
+
*/
|
|
632
|
+
function sanitizeCoverage(coverage, truthpack, realityReport) {
|
|
633
|
+
if (coverage && typeof coverage === "object") {
|
|
634
|
+
return {
|
|
635
|
+
routeCoverage: validateScore(coverage.routeCoverage),
|
|
636
|
+
envCoverage: validateScore(coverage.envCoverage),
|
|
637
|
+
authCoverage: coverage.authCoverage === null ? null : validateScore(coverage.authCoverage),
|
|
638
|
+
runtimeCoverage: coverage.runtimeCoverage === null ? null : Math.max(0, Number(coverage.runtimeCoverage) || 0),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Compute from truthpack/reality
|
|
643
|
+
const serverRoutes = Array.isArray(truthpack?.routes?.server) ? truthpack.routes.server.length : 0;
|
|
644
|
+
const envVars = Array.isArray(truthpack?.env?.vars) ? truthpack.env.vars.length : 0;
|
|
645
|
+
const envDeclared = Array.isArray(truthpack?.env?.declared) ? truthpack.env.declared.length : 0;
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
routeCoverage: serverRoutes > 0 ? 100 : 0,
|
|
649
|
+
envCoverage: envVars > 0 ? Math.round((envDeclared / envVars) * 100) : 100,
|
|
650
|
+
authCoverage: null,
|
|
651
|
+
runtimeCoverage: realityReport ? Math.max(0, Number(realityReport.requests?.length) || 0) : null,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Deep freeze an object to prevent mutation
|
|
657
|
+
*/
|
|
658
|
+
function deepFreeze(obj) {
|
|
659
|
+
if (obj === null || typeof obj !== "object") {
|
|
660
|
+
return obj;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
Object.freeze(obj);
|
|
664
|
+
|
|
665
|
+
for (const key of Object.keys(obj)) {
|
|
666
|
+
const value = obj[key];
|
|
667
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
668
|
+
deepFreeze(value);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return obj;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Build the "why tree" - top blockers with evidence chains.
|
|
677
|
+
* This is the key artifact for understanding WHY a verdict was reached.
|
|
678
|
+
*/
|
|
679
|
+
function buildWhyTree(findings) {
|
|
680
|
+
const blockers = findings
|
|
681
|
+
.filter(f => f.severity === "BLOCK")
|
|
682
|
+
.sort((a, b) => {
|
|
683
|
+
// Sort by confidence (high first), then by category importance
|
|
684
|
+
const confA = a.confidence === "high" ? 3 : a.confidence === "medium" ? 2 : 1;
|
|
685
|
+
const confB = b.confidence === "high" ? 3 : b.confidence === "medium" ? 2 : 1;
|
|
686
|
+
return confB - confA;
|
|
687
|
+
})
|
|
688
|
+
.slice(0, 3);
|
|
689
|
+
|
|
690
|
+
if (blockers.length === 0) {
|
|
691
|
+
// No blockers - verdict should be SHIP or WARN
|
|
692
|
+
const warnings = findings
|
|
693
|
+
.filter(f => f.severity === "WARN")
|
|
694
|
+
.slice(0, 3);
|
|
695
|
+
|
|
696
|
+
if (warnings.length === 0) {
|
|
697
|
+
return {
|
|
698
|
+
summary: "All checks passed",
|
|
699
|
+
topIssues: [],
|
|
700
|
+
fixMissions: [],
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
summary: `${warnings.length} warning(s) to review`,
|
|
706
|
+
topIssues: warnings.map(w => ({
|
|
707
|
+
id: w.id || w.fingerprint || sha256(w.title || "").slice(0, 8),
|
|
708
|
+
category: w.category,
|
|
709
|
+
title: w.title || w.message,
|
|
710
|
+
evidence: formatEvidence(w),
|
|
711
|
+
fixHint: w.fixHints?.[0] || null,
|
|
712
|
+
})),
|
|
713
|
+
fixMissions: [],
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return {
|
|
718
|
+
summary: `${blockers.length} blocker(s) must be fixed`,
|
|
719
|
+
topIssues: blockers.map(b => ({
|
|
720
|
+
id: b.id || b.fingerprint || sha256(b.title || "").slice(0, 8),
|
|
721
|
+
category: b.category,
|
|
722
|
+
title: b.title || b.message,
|
|
723
|
+
why: b.why || null,
|
|
724
|
+
evidence: formatEvidence(b),
|
|
725
|
+
fixHint: b.fixHints?.[0] || null,
|
|
726
|
+
})),
|
|
727
|
+
fixMissions: blockers.map(b => ({
|
|
728
|
+
target: b.evidence?.[0]?.file || "unknown",
|
|
729
|
+
action: b.fixHints?.[0] || `Fix ${b.category} issue`,
|
|
730
|
+
priority: "critical",
|
|
731
|
+
})),
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Format evidence for display
|
|
737
|
+
*/
|
|
738
|
+
function formatEvidence(finding) {
|
|
739
|
+
if (!finding.evidence?.length) {
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const ev = finding.evidence[0];
|
|
744
|
+
return {
|
|
745
|
+
file: ev.file || ev.path || null,
|
|
746
|
+
lines: ev.lines || (ev.line ? `${ev.line}` : null),
|
|
747
|
+
snippet: ev.snippet?.slice(0, 100) || null,
|
|
748
|
+
kind: ev.kind || "file",
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
753
|
+
// MANIFEST SIGNATURE
|
|
754
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Compute a signature for the manifest.
|
|
758
|
+
* This allows verification that the manifest hasn't been tampered with.
|
|
759
|
+
*/
|
|
760
|
+
function computeManifestSignature(manifest) {
|
|
761
|
+
// Create a canonical representation (excluding signature field)
|
|
762
|
+
const signableContent = {
|
|
763
|
+
version: manifest.version,
|
|
764
|
+
verdict: manifest.verdict,
|
|
765
|
+
repo: manifest.repo,
|
|
766
|
+
evidence: manifest.evidence,
|
|
767
|
+
checks: manifest.checks,
|
|
768
|
+
meta: {
|
|
769
|
+
generatedAt: manifest.meta.generatedAt,
|
|
770
|
+
tool: manifest.meta.tool,
|
|
771
|
+
toolVersion: manifest.meta.toolVersion,
|
|
772
|
+
},
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
const canonical = JSON.stringify(signableContent, Object.keys(signableContent).sort());
|
|
776
|
+
const hash = sha256(canonical);
|
|
777
|
+
|
|
778
|
+
return {
|
|
779
|
+
algorithm: "sha256",
|
|
780
|
+
digest: hash.slice(0, 32),
|
|
781
|
+
// In a full implementation, this would include cryptographic signing
|
|
782
|
+
// For now, we use a deterministic hash that can be verified
|
|
783
|
+
verifiable: true,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Verify a manifest signature
|
|
789
|
+
*/
|
|
790
|
+
function verifyManifestSignature(manifest) {
|
|
791
|
+
if (!manifest.signature) {
|
|
792
|
+
return { valid: false, reason: "No signature present" };
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const recomputed = computeManifestSignature({
|
|
796
|
+
...manifest,
|
|
797
|
+
signature: null, // Exclude signature for recomputation
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
const matches = recomputed.digest === manifest.signature.digest;
|
|
801
|
+
|
|
802
|
+
return {
|
|
803
|
+
valid: matches,
|
|
804
|
+
reason: matches ? "Signature valid" : "Signature mismatch - manifest may have been modified",
|
|
805
|
+
recomputedDigest: recomputed.digest,
|
|
806
|
+
storedDigest: manifest.signature.digest,
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
811
|
+
// CI OUTPUT FORMATS
|
|
812
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Generate a CI-friendly receipt (single line, parseable)
|
|
816
|
+
*/
|
|
817
|
+
function generateCIReceipt(manifest) {
|
|
818
|
+
const parts = [
|
|
819
|
+
`VERDICT=${manifest.verdict.status}`,
|
|
820
|
+
`EXIT_CODE=${manifest.verdict.exitCode}`,
|
|
821
|
+
`SCORE=${manifest.verdict.score}`,
|
|
822
|
+
`BLOCKERS=${manifest.evidence.summary.blockers}`,
|
|
823
|
+
`WARNINGS=${manifest.evidence.summary.warnings}`,
|
|
824
|
+
`COMMIT=${manifest.repo.commit}`,
|
|
825
|
+
`FINGERPRINT=${manifest.repo.fingerprint}`,
|
|
826
|
+
`DIRTY=${manifest.repo.isDirty}`,
|
|
827
|
+
`SIGNATURE=${manifest.signature.digest}`,
|
|
828
|
+
];
|
|
829
|
+
|
|
830
|
+
return parts.join(" ");
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Generate environment variables for CI
|
|
835
|
+
*/
|
|
836
|
+
function generateCIEnvVars(manifest) {
|
|
837
|
+
return {
|
|
838
|
+
VIBECHECK_VERDICT: manifest.verdict.status,
|
|
839
|
+
VIBECHECK_EXIT_CODE: String(manifest.verdict.exitCode),
|
|
840
|
+
VIBECHECK_SCORE: String(manifest.verdict.score),
|
|
841
|
+
VIBECHECK_CAN_SHIP: String(manifest.verdict.canShip),
|
|
842
|
+
VIBECHECK_BLOCKERS: String(manifest.evidence.summary.blockers),
|
|
843
|
+
VIBECHECK_WARNINGS: String(manifest.evidence.summary.warnings),
|
|
844
|
+
VIBECHECK_COMMIT: manifest.repo.commit,
|
|
845
|
+
VIBECHECK_BRANCH: manifest.repo.branch,
|
|
846
|
+
VIBECHECK_FINGERPRINT: manifest.repo.fingerprint,
|
|
847
|
+
VIBECHECK_SIGNATURE: manifest.signature.digest,
|
|
848
|
+
VIBECHECK_DIRTY: String(manifest.repo.isDirty),
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Generate GitHub Actions output format
|
|
854
|
+
*/
|
|
855
|
+
function generateGitHubActionsOutput(manifest) {
|
|
856
|
+
const outputs = generateCIEnvVars(manifest);
|
|
857
|
+
const lines = [];
|
|
858
|
+
|
|
859
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
860
|
+
const name = key.replace("VIBECHECK_", "").toLowerCase();
|
|
861
|
+
lines.push(`${name}=${value}`);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return lines.join("\n");
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
868
|
+
// BADGE GENERATION
|
|
869
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Generate badge info for embedding in README
|
|
873
|
+
*/
|
|
874
|
+
function generateBadgeInfo(manifest) {
|
|
875
|
+
const verdictConfig = VERDICTS[manifest.verdict.status] || VERDICTS.BLOCK;
|
|
876
|
+
|
|
877
|
+
return {
|
|
878
|
+
label: "vibecheck",
|
|
879
|
+
message: manifest.verdict.status,
|
|
880
|
+
color: verdictConfig.color.replace("#", ""),
|
|
881
|
+
score: manifest.verdict.score,
|
|
882
|
+
url: {
|
|
883
|
+
shields: `https://img.shields.io/badge/vibecheck-${manifest.verdict.status}-${verdictConfig.color.replace("#", "")}?style=flat-square`,
|
|
884
|
+
badgen: `https://badgen.net/badge/vibecheck/${manifest.verdict.status}/${verdictConfig.color.replace("#", "")}`,
|
|
885
|
+
},
|
|
886
|
+
markdown: `[}?style=flat-square)](https://vibecheckai.dev)`,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
891
|
+
// PERSISTENCE
|
|
892
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Atomically write a file (write to temp, then rename)
|
|
896
|
+
* @param {string} targetPath - Final destination path
|
|
897
|
+
* @param {string} content - Content to write
|
|
898
|
+
*/
|
|
899
|
+
function atomicWriteFile(targetPath, content) {
|
|
900
|
+
const dir = path.dirname(targetPath);
|
|
901
|
+
const tempPath = path.join(dir, `.${path.basename(targetPath)}.tmp.${process.pid}`);
|
|
902
|
+
|
|
903
|
+
try {
|
|
904
|
+
// Ensure directory exists
|
|
905
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
906
|
+
|
|
907
|
+
// Write to temp file
|
|
908
|
+
fs.writeFileSync(tempPath, content, { encoding: "utf8", mode: 0o644 });
|
|
909
|
+
|
|
910
|
+
// Atomically rename (overwrites existing)
|
|
911
|
+
fs.renameSync(tempPath, targetPath);
|
|
912
|
+
} catch (err) {
|
|
913
|
+
// Clean up temp file on error
|
|
914
|
+
try {
|
|
915
|
+
if (fs.existsSync(tempPath)) {
|
|
916
|
+
fs.unlinkSync(tempPath);
|
|
917
|
+
}
|
|
918
|
+
} catch {
|
|
919
|
+
// Ignore cleanup errors
|
|
920
|
+
}
|
|
921
|
+
throw new FileSystemError(
|
|
922
|
+
`Failed to write file: ${err.message}`,
|
|
923
|
+
"write",
|
|
924
|
+
targetPath
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Write manifest to disk with atomic writes.
|
|
931
|
+
*
|
|
932
|
+
* @param {string} repoRoot - Repository root path
|
|
933
|
+
* @param {Object} manifest - Ship manifest to write
|
|
934
|
+
* @returns {Object} Paths to written files
|
|
935
|
+
* @throws {FileSystemError} If write fails
|
|
936
|
+
*/
|
|
937
|
+
function writeShipManifest(repoRoot, manifest) {
|
|
938
|
+
// Validate inputs
|
|
939
|
+
const validatedRoot = validatePath(repoRoot, "repoRoot");
|
|
940
|
+
|
|
941
|
+
if (!manifest || typeof manifest !== "object") {
|
|
942
|
+
throw new ValidationError("manifest must be an object", "manifest", manifest);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const dir = path.join(validatedRoot, ".vibecheck", "ship");
|
|
946
|
+
|
|
947
|
+
// Prepare all content first (fail early if serialization fails)
|
|
948
|
+
let manifestJson, receipt, ghOutput;
|
|
949
|
+
|
|
950
|
+
try {
|
|
951
|
+
manifestJson = JSON.stringify(manifest, null, 2);
|
|
952
|
+
} catch (err) {
|
|
953
|
+
throw new ManifestError(
|
|
954
|
+
`Failed to serialize manifest: ${err.message}`,
|
|
955
|
+
"SERIALIZATION_ERROR"
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
receipt = generateCIReceipt(manifest);
|
|
960
|
+
ghOutput = generateGitHubActionsOutput(manifest);
|
|
961
|
+
|
|
962
|
+
// Ensure directory exists
|
|
963
|
+
try {
|
|
964
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o755 });
|
|
965
|
+
} catch (err) {
|
|
966
|
+
throw new FileSystemError(
|
|
967
|
+
`Failed to create directory: ${err.message}`,
|
|
968
|
+
"mkdir",
|
|
969
|
+
dir
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Write all files atomically
|
|
974
|
+
const paths = {
|
|
975
|
+
manifest: path.join(dir, "manifest.json"),
|
|
976
|
+
archive: null,
|
|
977
|
+
receipt: path.join(dir, "receipt.txt"),
|
|
978
|
+
githubOutput: path.join(dir, "github-output.txt"),
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
// Write main manifest
|
|
982
|
+
atomicWriteFile(paths.manifest, manifestJson);
|
|
983
|
+
|
|
984
|
+
// Write timestamped archive (best effort, don't fail on this)
|
|
985
|
+
try {
|
|
986
|
+
const timestamp = (manifest.meta?.generatedAt || new Date().toISOString())
|
|
987
|
+
.replace(/[:.]/g, "-")
|
|
988
|
+
.slice(0, 19);
|
|
989
|
+
|
|
990
|
+
// Validate timestamp format
|
|
991
|
+
if (/^[\d-T]+$/.test(timestamp)) {
|
|
992
|
+
paths.archive = path.join(dir, `manifest-${timestamp}.json`);
|
|
993
|
+
atomicWriteFile(paths.archive, manifestJson);
|
|
994
|
+
|
|
995
|
+
// Clean up old archives (keep last 10)
|
|
996
|
+
cleanupOldArchives(dir, 10);
|
|
997
|
+
}
|
|
998
|
+
} catch {
|
|
999
|
+
// Archive write failure is non-fatal
|
|
1000
|
+
paths.archive = null;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Write CI receipt
|
|
1004
|
+
atomicWriteFile(paths.receipt, receipt);
|
|
1005
|
+
|
|
1006
|
+
// Write GitHub Actions output
|
|
1007
|
+
atomicWriteFile(paths.githubOutput, ghOutput);
|
|
1008
|
+
|
|
1009
|
+
return paths;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Clean up old archive files, keeping the most recent N
|
|
1014
|
+
*/
|
|
1015
|
+
function cleanupOldArchives(dir, keepCount = 10) {
|
|
1016
|
+
try {
|
|
1017
|
+
const files = fs.readdirSync(dir)
|
|
1018
|
+
.filter(f => f.startsWith("manifest-") && f.endsWith(".json"))
|
|
1019
|
+
.map(f => ({
|
|
1020
|
+
name: f,
|
|
1021
|
+
path: path.join(dir, f),
|
|
1022
|
+
mtime: fs.statSync(path.join(dir, f)).mtimeMs,
|
|
1023
|
+
}))
|
|
1024
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
1025
|
+
|
|
1026
|
+
// Delete files beyond keepCount
|
|
1027
|
+
for (const file of files.slice(keepCount)) {
|
|
1028
|
+
try {
|
|
1029
|
+
fs.unlinkSync(file.path);
|
|
1030
|
+
} catch {
|
|
1031
|
+
// Ignore deletion errors
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
} catch {
|
|
1035
|
+
// Ignore cleanup errors
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Load latest manifest from disk with validation.
|
|
1041
|
+
*
|
|
1042
|
+
* @param {string} repoRoot - Repository root path
|
|
1043
|
+
* @returns {Object|null} Manifest or null if not found/invalid
|
|
1044
|
+
*/
|
|
1045
|
+
function loadShipManifest(repoRoot) {
|
|
1046
|
+
let validatedRoot;
|
|
1047
|
+
try {
|
|
1048
|
+
validatedRoot = validatePath(repoRoot, "repoRoot");
|
|
1049
|
+
} catch {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const manifestPath = path.join(validatedRoot, ".vibecheck", "ship", "manifest.json");
|
|
1054
|
+
|
|
1055
|
+
const content = safeReadFile(manifestPath, MAX_FILE_SIZE);
|
|
1056
|
+
if (!content) {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
try {
|
|
1061
|
+
const manifest = JSON.parse(content);
|
|
1062
|
+
|
|
1063
|
+
// Basic validation
|
|
1064
|
+
if (!manifest || typeof manifest !== "object") {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Check schema version
|
|
1069
|
+
if (manifest.schema && !manifest.schema.startsWith("vibecheck/ship-manifest/")) {
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Check required fields
|
|
1074
|
+
if (!manifest.verdict?.status || !manifest.repo?.fingerprint) {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
return manifest;
|
|
1079
|
+
} catch {
|
|
1080
|
+
// Invalid JSON
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1086
|
+
// HELPERS
|
|
1087
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1088
|
+
|
|
1089
|
+
function sha256(input) {
|
|
1090
|
+
return crypto.createHash("sha256").update(input).digest("hex");
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function getVibeCheckVersion() {
|
|
1094
|
+
try {
|
|
1095
|
+
const pkgPath = path.join(__dirname, "../../../package.json");
|
|
1096
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
1097
|
+
return pkg.version || "unknown";
|
|
1098
|
+
} catch {
|
|
1099
|
+
return "unknown";
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1104
|
+
// EXPORTS
|
|
1105
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1106
|
+
|
|
1107
|
+
module.exports = {
|
|
1108
|
+
// Core
|
|
1109
|
+
generateShipManifest,
|
|
1110
|
+
generateRepoFingerprint,
|
|
1111
|
+
buildWhyTree,
|
|
1112
|
+
|
|
1113
|
+
// Signature
|
|
1114
|
+
computeManifestSignature,
|
|
1115
|
+
verifyManifestSignature,
|
|
1116
|
+
|
|
1117
|
+
// CI outputs
|
|
1118
|
+
generateCIReceipt,
|
|
1119
|
+
generateCIEnvVars,
|
|
1120
|
+
generateGitHubActionsOutput,
|
|
1121
|
+
|
|
1122
|
+
// Badge
|
|
1123
|
+
generateBadgeInfo,
|
|
1124
|
+
|
|
1125
|
+
// Persistence
|
|
1126
|
+
writeShipManifest,
|
|
1127
|
+
loadShipManifest,
|
|
1128
|
+
|
|
1129
|
+
// Validation (for external use)
|
|
1130
|
+
validatePath,
|
|
1131
|
+
validateVerdict,
|
|
1132
|
+
validateScore,
|
|
1133
|
+
validateFindings,
|
|
1134
|
+
|
|
1135
|
+
// Utility
|
|
1136
|
+
safeReadFile,
|
|
1137
|
+
safeGitExec,
|
|
1138
|
+
atomicWriteFile,
|
|
1139
|
+
|
|
1140
|
+
// Error classes
|
|
1141
|
+
ManifestError,
|
|
1142
|
+
ValidationError,
|
|
1143
|
+
FileSystemError,
|
|
1144
|
+
|
|
1145
|
+
// Constants
|
|
1146
|
+
MANIFEST_VERSION,
|
|
1147
|
+
MANIFEST_SCHEMA,
|
|
1148
|
+
VERDICTS,
|
|
1149
|
+
FINGERPRINT_FILES,
|
|
1150
|
+
MAX_FILE_SIZE,
|
|
1151
|
+
MAX_FINDINGS,
|
|
1152
|
+
GIT_TIMEOUT,
|
|
1153
|
+
};
|