vibecheck-ai 2.0.2 → 5.0.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/.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 +380 -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,1875 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vibecheck MCP Server CLI - HARDENED v4.1.0
|
|
3
|
+
*
|
|
4
|
+
* "My IDE is now VibeCheck-powered"
|
|
5
|
+
*
|
|
6
|
+
* HARDENING FEATURES:
|
|
7
|
+
* - Comprehensive error codes and structured error handling
|
|
8
|
+
* - Retry logic with exponential backoff
|
|
9
|
+
* - Input validation and sanitization
|
|
10
|
+
* - Lock file to prevent multiple instances
|
|
11
|
+
* - Startup verification with health probe
|
|
12
|
+
* - Structured logging with log levels
|
|
13
|
+
* - Connection testing and verification
|
|
14
|
+
* - Graceful degradation and recovery
|
|
15
|
+
* - Signal handling and cleanup
|
|
16
|
+
* - Timeout management
|
|
17
|
+
* - Secret redaction
|
|
18
|
+
*
|
|
19
|
+
* Subcommands:
|
|
20
|
+
* serve - Start the MCP server (default)
|
|
21
|
+
* doctor - Health check with self-healing
|
|
22
|
+
* config - Print IDE-specific connection configs
|
|
23
|
+
* manifest - Machine-readable tool manifest for IDEs
|
|
24
|
+
* status - Check server health
|
|
25
|
+
* test - Test MCP server connection
|
|
26
|
+
*
|
|
27
|
+
* @version 4.1.0
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
"use strict";
|
|
31
|
+
|
|
32
|
+
const path = require("path");
|
|
33
|
+
const fs = require("fs");
|
|
34
|
+
const os = require("os");
|
|
35
|
+
const net = require("net");
|
|
36
|
+
const crypto = require("crypto");
|
|
37
|
+
const { spawn, execSync } = require("child_process");
|
|
38
|
+
const { EXIT } = require("./lib/exit-codes");
|
|
39
|
+
const { parseGlobalFlags, shouldSuppressOutput, isJsonMode } = require("./lib/global-flags");
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// CONSTANTS & CONFIGURATION
|
|
43
|
+
// ============================================================================
|
|
44
|
+
const VERSION = "4.1.0";
|
|
45
|
+
const DEFAULT_PORT = 3847;
|
|
46
|
+
const PORT_RANGE = { min: 3847, max: 3867 };
|
|
47
|
+
const FALLBACK_PORTS = [8000, 8080, 9000, 9999, 3000];
|
|
48
|
+
const SERVER_NAME = "vibecheck-mcp";
|
|
49
|
+
const LOCK_FILE_NAME = ".vibecheck-mcp.lock";
|
|
50
|
+
const LOG_FILE_NAME = "mcp-server.log";
|
|
51
|
+
const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
|
|
52
|
+
|
|
53
|
+
// Timeouts (ms)
|
|
54
|
+
const TIMEOUTS = {
|
|
55
|
+
STARTUP: 10000, // Max time to wait for server startup
|
|
56
|
+
HEALTH_PROBE: 3000, // Health check timeout
|
|
57
|
+
SHUTDOWN: 5000, // Graceful shutdown timeout
|
|
58
|
+
RETRY_BASE: 1000, // Base retry delay
|
|
59
|
+
RETRY_MAX: 30000, // Max retry delay
|
|
60
|
+
PORT_CHECK: 1000, // Port availability check
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Retry configuration
|
|
64
|
+
const RETRY_CONFIG = {
|
|
65
|
+
maxAttempts: 3,
|
|
66
|
+
backoffMultiplier: 2,
|
|
67
|
+
jitter: 0.1,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Error codes
|
|
71
|
+
const ERROR_CODES = {
|
|
72
|
+
SUCCESS: 0,
|
|
73
|
+
VALIDATION_ERROR: 1,
|
|
74
|
+
SERVER_NOT_FOUND: 2,
|
|
75
|
+
PORT_UNAVAILABLE: 3,
|
|
76
|
+
STARTUP_FAILED: 4,
|
|
77
|
+
ALREADY_RUNNING: 5,
|
|
78
|
+
CONFIG_ERROR: 6,
|
|
79
|
+
PERMISSION_DENIED: 7,
|
|
80
|
+
NETWORK_ERROR: 8,
|
|
81
|
+
TIMEOUT: 9,
|
|
82
|
+
INTERNAL_ERROR: 10,
|
|
83
|
+
NOT_CONFIGURED: 11,
|
|
84
|
+
HEALTH_CHECK_FAILED: 12,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// ============================================================================
|
|
88
|
+
// ANSI STYLING (Safe for non-TTY)
|
|
89
|
+
// ============================================================================
|
|
90
|
+
const SUPPORTS_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
91
|
+
const c = SUPPORTS_COLOR ? {
|
|
92
|
+
reset: "\x1b[0m",
|
|
93
|
+
bold: "\x1b[1m",
|
|
94
|
+
dim: "\x1b[2m",
|
|
95
|
+
italic: "\x1b[3m",
|
|
96
|
+
cyan: "\x1b[36m",
|
|
97
|
+
green: "\x1b[32m",
|
|
98
|
+
yellow: "\x1b[33m",
|
|
99
|
+
red: "\x1b[31m",
|
|
100
|
+
magenta: "\x1b[35m",
|
|
101
|
+
blue: "\x1b[34m",
|
|
102
|
+
gray: "\x1b[90m",
|
|
103
|
+
bgGreen: "\x1b[42m",
|
|
104
|
+
bgRed: "\x1b[41m",
|
|
105
|
+
bgYellow: "\x1b[43m",
|
|
106
|
+
bgBlue: "\x1b[44m",
|
|
107
|
+
} : Object.fromEntries([
|
|
108
|
+
"reset", "bold", "dim", "italic", "cyan", "green", "yellow", "red",
|
|
109
|
+
"magenta", "blue", "gray", "bgGreen", "bgRed", "bgYellow", "bgBlue"
|
|
110
|
+
].map(k => [k, ""]));
|
|
111
|
+
|
|
112
|
+
const sym = {
|
|
113
|
+
check: SUPPORTS_COLOR ? "✓" : "[OK]",
|
|
114
|
+
cross: SUPPORTS_COLOR ? "✗" : "[FAIL]",
|
|
115
|
+
warn: SUPPORTS_COLOR ? "⚠" : "[WARN]",
|
|
116
|
+
info: SUPPORTS_COLOR ? "ℹ" : "[INFO]",
|
|
117
|
+
arrow: SUPPORTS_COLOR ? "→" : "->",
|
|
118
|
+
box: SUPPORTS_COLOR ? "█" : "#",
|
|
119
|
+
dot: SUPPORTS_COLOR ? "•" : "-",
|
|
120
|
+
rocket: SUPPORTS_COLOR ? "🚀" : "[*]",
|
|
121
|
+
plug: SUPPORTS_COLOR ? "🔌" : "[PLUG]",
|
|
122
|
+
wrench: SUPPORTS_COLOR ? "🔧" : "[FIX]",
|
|
123
|
+
heart: SUPPORTS_COLOR ? "💚" : "[OK]",
|
|
124
|
+
shield: SUPPORTS_COLOR ? "🛡️" : "[SHIELD]",
|
|
125
|
+
brain: SUPPORTS_COLOR ? "🧠" : "[AI]",
|
|
126
|
+
clipboard: SUPPORTS_COLOR ? "📋" : "[COPY]",
|
|
127
|
+
folder: SUPPORTS_COLOR ? "📁" : "[DIR]",
|
|
128
|
+
lock: SUPPORTS_COLOR ? "🔒" : "[LOCK]",
|
|
129
|
+
key: SUPPORTS_COLOR ? "🔑" : "[KEY]",
|
|
130
|
+
timer: SUPPORTS_COLOR ? "⏱️" : "[TIME]",
|
|
131
|
+
retry: SUPPORTS_COLOR ? "🔄" : "[RETRY]",
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// STRUCTURED LOGGING
|
|
136
|
+
// ============================================================================
|
|
137
|
+
const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3, silent: 4 };
|
|
138
|
+
let currentLogLevel = LOG_LEVELS.info;
|
|
139
|
+
let logFile = null;
|
|
140
|
+
|
|
141
|
+
function setLogLevel(level) {
|
|
142
|
+
currentLogLevel = LOG_LEVELS[level] ?? LOG_LEVELS.info;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getLogFilePath(projectPath) {
|
|
146
|
+
const logDir = path.join(projectPath, ".vibecheck", "logs");
|
|
147
|
+
return path.join(logDir, LOG_FILE_NAME);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function initLogFile(projectPath) {
|
|
151
|
+
try {
|
|
152
|
+
const logPath = getLogFilePath(projectPath);
|
|
153
|
+
const logDir = path.dirname(logPath);
|
|
154
|
+
|
|
155
|
+
if (!fs.existsSync(logDir)) {
|
|
156
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Rotate log if too large
|
|
160
|
+
if (fs.existsSync(logPath)) {
|
|
161
|
+
const stats = fs.statSync(logPath);
|
|
162
|
+
if (stats.size > MAX_LOG_SIZE) {
|
|
163
|
+
const backupPath = `${logPath}.1`;
|
|
164
|
+
if (fs.existsSync(backupPath)) {
|
|
165
|
+
fs.unlinkSync(backupPath);
|
|
166
|
+
}
|
|
167
|
+
fs.renameSync(logPath, backupPath);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
logFile = fs.createWriteStream(logPath, { flags: "a" });
|
|
172
|
+
} catch {
|
|
173
|
+
// Logging is best-effort
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function log(level, message, meta = {}) {
|
|
178
|
+
if (LOG_LEVELS[level] < currentLogLevel) return;
|
|
179
|
+
|
|
180
|
+
const timestamp = new Date().toISOString();
|
|
181
|
+
const entry = {
|
|
182
|
+
timestamp,
|
|
183
|
+
level,
|
|
184
|
+
message,
|
|
185
|
+
...meta,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Write to log file
|
|
189
|
+
if (logFile) {
|
|
190
|
+
try {
|
|
191
|
+
logFile.write(JSON.stringify(entry) + "\n");
|
|
192
|
+
} catch {
|
|
193
|
+
// Best effort
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Console output (only for warn/error or if debug mode)
|
|
198
|
+
if (level === "error") {
|
|
199
|
+
console.error(`${c.red}[ERROR]${c.reset} ${message}`);
|
|
200
|
+
} else if (level === "warn" && currentLogLevel <= LOG_LEVELS.warn) {
|
|
201
|
+
console.error(`${c.yellow}[WARN]${c.reset} ${message}`);
|
|
202
|
+
} else if (level === "debug" && currentLogLevel === LOG_LEVELS.debug) {
|
|
203
|
+
console.error(`${c.gray}[DEBUG]${c.reset} ${message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ============================================================================
|
|
208
|
+
// INPUT VALIDATION & SANITIZATION
|
|
209
|
+
// ============================================================================
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Validate and sanitize a file path
|
|
213
|
+
*/
|
|
214
|
+
function validatePath(inputPath, basePath = process.cwd()) {
|
|
215
|
+
if (!inputPath || typeof inputPath !== "string") {
|
|
216
|
+
return { valid: false, error: "Path must be a non-empty string", code: ERROR_CODES.VALIDATION_ERROR };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Length check
|
|
220
|
+
if (inputPath.length > 4096) {
|
|
221
|
+
return { valid: false, error: "Path too long (max 4096 chars)", code: ERROR_CODES.VALIDATION_ERROR };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Null byte check
|
|
225
|
+
if (inputPath.includes("\0")) {
|
|
226
|
+
return { valid: false, error: "Path contains invalid characters", code: ERROR_CODES.VALIDATION_ERROR };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const resolved = path.resolve(basePath, inputPath);
|
|
231
|
+
const normalizedBase = path.resolve(basePath).replace(/\\/g, "/").toLowerCase();
|
|
232
|
+
const normalizedPath = resolved.replace(/\\/g, "/").toLowerCase();
|
|
233
|
+
|
|
234
|
+
// Path traversal check
|
|
235
|
+
const baseWithSep = normalizedBase.endsWith("/") ? normalizedBase : normalizedBase + "/";
|
|
236
|
+
if (!normalizedPath.startsWith(baseWithSep) && normalizedPath !== normalizedBase) {
|
|
237
|
+
// Allow if it's the base path itself or a child
|
|
238
|
+
if (!normalizedPath.startsWith(normalizedBase)) {
|
|
239
|
+
return { valid: false, error: "Path traversal detected", code: ERROR_CODES.VALIDATION_ERROR };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { valid: true, path: resolved };
|
|
244
|
+
} catch (err) {
|
|
245
|
+
return { valid: false, error: `Invalid path: ${err.message}`, code: ERROR_CODES.VALIDATION_ERROR };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Validate port number
|
|
251
|
+
*/
|
|
252
|
+
function validatePort(port) {
|
|
253
|
+
const num = parseInt(port, 10);
|
|
254
|
+
if (isNaN(num) || num < 1 || num > 65535) {
|
|
255
|
+
return { valid: false, error: `Invalid port: ${port} (must be 1-65535)`, code: ERROR_CODES.VALIDATION_ERROR };
|
|
256
|
+
}
|
|
257
|
+
return { valid: true, port: num };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Validate IDE name
|
|
262
|
+
*/
|
|
263
|
+
function validateIde(ide) {
|
|
264
|
+
const validIdes = ["cursor", "windsurf", "claude", "vscode", "all"];
|
|
265
|
+
const normalized = (ide || "all").toLowerCase();
|
|
266
|
+
if (!validIdes.includes(normalized)) {
|
|
267
|
+
return { valid: false, error: `Invalid IDE: ${ide}. Valid options: ${validIdes.join(", ")}`, code: ERROR_CODES.VALIDATION_ERROR };
|
|
268
|
+
}
|
|
269
|
+
return { valid: true, ide: normalized };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Redact sensitive values from output
|
|
274
|
+
*/
|
|
275
|
+
function redactSecrets(text) {
|
|
276
|
+
if (typeof text !== "string") return text;
|
|
277
|
+
|
|
278
|
+
const patterns = [
|
|
279
|
+
[/(?:sk_live_|sk_test_)[a-zA-Z0-9]{24,}/g, "[STRIPE_KEY_REDACTED]"],
|
|
280
|
+
[/(?:AKIA|ASIA)[A-Z0-9]{16}/g, "[AWS_KEY_REDACTED]"],
|
|
281
|
+
[/ghp_[a-zA-Z0-9]{36}/g, "[GITHUB_TOKEN_REDACTED]"],
|
|
282
|
+
[/xox[baprs]-[0-9A-Za-z\-]{10,}/g, "[SLACK_TOKEN_REDACTED]"],
|
|
283
|
+
[/eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g, "[JWT_REDACTED]"],
|
|
284
|
+
[/(?:password|secret|token|apikey|api_key)["']?\s*[:=]\s*["'][^"']{8,}["']/gi, "[SECRET_REDACTED]"],
|
|
285
|
+
[/VIBECHECK_API_KEY=\S+/g, "VIBECHECK_API_KEY=[REDACTED]"],
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
let result = text;
|
|
289
|
+
for (const [pattern, replacement] of patterns) {
|
|
290
|
+
result = result.replace(pattern, replacement);
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ============================================================================
|
|
296
|
+
// LOCK FILE MANAGEMENT
|
|
297
|
+
// ============================================================================
|
|
298
|
+
|
|
299
|
+
function getLockFilePath(projectPath) {
|
|
300
|
+
return path.join(projectPath, ".vibecheck", LOCK_FILE_NAME);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function createLockFile(projectPath, pid, port) {
|
|
304
|
+
const lockPath = getLockFilePath(projectPath);
|
|
305
|
+
const lockDir = path.dirname(lockPath);
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
if (!fs.existsSync(lockDir)) {
|
|
309
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const lockData = {
|
|
313
|
+
pid,
|
|
314
|
+
port,
|
|
315
|
+
startedAt: new Date().toISOString(),
|
|
316
|
+
hostname: os.hostname(),
|
|
317
|
+
nodeVersion: process.version,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
|
|
321
|
+
log("debug", "Lock file created", { lockPath, pid, port });
|
|
322
|
+
return { success: true };
|
|
323
|
+
} catch (err) {
|
|
324
|
+
log("error", "Failed to create lock file", { error: err.message });
|
|
325
|
+
return { success: false, error: err.message };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function readLockFile(projectPath) {
|
|
330
|
+
const lockPath = getLockFilePath(projectPath);
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
if (!fs.existsSync(lockPath)) {
|
|
334
|
+
return { exists: false };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const data = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
338
|
+
return { exists: true, data };
|
|
339
|
+
} catch {
|
|
340
|
+
return { exists: false };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function removeLockFile(projectPath) {
|
|
345
|
+
const lockPath = getLockFilePath(projectPath);
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
if (fs.existsSync(lockPath)) {
|
|
349
|
+
fs.unlinkSync(lockPath);
|
|
350
|
+
log("debug", "Lock file removed", { lockPath });
|
|
351
|
+
}
|
|
352
|
+
return { success: true };
|
|
353
|
+
} catch (err) {
|
|
354
|
+
log("warn", "Failed to remove lock file", { error: err.message });
|
|
355
|
+
return { success: false, error: err.message };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function isProcessRunning(pid) {
|
|
360
|
+
try {
|
|
361
|
+
process.kill(pid, 0);
|
|
362
|
+
return true;
|
|
363
|
+
} catch {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function checkExistingInstance(projectPath) {
|
|
369
|
+
const lock = readLockFile(projectPath);
|
|
370
|
+
|
|
371
|
+
if (!lock.exists) {
|
|
372
|
+
return { running: false };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const { pid, port, startedAt } = lock.data;
|
|
376
|
+
|
|
377
|
+
if (isProcessRunning(pid)) {
|
|
378
|
+
return {
|
|
379
|
+
running: true,
|
|
380
|
+
pid,
|
|
381
|
+
port,
|
|
382
|
+
startedAt,
|
|
383
|
+
message: `MCP server already running (PID: ${pid}, Port: ${port})`,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Stale lock file - remove it
|
|
388
|
+
log("info", "Removing stale lock file", { pid });
|
|
389
|
+
removeLockFile(projectPath);
|
|
390
|
+
return { running: false, wasStale: true };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ============================================================================
|
|
394
|
+
// RETRY LOGIC WITH EXPONENTIAL BACKOFF
|
|
395
|
+
// ============================================================================
|
|
396
|
+
|
|
397
|
+
async function sleep(ms) {
|
|
398
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function calculateBackoff(attempt, config = RETRY_CONFIG) {
|
|
402
|
+
const baseDelay = TIMEOUTS.RETRY_BASE * Math.pow(config.backoffMultiplier, attempt);
|
|
403
|
+
const jitter = baseDelay * config.jitter * (Math.random() * 2 - 1);
|
|
404
|
+
return Math.min(baseDelay + jitter, TIMEOUTS.RETRY_MAX);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function withRetry(fn, options = {}) {
|
|
408
|
+
const { maxAttempts = RETRY_CONFIG.maxAttempts, onRetry, shouldRetry } = options;
|
|
409
|
+
|
|
410
|
+
let lastError;
|
|
411
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
412
|
+
try {
|
|
413
|
+
return await fn(attempt);
|
|
414
|
+
} catch (err) {
|
|
415
|
+
lastError = err;
|
|
416
|
+
|
|
417
|
+
if (shouldRetry && !shouldRetry(err, attempt)) {
|
|
418
|
+
throw err;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (attempt < maxAttempts - 1) {
|
|
422
|
+
const delay = calculateBackoff(attempt);
|
|
423
|
+
log("debug", `Retry ${attempt + 1}/${maxAttempts} after ${delay}ms`, { error: err.message });
|
|
424
|
+
|
|
425
|
+
if (onRetry) {
|
|
426
|
+
onRetry(err, attempt, delay);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
await sleep(delay);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
throw lastError;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ============================================================================
|
|
438
|
+
// PORT MANAGEMENT
|
|
439
|
+
// ============================================================================
|
|
440
|
+
|
|
441
|
+
async function isPortAvailable(port, timeout = TIMEOUTS.PORT_CHECK) {
|
|
442
|
+
return new Promise((resolve) => {
|
|
443
|
+
const server = net.createServer();
|
|
444
|
+
const timeoutId = setTimeout(() => {
|
|
445
|
+
server.close();
|
|
446
|
+
resolve(false);
|
|
447
|
+
}, timeout);
|
|
448
|
+
|
|
449
|
+
server.once("error", () => {
|
|
450
|
+
clearTimeout(timeoutId);
|
|
451
|
+
resolve(false);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
server.once("listening", () => {
|
|
455
|
+
clearTimeout(timeoutId);
|
|
456
|
+
server.close();
|
|
457
|
+
resolve(true);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
server.listen(port, "127.0.0.1");
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function findAvailablePort(startPort = DEFAULT_PORT) {
|
|
465
|
+
// Try primary range
|
|
466
|
+
for (let port = startPort; port <= PORT_RANGE.max; port++) {
|
|
467
|
+
if (await isPortAvailable(port)) {
|
|
468
|
+
return { port, source: "primary" };
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Try fallback ports
|
|
473
|
+
for (const port of FALLBACK_PORTS) {
|
|
474
|
+
if (await isPortAvailable(port)) {
|
|
475
|
+
return { port, source: "fallback" };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return { port: null, error: "No available ports found" };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function findPortProcess(port) {
|
|
483
|
+
try {
|
|
484
|
+
if (process.platform === "win32") {
|
|
485
|
+
const result = execSync(`netstat -ano | findstr :${port}`, {
|
|
486
|
+
encoding: "utf8",
|
|
487
|
+
timeout: 5000,
|
|
488
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
489
|
+
});
|
|
490
|
+
const lines = result.trim().split("\n").filter(l => l.includes("LISTENING"));
|
|
491
|
+
if (lines.length > 0) {
|
|
492
|
+
const pid = lines[0].trim().split(/\s+/).pop();
|
|
493
|
+
try {
|
|
494
|
+
const taskResult = execSync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { encoding: "utf8" });
|
|
495
|
+
const name = taskResult.split(",")[0]?.replace(/"/g, "");
|
|
496
|
+
return { pid: parseInt(pid), name };
|
|
497
|
+
} catch {
|
|
498
|
+
return { pid: parseInt(pid), name: "unknown" };
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
const result = execSync(`lsof -i :${port} -t 2>/dev/null | head -1`, {
|
|
503
|
+
encoding: "utf8",
|
|
504
|
+
timeout: 5000,
|
|
505
|
+
});
|
|
506
|
+
const pid = result.trim();
|
|
507
|
+
if (pid) {
|
|
508
|
+
try {
|
|
509
|
+
const name = execSync(`ps -p ${pid} -o comm= 2>/dev/null`, { encoding: "utf8" }).trim();
|
|
510
|
+
return { pid: parseInt(pid), name };
|
|
511
|
+
} catch {
|
|
512
|
+
return { pid: parseInt(pid), name: "unknown" };
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
} catch {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ============================================================================
|
|
523
|
+
// PATH UTILITIES
|
|
524
|
+
// ============================================================================
|
|
525
|
+
|
|
526
|
+
function getProjectPath(inputPath) {
|
|
527
|
+
const result = validatePath(inputPath || process.cwd());
|
|
528
|
+
if (!result.valid) {
|
|
529
|
+
return process.cwd();
|
|
530
|
+
}
|
|
531
|
+
return result.path;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function getMcpServerPath() {
|
|
535
|
+
return path.join(__dirname, "..", "..", "mcp-server", "index.js");
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function mcpServerExists() {
|
|
539
|
+
const serverPath = getMcpServerPath();
|
|
540
|
+
return fs.existsSync(serverPath);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function getCliBinPath() {
|
|
544
|
+
return path.join(__dirname, "..", "vibecheck.js");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function getManifestPath() {
|
|
548
|
+
return path.join(__dirname, "..", "..", "mcp-server", "manifest.json");
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ============================================================================
|
|
552
|
+
// TOOL REGISTRY
|
|
553
|
+
// ============================================================================
|
|
554
|
+
const TOOL_CATEGORIES = {
|
|
555
|
+
setup: { name: "Setup", icon: "🔧", description: "Project initialization and health" },
|
|
556
|
+
analysis: { name: "Analysis", icon: "🔍", description: "Code analysis and auditing" },
|
|
557
|
+
truth: { name: "Truth", icon: "📜", description: "Ground truth and context generation" },
|
|
558
|
+
enforcement: { name: "Enforcement", icon: "🛡️", description: "Agent firewall and intent tracking" },
|
|
559
|
+
proof: { name: "Proof", icon: "✅", description: "Verification and evidence" },
|
|
560
|
+
automation: { name: "Automation", icon: "🤖", description: "CI/CD and deployment" },
|
|
561
|
+
output: { name: "Output", icon: "📦", description: "Reports and artifacts" },
|
|
562
|
+
config: { name: "Config", icon: "⚙️", description: "Configuration management" },
|
|
563
|
+
account: { name: "Account", icon: "👤", description: "Authentication and billing" },
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const CANONICAL_TOOLS = [
|
|
567
|
+
{ name: "vibecheck.link", cli: "link", category: "setup", tier: "free", description: "One-time project setup: config, contracts, scripts" },
|
|
568
|
+
{ name: "vibecheck.kickoff", cli: "kickoff", category: "setup", tier: "free", description: "Interactive guided onboarding experience" },
|
|
569
|
+
{ name: "vibecheck.doctor", cli: "doctor", category: "setup", tier: "free", description: "Environment, dependency, and configuration health check" },
|
|
570
|
+
{ name: "vibecheck.audit", cli: "audit", category: "analysis", tier: "free", description: "Analyze codebase for issues (routes, env, auth, security, dead code)" },
|
|
571
|
+
{ name: "vibecheck.forge", cli: "forge", category: "truth", tier: "free", description: "Generate IDE rules and AI context (.cursorrules, MDC, Copilot)" },
|
|
572
|
+
{ name: "vibecheck.shield", cli: "shield", category: "enforcement", tier: "free", description: "Agent Firewall - intercept, validate, enforce AI actions", proOptions: ["enforce", "lock", "install", "check"] },
|
|
573
|
+
{ name: "vibecheck.intent", cli: "intent", category: "enforcement", tier: "free", description: "Declare and manage AI session intent for enforcement" },
|
|
574
|
+
{ name: "vibecheck.ship", cli: "ship", category: "proof", tier: "pro", description: "Verdict engine - SHIP/WARN/BLOCK with evidence-backed decision" },
|
|
575
|
+
{ name: "vibecheck.fix", cli: "fix", category: "proof", tier: "pro", description: "AI-powered auto-fix for detected findings" },
|
|
576
|
+
{ name: "vibecheck.prove", cli: "prove", category: "proof", tier: "pro", description: "Full proof loop with runtime verification" },
|
|
577
|
+
{ name: "vibecheck.reality", cli: "reality", category: "proof", tier: "pro", description: "Browser-based runtime verification with auth boundary testing" },
|
|
578
|
+
{ name: "vibecheck.checkpoint", cli: "checkpoint", category: "proof", tier: "pro", description: "Compare baseline vs current state" },
|
|
579
|
+
{ name: "vibecheck.launch", cli: "launch", category: "automation", tier: "pro", description: "CI/CD enforcement - preflight checks and deploy gates" },
|
|
580
|
+
{ name: "vibecheck.packs", cli: "packs", category: "output", tier: "free", description: "Generate shareable artifact packs: reports, evidence, proof graphs" },
|
|
581
|
+
{ name: "vibecheck.seal", cli: "seal", category: "output", tier: "pro", description: "Generate verification seal/badge with cryptographic attestation" },
|
|
582
|
+
{ name: "vibecheck.safelist", cli: "safelist", category: "config", tier: "free", description: "Manage finding safelist for false positives" },
|
|
583
|
+
{ name: "vibecheck.auth", cli: "auth", category: "account", tier: "free", description: "Authentication management: login, logout, whoami" },
|
|
584
|
+
{ name: "vibecheck.approve", cli: "approve", category: "proof", tier: "pro", description: "Authority verdicts - PROCEED/STOP/DEFER with proofs" },
|
|
585
|
+
{ name: "vibecheck.polish", cli: "polish", category: "proof", tier: "pro", description: "Code polish and cleanup" },
|
|
586
|
+
{ name: "vibecheck.labs", cli: "labs", category: "setup", tier: "free", description: "Access experimental features" },
|
|
587
|
+
{ name: "vibecheck.health", cli: "health", category: "setup", tier: "free", description: "MCP server health check" },
|
|
588
|
+
{ name: "vibecheck.manifest", cli: "manifest", category: "setup", tier: "free", description: "Get tool manifest" },
|
|
589
|
+
];
|
|
590
|
+
|
|
591
|
+
// ============================================================================
|
|
592
|
+
// IDE CONFIGURATIONS
|
|
593
|
+
// ============================================================================
|
|
594
|
+
const IDE_CONFIGS = {
|
|
595
|
+
cursor: {
|
|
596
|
+
name: "Cursor",
|
|
597
|
+
icon: "⌘",
|
|
598
|
+
configPath: {
|
|
599
|
+
win32: path.join(os.homedir(), ".cursor", "mcp.json"),
|
|
600
|
+
darwin: path.join(os.homedir(), ".cursor", "mcp.json"),
|
|
601
|
+
linux: path.join(os.homedir(), ".cursor", "mcp.json"),
|
|
602
|
+
},
|
|
603
|
+
format: "json",
|
|
604
|
+
instructions: "Add to Cursor Settings → MCP, or create ~/.cursor/mcp.json",
|
|
605
|
+
},
|
|
606
|
+
windsurf: {
|
|
607
|
+
name: "Windsurf",
|
|
608
|
+
icon: "🏄",
|
|
609
|
+
configPath: { win32: null, darwin: null, linux: null },
|
|
610
|
+
format: "json",
|
|
611
|
+
instructions: "Open Command Palette → 'Windsurf: Open MCP Config'",
|
|
612
|
+
},
|
|
613
|
+
claude: {
|
|
614
|
+
name: "Claude Desktop",
|
|
615
|
+
icon: "🤖",
|
|
616
|
+
configPath: {
|
|
617
|
+
win32: path.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json"),
|
|
618
|
+
darwin: path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
619
|
+
linux: path.join(os.homedir(), ".config", "claude", "claude_desktop_config.json"),
|
|
620
|
+
},
|
|
621
|
+
format: "json",
|
|
622
|
+
instructions: "Edit claude_desktop_config.json in your Claude config directory",
|
|
623
|
+
},
|
|
624
|
+
vscode: {
|
|
625
|
+
name: "VS Code (Continue)",
|
|
626
|
+
icon: "💻",
|
|
627
|
+
configPath: {
|
|
628
|
+
win32: path.join(os.homedir(), ".continue", "config.json"),
|
|
629
|
+
darwin: path.join(os.homedir(), ".continue", "config.json"),
|
|
630
|
+
linux: path.join(os.homedir(), ".continue", "config.json"),
|
|
631
|
+
},
|
|
632
|
+
format: "continue",
|
|
633
|
+
instructions: "Add to ~/.continue/config.json if using Continue extension",
|
|
634
|
+
},
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// ============================================================================
|
|
638
|
+
// STRUCTURED ERROR RESPONSES
|
|
639
|
+
// ============================================================================
|
|
640
|
+
|
|
641
|
+
function createError(code, message, details = {}) {
|
|
642
|
+
return {
|
|
643
|
+
success: false,
|
|
644
|
+
error: {
|
|
645
|
+
code,
|
|
646
|
+
message,
|
|
647
|
+
...details,
|
|
648
|
+
},
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function createSuccess(data, meta = {}) {
|
|
653
|
+
return {
|
|
654
|
+
success: true,
|
|
655
|
+
data,
|
|
656
|
+
meta: {
|
|
657
|
+
version: VERSION,
|
|
658
|
+
timestamp: new Date().toISOString(),
|
|
659
|
+
...meta,
|
|
660
|
+
},
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// ============================================================================
|
|
665
|
+
// DOCTOR - Health Check with Self-Healing
|
|
666
|
+
// ============================================================================
|
|
667
|
+
|
|
668
|
+
async function runDoctor(opts, globalFlags) {
|
|
669
|
+
const json = isJsonMode(globalFlags);
|
|
670
|
+
const quiet = shouldSuppressOutput(globalFlags);
|
|
671
|
+
const checks = [];
|
|
672
|
+
const fixes = [];
|
|
673
|
+
const projectPath = getProjectPath(opts.path);
|
|
674
|
+
|
|
675
|
+
if (!quiet && !json) {
|
|
676
|
+
console.log(`\n${c.bold}${sym.wrench} VibeCheck MCP Doctor v${VERSION}${c.reset}\n`);
|
|
677
|
+
console.log(`${c.dim}Running comprehensive health checks...${c.reset}\n`);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Check 1: Node.js version
|
|
681
|
+
const nodeVersion = process.version;
|
|
682
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0]);
|
|
683
|
+
const nodeOk = nodeMajor >= 18;
|
|
684
|
+
checks.push({
|
|
685
|
+
id: "node-version",
|
|
686
|
+
name: "Node.js Version",
|
|
687
|
+
status: nodeOk ? "pass" : "fail",
|
|
688
|
+
current: nodeVersion,
|
|
689
|
+
required: ">=18.0.0",
|
|
690
|
+
message: nodeOk ? `Node.js ${nodeVersion}` : `Node.js ${nodeVersion} too old`,
|
|
691
|
+
fix: nodeOk ? null : "Install Node.js 18+ from https://nodejs.org",
|
|
692
|
+
critical: true,
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// Check 2: MCP Server exists
|
|
696
|
+
const serverPath = getMcpServerPath();
|
|
697
|
+
const serverExists = mcpServerExists();
|
|
698
|
+
checks.push({
|
|
699
|
+
id: "mcp-server",
|
|
700
|
+
name: "MCP Server",
|
|
701
|
+
status: serverExists ? "pass" : "fail",
|
|
702
|
+
path: serverPath,
|
|
703
|
+
message: serverExists ? "Server module found" : "Server module not found",
|
|
704
|
+
fix: serverExists ? null : "Run 'npm install' in vibecheck directory",
|
|
705
|
+
critical: true,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// Check 3: Port availability
|
|
709
|
+
const portAvailable = await isPortAvailable(DEFAULT_PORT);
|
|
710
|
+
let portMessage = portAvailable ? `Port ${DEFAULT_PORT} available` : `Port ${DEFAULT_PORT} in use`;
|
|
711
|
+
let portFix = null;
|
|
712
|
+
let portProcess = null;
|
|
713
|
+
|
|
714
|
+
if (!portAvailable) {
|
|
715
|
+
portProcess = findPortProcess(DEFAULT_PORT);
|
|
716
|
+
if (portProcess) {
|
|
717
|
+
portMessage = `Port ${DEFAULT_PORT} in use by ${portProcess.name} (PID: ${portProcess.pid})`;
|
|
718
|
+
portFix = `Kill process ${portProcess.pid} or use --port flag`;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
checks.push({
|
|
723
|
+
id: "port-available",
|
|
724
|
+
name: "Default Port",
|
|
725
|
+
status: portAvailable ? "pass" : "warn",
|
|
726
|
+
port: DEFAULT_PORT,
|
|
727
|
+
process: portProcess,
|
|
728
|
+
message: portMessage,
|
|
729
|
+
fix: portFix,
|
|
730
|
+
critical: false,
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// Check 4: Existing instance
|
|
734
|
+
const existingInstance = checkExistingInstance(projectPath);
|
|
735
|
+
checks.push({
|
|
736
|
+
id: "no-existing-instance",
|
|
737
|
+
name: "No Existing Instance",
|
|
738
|
+
status: existingInstance.running ? "warn" : "pass",
|
|
739
|
+
message: existingInstance.running
|
|
740
|
+
? `Server running (PID: ${existingInstance.pid})`
|
|
741
|
+
: existingInstance.wasStale ? "Cleaned stale lock" : "No instance running",
|
|
742
|
+
pid: existingInstance.pid,
|
|
743
|
+
fix: existingInstance.running ? `Stop existing server or use different port` : null,
|
|
744
|
+
critical: false,
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
// Check 5: Project config
|
|
748
|
+
const configPaths = [
|
|
749
|
+
path.join(projectPath, ".vibecheckrc"),
|
|
750
|
+
path.join(projectPath, "vibecheck.config.json"),
|
|
751
|
+
path.join(projectPath, ".vibecheck", "config.json"),
|
|
752
|
+
];
|
|
753
|
+
const configExists = configPaths.some(p => fs.existsSync(p));
|
|
754
|
+
const configPath = configPaths.find(p => fs.existsSync(p));
|
|
755
|
+
checks.push({
|
|
756
|
+
id: "project-config",
|
|
757
|
+
name: "Project Config",
|
|
758
|
+
status: configExists ? "pass" : "warn",
|
|
759
|
+
path: configPath || projectPath,
|
|
760
|
+
message: configExists ? `Config found: ${path.basename(configPath)}` : "No config (run 'vibecheck link')",
|
|
761
|
+
fix: configExists ? null : "Run 'vibecheck link' to initialize",
|
|
762
|
+
critical: false,
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
// Check 6: Environment variables
|
|
766
|
+
const apiKey = process.env.VIBECHECK_API_KEY;
|
|
767
|
+
checks.push({
|
|
768
|
+
id: "api-key",
|
|
769
|
+
name: "API Key",
|
|
770
|
+
status: apiKey ? "pass" : "info",
|
|
771
|
+
message: apiKey ? "VIBECHECK_API_KEY configured" : "No API key (free tier)",
|
|
772
|
+
fix: apiKey ? null : "Set VIBECHECK_API_KEY for PRO features",
|
|
773
|
+
critical: false,
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
// Check 7: Write permissions
|
|
777
|
+
let canWrite = false;
|
|
778
|
+
try {
|
|
779
|
+
const testFile = path.join(projectPath, ".vibecheck", ".write-test");
|
|
780
|
+
const testDir = path.dirname(testFile);
|
|
781
|
+
if (!fs.existsSync(testDir)) {
|
|
782
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
783
|
+
}
|
|
784
|
+
fs.writeFileSync(testFile, "test");
|
|
785
|
+
fs.unlinkSync(testFile);
|
|
786
|
+
canWrite = true;
|
|
787
|
+
} catch {
|
|
788
|
+
canWrite = false;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
checks.push({
|
|
792
|
+
id: "write-permissions",
|
|
793
|
+
name: "Write Permissions",
|
|
794
|
+
status: canWrite ? "pass" : "fail",
|
|
795
|
+
path: path.join(projectPath, ".vibecheck"),
|
|
796
|
+
message: canWrite ? "Can write to .vibecheck/" : "Cannot write to project directory",
|
|
797
|
+
fix: canWrite ? null : "Check directory permissions",
|
|
798
|
+
critical: true,
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// Check 8: IDE integrations
|
|
802
|
+
const platform = process.platform;
|
|
803
|
+
const ideChecks = [];
|
|
804
|
+
|
|
805
|
+
for (const [ide, config] of Object.entries(IDE_CONFIGS)) {
|
|
806
|
+
const configPath = config.configPath[platform];
|
|
807
|
+
if (configPath) {
|
|
808
|
+
const exists = fs.existsSync(configPath);
|
|
809
|
+
let hasVibecheck = false;
|
|
810
|
+
let parseError = null;
|
|
811
|
+
|
|
812
|
+
if (exists) {
|
|
813
|
+
try {
|
|
814
|
+
const content = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
815
|
+
hasVibecheck = !!(content.mcpServers?.vibecheck || content.mcpServers?.["vibecheck-local"]);
|
|
816
|
+
} catch (err) {
|
|
817
|
+
parseError = err.message;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
ideChecks.push({
|
|
822
|
+
ide: config.name,
|
|
823
|
+
key: ide,
|
|
824
|
+
configExists: exists,
|
|
825
|
+
vibecheckConfigured: hasVibecheck,
|
|
826
|
+
parseError,
|
|
827
|
+
path: configPath,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const configuredIdes = ideChecks.filter(c => c.vibecheckConfigured);
|
|
833
|
+
checks.push({
|
|
834
|
+
id: "ide-integration",
|
|
835
|
+
name: "IDE Integration",
|
|
836
|
+
status: configuredIdes.length > 0 ? "pass" : "warn",
|
|
837
|
+
message: configuredIdes.length > 0
|
|
838
|
+
? `Configured: ${configuredIdes.map(c => c.ide).join(", ")}`
|
|
839
|
+
: "No IDE configured",
|
|
840
|
+
fix: configuredIdes.length > 0 ? null : "Run 'vibecheck mcp config --ide cursor --write'",
|
|
841
|
+
details: ideChecks,
|
|
842
|
+
critical: false,
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
// Check 9: Manifest file
|
|
846
|
+
const manifestPath = getManifestPath();
|
|
847
|
+
const manifestExists = fs.existsSync(manifestPath);
|
|
848
|
+
checks.push({
|
|
849
|
+
id: "manifest",
|
|
850
|
+
name: "Tool Manifest",
|
|
851
|
+
status: manifestExists ? "pass" : "warn",
|
|
852
|
+
path: manifestPath,
|
|
853
|
+
message: manifestExists ? "Manifest available" : "Manifest not found",
|
|
854
|
+
fix: manifestExists ? null : "Manifest will be generated on server start",
|
|
855
|
+
critical: false,
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
// Self-healing
|
|
859
|
+
if (opts.fix) {
|
|
860
|
+
for (const check of checks) {
|
|
861
|
+
if (check.status !== "pass" && check.fix && !check.critical) {
|
|
862
|
+
// Auto-fix: Create project config
|
|
863
|
+
if (check.id === "project-config" && !configExists) {
|
|
864
|
+
try {
|
|
865
|
+
const configDir = path.join(projectPath, ".vibecheck");
|
|
866
|
+
if (!fs.existsSync(configDir)) {
|
|
867
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
868
|
+
}
|
|
869
|
+
const rcPath = path.join(projectPath, ".vibecheckrc");
|
|
870
|
+
fs.writeFileSync(rcPath, JSON.stringify({
|
|
871
|
+
version: "1.0.0",
|
|
872
|
+
created: new Date().toISOString(),
|
|
873
|
+
mcp: { autoStart: false },
|
|
874
|
+
}, null, 2));
|
|
875
|
+
fixes.push({ id: check.id, action: "Created .vibecheckrc" });
|
|
876
|
+
check.status = "fixed";
|
|
877
|
+
} catch (err) {
|
|
878
|
+
fixes.push({ id: check.id, action: `Failed: ${err.message}`, success: false });
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Auto-fix: Remove stale lock
|
|
883
|
+
if (check.id === "no-existing-instance" && existingInstance.wasStale) {
|
|
884
|
+
fixes.push({ id: check.id, action: "Removed stale lock file" });
|
|
885
|
+
check.status = "fixed";
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Calculate summary
|
|
892
|
+
const summary = {
|
|
893
|
+
pass: checks.filter(c => c.status === "pass").length,
|
|
894
|
+
warn: checks.filter(c => c.status === "warn").length,
|
|
895
|
+
fail: checks.filter(c => c.status === "fail").length,
|
|
896
|
+
info: checks.filter(c => c.status === "info").length,
|
|
897
|
+
fixed: checks.filter(c => c.status === "fixed").length,
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
const healthy = summary.fail === 0;
|
|
901
|
+
const hasWarnings = summary.warn > 0;
|
|
902
|
+
const criticalFail = checks.some(c => c.status === "fail" && c.critical);
|
|
903
|
+
|
|
904
|
+
// Output
|
|
905
|
+
if (json) {
|
|
906
|
+
console.log(JSON.stringify(createSuccess({
|
|
907
|
+
healthy,
|
|
908
|
+
criticalFail,
|
|
909
|
+
checks,
|
|
910
|
+
fixes: fixes.length > 0 ? fixes : undefined,
|
|
911
|
+
summary,
|
|
912
|
+
}), null, 2));
|
|
913
|
+
return healthy ? EXIT.SUCCESS : EXIT.INTERNAL_ERROR;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Pretty output
|
|
917
|
+
for (const check of checks) {
|
|
918
|
+
const icon = check.status === "pass" ? `${c.green}${sym.check}${c.reset}` :
|
|
919
|
+
check.status === "fail" ? `${c.red}${sym.cross}${c.reset}` :
|
|
920
|
+
check.status === "warn" ? `${c.yellow}${sym.warn}${c.reset}` :
|
|
921
|
+
check.status === "fixed" ? `${c.cyan}${sym.wrench}${c.reset}` :
|
|
922
|
+
`${c.blue}${sym.info}${c.reset}`;
|
|
923
|
+
|
|
924
|
+
const critical = check.critical && check.status === "fail" ? ` ${c.red}(CRITICAL)${c.reset}` : "";
|
|
925
|
+
|
|
926
|
+
console.log(` ${icon} ${c.bold}${check.name}${c.reset}${critical}`);
|
|
927
|
+
console.log(` ${c.dim}${check.message}${c.reset}`);
|
|
928
|
+
|
|
929
|
+
if (check.fix && check.status !== "fixed" && check.status !== "pass") {
|
|
930
|
+
console.log(` ${c.yellow}${sym.arrow} ${check.fix}${c.reset}`);
|
|
931
|
+
}
|
|
932
|
+
console.log();
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// Summary
|
|
936
|
+
console.log(`${c.dim}${"─".repeat(55)}${c.reset}\n`);
|
|
937
|
+
|
|
938
|
+
if (healthy && !hasWarnings) {
|
|
939
|
+
console.log(`${c.green}${sym.heart} All checks passed!${c.reset}\n`);
|
|
940
|
+
console.log(`${c.dim}Your MCP setup is bulletproof. Start with:${c.reset}`);
|
|
941
|
+
console.log(` ${c.cyan}vibecheck mcp serve${c.reset}\n`);
|
|
942
|
+
} else if (criticalFail) {
|
|
943
|
+
console.log(`${c.red}${sym.cross} Critical issues found${c.reset}\n`);
|
|
944
|
+
console.log(`${c.dim}Fix critical issues before starting the server.${c.reset}\n`);
|
|
945
|
+
} else if (summary.fail > 0) {
|
|
946
|
+
console.log(`${c.red}${sym.cross} ${summary.fail} check(s) failed${c.reset}\n`);
|
|
947
|
+
console.log(`${c.dim}Fix issues above, then run:${c.reset}`);
|
|
948
|
+
console.log(` ${c.cyan}vibecheck mcp doctor --fix${c.reset}\n`);
|
|
949
|
+
} else {
|
|
950
|
+
console.log(`${c.yellow}${sym.warn} ${summary.warn} warning(s)${c.reset}\n`);
|
|
951
|
+
console.log(`${c.dim}Server will work, but consider fixing warnings.${c.reset}`);
|
|
952
|
+
console.log(` ${c.cyan}vibecheck mcp serve${c.reset}\n`);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (fixes.length > 0) {
|
|
956
|
+
console.log(`${c.cyan}${sym.wrench} Auto-fixed ${fixes.length} issue(s)${c.reset}\n`);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
return healthy ? EXIT.SUCCESS : (criticalFail ? ERROR_CODES.HEALTH_CHECK_FAILED : EXIT.SUCCESS);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// ============================================================================
|
|
963
|
+
// CONFIG - Generate IDE-specific Configurations
|
|
964
|
+
// ============================================================================
|
|
965
|
+
|
|
966
|
+
function generateConfig(ide, opts) {
|
|
967
|
+
const projectPath = getProjectPath(opts.path);
|
|
968
|
+
const mcpServerPath = getMcpServerPath();
|
|
969
|
+
const cliBinPath = getCliBinPath();
|
|
970
|
+
|
|
971
|
+
const useNpx = opts.npx || !fs.existsSync(mcpServerPath);
|
|
972
|
+
const useCli = opts.cli;
|
|
973
|
+
|
|
974
|
+
let command, args, cwd;
|
|
975
|
+
|
|
976
|
+
if (useNpx) {
|
|
977
|
+
command = "npx";
|
|
978
|
+
args = ["-y", "@vibecheck/mcp-server"];
|
|
979
|
+
} else if (useCli) {
|
|
980
|
+
command = "node";
|
|
981
|
+
args = [cliBinPath, "mcp", "serve"];
|
|
982
|
+
cwd = projectPath;
|
|
983
|
+
} else {
|
|
984
|
+
command = "node";
|
|
985
|
+
args = [mcpServerPath];
|
|
986
|
+
cwd = projectPath;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const env = {};
|
|
990
|
+
if (opts.apiKey || process.env.VIBECHECK_API_KEY) {
|
|
991
|
+
env.VIBECHECK_API_KEY = opts.apiKey || "${VIBECHECK_API_KEY}";
|
|
992
|
+
}
|
|
993
|
+
env.VIBECHECK_PROJECT_PATH = projectPath;
|
|
994
|
+
|
|
995
|
+
if (ide === "vscode") {
|
|
996
|
+
return {
|
|
997
|
+
mcpServers: [{
|
|
998
|
+
name: "vibecheck",
|
|
999
|
+
command,
|
|
1000
|
+
args,
|
|
1001
|
+
...(cwd ? { cwd } : {}),
|
|
1002
|
+
env: Object.keys(env).length > 0 ? env : undefined,
|
|
1003
|
+
}],
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
return {
|
|
1008
|
+
mcpServers: {
|
|
1009
|
+
vibecheck: {
|
|
1010
|
+
command,
|
|
1011
|
+
args,
|
|
1012
|
+
...(cwd ? { cwd } : {}),
|
|
1013
|
+
env: Object.keys(env).length > 0 ? env : undefined,
|
|
1014
|
+
},
|
|
1015
|
+
},
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
async function runConfig(opts, globalFlags) {
|
|
1020
|
+
const json = isJsonMode(globalFlags);
|
|
1021
|
+
const quiet = shouldSuppressOutput(globalFlags);
|
|
1022
|
+
|
|
1023
|
+
// Validate IDE
|
|
1024
|
+
const ideValidation = validateIde(opts.ide);
|
|
1025
|
+
if (!ideValidation.valid) {
|
|
1026
|
+
if (json) {
|
|
1027
|
+
console.log(JSON.stringify(createError(ideValidation.code, ideValidation.error)));
|
|
1028
|
+
} else {
|
|
1029
|
+
console.error(`${c.red}${sym.cross} ${ideValidation.error}${c.reset}`);
|
|
1030
|
+
}
|
|
1031
|
+
return ERROR_CODES.VALIDATION_ERROR;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const ide = ideValidation.ide;
|
|
1035
|
+
|
|
1036
|
+
if (!quiet && !json) {
|
|
1037
|
+
console.log(`\n${c.bold}${sym.plug} VibeCheck MCP Configuration${c.reset}\n`);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
const configs = {};
|
|
1041
|
+
const ides = ide === "all" ? Object.keys(IDE_CONFIGS) : [ide];
|
|
1042
|
+
|
|
1043
|
+
for (const ideKey of ides) {
|
|
1044
|
+
const ideConfig = IDE_CONFIGS[ideKey];
|
|
1045
|
+
if (!ideConfig) continue;
|
|
1046
|
+
|
|
1047
|
+
const config = generateConfig(ideKey, opts);
|
|
1048
|
+
configs[ideKey] = config;
|
|
1049
|
+
|
|
1050
|
+
if (!json && !quiet) {
|
|
1051
|
+
const configPath = ideConfig.configPath[process.platform];
|
|
1052
|
+
|
|
1053
|
+
console.log(`${c.cyan}${ideConfig.icon} ${ideConfig.name}${c.reset}`);
|
|
1054
|
+
console.log(`${c.dim}${ideConfig.instructions}${c.reset}`);
|
|
1055
|
+
if (configPath) {
|
|
1056
|
+
console.log(`${c.dim}Path: ${configPath}${c.reset}`);
|
|
1057
|
+
}
|
|
1058
|
+
console.log();
|
|
1059
|
+
console.log(`${c.green}┌${"─".repeat(53)}┐${c.reset}`);
|
|
1060
|
+
const configLines = JSON.stringify(config, null, 2).split("\n");
|
|
1061
|
+
for (const line of configLines) {
|
|
1062
|
+
console.log(`${c.green}│${c.reset} ${line}`);
|
|
1063
|
+
}
|
|
1064
|
+
console.log(`${c.green}└${"─".repeat(53)}┘${c.reset}`);
|
|
1065
|
+
console.log();
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (json) {
|
|
1070
|
+
console.log(JSON.stringify(createSuccess(ide === "all" ? configs : configs[ide])));
|
|
1071
|
+
return EXIT.SUCCESS;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (!quiet) {
|
|
1075
|
+
console.log(`${c.dim}${"─".repeat(55)}${c.reset}\n`);
|
|
1076
|
+
console.log(`${c.bold}Quick Setup:${c.reset}`);
|
|
1077
|
+
console.log(` 1. Copy the config above to your IDE's MCP settings`);
|
|
1078
|
+
console.log(` 2. Restart your IDE`);
|
|
1079
|
+
console.log(` 3. Ask: "${c.cyan}What vibecheck tools are available?${c.reset}"\n`);
|
|
1080
|
+
|
|
1081
|
+
// Auto-write
|
|
1082
|
+
if (opts.write && ide !== "all") {
|
|
1083
|
+
const ideConfig = IDE_CONFIGS[ide];
|
|
1084
|
+
const configPath = ideConfig?.configPath[process.platform];
|
|
1085
|
+
|
|
1086
|
+
if (configPath) {
|
|
1087
|
+
try {
|
|
1088
|
+
const dir = path.dirname(configPath);
|
|
1089
|
+
if (!fs.existsSync(dir)) {
|
|
1090
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
let existingConfig = {};
|
|
1094
|
+
if (fs.existsSync(configPath)) {
|
|
1095
|
+
try {
|
|
1096
|
+
existingConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
1097
|
+
} catch {
|
|
1098
|
+
// Start fresh
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
const newConfig = generateConfig(ide, opts);
|
|
1103
|
+
const merged = {
|
|
1104
|
+
...existingConfig,
|
|
1105
|
+
mcpServers: {
|
|
1106
|
+
...(existingConfig.mcpServers || {}),
|
|
1107
|
+
...(newConfig.mcpServers || {}),
|
|
1108
|
+
},
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
fs.writeFileSync(configPath, JSON.stringify(merged, null, 2));
|
|
1112
|
+
console.log(`${c.green}${sym.check} Config written to: ${configPath}${c.reset}\n`);
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
console.log(`${c.red}${sym.cross} Write failed: ${err.message}${c.reset}\n`);
|
|
1115
|
+
return ERROR_CODES.PERMISSION_DENIED;
|
|
1116
|
+
}
|
|
1117
|
+
} else {
|
|
1118
|
+
console.log(`${c.yellow}${sym.warn} No config path for ${ide} on ${process.platform}${c.reset}\n`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return EXIT.SUCCESS;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// ============================================================================
|
|
1127
|
+
// MANIFEST - Machine-readable Tool Manifest
|
|
1128
|
+
// ============================================================================
|
|
1129
|
+
|
|
1130
|
+
async function runManifest(opts, globalFlags) {
|
|
1131
|
+
const json = isJsonMode(globalFlags) || opts.format === "json";
|
|
1132
|
+
const format = opts.format || "json";
|
|
1133
|
+
|
|
1134
|
+
// Try to read from file first
|
|
1135
|
+
const manifestPath = getManifestPath();
|
|
1136
|
+
let manifest;
|
|
1137
|
+
|
|
1138
|
+
if (fs.existsSync(manifestPath)) {
|
|
1139
|
+
try {
|
|
1140
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
1141
|
+
} catch {
|
|
1142
|
+
// Fall back to generated
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (!manifest) {
|
|
1147
|
+
manifest = {
|
|
1148
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1149
|
+
name: "vibecheck-mcp",
|
|
1150
|
+
version: VERSION,
|
|
1151
|
+
description: "VibeCheck MCP Server - AI-powered code verification",
|
|
1152
|
+
tools: CANONICAL_TOOLS,
|
|
1153
|
+
categories: TOOL_CATEGORIES,
|
|
1154
|
+
generatedAt: new Date().toISOString(),
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
if (format === "summary") {
|
|
1159
|
+
const output = {
|
|
1160
|
+
name: manifest.name,
|
|
1161
|
+
version: manifest.version,
|
|
1162
|
+
toolCount: manifest.tools?.length || CANONICAL_TOOLS.length,
|
|
1163
|
+
categories: Object.keys(manifest.categories || TOOL_CATEGORIES),
|
|
1164
|
+
tiers: {
|
|
1165
|
+
free: (manifest.tools || CANONICAL_TOOLS).filter(t => t.tier === "free").length,
|
|
1166
|
+
pro: (manifest.tools || CANONICAL_TOOLS).filter(t => t.tier === "pro").length,
|
|
1167
|
+
},
|
|
1168
|
+
};
|
|
1169
|
+
console.log(JSON.stringify(createSuccess(output), null, 2));
|
|
1170
|
+
} else {
|
|
1171
|
+
console.log(JSON.stringify(json ? createSuccess(manifest) : manifest, null, 2));
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
return EXIT.SUCCESS;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// ============================================================================
|
|
1178
|
+
// SERVE - Start the MCP Server (HARDENED)
|
|
1179
|
+
// ============================================================================
|
|
1180
|
+
|
|
1181
|
+
async function runServe(opts, globalFlags) {
|
|
1182
|
+
const json = isJsonMode(globalFlags);
|
|
1183
|
+
const quiet = shouldSuppressOutput(globalFlags);
|
|
1184
|
+
const projectPath = getProjectPath(opts.path);
|
|
1185
|
+
|
|
1186
|
+
// Initialize logging
|
|
1187
|
+
if (opts.debug) {
|
|
1188
|
+
setLogLevel("debug");
|
|
1189
|
+
}
|
|
1190
|
+
initLogFile(projectPath);
|
|
1191
|
+
|
|
1192
|
+
log("info", "Starting MCP server", { projectPath, version: VERSION });
|
|
1193
|
+
|
|
1194
|
+
// Validate server exists
|
|
1195
|
+
const serverPath = getMcpServerPath();
|
|
1196
|
+
if (!fs.existsSync(serverPath)) {
|
|
1197
|
+
const error = createError(ERROR_CODES.SERVER_NOT_FOUND, "MCP server not found", { path: serverPath });
|
|
1198
|
+
if (json) {
|
|
1199
|
+
console.log(JSON.stringify(error));
|
|
1200
|
+
} else {
|
|
1201
|
+
console.error(`${c.red}${sym.cross} MCP server not found: ${serverPath}${c.reset}`);
|
|
1202
|
+
console.error(`${c.dim}Run 'npm install' in vibecheck directory${c.reset}`);
|
|
1203
|
+
}
|
|
1204
|
+
log("error", "Server not found", { serverPath });
|
|
1205
|
+
return ERROR_CODES.SERVER_NOT_FOUND;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Check for existing instance
|
|
1209
|
+
const existingInstance = checkExistingInstance(projectPath);
|
|
1210
|
+
if (existingInstance.running) {
|
|
1211
|
+
if (opts.force) {
|
|
1212
|
+
log("warn", "Force-starting despite existing instance", { pid: existingInstance.pid });
|
|
1213
|
+
try {
|
|
1214
|
+
process.kill(existingInstance.pid, "SIGTERM");
|
|
1215
|
+
await sleep(1000);
|
|
1216
|
+
} catch {
|
|
1217
|
+
// Process may already be dead
|
|
1218
|
+
}
|
|
1219
|
+
removeLockFile(projectPath);
|
|
1220
|
+
} else {
|
|
1221
|
+
const error = createError(ERROR_CODES.ALREADY_RUNNING, existingInstance.message, {
|
|
1222
|
+
pid: existingInstance.pid,
|
|
1223
|
+
port: existingInstance.port,
|
|
1224
|
+
});
|
|
1225
|
+
if (json) {
|
|
1226
|
+
console.log(JSON.stringify(error));
|
|
1227
|
+
} else {
|
|
1228
|
+
console.error(`${c.red}${sym.lock} ${existingInstance.message}${c.reset}`);
|
|
1229
|
+
console.error(`${c.dim}Use --force to override${c.reset}`);
|
|
1230
|
+
}
|
|
1231
|
+
return ERROR_CODES.ALREADY_RUNNING;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// Port handling with retry
|
|
1236
|
+
let port = opts.port || DEFAULT_PORT;
|
|
1237
|
+
let portInfo = null;
|
|
1238
|
+
|
|
1239
|
+
const portResult = await withRetry(
|
|
1240
|
+
async (attempt) => {
|
|
1241
|
+
if (await isPortAvailable(port)) {
|
|
1242
|
+
return { port, available: true };
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
if (opts.autoPort !== false) {
|
|
1246
|
+
const available = await findAvailablePort(port + 1);
|
|
1247
|
+
if (available.port) {
|
|
1248
|
+
return { port: available.port, available: true, source: available.source };
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
throw new Error(`Port ${port} unavailable`);
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
maxAttempts: 2,
|
|
1256
|
+
onRetry: (err, attempt) => {
|
|
1257
|
+
log("debug", `Port retry ${attempt + 1}`, { error: err.message });
|
|
1258
|
+
},
|
|
1259
|
+
}
|
|
1260
|
+
).catch(err => ({ available: false, error: err.message }));
|
|
1261
|
+
|
|
1262
|
+
if (!portResult.available) {
|
|
1263
|
+
portInfo = findPortProcess(port);
|
|
1264
|
+
const error = createError(ERROR_CODES.PORT_UNAVAILABLE, `No available ports`, {
|
|
1265
|
+
requestedPort: port,
|
|
1266
|
+
blockedBy: portInfo,
|
|
1267
|
+
});
|
|
1268
|
+
if (json) {
|
|
1269
|
+
console.log(JSON.stringify(error));
|
|
1270
|
+
} else {
|
|
1271
|
+
console.error(`${c.red}${sym.cross} Port ${port} unavailable${portInfo ? ` (${portInfo.name})` : ""}${c.reset}`);
|
|
1272
|
+
}
|
|
1273
|
+
log("error", "No available ports", { port, portInfo });
|
|
1274
|
+
return ERROR_CODES.PORT_UNAVAILABLE;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
if (portResult.port !== port) {
|
|
1278
|
+
if (!quiet && !json) {
|
|
1279
|
+
console.log(`${c.yellow}${sym.warn} Port ${port} in use, using ${portResult.port}${c.reset}\n`);
|
|
1280
|
+
}
|
|
1281
|
+
log("info", "Using alternate port", { requested: port, actual: portResult.port });
|
|
1282
|
+
port = portResult.port;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// Banner
|
|
1286
|
+
if (!quiet && !json) {
|
|
1287
|
+
console.log(`
|
|
1288
|
+
${c.cyan}${c.bold}╔════════════════════════════════════════════════════════════════╗
|
|
1289
|
+
║ ║
|
|
1290
|
+
║ ${sym.brain} VibeCheck MCP Server v${VERSION} ║
|
|
1291
|
+
║ Your IDE is now VibeCheck-powered ║
|
|
1292
|
+
║ ║
|
|
1293
|
+
║ ${c.dim}Hardened Build - Production Ready${c.reset}${c.cyan}${c.bold} ║
|
|
1294
|
+
║ ║
|
|
1295
|
+
╚════════════════════════════════════════════════════════════════╝${c.reset}
|
|
1296
|
+
`);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// Environment setup
|
|
1300
|
+
const env = {
|
|
1301
|
+
...process.env,
|
|
1302
|
+
VIBECHECK_PROJECT_PATH: projectPath,
|
|
1303
|
+
VIBECHECK_MCP_PORT: String(port),
|
|
1304
|
+
VIBECHECK_MCP_VERSION: VERSION,
|
|
1305
|
+
NODE_ENV: process.env.NODE_ENV || "production",
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
if (opts.debug) {
|
|
1309
|
+
env.VIBECHECK_DEBUG = "true";
|
|
1310
|
+
env.DEBUG = "vibecheck:*";
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Spawn server process
|
|
1314
|
+
const server = spawn("node", [serverPath], {
|
|
1315
|
+
cwd: projectPath,
|
|
1316
|
+
env,
|
|
1317
|
+
stdio: opts.stdio ? "inherit" : ["pipe", "pipe", "pipe"],
|
|
1318
|
+
detached: false,
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
if (!server.pid) {
|
|
1322
|
+
const error = createError(ERROR_CODES.STARTUP_FAILED, "Failed to spawn server process");
|
|
1323
|
+
if (json) {
|
|
1324
|
+
console.log(JSON.stringify(error));
|
|
1325
|
+
} else {
|
|
1326
|
+
console.error(`${c.red}${sym.cross} Failed to start server${c.reset}`);
|
|
1327
|
+
}
|
|
1328
|
+
log("error", "Spawn failed");
|
|
1329
|
+
return ERROR_CODES.STARTUP_FAILED;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// Create lock file
|
|
1333
|
+
createLockFile(projectPath, server.pid, port);
|
|
1334
|
+
|
|
1335
|
+
// Track server state
|
|
1336
|
+
let serverReady = false;
|
|
1337
|
+
let serverError = null;
|
|
1338
|
+
let outputBuffer = "";
|
|
1339
|
+
|
|
1340
|
+
// Handle server output
|
|
1341
|
+
if (!opts.stdio) {
|
|
1342
|
+
server.stdout.on("data", (data) => {
|
|
1343
|
+
const text = data.toString();
|
|
1344
|
+
outputBuffer += text;
|
|
1345
|
+
|
|
1346
|
+
// Check for ready signal
|
|
1347
|
+
if (text.includes("Server running") || text.includes("MCP server started")) {
|
|
1348
|
+
serverReady = true;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
if (!quiet) {
|
|
1352
|
+
process.stdout.write(redactSecrets(text));
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
log("debug", "stdout", { data: redactSecrets(text.trim()) });
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
server.stderr.on("data", (data) => {
|
|
1359
|
+
const text = data.toString();
|
|
1360
|
+
|
|
1361
|
+
// Check for errors
|
|
1362
|
+
if (text.includes("Error") || text.includes("EADDRINUSE")) {
|
|
1363
|
+
serverError = text.trim();
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
if (!quiet) {
|
|
1367
|
+
process.stderr.write(redactSecrets(text));
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
log("warn", "stderr", { data: redactSecrets(text.trim()) });
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// Server error handler
|
|
1375
|
+
server.on("error", (err) => {
|
|
1376
|
+
serverError = err.message;
|
|
1377
|
+
log("error", "Server error", { error: err.message });
|
|
1378
|
+
|
|
1379
|
+
if (json) {
|
|
1380
|
+
console.log(JSON.stringify(createError(ERROR_CODES.STARTUP_FAILED, err.message)));
|
|
1381
|
+
} else {
|
|
1382
|
+
console.error(`${c.red}${sym.cross} Server error: ${err.message}${c.reset}`);
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
// Wait for server startup with timeout
|
|
1387
|
+
const startupResult = await Promise.race([
|
|
1388
|
+
new Promise((resolve) => {
|
|
1389
|
+
const checkInterval = setInterval(() => {
|
|
1390
|
+
if (serverReady) {
|
|
1391
|
+
clearInterval(checkInterval);
|
|
1392
|
+
resolve({ ready: true });
|
|
1393
|
+
}
|
|
1394
|
+
if (serverError) {
|
|
1395
|
+
clearInterval(checkInterval);
|
|
1396
|
+
resolve({ ready: false, error: serverError });
|
|
1397
|
+
}
|
|
1398
|
+
}, 100);
|
|
1399
|
+
}),
|
|
1400
|
+
sleep(TIMEOUTS.STARTUP).then(() => ({ ready: false, error: "Startup timeout" })),
|
|
1401
|
+
]);
|
|
1402
|
+
|
|
1403
|
+
if (!startupResult.ready && !opts.stdio) {
|
|
1404
|
+
// Server didn't signal ready, but may still be running
|
|
1405
|
+
// Check if process is alive
|
|
1406
|
+
if (!isProcessRunning(server.pid)) {
|
|
1407
|
+
removeLockFile(projectPath);
|
|
1408
|
+
const error = createError(ERROR_CODES.STARTUP_FAILED, startupResult.error || "Server exited unexpectedly");
|
|
1409
|
+
if (json) {
|
|
1410
|
+
console.log(JSON.stringify(error));
|
|
1411
|
+
}
|
|
1412
|
+
return ERROR_CODES.STARTUP_FAILED;
|
|
1413
|
+
}
|
|
1414
|
+
// Process is running, assume it's okay
|
|
1415
|
+
serverReady = true;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Success output
|
|
1419
|
+
if (!quiet && !json) {
|
|
1420
|
+
console.log(`${c.green}${sym.rocket} Server started successfully${c.reset}`);
|
|
1421
|
+
console.log(`${c.dim}├─ PID: ${server.pid}${c.reset}`);
|
|
1422
|
+
console.log(`${c.dim}├─ Port: ${port}${c.reset}`);
|
|
1423
|
+
console.log(`${c.dim}├─ Project: ${projectPath}${c.reset}`);
|
|
1424
|
+
console.log(`${c.dim}└─ Lock: ${getLockFilePath(projectPath)}${c.reset}`);
|
|
1425
|
+
console.log();
|
|
1426
|
+
|
|
1427
|
+
// Tool summary
|
|
1428
|
+
const freeTools = CANONICAL_TOOLS.filter(t => t.tier === "free");
|
|
1429
|
+
const proTools = CANONICAL_TOOLS.filter(t => t.tier === "pro");
|
|
1430
|
+
|
|
1431
|
+
console.log(`${c.bold}Available Tools (${CANONICAL_TOOLS.length}):${c.reset}`);
|
|
1432
|
+
console.log(` ${c.green}FREE${c.reset} (${freeTools.length}): ${freeTools.slice(0, 6).map(t => t.cli).join(", ")}...`);
|
|
1433
|
+
console.log(` ${c.magenta}PRO${c.reset} (${proTools.length}): ${proTools.slice(0, 6).map(t => t.cli).join(", ")}...`);
|
|
1434
|
+
console.log();
|
|
1435
|
+
|
|
1436
|
+
console.log(`${c.dim}${"─".repeat(60)}${c.reset}`);
|
|
1437
|
+
console.log(`${c.dim}Press Ctrl+C for graceful shutdown${c.reset}`);
|
|
1438
|
+
console.log(`${c.dim}${"─".repeat(60)}${c.reset}\n`);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
log("info", "Server started", { pid: server.pid, port });
|
|
1442
|
+
|
|
1443
|
+
// Graceful shutdown handler
|
|
1444
|
+
let shuttingDown = false;
|
|
1445
|
+
|
|
1446
|
+
const cleanup = async (signal) => {
|
|
1447
|
+
if (shuttingDown) return;
|
|
1448
|
+
shuttingDown = true;
|
|
1449
|
+
|
|
1450
|
+
log("info", "Shutdown initiated", { signal });
|
|
1451
|
+
|
|
1452
|
+
if (!quiet && !json) {
|
|
1453
|
+
console.log(`\n${c.dim}${sym.timer} Shutting down gracefully...${c.reset}`);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// Send SIGTERM first
|
|
1457
|
+
server.kill("SIGTERM");
|
|
1458
|
+
|
|
1459
|
+
// Wait for graceful shutdown
|
|
1460
|
+
const shutdownResult = await Promise.race([
|
|
1461
|
+
new Promise((resolve) => {
|
|
1462
|
+
server.once("close", () => resolve({ clean: true }));
|
|
1463
|
+
}),
|
|
1464
|
+
sleep(TIMEOUTS.SHUTDOWN).then(() => ({ clean: false })),
|
|
1465
|
+
]);
|
|
1466
|
+
|
|
1467
|
+
if (!shutdownResult.clean) {
|
|
1468
|
+
log("warn", "Force killing server");
|
|
1469
|
+
server.kill("SIGKILL");
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Cleanup
|
|
1473
|
+
removeLockFile(projectPath);
|
|
1474
|
+
|
|
1475
|
+
if (logFile) {
|
|
1476
|
+
logFile.end();
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
if (!quiet && !json) {
|
|
1480
|
+
console.log(`${c.green}${sym.check} Server stopped${c.reset}\n`);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
log("info", "Server stopped", { clean: shutdownResult.clean });
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1486
|
+
// Register signal handlers
|
|
1487
|
+
process.on("SIGINT", () => cleanup("SIGINT").then(() => process.exit(0)));
|
|
1488
|
+
process.on("SIGTERM", () => cleanup("SIGTERM").then(() => process.exit(0)));
|
|
1489
|
+
|
|
1490
|
+
// Handle uncaught errors
|
|
1491
|
+
process.on("uncaughtException", (err) => {
|
|
1492
|
+
log("error", "Uncaught exception", { error: err.message, stack: err.stack });
|
|
1493
|
+
cleanup("uncaughtException").then(() => process.exit(1));
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
// Server close handler
|
|
1497
|
+
server.on("close", (code, signal) => {
|
|
1498
|
+
if (!shuttingDown) {
|
|
1499
|
+
log("warn", "Server exited unexpectedly", { code, signal });
|
|
1500
|
+
removeLockFile(projectPath);
|
|
1501
|
+
|
|
1502
|
+
if (!quiet && !json) {
|
|
1503
|
+
if (code === 0) {
|
|
1504
|
+
console.log(`\n${c.green}${sym.check} Server exited cleanly${c.reset}`);
|
|
1505
|
+
} else {
|
|
1506
|
+
console.log(`\n${c.yellow}${sym.warn} Server exited (code: ${code}, signal: ${signal})${c.reset}`);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
// Keep process alive
|
|
1513
|
+
return new Promise((resolve) => {
|
|
1514
|
+
server.on("close", (code) => {
|
|
1515
|
+
resolve(code === 0 ? EXIT.SUCCESS : ERROR_CODES.INTERNAL_ERROR);
|
|
1516
|
+
});
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// ============================================================================
|
|
1521
|
+
// STATUS - Quick Status Check
|
|
1522
|
+
// ============================================================================
|
|
1523
|
+
|
|
1524
|
+
async function runStatus(opts, globalFlags) {
|
|
1525
|
+
const json = isJsonMode(globalFlags);
|
|
1526
|
+
const quiet = shouldSuppressOutput(globalFlags);
|
|
1527
|
+
const projectPath = getProjectPath(opts.path);
|
|
1528
|
+
|
|
1529
|
+
const status = {
|
|
1530
|
+
version: VERSION,
|
|
1531
|
+
node: process.version,
|
|
1532
|
+
platform: process.platform,
|
|
1533
|
+
arch: process.arch,
|
|
1534
|
+
projectPath,
|
|
1535
|
+
serverPath: getMcpServerPath(),
|
|
1536
|
+
serverExists: mcpServerExists(),
|
|
1537
|
+
manifestPath: getManifestPath(),
|
|
1538
|
+
manifestExists: fs.existsSync(getManifestPath()),
|
|
1539
|
+
lockFile: getLockFilePath(projectPath),
|
|
1540
|
+
defaultPort: DEFAULT_PORT,
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
// Check running instance
|
|
1544
|
+
const instance = checkExistingInstance(projectPath);
|
|
1545
|
+
status.running = instance.running;
|
|
1546
|
+
if (instance.running) {
|
|
1547
|
+
status.pid = instance.pid;
|
|
1548
|
+
status.runningPort = instance.port;
|
|
1549
|
+
status.startedAt = instance.startedAt;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// Check port
|
|
1553
|
+
status.portAvailable = await isPortAvailable(DEFAULT_PORT);
|
|
1554
|
+
if (!status.portAvailable) {
|
|
1555
|
+
const proc = findPortProcess(DEFAULT_PORT);
|
|
1556
|
+
if (proc) {
|
|
1557
|
+
status.portBlockedBy = proc;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
// Check config
|
|
1562
|
+
const configPaths = [
|
|
1563
|
+
path.join(projectPath, ".vibecheckrc"),
|
|
1564
|
+
path.join(projectPath, "vibecheck.config.json"),
|
|
1565
|
+
];
|
|
1566
|
+
status.projectConfigured = configPaths.some(p => fs.existsSync(p));
|
|
1567
|
+
status.apiKeySet = !!process.env.VIBECHECK_API_KEY;
|
|
1568
|
+
|
|
1569
|
+
if (json) {
|
|
1570
|
+
console.log(JSON.stringify(createSuccess(status), null, 2));
|
|
1571
|
+
return EXIT.SUCCESS;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
if (!quiet) {
|
|
1575
|
+
console.log(`\n${c.bold}${sym.info} VibeCheck MCP Status${c.reset}\n`);
|
|
1576
|
+
|
|
1577
|
+
const check = (v) => v ? `${c.green}${sym.check}${c.reset}` : `${c.red}${sym.cross}${c.reset}`;
|
|
1578
|
+
const warn = (v) => v ? `${c.green}${sym.check}${c.reset}` : `${c.yellow}${sym.warn}${c.reset}`;
|
|
1579
|
+
|
|
1580
|
+
console.log(` ${c.dim}Version:${c.reset} ${status.version}`);
|
|
1581
|
+
console.log(` ${c.dim}Node.js:${c.reset} ${status.node}`);
|
|
1582
|
+
console.log(` ${c.dim}Platform:${c.reset} ${status.platform}/${status.arch}`);
|
|
1583
|
+
console.log();
|
|
1584
|
+
console.log(` ${c.dim}Server:${c.reset} ${check(status.serverExists)} ${status.serverExists ? "Found" : "Not found"}`);
|
|
1585
|
+
console.log(` ${c.dim}Running:${c.reset} ${status.running ? `${c.green}Yes${c.reset} (PID: ${status.pid})` : `${c.dim}No${c.reset}`}`);
|
|
1586
|
+
console.log(` ${c.dim}Port ${DEFAULT_PORT}:${c.reset} ${warn(status.portAvailable)} ${status.portAvailable ? "Available" : `In use${status.portBlockedBy ? ` (${status.portBlockedBy.name})` : ""}`}`);
|
|
1587
|
+
console.log();
|
|
1588
|
+
console.log(` ${c.dim}Project:${c.reset} ${warn(status.projectConfigured)} ${status.projectConfigured ? "Configured" : "Not configured"}`);
|
|
1589
|
+
console.log(` ${c.dim}API Key:${c.reset} ${status.apiKeySet ? `${c.green}${sym.check}${c.reset} Set` : `${c.dim}Not set${c.reset}`}`);
|
|
1590
|
+
console.log();
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
return EXIT.SUCCESS;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// ============================================================================
|
|
1597
|
+
// TEST - Test MCP Connection
|
|
1598
|
+
// ============================================================================
|
|
1599
|
+
|
|
1600
|
+
async function runTest(opts, globalFlags) {
|
|
1601
|
+
const json = isJsonMode(globalFlags);
|
|
1602
|
+
const quiet = shouldSuppressOutput(globalFlags);
|
|
1603
|
+
const projectPath = getProjectPath(opts.path);
|
|
1604
|
+
|
|
1605
|
+
if (!quiet && !json) {
|
|
1606
|
+
console.log(`\n${c.bold}${sym.plug} Testing MCP Connection${c.reset}\n`);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const results = {
|
|
1610
|
+
serverExists: mcpServerExists(),
|
|
1611
|
+
manifestExists: fs.existsSync(getManifestPath()),
|
|
1612
|
+
nodeVersion: process.version,
|
|
1613
|
+
nodeMajor: parseInt(process.version.slice(1).split(".")[0]),
|
|
1614
|
+
};
|
|
1615
|
+
|
|
1616
|
+
results.nodeOk = results.nodeMajor >= 18;
|
|
1617
|
+
|
|
1618
|
+
// Check running instance
|
|
1619
|
+
const instance = checkExistingInstance(projectPath);
|
|
1620
|
+
results.instanceRunning = instance.running;
|
|
1621
|
+
|
|
1622
|
+
// Calculate overall status
|
|
1623
|
+
results.ready = results.serverExists && results.nodeOk;
|
|
1624
|
+
results.canStart = results.ready && !instance.running;
|
|
1625
|
+
|
|
1626
|
+
if (json) {
|
|
1627
|
+
console.log(JSON.stringify(createSuccess(results), null, 2));
|
|
1628
|
+
return results.ready ? EXIT.SUCCESS : ERROR_CODES.HEALTH_CHECK_FAILED;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
if (!quiet) {
|
|
1632
|
+
const check = (v) => v ? `${c.green}${sym.check}${c.reset}` : `${c.red}${sym.cross}${c.reset}`;
|
|
1633
|
+
|
|
1634
|
+
console.log(` ${check(results.nodeOk)} Node.js ${results.nodeVersion} ${results.nodeOk ? "" : "(need >=18)"}`);
|
|
1635
|
+
console.log(` ${check(results.serverExists)} MCP server module`);
|
|
1636
|
+
console.log(` ${check(results.manifestExists)} Tool manifest`);
|
|
1637
|
+
console.log(` ${check(!results.instanceRunning)} No existing instance`);
|
|
1638
|
+
console.log();
|
|
1639
|
+
|
|
1640
|
+
if (results.ready) {
|
|
1641
|
+
console.log(`${c.green}${sym.heart} Ready to start!${c.reset}`);
|
|
1642
|
+
console.log(`${c.dim}Run: vibecheck mcp serve${c.reset}\n`);
|
|
1643
|
+
} else {
|
|
1644
|
+
console.log(`${c.red}${sym.cross} Not ready - fix issues above${c.reset}\n`);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
return results.ready ? EXIT.SUCCESS : ERROR_CODES.HEALTH_CHECK_FAILED;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
// ============================================================================
|
|
1652
|
+
// HELP
|
|
1653
|
+
// ============================================================================
|
|
1654
|
+
|
|
1655
|
+
function printHelp() {
|
|
1656
|
+
console.log(`
|
|
1657
|
+
${c.cyan}${c.bold}vibecheck mcp${c.reset} - Hardened MCP Server for AI-Powered IDEs
|
|
1658
|
+
|
|
1659
|
+
${c.bold}VERSION${c.reset}
|
|
1660
|
+
${VERSION} (Hardened Build)
|
|
1661
|
+
|
|
1662
|
+
${c.bold}USAGE${c.reset}
|
|
1663
|
+
vibecheck mcp [command] [options]
|
|
1664
|
+
|
|
1665
|
+
${c.bold}COMMANDS${c.reset}
|
|
1666
|
+
${c.cyan}serve${c.reset} Start the MCP server (default)
|
|
1667
|
+
${c.cyan}doctor${c.reset} Comprehensive health check with auto-healing
|
|
1668
|
+
${c.cyan}config${c.reset} Generate IDE-specific connection configs
|
|
1669
|
+
${c.cyan}manifest${c.reset} Export machine-readable tool manifest
|
|
1670
|
+
${c.cyan}status${c.reset} Quick status check
|
|
1671
|
+
${c.cyan}test${c.reset} Test MCP readiness
|
|
1672
|
+
|
|
1673
|
+
${c.bold}SERVE OPTIONS${c.reset}
|
|
1674
|
+
--port <port> Port number (default: ${DEFAULT_PORT})
|
|
1675
|
+
--path <path> Project path (default: cwd)
|
|
1676
|
+
--no-auto-port Don't auto-select port if blocked
|
|
1677
|
+
--force Force start (kill existing instance)
|
|
1678
|
+
--debug Enable debug logging
|
|
1679
|
+
--stdio Use stdio mode (inherit streams)
|
|
1680
|
+
|
|
1681
|
+
${c.bold}DOCTOR OPTIONS${c.reset}
|
|
1682
|
+
--fix Auto-fix issues where possible
|
|
1683
|
+
--path <path> Project to check
|
|
1684
|
+
|
|
1685
|
+
${c.bold}CONFIG OPTIONS${c.reset}
|
|
1686
|
+
--ide <ide> Target IDE (cursor, windsurf, claude, vscode, all)
|
|
1687
|
+
--write Write config to IDE config file
|
|
1688
|
+
--npx Use npx command
|
|
1689
|
+
--cli Use CLI wrapper
|
|
1690
|
+
--api-key <key> Include API key
|
|
1691
|
+
|
|
1692
|
+
${c.bold}MANIFEST OPTIONS${c.reset}
|
|
1693
|
+
--format <fmt> Output format (json, summary)
|
|
1694
|
+
|
|
1695
|
+
${c.bold}GLOBAL OPTIONS${c.reset}
|
|
1696
|
+
--json JSON output mode
|
|
1697
|
+
--quiet Suppress non-essential output
|
|
1698
|
+
|
|
1699
|
+
${c.bold}HARDENING FEATURES${c.reset}
|
|
1700
|
+
${sym.lock} Lock file prevents multiple instances
|
|
1701
|
+
${sym.retry} Retry logic with exponential backoff
|
|
1702
|
+
${sym.shield} Input validation and sanitization
|
|
1703
|
+
${sym.key} Secret redaction in logs
|
|
1704
|
+
${sym.timer} Timeout management
|
|
1705
|
+
${sym.heart} Health probes and self-healing
|
|
1706
|
+
|
|
1707
|
+
${c.bold}EXAMPLES${c.reset}
|
|
1708
|
+
${c.dim}# Full health check with auto-fix${c.reset}
|
|
1709
|
+
vibecheck mcp doctor --fix
|
|
1710
|
+
|
|
1711
|
+
${c.dim}# Start server with debug logging${c.reset}
|
|
1712
|
+
vibecheck mcp serve --debug
|
|
1713
|
+
|
|
1714
|
+
${c.dim}# Generate and write Cursor config${c.reset}
|
|
1715
|
+
vibecheck mcp config --ide cursor --write
|
|
1716
|
+
|
|
1717
|
+
${c.dim}# Quick status check${c.reset}
|
|
1718
|
+
vibecheck mcp status --json
|
|
1719
|
+
|
|
1720
|
+
${c.bold}ERROR CODES${c.reset}
|
|
1721
|
+
0 Success
|
|
1722
|
+
1 Validation error
|
|
1723
|
+
2 Server not found
|
|
1724
|
+
3 Port unavailable
|
|
1725
|
+
4 Startup failed
|
|
1726
|
+
5 Already running
|
|
1727
|
+
12 Health check failed
|
|
1728
|
+
|
|
1729
|
+
${c.bold}DOCUMENTATION${c.reset}
|
|
1730
|
+
https://vibecheckai.dev/docs/mcp
|
|
1731
|
+
`);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// ============================================================================
|
|
1735
|
+
// ARGUMENT PARSING
|
|
1736
|
+
// ============================================================================
|
|
1737
|
+
|
|
1738
|
+
function parseArgs(args) {
|
|
1739
|
+
const opts = {
|
|
1740
|
+
command: null,
|
|
1741
|
+
port: null,
|
|
1742
|
+
path: null,
|
|
1743
|
+
autoPort: true,
|
|
1744
|
+
force: false,
|
|
1745
|
+
debug: false,
|
|
1746
|
+
stdio: false,
|
|
1747
|
+
fix: false,
|
|
1748
|
+
ide: "all",
|
|
1749
|
+
write: false,
|
|
1750
|
+
npx: false,
|
|
1751
|
+
cli: false,
|
|
1752
|
+
apiKey: null,
|
|
1753
|
+
format: "json",
|
|
1754
|
+
help: false,
|
|
1755
|
+
};
|
|
1756
|
+
|
|
1757
|
+
const commands = ["serve", "doctor", "config", "manifest", "status", "test"];
|
|
1758
|
+
|
|
1759
|
+
for (let i = 0; i < args.length; i++) {
|
|
1760
|
+
const a = args[i];
|
|
1761
|
+
|
|
1762
|
+
if (commands.includes(a) && !opts.command) {
|
|
1763
|
+
opts.command = a;
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
switch (a) {
|
|
1768
|
+
case "--port":
|
|
1769
|
+
opts.port = parseInt(args[++i]);
|
|
1770
|
+
break;
|
|
1771
|
+
case "--path":
|
|
1772
|
+
opts.path = args[++i];
|
|
1773
|
+
break;
|
|
1774
|
+
case "--no-auto-port":
|
|
1775
|
+
opts.autoPort = false;
|
|
1776
|
+
break;
|
|
1777
|
+
case "--force":
|
|
1778
|
+
opts.force = true;
|
|
1779
|
+
break;
|
|
1780
|
+
case "--debug":
|
|
1781
|
+
opts.debug = true;
|
|
1782
|
+
break;
|
|
1783
|
+
case "--stdio":
|
|
1784
|
+
opts.stdio = true;
|
|
1785
|
+
break;
|
|
1786
|
+
case "--fix":
|
|
1787
|
+
opts.fix = true;
|
|
1788
|
+
break;
|
|
1789
|
+
case "--ide":
|
|
1790
|
+
opts.ide = args[++i];
|
|
1791
|
+
break;
|
|
1792
|
+
case "--write":
|
|
1793
|
+
opts.write = true;
|
|
1794
|
+
break;
|
|
1795
|
+
case "--npx":
|
|
1796
|
+
opts.npx = true;
|
|
1797
|
+
break;
|
|
1798
|
+
case "--cli":
|
|
1799
|
+
opts.cli = true;
|
|
1800
|
+
break;
|
|
1801
|
+
case "--api-key":
|
|
1802
|
+
opts.apiKey = args[++i];
|
|
1803
|
+
break;
|
|
1804
|
+
case "--format":
|
|
1805
|
+
opts.format = args[++i];
|
|
1806
|
+
break;
|
|
1807
|
+
case "--help":
|
|
1808
|
+
case "-h":
|
|
1809
|
+
opts.help = true;
|
|
1810
|
+
break;
|
|
1811
|
+
default:
|
|
1812
|
+
if (a.startsWith("--port=")) opts.port = parseInt(a.split("=")[1]);
|
|
1813
|
+
else if (a.startsWith("--path=")) opts.path = a.split("=")[1];
|
|
1814
|
+
else if (a.startsWith("--ide=")) opts.ide = a.split("=")[1];
|
|
1815
|
+
else if (a.startsWith("--api-key=")) opts.apiKey = a.split("=")[1];
|
|
1816
|
+
else if (a.startsWith("--format=")) opts.format = a.split("=")[1];
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// Default command
|
|
1821
|
+
if (!opts.command) {
|
|
1822
|
+
opts.command = "serve";
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
return opts;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// ============================================================================
|
|
1829
|
+
// MAIN ENTRY POINT
|
|
1830
|
+
// ============================================================================
|
|
1831
|
+
|
|
1832
|
+
async function runMcp(args) {
|
|
1833
|
+
const opts = parseArgs(args);
|
|
1834
|
+
const { flags: globalFlags } = parseGlobalFlags(args);
|
|
1835
|
+
|
|
1836
|
+
if (opts.help) {
|
|
1837
|
+
printHelp();
|
|
1838
|
+
return EXIT.SUCCESS;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
try {
|
|
1842
|
+
switch (opts.command) {
|
|
1843
|
+
case "serve":
|
|
1844
|
+
return await runServe(opts, globalFlags);
|
|
1845
|
+
case "doctor":
|
|
1846
|
+
return await runDoctor(opts, globalFlags);
|
|
1847
|
+
case "config":
|
|
1848
|
+
return await runConfig(opts, globalFlags);
|
|
1849
|
+
case "manifest":
|
|
1850
|
+
return await runManifest(opts, globalFlags);
|
|
1851
|
+
case "status":
|
|
1852
|
+
return await runStatus(opts, globalFlags);
|
|
1853
|
+
case "test":
|
|
1854
|
+
return await runTest(opts, globalFlags);
|
|
1855
|
+
default:
|
|
1856
|
+
printHelp();
|
|
1857
|
+
return ERROR_CODES.VALIDATION_ERROR;
|
|
1858
|
+
}
|
|
1859
|
+
} catch (err) {
|
|
1860
|
+
log("error", "Unhandled error", { error: err.message, stack: err.stack });
|
|
1861
|
+
|
|
1862
|
+
if (isJsonMode(globalFlags)) {
|
|
1863
|
+
console.log(JSON.stringify(createError(ERROR_CODES.INTERNAL_ERROR, err.message)));
|
|
1864
|
+
} else {
|
|
1865
|
+
console.error(`${c.red}${sym.cross} Error: ${err.message}${c.reset}`);
|
|
1866
|
+
if (opts.debug) {
|
|
1867
|
+
console.error(err.stack);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
return ERROR_CODES.INTERNAL_ERROR;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
module.exports = { runMcp, ERROR_CODES };
|