@vibecheckai/cli 3.5.0 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/registry.js +174 -449
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/generators/mcp.js +13 -15
- package/bin/runners/context/proof-context.js +1 -248
- package/bin/runners/lib/analysis-core.js +180 -198
- package/bin/runners/lib/analyzers.js +241 -2212
- package/bin/runners/lib/cli-output.js +210 -242
- package/bin/runners/lib/detectors-v2.js +785 -547
- package/bin/runners/lib/entitlements-v2.js +431 -161
- package/bin/runners/lib/error-handler.js +9 -16
- package/bin/runners/lib/global-flags.js +0 -37
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/route-truth.js +322 -1167
- package/bin/runners/lib/scan-output.js +467 -493
- package/bin/runners/lib/ship-output.js +27 -280
- package/bin/runners/lib/terminal-ui.js +700 -310
- package/bin/runners/lib/truth.js +321 -1004
- package/bin/runners/lib/unified-output.js +158 -162
- package/bin/runners/lib/upsell.js +204 -104
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAllowlist.js +324 -0
- package/bin/runners/runAuth.js +94 -344
- package/bin/runners/runCheckpoint.js +45 -43
- package/bin/runners/runContext.js +24 -139
- package/bin/runners/runDoctor.js +101 -136
- package/bin/runners/runEvidencePack.js +219 -0
- package/bin/runners/runFix.js +71 -82
- package/bin/runners/runGuard.js +119 -606
- package/bin/runners/runInit.js +60 -22
- package/bin/runners/runInstall.js +281 -0
- package/bin/runners/runLabs.js +341 -0
- package/bin/runners/runMcp.js +62 -139
- package/bin/runners/runPolish.js +83 -282
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +58 -33
- package/bin/runners/runReality.js +58 -81
- package/bin/runners/runReport.js +7 -34
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +844 -219
- package/bin/runners/runShip.js +59 -721
- package/bin/runners/runValidate.js +11 -24
- package/bin/runners/runWatch.js +76 -131
- package/bin/vibecheck.js +69 -295
- package/mcp-server/ARCHITECTURE.md +339 -0
- package/mcp-server/__tests__/cache.test.ts +313 -0
- package/mcp-server/__tests__/executor.test.ts +239 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +1 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/package.json +5 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +5 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +4 -0
- package/mcp-server/__tests__/ids.test.ts +345 -0
- package/mcp-server/__tests__/integration/tools.test.ts +410 -0
- package/mcp-server/__tests__/registry.test.ts +365 -0
- package/mcp-server/__tests__/sandbox.test.ts +323 -0
- package/mcp-server/__tests__/schemas.test.ts +372 -0
- package/mcp-server/benchmarks/run-benchmarks.ts +304 -0
- package/mcp-server/examples/doctor.request.json +14 -0
- package/mcp-server/examples/doctor.response.json +53 -0
- package/mcp-server/examples/error.response.json +15 -0
- package/mcp-server/examples/scan.request.json +14 -0
- package/mcp-server/examples/scan.response.json +108 -0
- package/mcp-server/handlers/tool-handler.ts +671 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index-v3.ts +293 -0
- package/mcp-server/index.js +1080 -1757
- package/mcp-server/index.old.js +4137 -0
- package/mcp-server/lib/cache.ts +341 -0
- package/mcp-server/lib/errors.ts +346 -0
- package/mcp-server/lib/executor.ts +792 -0
- package/mcp-server/lib/ids.ts +238 -0
- package/mcp-server/lib/logger.ts +368 -0
- package/mcp-server/lib/metrics.ts +365 -0
- package/mcp-server/lib/sandbox.ts +337 -0
- package/mcp-server/lib/validator.ts +229 -0
- package/mcp-server/package-lock.json +165 -0
- package/mcp-server/package.json +32 -7
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/registry/tools.json +476 -0
- package/mcp-server/schemas/error-envelope.schema.json +125 -0
- package/mcp-server/schemas/finding.schema.json +167 -0
- package/mcp-server/schemas/report-artifact.schema.json +88 -0
- package/mcp-server/schemas/run-request.schema.json +75 -0
- package/mcp-server/schemas/verdict.schema.json +168 -0
- package/mcp-server/tier-auth.d.ts +71 -0
- package/mcp-server/tier-auth.js +371 -183
- package/mcp-server/truth-context.js +90 -131
- package/mcp-server/truth-firewall-tools.js +1000 -1611
- package/mcp-server/tsconfig.json +34 -0
- package/mcp-server/vibecheck-tools.js +2 -2
- package/mcp-server/vitest.config.ts +16 -0
- package/package.json +3 -4
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +0 -474
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +0 -488
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +0 -228
- package/bin/runners/lib/agent-firewall/change-packet/store.js +0 -200
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +0 -21
- package/bin/runners/lib/agent-firewall/claims/extractor.js +0 -303
- package/bin/runners/lib/agent-firewall/claims/patterns.js +0 -24
- package/bin/runners/lib/agent-firewall/critic/index.js +0 -151
- package/bin/runners/lib/agent-firewall/critic/judge.js +0 -432
- package/bin/runners/lib/agent-firewall/critic/prompts.js +0 -305
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +0 -88
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +0 -75
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +0 -127
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +0 -102
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +0 -213
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +0 -145
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +0 -19
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +0 -87
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +0 -184
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +0 -163
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +0 -107
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +0 -68
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +0 -66
- package/bin/runners/lib/agent-firewall/interceptor/base.js +0 -304
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +0 -35
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +0 -35
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +0 -34
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +0 -465
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +0 -604
- package/bin/runners/lib/agent-firewall/lawbook/index.js +0 -304
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +0 -514
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +0 -420
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/agent-firewall/logger.js +0 -141
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +0 -90
- package/bin/runners/lib/agent-firewall/policy/engine.js +0 -103
- package/bin/runners/lib/agent-firewall/policy/loader.js +0 -451
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +0 -50
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +0 -50
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +0 -86
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +0 -162
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +0 -189
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +0 -93
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +0 -57
- package/bin/runners/lib/agent-firewall/policy/schema.json +0 -183
- package/bin/runners/lib/agent-firewall/policy/verdict.js +0 -54
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +0 -394
- package/bin/runners/lib/agent-firewall/proposal/index.js +0 -212
- package/bin/runners/lib/agent-firewall/proposal/schema.js +0 -251
- package/bin/runners/lib/agent-firewall/proposal/validator.js +0 -386
- package/bin/runners/lib/agent-firewall/reality/index.js +0 -332
- package/bin/runners/lib/agent-firewall/reality/state.js +0 -625
- package/bin/runners/lib/agent-firewall/reality/watcher.js +0 -322
- package/bin/runners/lib/agent-firewall/risk/index.js +0 -173
- package/bin/runners/lib/agent-firewall/risk/scorer.js +0 -328
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +0 -321
- package/bin/runners/lib/agent-firewall/risk/vectors.js +0 -421
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +0 -472
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +0 -346
- package/bin/runners/lib/agent-firewall/simulator/index.js +0 -181
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +0 -380
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +0 -661
- package/bin/runners/lib/agent-firewall/time-machine/index.js +0 -267
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +0 -436
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +0 -490
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +0 -530
- package/bin/runners/lib/agent-firewall/truthpack/index.js +0 -67
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +0 -137
- package/bin/runners/lib/agent-firewall/unblock/planner.js +0 -337
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +0 -118
- package/bin/runners/lib/api-client.js +0 -269
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/authority-badge.js +0 -425
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/accessibility-engine.js +0 -390
- package/bin/runners/lib/engines/api-consistency-engine.js +0 -467
- package/bin/runners/lib/engines/ast-cache.js +0 -99
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/code-quality-engine.js +0 -255
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/console-logs-engine.js +0 -115
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +0 -533
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/dead-code-engine.js +0 -198
- package/bin/runners/lib/engines/deprecated-api-engine.js +0 -226
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/empty-catch-engine.js +0 -260
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/file-filter.js +0 -131
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +0 -251
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/mock-data-engine.js +0 -315
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/parallel-processor.js +0 -71
- package/bin/runners/lib/engines/performance-issues-engine.js +0 -405
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +0 -571
- package/bin/runners/lib/engines/todo-fixme-engine.js +0 -115
- package/bin/runners/lib/engines/type-aware-engine.js +0 -376
- package/bin/runners/lib/engines/unsafe-regex-engine.js +0 -225
- package/bin/runners/lib/engines/vibecheck-engines/README.md +0 -53
- package/bin/runners/lib/engines/vibecheck-engines/index.js +0 -124
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +0 -439
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines/package.json +0 -13
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/exit-codes.js +0 -275
- package/bin/runners/lib/fingerprint.js +0 -377
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/help-formatter.js +0 -413
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/logger.js +0 -38
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/ship-output-enterprise.js +0 -239
- package/bin/runners/lib/unified-cli-output.js +0 -777
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAgent.d.ts +0 -5
- package/bin/runners/runAgent.js +0 -164
- package/bin/runners/runApprove.js +0 -1233
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runClassify.js +0 -862
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runContext.d.ts +0 -4
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runFirewall.d.ts +0 -5
- package/bin/runners/runFirewall.js +0 -137
- package/bin/runners/runFirewallHook.d.ts +0 -5
- package/bin/runners/runFirewallHook.js +0 -59
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runPolish.d.ts +0 -4
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runTruth.d.ts +0 -5
- package/bin/runners/runTruth.js +0 -104
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/HARDENING_SUMMARY.md +0 -299
- package/mcp-server/agent-firewall-interceptor.js +0 -500
- package/mcp-server/authority-tools.js +0 -569
- package/mcp-server/conductor/conflict-resolver.js +0 -588
- package/mcp-server/conductor/execution-planner.js +0 -544
- package/mcp-server/conductor/index.js +0 -377
- package/mcp-server/conductor/lock-manager.js +0 -615
- package/mcp-server/conductor/request-queue.js +0 -550
- package/mcp-server/conductor/session-manager.js +0 -500
- package/mcp-server/conductor/tools.js +0 -510
- package/mcp-server/lib/api-client.cjs +0 -13
- package/mcp-server/lib/logger.cjs +0 -30
- package/mcp-server/logger.js +0 -173
- package/mcp-server/tools-v3.js +0 -1039
- package/mcp-server/tools.js +0 -495
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
package/bin/runners/runScan.js
CHANGED
|
@@ -1,43 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vibecheck Scan -
|
|
2
|
+
* vibecheck Scan - Route Integrity & Code Analysis
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - type-aware-engine (type safety)
|
|
9
|
-
* - error-handling-engine (reliability)
|
|
4
|
+
* The ultimate scanner combining:
|
|
5
|
+
* - Route integrity (dead links, orphan routes, coverage)
|
|
6
|
+
* - Security analysis (secrets, auth, vulnerabilities)
|
|
7
|
+
* - Code quality (mocks, placeholders, hygiene)
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Modes:
|
|
10
|
+
* - vibecheck scan: Layer 1 (AST) - Fast static analysis
|
|
11
|
+
* - vibecheck scan --truth: Layer 1+2 (+ build manifests) - CI/ship
|
|
12
|
+
* - vibecheck scan --reality --url <url>: Layer 1+2+3 (+ Playwright) - Full proof
|
|
14
13
|
*/
|
|
15
14
|
|
|
16
15
|
const path = require("path");
|
|
17
16
|
const fs = require("fs");
|
|
18
17
|
const { withErrorHandling, createUserError } = require("./lib/error-handler");
|
|
19
|
-
const { enforceLimit, trackUsage
|
|
18
|
+
const { enforceLimit, trackUsage } = require("./lib/entitlements");
|
|
20
19
|
const { emitScanStart, emitScanComplete } = require("./lib/audit-bridge");
|
|
21
20
|
const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
|
|
22
|
-
const { EXIT, verdictToExitCode } = require("./lib/exit-codes");
|
|
23
|
-
|
|
24
|
-
// Import orchestrator for engine management
|
|
25
|
-
const { runScanEngines, SCAN_ENGINES } = require("./lib/engines/orchestrator");
|
|
26
21
|
|
|
27
22
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
-
// TERMINAL UI
|
|
23
|
+
// ENHANCED TERMINAL UI & OUTPUT MODULES
|
|
29
24
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
30
25
|
|
|
31
26
|
const {
|
|
32
27
|
ansi,
|
|
33
28
|
colors,
|
|
34
29
|
Spinner,
|
|
30
|
+
PhaseProgress,
|
|
31
|
+
renderBanner,
|
|
32
|
+
renderSection,
|
|
35
33
|
formatDuration,
|
|
36
34
|
} = require("./lib/terminal-ui");
|
|
37
35
|
|
|
38
36
|
const {
|
|
37
|
+
formatScanOutput,
|
|
39
38
|
formatSARIF,
|
|
40
39
|
getExitCode,
|
|
40
|
+
printError,
|
|
41
|
+
EXIT_CODES,
|
|
41
42
|
calculateScore,
|
|
42
43
|
} = require("./lib/scan-output");
|
|
43
44
|
|
|
@@ -50,7 +51,7 @@ ${ansi.rgb(120, 120, 255)} ╚████╔╝ ██║██████
|
|
|
50
51
|
${ansi.rgb(150, 100, 255)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${ansi.reset}
|
|
51
52
|
|
|
52
53
|
${ansi.dim} ┌─────────────────────────────────────────────────────────────────────┐${ansi.reset}
|
|
53
|
-
${ansi.dim} │${ansi.reset} ${ansi.rgb(255, 255, 255)}${ansi.bold}
|
|
54
|
+
${ansi.dim} │${ansi.reset} ${ansi.rgb(255, 255, 255)}${ansi.bold}Route Integrity${ansi.reset} ${ansi.dim}•${ansi.reset} ${ansi.rgb(200, 200, 200)}Security${ansi.reset} ${ansi.dim}•${ansi.reset} ${ansi.rgb(150, 150, 150)}Quality${ansi.reset} ${ansi.dim}•${ansi.reset} ${ansi.rgb(100, 100, 100)}Ship with Confidence${ansi.reset} ${ansi.dim}│${ansi.reset}
|
|
54
55
|
${ansi.dim} └─────────────────────────────────────────────────────────────────────┘${ansi.reset}
|
|
55
56
|
`;
|
|
56
57
|
|
|
@@ -58,29 +59,62 @@ function printBanner() {
|
|
|
58
59
|
console.log(BANNER);
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// Legacy compatibility functions - now use enhanced modules
|
|
63
|
+
function printCoverageMap(coverageMap) {
|
|
64
|
+
const { renderCoverageMap } = require("./lib/scan-output");
|
|
65
|
+
console.log(renderCoverageMap(coverageMap));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function printBreakdown(breakdown) {
|
|
69
|
+
const { renderBreakdown } = require("./lib/scan-output");
|
|
70
|
+
console.log(renderBreakdown(breakdown));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function printBlockers(blockers) {
|
|
74
|
+
const { renderBlockers } = require("./lib/scan-output");
|
|
75
|
+
console.log(renderBlockers(blockers));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function printLayers(layers) {
|
|
79
|
+
const { renderLayers } = require("./lib/scan-output");
|
|
80
|
+
console.log(renderLayers(layers));
|
|
81
|
+
}
|
|
82
|
+
|
|
61
83
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
62
84
|
// ARGS PARSER
|
|
63
85
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
64
86
|
|
|
65
87
|
function parseArgs(args) {
|
|
88
|
+
// Parse global flags first
|
|
66
89
|
const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
|
|
67
90
|
|
|
68
91
|
const opts = {
|
|
69
92
|
path: globalFlags.path || process.cwd(),
|
|
93
|
+
truth: false,
|
|
94
|
+
reality: false,
|
|
95
|
+
realitySniff: false,
|
|
96
|
+
baseUrl: null,
|
|
70
97
|
json: globalFlags.json || false,
|
|
71
98
|
sarif: false,
|
|
72
99
|
verbose: globalFlags.verbose || false,
|
|
73
100
|
help: globalFlags.help || false,
|
|
74
|
-
|
|
101
|
+
autofix: false,
|
|
102
|
+
save: true, // Always save results by default
|
|
75
103
|
noBanner: globalFlags.noBanner || false,
|
|
76
104
|
ci: globalFlags.ci || false,
|
|
77
105
|
quiet: globalFlags.quiet || false,
|
|
78
106
|
};
|
|
79
107
|
|
|
108
|
+
// Parse command-specific args from cleanArgs
|
|
80
109
|
for (let i = 0; i < cleanArgs.length; i++) {
|
|
81
110
|
const arg = cleanArgs[i];
|
|
82
111
|
|
|
83
|
-
if (arg === '--
|
|
112
|
+
if (arg === '--truth' || arg === '-t') opts.truth = true;
|
|
113
|
+
else if (arg === '--reality' || arg === '-r') { opts.reality = true; opts.truth = true; }
|
|
114
|
+
else if (arg === '--reality-sniff' || arg === '--sniff') opts.realitySniff = true;
|
|
115
|
+
else if (arg === '--url' || arg === '-u') { opts.baseUrl = cleanArgs[++i]; opts.reality = true; opts.truth = true; }
|
|
116
|
+
else if (arg === '--sarif') opts.sarif = true;
|
|
117
|
+
else if (arg === '--autofix' || arg === '--fix' || arg === '-f') opts.autofix = true;
|
|
84
118
|
else if (arg === '--no-save') opts.save = false;
|
|
85
119
|
else if (arg === '--path' || arg === '-p') opts.path = cleanArgs[++i] || process.cwd();
|
|
86
120
|
else if (arg.startsWith('--path=')) opts.path = arg.split('=')[1];
|
|
@@ -95,51 +129,319 @@ function printHelp(showBanner = true) {
|
|
|
95
129
|
console.log(BANNER);
|
|
96
130
|
}
|
|
97
131
|
console.log(`
|
|
98
|
-
${ansi.bold}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
${ansi.bold}
|
|
109
|
-
${colors.accent}--
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
${colors.accent}--
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
${colors.accent}--
|
|
116
|
-
${colors.accent}--
|
|
117
|
-
${colors.accent}--
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
${ansi.dim}# Quick scan (default)${ansi.reset}
|
|
132
|
+
${ansi.bold}Usage:${ansi.reset} vibecheck scan ${ansi.dim}(s)${ansi.reset} [path] [options]
|
|
133
|
+
|
|
134
|
+
${ansi.bold}Aliases:${ansi.reset} ${ansi.dim}s${ansi.reset}
|
|
135
|
+
|
|
136
|
+
${ansi.bold}Scan Modes:${ansi.reset}
|
|
137
|
+
${colors.accent}(default)${ansi.reset} Layer 1: AST static analysis ${ansi.dim}(fast)${ansi.reset}
|
|
138
|
+
${colors.accent}--truth, -t${ansi.reset} Layer 1+2: Include build manifest verification ${ansi.dim}(CI/ship)${ansi.reset}
|
|
139
|
+
${colors.accent}--reality, -r${ansi.reset} Layer 1+2+3: Include Playwright runtime proof ${ansi.dim}(full)${ansi.reset}
|
|
140
|
+
${colors.accent}--reality-sniff${ansi.reset} Include Reality Sniff AI artifact detection ${ansi.dim}(recommended)${ansi.reset}
|
|
141
|
+
|
|
142
|
+
${ansi.bold}Fix Mode:${ansi.reset}
|
|
143
|
+
${colors.accent}--autofix, -f${ansi.reset} Apply safe fixes + generate AI missions ${ansi.rgb(0, 200, 255)}[STARTER]${ansi.reset}
|
|
144
|
+
|
|
145
|
+
${ansi.bold}Options:${ansi.reset}
|
|
146
|
+
${colors.accent}--url, -u${ansi.reset} Base URL for reality testing (e.g., http://localhost:3000)
|
|
147
|
+
${colors.accent}--verbose, -v${ansi.reset} Show detailed progress
|
|
148
|
+
${colors.accent}--json${ansi.reset} Output results as JSON
|
|
149
|
+
${colors.accent}--sarif${ansi.reset} Output in SARIF format (GitHub code scanning)
|
|
150
|
+
${colors.accent}--no-save${ansi.reset} Don't save results to .vibecheck/results/
|
|
151
|
+
${colors.accent}--help, -h${ansi.reset} Show this help
|
|
152
|
+
|
|
153
|
+
${ansi.bold}Examples:${ansi.reset}
|
|
154
|
+
${ansi.dim}# Quick scan (AST only)${ansi.reset}
|
|
123
155
|
vibecheck scan
|
|
124
156
|
|
|
125
|
-
${ansi.dim}#
|
|
126
|
-
vibecheck scan --
|
|
157
|
+
${ansi.dim}# Scan + autofix with missions${ansi.reset}
|
|
158
|
+
vibecheck scan --autofix
|
|
127
159
|
|
|
128
|
-
${ansi.dim}#
|
|
129
|
-
vibecheck scan
|
|
160
|
+
${ansi.dim}# CI/CD scan with manifest verification${ansi.reset}
|
|
161
|
+
vibecheck scan --truth
|
|
130
162
|
|
|
131
|
-
|
|
163
|
+
${ansi.dim}# Full proof with Playwright${ansi.reset}
|
|
164
|
+
vibecheck scan --reality --url http://localhost:3000
|
|
132
165
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
${ansi.dim}─────────────────────────────────────────────────────────────${ansi.reset}
|
|
137
|
-
${ansi.dim}Documentation: https://docs.vibecheckai.dev/cli/scan${ansi.reset}
|
|
166
|
+
${ansi.bold}Output:${ansi.reset}
|
|
167
|
+
Results saved to: .vibecheck/results/latest.json
|
|
168
|
+
Missions saved to: .vibecheck/missions/ ${ansi.dim}(with --autofix)${ansi.reset}
|
|
138
169
|
`);
|
|
139
170
|
}
|
|
140
171
|
|
|
141
172
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
142
|
-
//
|
|
173
|
+
// AUTOFIX & MISSION GENERATION
|
|
174
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Normalize severity values to standard format
|
|
178
|
+
*/
|
|
179
|
+
function normalizeSeverity(severity) {
|
|
180
|
+
if (!severity) return 'medium';
|
|
181
|
+
const sev = String(severity).toLowerCase();
|
|
182
|
+
if (sev === 'block' || sev === 'critical') return 'critical';
|
|
183
|
+
if (sev === 'high') return 'high';
|
|
184
|
+
if (sev === 'warn' || sev === 'warning' || sev === 'medium') return 'medium';
|
|
185
|
+
if (sev === 'info' || sev === 'low') return 'low';
|
|
186
|
+
return 'medium'; // Default fallback
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function generateMissions(findings, projectPath, opts) {
|
|
190
|
+
const missionsDir = path.join(projectPath, '.vibecheck', 'missions');
|
|
191
|
+
|
|
192
|
+
// Ensure missions directory exists
|
|
193
|
+
if (!fs.existsSync(missionsDir)) {
|
|
194
|
+
fs.mkdirSync(missionsDir, { recursive: true });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const missions = [];
|
|
198
|
+
const missionIndex = [];
|
|
199
|
+
|
|
200
|
+
for (let i = 0; i < findings.length; i++) {
|
|
201
|
+
const finding = findings[i];
|
|
202
|
+
const missionId = `mission-${String(i + 1).padStart(3, '0')}`;
|
|
203
|
+
const normalizedSeverity = normalizeSeverity(finding.severity);
|
|
204
|
+
|
|
205
|
+
// Generate AI-ready mission prompt
|
|
206
|
+
const mission = {
|
|
207
|
+
id: missionId,
|
|
208
|
+
createdAt: new Date().toISOString(),
|
|
209
|
+
finding: {
|
|
210
|
+
id: finding.id || `finding-${i + 1}`,
|
|
211
|
+
severity: normalizedSeverity,
|
|
212
|
+
originalSeverity: finding.severity, // Keep original for reference
|
|
213
|
+
category: finding.category,
|
|
214
|
+
title: finding.title || finding.message,
|
|
215
|
+
file: finding.file,
|
|
216
|
+
line: finding.line,
|
|
217
|
+
},
|
|
218
|
+
prompt: generateMissionPrompt(finding),
|
|
219
|
+
constraints: generateConstraints(finding),
|
|
220
|
+
verification: generateVerificationSteps(finding),
|
|
221
|
+
status: 'pending',
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
missions.push(mission);
|
|
225
|
+
missionIndex.push({
|
|
226
|
+
id: missionId,
|
|
227
|
+
severity: normalizedSeverity,
|
|
228
|
+
title: finding.title || finding.message,
|
|
229
|
+
file: finding.file,
|
|
230
|
+
status: 'pending',
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Save individual mission JSON
|
|
234
|
+
const missionPath = path.join(missionsDir, `${missionId}.json`);
|
|
235
|
+
fs.writeFileSync(missionPath, JSON.stringify(mission, null, 2));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Generate MISSIONS.md index
|
|
239
|
+
const missionsMarkdown = generateMissionsMarkdown(missionIndex, projectPath);
|
|
240
|
+
fs.writeFileSync(path.join(missionsDir, 'MISSIONS.md'), missionsMarkdown);
|
|
241
|
+
|
|
242
|
+
// Save missions summary
|
|
243
|
+
const summary = {
|
|
244
|
+
generatedAt: new Date().toISOString(),
|
|
245
|
+
totalMissions: missions.length,
|
|
246
|
+
bySeverity: {
|
|
247
|
+
critical: missionIndex.filter(m => m.severity === 'critical').length,
|
|
248
|
+
high: missionIndex.filter(m => m.severity === 'high').length,
|
|
249
|
+
medium: missionIndex.filter(m => m.severity === 'medium').length,
|
|
250
|
+
low: missionIndex.filter(m => m.severity === 'low').length,
|
|
251
|
+
},
|
|
252
|
+
missions: missionIndex,
|
|
253
|
+
};
|
|
254
|
+
fs.writeFileSync(path.join(missionsDir, 'summary.json'), JSON.stringify(summary, null, 2));
|
|
255
|
+
|
|
256
|
+
return { missions, summary };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function generateMissionPrompt(finding) {
|
|
260
|
+
const file = finding.file || 'unknown file';
|
|
261
|
+
const line = finding.line ? ` at line ${finding.line}` : '';
|
|
262
|
+
const title = finding.title || finding.message || 'Unknown issue';
|
|
263
|
+
const category = finding.category || 'general';
|
|
264
|
+
|
|
265
|
+
let prompt = `## Mission: Fix ${title}
|
|
266
|
+
|
|
267
|
+
**File:** \`${file}\`${line}
|
|
268
|
+
**Category:** ${category}
|
|
269
|
+
**Severity:** ${finding.severity || 'medium'}
|
|
270
|
+
|
|
271
|
+
### Problem
|
|
272
|
+
${finding.message || title}
|
|
273
|
+
|
|
274
|
+
### Context
|
|
275
|
+
`;
|
|
276
|
+
|
|
277
|
+
// Add category-specific context
|
|
278
|
+
if (category === 'ROUTE' || category === 'routes') {
|
|
279
|
+
prompt += `This is a route integrity issue. The route may be:
|
|
280
|
+
- Referenced but not defined
|
|
281
|
+
- Defined but not reachable
|
|
282
|
+
- Missing proper error handling
|
|
283
|
+
`;
|
|
284
|
+
} else if (category === 'ENV' || category === 'env') {
|
|
285
|
+
prompt += `This is an environment variable issue. The variable may be:
|
|
286
|
+
- Used but not defined in .env
|
|
287
|
+
- Missing from .env.example
|
|
288
|
+
- Not documented
|
|
289
|
+
`;
|
|
290
|
+
} else if (category === 'AUTH' || category === 'auth' || category === 'security') {
|
|
291
|
+
prompt += `This is a security/authentication issue. Check for:
|
|
292
|
+
- Proper authentication middleware
|
|
293
|
+
- Authorization checks
|
|
294
|
+
- Secure defaults
|
|
295
|
+
`;
|
|
296
|
+
} else if (category === 'MOCK' || category === 'mock') {
|
|
297
|
+
prompt += `This is a mock/placeholder issue. The code may contain:
|
|
298
|
+
- TODO comments that need implementation
|
|
299
|
+
- Placeholder data that should be real
|
|
300
|
+
- Fake success responses
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
prompt += `
|
|
305
|
+
### Requirements
|
|
306
|
+
1. Fix the issue while maintaining existing functionality
|
|
307
|
+
2. Follow the project's coding style and patterns
|
|
308
|
+
3. Add appropriate error handling
|
|
309
|
+
4. Update tests if they exist
|
|
310
|
+
|
|
311
|
+
### Verification
|
|
312
|
+
After making changes:
|
|
313
|
+
1. Run \`vibecheck scan\` to verify the issue is resolved
|
|
314
|
+
2. Run any existing tests for the affected file
|
|
315
|
+
3. Manually verify the functionality if applicable
|
|
316
|
+
`;
|
|
317
|
+
|
|
318
|
+
if (finding.fix || finding.fixSuggestion) {
|
|
319
|
+
prompt += `
|
|
320
|
+
### Suggested Fix
|
|
321
|
+
${finding.fix || finding.fixSuggestion}
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return prompt;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function generateConstraints(finding) {
|
|
329
|
+
const constraints = [
|
|
330
|
+
'Do not break existing functionality',
|
|
331
|
+
'Follow existing code patterns in the project',
|
|
332
|
+
'Add error handling where appropriate',
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
if (finding.category === 'security' || finding.category === 'AUTH') {
|
|
336
|
+
constraints.push('Ensure no security regressions');
|
|
337
|
+
constraints.push('Follow OWASP security guidelines');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (finding.category === 'ROUTE' || finding.category === 'routes') {
|
|
341
|
+
constraints.push('Maintain API backwards compatibility');
|
|
342
|
+
constraints.push('Update route documentation if changed');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return constraints;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function generateVerificationSteps(finding) {
|
|
349
|
+
const steps = [
|
|
350
|
+
'Run `vibecheck scan` and confirm this issue no longer appears',
|
|
351
|
+
'Run `vibecheck checkpoint` to ensure no regressions',
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
if (finding.file) {
|
|
355
|
+
steps.push(`Review changes in \`${finding.file}\``);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (finding.category === 'ROUTE' || finding.category === 'routes') {
|
|
359
|
+
steps.push('Test the affected route(s) manually or with automated tests');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (finding.category === 'ENV' || finding.category === 'env') {
|
|
363
|
+
steps.push('Verify environment variable is properly documented');
|
|
364
|
+
steps.push('Check .env.example is updated');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return steps;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function generateMissionsMarkdown(missionIndex, projectPath) {
|
|
371
|
+
const projectName = path.basename(projectPath);
|
|
372
|
+
const now = new Date().toISOString();
|
|
373
|
+
|
|
374
|
+
let md = `# Vibecheck Missions
|
|
375
|
+
|
|
376
|
+
> Generated: ${now}
|
|
377
|
+
> Project: ${projectName}
|
|
378
|
+
|
|
379
|
+
## Summary
|
|
380
|
+
|
|
381
|
+
| Severity | Count |
|
|
382
|
+
|----------|-------|
|
|
383
|
+
| Critical | ${missionIndex.filter(m => m.severity === 'critical').length} |
|
|
384
|
+
| High | ${missionIndex.filter(m => m.severity === 'high').length} |
|
|
385
|
+
| Medium | ${missionIndex.filter(m => m.severity === 'medium').length} |
|
|
386
|
+
| Low | ${missionIndex.filter(m => m.severity === 'low').length} |
|
|
387
|
+
| **Total** | **${missionIndex.length}** |
|
|
388
|
+
|
|
389
|
+
## Missions
|
|
390
|
+
|
|
391
|
+
`;
|
|
392
|
+
|
|
393
|
+
// Group by severity
|
|
394
|
+
const bySeverity = {
|
|
395
|
+
critical: missionIndex.filter(m => m.severity === 'critical'),
|
|
396
|
+
high: missionIndex.filter(m => m.severity === 'high'),
|
|
397
|
+
medium: missionIndex.filter(m => m.severity === 'medium'),
|
|
398
|
+
low: missionIndex.filter(m => m.severity === 'low'),
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
for (const [severity, missions] of Object.entries(bySeverity)) {
|
|
402
|
+
if (missions.length === 0) continue;
|
|
403
|
+
|
|
404
|
+
const emoji = severity === 'critical' ? '🔴' : severity === 'high' ? '🟠' : severity === 'medium' ? '🟡' : '🔵';
|
|
405
|
+
md += `### ${emoji} ${severity.charAt(0).toUpperCase() + severity.slice(1)} (${missions.length})\n\n`;
|
|
406
|
+
|
|
407
|
+
for (const mission of missions) {
|
|
408
|
+
const checkbox = mission.status === 'completed' ? '[x]' : '[ ]';
|
|
409
|
+
md += `- ${checkbox} **${mission.id}**: ${mission.title}\n`;
|
|
410
|
+
if (mission.file) {
|
|
411
|
+
md += ` - File: \`${mission.file}\`\n`;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
md += '\n';
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
md += `---
|
|
418
|
+
|
|
419
|
+
## How to Use
|
|
420
|
+
|
|
421
|
+
1. **Review missions**: Read each mission file in \`.vibecheck/missions/\`
|
|
422
|
+
2. **Copy prompts**: Use the prompt in each mission file with your AI assistant
|
|
423
|
+
3. **Verify fixes**: Run \`vibecheck scan\` after each fix
|
|
424
|
+
4. **Track progress**: Update mission status in this file
|
|
425
|
+
|
|
426
|
+
## Commands
|
|
427
|
+
|
|
428
|
+
\`\`\`bash
|
|
429
|
+
# Re-scan after fixes
|
|
430
|
+
vibecheck scan
|
|
431
|
+
|
|
432
|
+
# Check progress
|
|
433
|
+
vibecheck checkpoint
|
|
434
|
+
|
|
435
|
+
# Final verification
|
|
436
|
+
vibecheck ship
|
|
437
|
+
\`\`\`
|
|
438
|
+
`;
|
|
439
|
+
|
|
440
|
+
return md;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
444
|
+
// MAIN SCAN FUNCTION - ROUTE INTEGRITY SYSTEM
|
|
143
445
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
144
446
|
|
|
145
447
|
async function runScan(args) {
|
|
@@ -151,7 +453,7 @@ async function runScan(args) {
|
|
|
151
453
|
return 0;
|
|
152
454
|
}
|
|
153
455
|
|
|
154
|
-
// Entitlement check (
|
|
456
|
+
// Entitlement check (graceful offline handling)
|
|
155
457
|
try {
|
|
156
458
|
await enforceLimit('scans');
|
|
157
459
|
await trackUsage('scans');
|
|
@@ -160,239 +462,562 @@ async function runScan(args) {
|
|
|
160
462
|
console.error(err.upgradePrompt || err.message);
|
|
161
463
|
return 1;
|
|
162
464
|
}
|
|
163
|
-
// Network
|
|
164
|
-
if (
|
|
165
|
-
|
|
465
|
+
// Network error - fall back to free tier only (SECURITY: never grant paid features offline)
|
|
466
|
+
if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.code === 'ENOTFOUND' || err.name === 'NetworkError') {
|
|
467
|
+
console.warn(` ${colors.warning}⚠${ansi.reset} API unavailable, running in ${colors.success}FREE${ansi.reset} tier mode`);
|
|
468
|
+
console.warn(` ${ansi.dim}Paid features require API connection. Continuing with free features only.${ansi.reset}\n`);
|
|
469
|
+
// Continue with free tier features only - scan command is free tier
|
|
470
|
+
} else {
|
|
471
|
+
throw err; // Re-throw unexpected errors
|
|
166
472
|
}
|
|
167
473
|
}
|
|
168
474
|
|
|
169
|
-
// Print banner
|
|
475
|
+
// Print banner (respects --no-banner, VIBECHECK_NO_BANNER, --ci, --quiet, --json)
|
|
170
476
|
if (shouldShowBanner(opts)) {
|
|
171
477
|
printBanner();
|
|
172
478
|
}
|
|
173
479
|
|
|
174
480
|
const projectPath = path.resolve(opts.path);
|
|
175
481
|
const startTime = Date.now();
|
|
176
|
-
const projectName = path.basename(projectPath);
|
|
177
482
|
|
|
178
|
-
// Emit audit event
|
|
483
|
+
// Emit audit event for scan start
|
|
179
484
|
emitScanStart(projectPath, args);
|
|
485
|
+
const projectName = path.basename(projectPath);
|
|
180
486
|
|
|
181
487
|
// Validate project path
|
|
182
488
|
if (!fs.existsSync(projectPath)) {
|
|
183
489
|
throw createUserError(`Project path does not exist: ${projectPath}`, "ValidationError");
|
|
184
490
|
}
|
|
185
491
|
|
|
492
|
+
// Determine layers
|
|
493
|
+
const layers = {
|
|
494
|
+
ast: true,
|
|
495
|
+
truth: opts.truth,
|
|
496
|
+
reality: opts.reality,
|
|
497
|
+
realitySniff: opts.realitySniff,
|
|
498
|
+
};
|
|
499
|
+
|
|
186
500
|
// Print scan info
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
501
|
+
const layerNames = [];
|
|
502
|
+
if (layers.ast) layerNames.push('AST');
|
|
503
|
+
if (layers.truth) layerNames.push('Truth');
|
|
504
|
+
if (layers.reality) layerNames.push('Reality');
|
|
505
|
+
if (layers.realitySniff) layerNames.push('Reality Sniff');
|
|
506
|
+
|
|
507
|
+
console.log(` ${ansi.dim}Project:${ansi.reset} ${ansi.bold}${projectName}${ansi.reset}`);
|
|
508
|
+
console.log(` ${ansi.dim}Path:${ansi.reset} ${projectPath}`);
|
|
509
|
+
console.log(` ${ansi.dim}Layers:${ansi.reset} ${colors.accent}${layerNames.join(' → ')}${ansi.reset}`);
|
|
510
|
+
console.log();
|
|
511
|
+
|
|
512
|
+
// Reality layer requires URL
|
|
513
|
+
if (opts.reality && !opts.baseUrl) {
|
|
514
|
+
console.log(` ${colors.warning}⚠${ansi.reset} ${ansi.bold}Reality layer requires --url${ansi.reset}`);
|
|
515
|
+
console.log(` ${ansi.dim}Example: vibecheck scan --reality --url http://localhost:3000${ansi.reset}`);
|
|
191
516
|
console.log();
|
|
517
|
+
return 1;
|
|
192
518
|
}
|
|
193
519
|
|
|
520
|
+
// Initialize spinner outside try block for error handling
|
|
194
521
|
let spinner = null;
|
|
195
522
|
|
|
196
523
|
try {
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
524
|
+
// Import systems - try TypeScript compiled first, fallback to JS runtime
|
|
525
|
+
let scanRouteIntegrity;
|
|
526
|
+
let useFallbackScanner = false;
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
scanRouteIntegrity = require('../../dist/lib/route-integrity').scanRouteIntegrity;
|
|
530
|
+
} catch (e) {
|
|
531
|
+
// Fallback to JS-based scanner using truth.js and analyzers.js
|
|
532
|
+
useFallbackScanner = true;
|
|
533
|
+
const { buildTruthpack } = require('./lib/truth');
|
|
534
|
+
const { findMissingRoutes, findEnvGaps, findFakeSuccess, findGhostAuth } = require('./lib/analyzers');
|
|
535
|
+
|
|
536
|
+
scanRouteIntegrity = async function({ projectPath, layers, baseUrl, verbose }) {
|
|
537
|
+
// Build truthpack for route analysis
|
|
538
|
+
const truthpack = await buildTruthpack({ repoRoot: projectPath });
|
|
539
|
+
|
|
540
|
+
// Run analyzers
|
|
541
|
+
const findings = [];
|
|
542
|
+
findings.push(...findMissingRoutes(truthpack));
|
|
543
|
+
findings.push(...findEnvGaps(truthpack));
|
|
544
|
+
findings.push(...findFakeSuccess(projectPath));
|
|
545
|
+
findings.push(...findGhostAuth(truthpack, projectPath));
|
|
546
|
+
|
|
547
|
+
// Convert to scan format matching TypeScript scanner output
|
|
548
|
+
const shipBlockers = findings.map((f, i) => ({
|
|
549
|
+
id: f.id || `finding-${i}`,
|
|
550
|
+
ruleId: f.category,
|
|
551
|
+
category: f.category,
|
|
552
|
+
severity: f.severity === 'BLOCK' ? 'critical' : f.severity === 'WARN' ? 'warning' : 'info',
|
|
553
|
+
title: f.title,
|
|
554
|
+
message: f.title,
|
|
555
|
+
description: f.why,
|
|
556
|
+
file: f.evidence?.[0]?.file || '',
|
|
557
|
+
line: parseInt(f.evidence?.[0]?.lines?.split('-')[0]) || 1,
|
|
558
|
+
evidence: f.evidence || [],
|
|
559
|
+
fixHints: f.fixHints || [],
|
|
560
|
+
autofixAvailable: false,
|
|
561
|
+
verdict: f.severity === 'BLOCK' ? 'FAIL' : 'WARN',
|
|
562
|
+
}));
|
|
563
|
+
|
|
564
|
+
// Return structure matching TypeScript scanner
|
|
565
|
+
return {
|
|
566
|
+
report: {
|
|
567
|
+
shipBlockers,
|
|
568
|
+
realitySniffFindings: [],
|
|
569
|
+
routeMap: truthpack.routes,
|
|
570
|
+
summary: {
|
|
571
|
+
total: shipBlockers.length,
|
|
572
|
+
critical: shipBlockers.filter(f => f.severity === 'critical').length,
|
|
573
|
+
warning: shipBlockers.filter(f => f.severity === 'warning').length,
|
|
574
|
+
},
|
|
575
|
+
verdict: shipBlockers.some(f => f.severity === 'critical') ? 'BLOCK' :
|
|
576
|
+
shipBlockers.some(f => f.severity === 'warning') ? 'WARN' : 'SHIP'
|
|
577
|
+
},
|
|
578
|
+
outputPaths: {},
|
|
579
|
+
truthpack
|
|
580
|
+
};
|
|
581
|
+
};
|
|
201
582
|
}
|
|
202
|
-
|
|
203
|
-
// Run the 5 quick scan engines via orchestrator
|
|
204
|
-
const scanResult = await runScanEngines(projectPath, {
|
|
205
|
-
maxFiles: 500,
|
|
206
|
-
onProgress: opts.verbose ? (phase, pct) => {
|
|
207
|
-
if (spinner) spinner.text = `${phase}: ${pct}%`;
|
|
208
|
-
} : undefined,
|
|
209
|
-
});
|
|
210
583
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
584
|
+
// Try to import new unified output system (may not be compiled yet)
|
|
585
|
+
let buildVerdictOutput, normalizeFinding, formatStandardOutput, formatScanOutputFromUnified, getExitCodeFromUnified, CacheManager;
|
|
586
|
+
let useUnifiedOutput = false;
|
|
587
|
+
|
|
588
|
+
try {
|
|
589
|
+
const outputContract = require('../../dist/lib/cli/output-contract');
|
|
590
|
+
buildVerdictOutput = outputContract.buildVerdictOutput;
|
|
591
|
+
normalizeFinding = outputContract.normalizeFinding;
|
|
592
|
+
formatStandardOutput = outputContract.formatStandardOutput;
|
|
593
|
+
|
|
594
|
+
const unifiedOutput = require('./lib/unified-output');
|
|
595
|
+
formatScanOutputFromUnified = unifiedOutput.formatScanOutput;
|
|
596
|
+
getExitCodeFromUnified = unifiedOutput.getExitCode;
|
|
597
|
+
|
|
598
|
+
const cacheModule = require('../../dist/lib/cli/cache-manager');
|
|
599
|
+
CacheManager = cacheModule.CacheManager;
|
|
600
|
+
useUnifiedOutput = true;
|
|
601
|
+
} catch (error) {
|
|
602
|
+
// Fallback to old system if new one not available
|
|
603
|
+
if (opts.verbose) {
|
|
604
|
+
console.warn('Unified output system not available, using enhanced format');
|
|
605
|
+
}
|
|
606
|
+
useUnifiedOutput = false;
|
|
215
607
|
}
|
|
216
608
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
category: f.category || 'CodeQuality',
|
|
222
|
-
severity: normalizeSeverity(f.severity),
|
|
223
|
-
title: f.title || f.message,
|
|
224
|
-
message: f.message || f.title,
|
|
225
|
-
file: f.file || '',
|
|
226
|
-
line: f.line || 1,
|
|
227
|
-
confidence: f.confidence || 'medium',
|
|
228
|
-
engine: f.engine,
|
|
229
|
-
fixHint: f.fixHint,
|
|
230
|
-
}));
|
|
609
|
+
// Initialize cache if available
|
|
610
|
+
let cache = null;
|
|
611
|
+
let cached = false;
|
|
612
|
+
let cachedResult = null;
|
|
231
613
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
614
|
+
if (CacheManager) {
|
|
615
|
+
cache = new CacheManager(projectPath);
|
|
616
|
+
const cacheKey = 'scan';
|
|
617
|
+
|
|
618
|
+
// Compute project hash for caching
|
|
619
|
+
const sourceFiles = await findSourceFiles(projectPath);
|
|
620
|
+
const projectHash = await cache.computeProjectHash(sourceFiles, { layers, baseUrl: opts.baseUrl });
|
|
621
|
+
|
|
622
|
+
// Check cache
|
|
623
|
+
if (!opts.verbose) {
|
|
624
|
+
cachedResult = await cache.get(cacheKey, projectHash);
|
|
625
|
+
if (cachedResult && buildVerdictOutput) {
|
|
626
|
+
cached = true;
|
|
627
|
+
// Use cached result
|
|
628
|
+
const verdict = buildVerdictOutput(cachedResult.findings, cachedResult.timings, true);
|
|
629
|
+
const output = formatStandardOutput(verdict, cachedResult.findings, cachedResult.scanId, projectPath, {
|
|
630
|
+
version: require('../../package.json').version || '1.0.0',
|
|
631
|
+
nodeVersion: process.version,
|
|
632
|
+
platform: process.platform,
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
if (opts.json) {
|
|
636
|
+
console.log(JSON.stringify(output, null, 2));
|
|
637
|
+
return getExitCode(verdict);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
console.log(formatScanOutput({ verdict, findings: cachedResult.findings, cached }, { verbose: opts.verbose }));
|
|
641
|
+
return getExitCode(verdict);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Start scanning with enhanced spinner
|
|
647
|
+
const timings = { discovery: 0, analysis: 0, verification: 0, detection: 0, total: 0 };
|
|
648
|
+
timings.discovery = Date.now();
|
|
649
|
+
|
|
650
|
+
spinner = new Spinner({ color: colors.primary });
|
|
651
|
+
spinner.start('Analyzing codebase...');
|
|
652
|
+
|
|
653
|
+
const result = await scanRouteIntegrity({
|
|
654
|
+
projectPath,
|
|
655
|
+
layers,
|
|
656
|
+
baseUrl: opts.baseUrl,
|
|
657
|
+
verbose: opts.verbose,
|
|
658
|
+
onProgress: opts.verbose ? (phase, progress) => {
|
|
659
|
+
spinner.succeed(`${phase}: ${Math.round(progress)}%`);
|
|
660
|
+
if (progress < 100) spinner.start(`Running ${phase}...`);
|
|
661
|
+
} : undefined,
|
|
241
662
|
});
|
|
242
663
|
|
|
243
|
-
|
|
244
|
-
// OUTPUT
|
|
245
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
664
|
+
timings.analysis = Date.now() - timings.discovery;
|
|
246
665
|
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
},
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
666
|
+
// Run new detection engines (Dead UI, Billing Bypass, Fake Success)
|
|
667
|
+
let detectionFindings = [];
|
|
668
|
+
try {
|
|
669
|
+
startSpinner('Running detection engines...');
|
|
670
|
+
const detectionStart = Date.now();
|
|
671
|
+
|
|
672
|
+
// Dynamic import for TypeScript detection engines
|
|
673
|
+
const { DeadUIDetector } = require('../../dist/engines/detection/dead-ui-detector');
|
|
674
|
+
const { BillingBypassDetector } = require('../../dist/engines/detection/billing-bypass-detector');
|
|
675
|
+
const { FakeSuccessDetector } = require('../../dist/engines/detection/fake-success-detector');
|
|
676
|
+
|
|
677
|
+
const exclude = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage', '_archive'];
|
|
678
|
+
|
|
679
|
+
// Run detectors in parallel
|
|
680
|
+
const [deadUIResult, billingResult, fakeSuccessResult] = await Promise.all([
|
|
681
|
+
new DeadUIDetector(projectPath).scan({ exclude }),
|
|
682
|
+
new BillingBypassDetector(projectPath).scan({ exclude }),
|
|
683
|
+
new FakeSuccessDetector(projectPath).scan({ exclude }),
|
|
684
|
+
]);
|
|
685
|
+
|
|
686
|
+
// Convert to normalized findings format
|
|
687
|
+
for (const finding of deadUIResult.findings) {
|
|
688
|
+
detectionFindings.push({
|
|
689
|
+
id: finding.id,
|
|
690
|
+
ruleId: finding.type,
|
|
691
|
+
category: 'DEAD_UI',
|
|
692
|
+
severity: finding.severity,
|
|
693
|
+
title: finding.message,
|
|
694
|
+
description: finding.suggestion,
|
|
695
|
+
file: finding.file,
|
|
696
|
+
line: finding.line,
|
|
697
|
+
evidence: finding.evidence,
|
|
698
|
+
autofixAvailable: false,
|
|
699
|
+
verdict: 'FAIL',
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
for (const finding of billingResult.findings) {
|
|
704
|
+
detectionFindings.push({
|
|
705
|
+
id: finding.id,
|
|
706
|
+
ruleId: finding.type,
|
|
707
|
+
category: 'BILLING',
|
|
708
|
+
severity: finding.severity,
|
|
709
|
+
title: finding.message,
|
|
710
|
+
description: finding.suggestion,
|
|
711
|
+
file: finding.file,
|
|
712
|
+
line: finding.line,
|
|
713
|
+
evidence: finding.evidence,
|
|
714
|
+
autofixAvailable: false,
|
|
715
|
+
verdict: 'FAIL',
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
for (const finding of fakeSuccessResult.findings) {
|
|
720
|
+
detectionFindings.push({
|
|
721
|
+
id: finding.id,
|
|
722
|
+
ruleId: finding.type,
|
|
723
|
+
category: 'FAKE_SUCCESS',
|
|
724
|
+
severity: finding.severity,
|
|
725
|
+
title: finding.message,
|
|
726
|
+
description: finding.suggestion,
|
|
727
|
+
file: finding.file,
|
|
728
|
+
line: finding.line,
|
|
729
|
+
evidence: finding.evidence,
|
|
730
|
+
autofixAvailable: false,
|
|
731
|
+
verdict: 'FAIL',
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
timings.detection = Date.now() - detectionStart;
|
|
736
|
+
spinner.succeed(`Detection complete (${detectionFindings.length} findings)`);
|
|
737
|
+
} catch (detectionError) {
|
|
738
|
+
// Detection engines not compiled yet - continue without them
|
|
739
|
+
if (opts.verbose) {
|
|
740
|
+
console.log(` ${ansi.dim}Detection engines not available: ${detectionError.message}${ansi.reset}`);
|
|
741
|
+
}
|
|
742
|
+
spinner.warn('Detection skipped (not compiled)');
|
|
277
743
|
}
|
|
278
744
|
|
|
279
|
-
|
|
745
|
+
timings.verification = Date.now() - timings.analysis - timings.discovery;
|
|
746
|
+
timings.total = Date.now() - startTime;
|
|
747
|
+
|
|
748
|
+
spinner.succeed('Analysis complete');
|
|
749
|
+
|
|
750
|
+
const { report, outputPaths } = result;
|
|
751
|
+
|
|
752
|
+
// Use new unified output if available, otherwise fallback to enhanced format
|
|
753
|
+
if (useUnifiedOutput && buildVerdictOutput && normalizeFinding && formatScanOutputFromUnified) {
|
|
754
|
+
// Normalize findings with stable IDs
|
|
755
|
+
const existingIDs = new Set();
|
|
756
|
+
const normalizedFindings = [];
|
|
757
|
+
|
|
758
|
+
// Normalize route integrity findings
|
|
759
|
+
if (report.shipBlockers) {
|
|
760
|
+
for (let i = 0; i < report.shipBlockers.length; i++) {
|
|
761
|
+
const blocker = report.shipBlockers[i];
|
|
762
|
+
const category = blocker.category || 'ROUTE';
|
|
763
|
+
const normalized = normalizeFinding(blocker, category, i, existingIDs);
|
|
764
|
+
normalizedFindings.push(normalized);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Normalize Reality Sniff findings if present
|
|
769
|
+
if (report.realitySniffFindings) {
|
|
770
|
+
for (let i = 0; i < report.realitySniffFindings.length; i++) {
|
|
771
|
+
const finding = report.realitySniffFindings[i];
|
|
772
|
+
const category = finding.ruleId?.startsWith('auth') ? 'AUTH' : 'REALITY';
|
|
773
|
+
const normalized = normalizeFinding(finding, category, normalizedFindings.length, existingIDs);
|
|
774
|
+
normalizedFindings.push(normalized);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Add detection engine findings (Dead UI, Billing, Fake Success)
|
|
779
|
+
for (const finding of detectionFindings) {
|
|
780
|
+
normalizedFindings.push(finding);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Build verdict
|
|
784
|
+
const verdict = buildVerdictOutput(normalizedFindings, timings, false);
|
|
785
|
+
const scanId = `scan_${Date.now()}`;
|
|
786
|
+
|
|
787
|
+
// Cache result
|
|
788
|
+
if (cache) {
|
|
789
|
+
const sourceFiles = await findSourceFiles(projectPath);
|
|
790
|
+
const projectHash = await cache.computeProjectHash(sourceFiles, { layers, baseUrl: opts.baseUrl });
|
|
791
|
+
await cache.set('scan', projectHash, {
|
|
792
|
+
findings: normalizedFindings,
|
|
793
|
+
timings,
|
|
794
|
+
scanId,
|
|
795
|
+
}, {
|
|
796
|
+
filesScanned: sourceFiles.length,
|
|
797
|
+
findings: normalizedFindings.length,
|
|
798
|
+
duration: timings.total,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Build standard output
|
|
803
|
+
const standardOutput = formatStandardOutput(verdict, normalizedFindings, scanId, projectPath, {
|
|
804
|
+
version: require('../../package.json').version || '1.0.0',
|
|
805
|
+
nodeVersion: process.version,
|
|
806
|
+
platform: process.platform,
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// JSON output mode
|
|
810
|
+
if (opts.json) {
|
|
811
|
+
console.log(JSON.stringify(standardOutput, null, 2));
|
|
812
|
+
return getExitCodeFromUnified ? getExitCodeFromUnified(verdict) : getExitCode(verdict);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// SARIF output mode
|
|
280
816
|
if (opts.sarif) {
|
|
281
|
-
const sarif = formatSARIF(
|
|
817
|
+
const sarif = formatSARIF(normalizedFindings, {
|
|
282
818
|
projectPath,
|
|
283
819
|
version: require('../../package.json').version || '1.0.0'
|
|
284
820
|
});
|
|
285
821
|
console.log(JSON.stringify(sarif, null, 2));
|
|
286
|
-
return
|
|
822
|
+
return getExitCodeFromUnified ? getExitCodeFromUnified(verdict) : getExitCode(verdict);
|
|
287
823
|
}
|
|
288
|
-
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
824
|
+
|
|
825
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
826
|
+
// ENHANCED OUTPUT
|
|
827
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
828
|
+
|
|
829
|
+
// Use enhanced output formatter (from scan-output.js)
|
|
830
|
+
const resultForOutput = {
|
|
831
|
+
verdict,
|
|
832
|
+
findings: normalizedFindings,
|
|
833
|
+
layers: report.layers || [],
|
|
834
|
+
coverage: report.coverageMap,
|
|
835
|
+
breakdown: report.score?.breakdown,
|
|
836
|
+
timings,
|
|
837
|
+
cached,
|
|
838
|
+
};
|
|
839
|
+
console.log(formatScanOutput(resultForOutput, { verbose: opts.verbose }));
|
|
840
|
+
|
|
841
|
+
// Additional details if verbose
|
|
842
|
+
if (opts.verbose) {
|
|
843
|
+
printBreakdown(report.score.breakdown);
|
|
844
|
+
printCoverageMap(report.coverageMap);
|
|
845
|
+
printLayers(report.layers);
|
|
846
|
+
|
|
847
|
+
printSection('REPORTS', '📄');
|
|
311
848
|
console.log();
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
console.log(` ${colors.warning}${ansi.bold}Warnings (${warnings.length})${ansi.reset}`);
|
|
317
|
-
for (const f of warnings.slice(0, 5)) {
|
|
318
|
-
console.log(` ${colors.warning}◐${ansi.reset} ${f.title}`);
|
|
319
|
-
if (f.file) console.log(` ${ansi.dim}${f.file}:${f.line}${ansi.reset}`);
|
|
849
|
+
console.log(` ${c.cyan}${outputPaths.md}${c.reset}`);
|
|
850
|
+
console.log(` ${c.dim}${outputPaths.json}${c.reset}`);
|
|
851
|
+
if (outputPaths.sarif) {
|
|
852
|
+
console.log(` ${c.dim}${outputPaths.sarif}${c.reset}`);
|
|
320
853
|
}
|
|
321
|
-
if (warnings.length > 5) {
|
|
322
|
-
console.log(` ${ansi.dim}... and ${warnings.length - 5} more${ansi.reset}`);
|
|
323
|
-
}
|
|
324
|
-
console.log();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Pro upsell
|
|
328
|
-
if (!opts.quiet) {
|
|
329
|
-
console.log(` ${ansi.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ansi.reset}`);
|
|
330
|
-
console.log(` ${ansi.dim}Quick scan ran ${SCAN_ENGINES.length} engines.${ansi.reset}`);
|
|
331
|
-
console.log(` ${colors.accent}For comprehensive analysis (17+ engines + validators):${ansi.reset}`);
|
|
332
|
-
console.log(` ${colors.success}vibecheck ship${ansi.reset} ${ansi.dim}[PRO]${ansi.reset}`);
|
|
333
|
-
console.log();
|
|
334
854
|
}
|
|
335
|
-
|
|
336
|
-
// Emit completion
|
|
337
|
-
emitScanComplete(projectPath, verdict === 'PASS' ? 'success' : 'failure', {
|
|
338
|
-
score,
|
|
339
|
-
issueCount: findings.length,
|
|
340
|
-
durationMs: duration,
|
|
341
|
-
});
|
|
342
855
|
|
|
343
|
-
//
|
|
856
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
857
|
+
// SAVE RESULTS
|
|
858
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
859
|
+
|
|
344
860
|
if (opts.save) {
|
|
345
861
|
const resultsDir = path.join(projectPath, '.vibecheck', 'results');
|
|
346
862
|
if (!fs.existsSync(resultsDir)) {
|
|
347
863
|
fs.mkdirSync(resultsDir, { recursive: true });
|
|
348
864
|
}
|
|
349
865
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
866
|
+
// Save latest.json
|
|
867
|
+
const latestPath = path.join(resultsDir, 'latest.json');
|
|
868
|
+
fs.writeFileSync(latestPath, JSON.stringify(standardOutput, null, 2));
|
|
869
|
+
|
|
870
|
+
// Save timestamped copy
|
|
871
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
872
|
+
const historyDir = path.join(resultsDir, 'history');
|
|
873
|
+
if (!fs.existsSync(historyDir)) {
|
|
874
|
+
fs.mkdirSync(historyDir, { recursive: true });
|
|
875
|
+
}
|
|
876
|
+
fs.writeFileSync(path.join(historyDir, `scan-${timestamp}.json`), JSON.stringify(standardOutput, null, 2));
|
|
877
|
+
|
|
878
|
+
if (!opts.json) {
|
|
879
|
+
console.log(`\n ${ansi.dim}Results saved to: ${latestPath}${ansi.reset}`);
|
|
880
|
+
}
|
|
362
881
|
}
|
|
363
882
|
|
|
364
|
-
|
|
883
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
884
|
+
// AUTOFIX MODE - Generate missions
|
|
885
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
886
|
+
|
|
887
|
+
if (opts.autofix && normalizedFindings.length > 0) {
|
|
888
|
+
// Check entitlement
|
|
889
|
+
const entitlementsV2 = require("./lib/entitlements-v2");
|
|
890
|
+
const access = await entitlementsV2.enforce("scan.autofix", {
|
|
891
|
+
projectPath,
|
|
892
|
+
silent: false,
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
if (!access.allowed) {
|
|
896
|
+
console.log(`\n ${colors.warning}${icons.warning}${ansi.reset} ${ansi.bold}--autofix requires STARTER plan${ansi.reset}`);
|
|
897
|
+
console.log(` ${ansi.dim}Upgrade at: https://vibecheckai.dev/pricing${ansi.reset}`);
|
|
898
|
+
console.log(` ${ansi.dim}Scan results saved. Run 'vibecheck fix' for manual mission generation.${ansi.reset}\n`);
|
|
899
|
+
} else {
|
|
900
|
+
console.log(`\n ${colors.accent}${icons.lightning}${ansi.reset} ${ansi.bold}Generating AI missions...${ansi.reset}\n`);
|
|
901
|
+
|
|
902
|
+
const { missions, summary } = await generateMissions(normalizedFindings, projectPath, opts);
|
|
903
|
+
|
|
904
|
+
console.log(` ${colors.success}✓${ansi.reset} Generated ${missions.length} missions`);
|
|
905
|
+
console.log(` ${ansi.dim}Critical: ${summary.bySeverity.critical}${ansi.reset}`);
|
|
906
|
+
console.log(` ${ansi.dim}High: ${summary.bySeverity.high}${ansi.reset}`);
|
|
907
|
+
console.log(` ${ansi.dim}Medium: ${summary.bySeverity.medium}${ansi.reset}`);
|
|
908
|
+
console.log(` ${ansi.dim}Low: ${summary.bySeverity.low}${ansi.reset}`);
|
|
909
|
+
console.log();
|
|
910
|
+
console.log(` ${ansi.dim}Missions saved to: .vibecheck/missions/${ansi.reset}`);
|
|
911
|
+
console.log(` ${ansi.dim}Open MISSIONS.md for prompts to use with your AI assistant.${ansi.reset}`);
|
|
912
|
+
console.log();
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Emit audit event for scan complete
|
|
917
|
+
emitScanComplete(projectPath, verdict.verdict === 'PASS' ? 'success' : 'failure', {
|
|
918
|
+
score: report.score?.overall || (verdict.verdict === 'PASS' ? 100 : 50),
|
|
919
|
+
grade: report.score?.grade || (verdict.verdict === 'PASS' ? 'A' : 'F'),
|
|
920
|
+
issueCount: verdict.summary.blockers,
|
|
921
|
+
durationMs: timings.total,
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
return getExitCodeFromUnified ? getExitCodeFromUnified(verdict) : getExitCode(verdict);
|
|
925
|
+
} else {
|
|
926
|
+
// Legacy fallback output when unified output system isn't available
|
|
927
|
+
const findings = [...(report.shipBlockers || []), ...detectionFindings];
|
|
928
|
+
const criticalCount = findings.filter(f => f.severity === 'critical' || f.severity === 'BLOCK').length;
|
|
929
|
+
const warningCount = findings.filter(f => f.severity === 'warning' || f.severity === 'WARN').length;
|
|
930
|
+
|
|
931
|
+
const verdict = criticalCount > 0 ? 'BLOCK' : warningCount > 0 ? 'WARN' : 'SHIP';
|
|
932
|
+
|
|
933
|
+
// Use enhanced output formatter for legacy fallback
|
|
934
|
+
const severityCounts = {
|
|
935
|
+
critical: criticalCount,
|
|
936
|
+
high: 0,
|
|
937
|
+
medium: warningCount,
|
|
938
|
+
low: findings.length - criticalCount - warningCount,
|
|
939
|
+
};
|
|
940
|
+
const score = calculateScore(severityCounts);
|
|
941
|
+
|
|
942
|
+
const result = {
|
|
943
|
+
verdict: { verdict, score },
|
|
944
|
+
findings: findings.map(f => ({
|
|
945
|
+
severity: f.severity === 'critical' || f.severity === 'BLOCK' ? 'critical' :
|
|
946
|
+
f.severity === 'warning' || f.severity === 'WARN' ? 'medium' : 'low',
|
|
947
|
+
category: f.category || 'ROUTE',
|
|
948
|
+
title: f.title || f.message,
|
|
949
|
+
message: f.message || f.title,
|
|
950
|
+
file: f.file,
|
|
951
|
+
line: f.line,
|
|
952
|
+
fix: f.fixSuggestion,
|
|
953
|
+
})),
|
|
954
|
+
layers: [],
|
|
955
|
+
timings,
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
console.log(formatScanOutput(result, { verbose: opts.verbose }));
|
|
959
|
+
|
|
960
|
+
// Emit audit event
|
|
961
|
+
emitScanComplete(projectPath, verdict === 'SHIP' ? 'success' : 'failure', {
|
|
962
|
+
score: verdict === 'SHIP' ? 100 : verdict === 'WARN' ? 70 : 40,
|
|
963
|
+
issueCount: criticalCount + warningCount,
|
|
964
|
+
durationMs: timings.total,
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
return verdict === 'SHIP' ? 0 : verdict === 'WARN' ? 1 : 2;
|
|
968
|
+
}
|
|
365
969
|
|
|
366
970
|
} catch (error) {
|
|
367
971
|
if (spinner) {
|
|
368
972
|
spinner.fail(`Scan failed: ${error.message}`);
|
|
369
973
|
}
|
|
370
974
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
console.error(` ${ansi.dim}${error.stack}${ansi.reset}`);
|
|
374
|
-
}
|
|
975
|
+
// Use enhanced error handling
|
|
976
|
+
const exitCode = printError(error, 'Scan');
|
|
375
977
|
|
|
978
|
+
// Emit audit event for scan error
|
|
376
979
|
emitScanComplete(projectPath, 'error', {
|
|
377
980
|
errorCode: error.code || 'SCAN_ERROR',
|
|
378
981
|
errorMessage: error.message,
|
|
379
982
|
durationMs: Date.now() - startTime,
|
|
380
983
|
});
|
|
381
984
|
|
|
382
|
-
return
|
|
985
|
+
return exitCode;
|
|
383
986
|
}
|
|
384
987
|
}
|
|
385
988
|
|
|
386
|
-
// Helper to
|
|
387
|
-
function
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
989
|
+
// Helper function to find source files for cache hash
|
|
990
|
+
async function findSourceFiles(projectPath) {
|
|
991
|
+
const files = [];
|
|
992
|
+
const fs = require('fs');
|
|
993
|
+
const path = require('path');
|
|
994
|
+
|
|
995
|
+
async function walk(dir) {
|
|
996
|
+
try {
|
|
997
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
998
|
+
for (const entry of entries) {
|
|
999
|
+
const fullPath = path.join(dir, entry.name);
|
|
1000
|
+
if (entry.isDirectory()) {
|
|
1001
|
+
if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
1002
|
+
await walk(fullPath);
|
|
1003
|
+
}
|
|
1004
|
+
} else if (entry.isFile()) {
|
|
1005
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1006
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
1007
|
+
files.push(fullPath);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
} catch {
|
|
1012
|
+
// Skip inaccessible directories
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
await walk(projectPath);
|
|
1017
|
+
return files;
|
|
393
1018
|
}
|
|
394
1019
|
|
|
395
|
-
// Export
|
|
1020
|
+
// Export with error handling wrapper
|
|
396
1021
|
module.exports = {
|
|
397
1022
|
runScan: withErrorHandling(runScan, "Scan failed"),
|
|
398
1023
|
};
|