@vibecheckai/cli 3.5.0 ā 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/registry.js +174 -449
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/generators/mcp.js +13 -15
- package/bin/runners/context/proof-context.js +1 -248
- package/bin/runners/lib/analysis-core.js +180 -198
- package/bin/runners/lib/analyzers.js +241 -2212
- package/bin/runners/lib/cli-output.js +210 -242
- package/bin/runners/lib/detectors-v2.js +785 -547
- package/bin/runners/lib/entitlements-v2.js +431 -161
- package/bin/runners/lib/error-handler.js +9 -16
- package/bin/runners/lib/global-flags.js +0 -37
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/route-truth.js +322 -1167
- package/bin/runners/lib/scan-output.js +467 -493
- package/bin/runners/lib/ship-output.js +27 -280
- package/bin/runners/lib/terminal-ui.js +700 -310
- package/bin/runners/lib/truth.js +321 -1004
- package/bin/runners/lib/unified-output.js +158 -162
- package/bin/runners/lib/upsell.js +204 -104
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAllowlist.js +324 -0
- package/bin/runners/runAuth.js +94 -344
- package/bin/runners/runCheckpoint.js +45 -43
- package/bin/runners/runContext.js +24 -139
- package/bin/runners/runDoctor.js +101 -136
- package/bin/runners/runEvidencePack.js +219 -0
- package/bin/runners/runFix.js +71 -82
- package/bin/runners/runGuard.js +119 -606
- package/bin/runners/runInit.js +60 -22
- package/bin/runners/runInstall.js +281 -0
- package/bin/runners/runLabs.js +341 -0
- package/bin/runners/runMcp.js +62 -139
- package/bin/runners/runPolish.js +83 -282
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +58 -33
- package/bin/runners/runReality.js +58 -81
- package/bin/runners/runReport.js +7 -34
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +844 -219
- package/bin/runners/runShip.js +59 -721
- package/bin/runners/runValidate.js +11 -24
- package/bin/runners/runWatch.js +76 -131
- package/bin/vibecheck.js +69 -295
- package/mcp-server/ARCHITECTURE.md +339 -0
- package/mcp-server/__tests__/cache.test.ts +313 -0
- package/mcp-server/__tests__/executor.test.ts +239 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +1 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/package.json +5 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +5 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +4 -0
- package/mcp-server/__tests__/ids.test.ts +345 -0
- package/mcp-server/__tests__/integration/tools.test.ts +410 -0
- package/mcp-server/__tests__/registry.test.ts +365 -0
- package/mcp-server/__tests__/sandbox.test.ts +323 -0
- package/mcp-server/__tests__/schemas.test.ts +372 -0
- package/mcp-server/benchmarks/run-benchmarks.ts +304 -0
- package/mcp-server/examples/doctor.request.json +14 -0
- package/mcp-server/examples/doctor.response.json +53 -0
- package/mcp-server/examples/error.response.json +15 -0
- package/mcp-server/examples/scan.request.json +14 -0
- package/mcp-server/examples/scan.response.json +108 -0
- package/mcp-server/handlers/tool-handler.ts +671 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index-v3.ts +293 -0
- package/mcp-server/index.js +1080 -1757
- package/mcp-server/index.old.js +4137 -0
- package/mcp-server/lib/cache.ts +341 -0
- package/mcp-server/lib/errors.ts +346 -0
- package/mcp-server/lib/executor.ts +792 -0
- package/mcp-server/lib/ids.ts +238 -0
- package/mcp-server/lib/logger.ts +368 -0
- package/mcp-server/lib/metrics.ts +365 -0
- package/mcp-server/lib/sandbox.ts +337 -0
- package/mcp-server/lib/validator.ts +229 -0
- package/mcp-server/package-lock.json +165 -0
- package/mcp-server/package.json +32 -7
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/registry/tools.json +476 -0
- package/mcp-server/schemas/error-envelope.schema.json +125 -0
- package/mcp-server/schemas/finding.schema.json +167 -0
- package/mcp-server/schemas/report-artifact.schema.json +88 -0
- package/mcp-server/schemas/run-request.schema.json +75 -0
- package/mcp-server/schemas/verdict.schema.json +168 -0
- package/mcp-server/tier-auth.d.ts +71 -0
- package/mcp-server/tier-auth.js +371 -183
- package/mcp-server/truth-context.js +90 -131
- package/mcp-server/truth-firewall-tools.js +1000 -1611
- package/mcp-server/tsconfig.json +34 -0
- package/mcp-server/vibecheck-tools.js +2 -2
- package/mcp-server/vitest.config.ts +16 -0
- package/package.json +3 -4
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +0 -474
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +0 -488
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +0 -228
- package/bin/runners/lib/agent-firewall/change-packet/store.js +0 -200
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +0 -21
- package/bin/runners/lib/agent-firewall/claims/extractor.js +0 -303
- package/bin/runners/lib/agent-firewall/claims/patterns.js +0 -24
- package/bin/runners/lib/agent-firewall/critic/index.js +0 -151
- package/bin/runners/lib/agent-firewall/critic/judge.js +0 -432
- package/bin/runners/lib/agent-firewall/critic/prompts.js +0 -305
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +0 -88
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +0 -75
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +0 -127
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +0 -102
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +0 -213
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +0 -145
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +0 -19
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +0 -87
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +0 -184
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +0 -163
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +0 -107
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +0 -68
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +0 -66
- package/bin/runners/lib/agent-firewall/interceptor/base.js +0 -304
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +0 -35
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +0 -35
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +0 -34
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +0 -465
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +0 -604
- package/bin/runners/lib/agent-firewall/lawbook/index.js +0 -304
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +0 -514
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +0 -420
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/agent-firewall/logger.js +0 -141
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +0 -90
- package/bin/runners/lib/agent-firewall/policy/engine.js +0 -103
- package/bin/runners/lib/agent-firewall/policy/loader.js +0 -451
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +0 -50
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +0 -50
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +0 -86
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +0 -162
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +0 -189
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +0 -93
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +0 -57
- package/bin/runners/lib/agent-firewall/policy/schema.json +0 -183
- package/bin/runners/lib/agent-firewall/policy/verdict.js +0 -54
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +0 -394
- package/bin/runners/lib/agent-firewall/proposal/index.js +0 -212
- package/bin/runners/lib/agent-firewall/proposal/schema.js +0 -251
- package/bin/runners/lib/agent-firewall/proposal/validator.js +0 -386
- package/bin/runners/lib/agent-firewall/reality/index.js +0 -332
- package/bin/runners/lib/agent-firewall/reality/state.js +0 -625
- package/bin/runners/lib/agent-firewall/reality/watcher.js +0 -322
- package/bin/runners/lib/agent-firewall/risk/index.js +0 -173
- package/bin/runners/lib/agent-firewall/risk/scorer.js +0 -328
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +0 -321
- package/bin/runners/lib/agent-firewall/risk/vectors.js +0 -421
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +0 -472
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +0 -346
- package/bin/runners/lib/agent-firewall/simulator/index.js +0 -181
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +0 -380
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +0 -661
- package/bin/runners/lib/agent-firewall/time-machine/index.js +0 -267
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +0 -436
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +0 -490
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +0 -530
- package/bin/runners/lib/agent-firewall/truthpack/index.js +0 -67
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +0 -137
- package/bin/runners/lib/agent-firewall/unblock/planner.js +0 -337
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +0 -118
- package/bin/runners/lib/api-client.js +0 -269
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/authority-badge.js +0 -425
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/accessibility-engine.js +0 -390
- package/bin/runners/lib/engines/api-consistency-engine.js +0 -467
- package/bin/runners/lib/engines/ast-cache.js +0 -99
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/code-quality-engine.js +0 -255
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/console-logs-engine.js +0 -115
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +0 -533
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/dead-code-engine.js +0 -198
- package/bin/runners/lib/engines/deprecated-api-engine.js +0 -226
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/empty-catch-engine.js +0 -260
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/file-filter.js +0 -131
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +0 -251
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/mock-data-engine.js +0 -315
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/parallel-processor.js +0 -71
- package/bin/runners/lib/engines/performance-issues-engine.js +0 -405
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +0 -571
- package/bin/runners/lib/engines/todo-fixme-engine.js +0 -115
- package/bin/runners/lib/engines/type-aware-engine.js +0 -376
- package/bin/runners/lib/engines/unsafe-regex-engine.js +0 -225
- package/bin/runners/lib/engines/vibecheck-engines/README.md +0 -53
- package/bin/runners/lib/engines/vibecheck-engines/index.js +0 -124
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +0 -439
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines/package.json +0 -13
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/exit-codes.js +0 -275
- package/bin/runners/lib/fingerprint.js +0 -377
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/help-formatter.js +0 -413
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/logger.js +0 -38
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/ship-output-enterprise.js +0 -239
- package/bin/runners/lib/unified-cli-output.js +0 -777
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAgent.d.ts +0 -5
- package/bin/runners/runAgent.js +0 -164
- package/bin/runners/runApprove.js +0 -1233
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runClassify.js +0 -862
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runContext.d.ts +0 -4
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runFirewall.d.ts +0 -5
- package/bin/runners/runFirewall.js +0 -137
- package/bin/runners/runFirewallHook.d.ts +0 -5
- package/bin/runners/runFirewallHook.js +0 -59
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runPolish.d.ts +0 -4
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runTruth.d.ts +0 -5
- package/bin/runners/runTruth.js +0 -104
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/HARDENING_SUMMARY.md +0 -299
- package/mcp-server/agent-firewall-interceptor.js +0 -500
- package/mcp-server/authority-tools.js +0 -569
- package/mcp-server/conductor/conflict-resolver.js +0 -588
- package/mcp-server/conductor/execution-planner.js +0 -544
- package/mcp-server/conductor/index.js +0 -377
- package/mcp-server/conductor/lock-manager.js +0 -615
- package/mcp-server/conductor/request-queue.js +0 -550
- package/mcp-server/conductor/session-manager.js +0 -500
- package/mcp-server/conductor/tools.js +0 -510
- package/mcp-server/lib/api-client.cjs +0 -13
- package/mcp-server/lib/logger.cjs +0 -30
- package/mcp-server/logger.js +0 -173
- package/mcp-server/tools-v3.js +0 -1039
- package/mcp-server/tools.js +0 -495
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
package/mcp-server/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* vibecheck MCP Server v2.
|
|
4
|
+
* vibecheck MCP Server v2.0 - Clean Product Surface
|
|
5
5
|
*
|
|
6
6
|
* Curated Tools for AI Agents:
|
|
7
7
|
* vibecheck.ctx - Build truthpack/context
|
|
@@ -15,16 +15,6 @@
|
|
|
15
15
|
* vibecheck.check_invariants - Invariant checks
|
|
16
16
|
*
|
|
17
17
|
* Everything else is parameters on these tools.
|
|
18
|
-
*
|
|
19
|
-
* HARDENING FEATURES (v2.1.0):
|
|
20
|
-
* - Input validation: URL, path, string, array, number sanitization
|
|
21
|
-
* - Output sanitization: Redaction of secrets, truncation of large outputs
|
|
22
|
-
* - Rate limiting: 120 calls/minute per server instance
|
|
23
|
-
* - Path security: Traversal prevention, project root sandboxing
|
|
24
|
-
* - Safe JSON parsing: Size limits, error handling
|
|
25
|
-
* - Error handling: Consistent error codes, sanitized messages
|
|
26
|
-
* - Resource safety: Bounded timeouts, memory limits
|
|
27
|
-
* - Graceful degradation: Partial output on failures
|
|
28
18
|
*/
|
|
29
19
|
|
|
30
20
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -37,424 +27,14 @@ import {
|
|
|
37
27
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
38
28
|
|
|
39
29
|
import fs from "fs/promises";
|
|
40
|
-
import fsSync from "fs";
|
|
41
30
|
import path from "path";
|
|
42
31
|
import { fileURLToPath } from "url";
|
|
43
|
-
import {
|
|
44
|
-
import { promisify } from "util";
|
|
45
|
-
|
|
46
|
-
// Import API client for dashboard integration (optional - may use CommonJS)
|
|
47
|
-
import { createRequire } from "module";
|
|
48
|
-
const require = createRequire(import.meta.url);
|
|
49
|
-
const {
|
|
50
|
-
createScan,
|
|
51
|
-
updateScanProgress,
|
|
52
|
-
submitScanResults,
|
|
53
|
-
reportScanError,
|
|
54
|
-
isApiAvailable
|
|
55
|
-
} = require("./lib/api-client.cjs");
|
|
56
|
-
|
|
57
|
-
const execFileAsync = promisify(execFile);
|
|
32
|
+
import { execSync } from "child_process";
|
|
58
33
|
|
|
59
34
|
const __filename = fileURLToPath(import.meta.url);
|
|
60
35
|
const __dirname = path.dirname(__filename);
|
|
61
36
|
|
|
62
|
-
|
|
63
|
-
// CENTRALIZED CONFIGURATION
|
|
64
|
-
// ============================================================================
|
|
65
|
-
const CONFIG = {
|
|
66
|
-
VERSION: "2.2.0",
|
|
67
|
-
BIN_PATH: path.join(__dirname, "..", "bin", "vibecheck.js"),
|
|
68
|
-
OUTPUT_DIR: ".vibecheck",
|
|
69
|
-
ENV_DEFAULTS: { VIBECHECK_SKIP_AUTH: "1" },
|
|
70
|
-
TIMEOUTS: {
|
|
71
|
-
DEFAULT: 30000, // 30 seconds
|
|
72
|
-
SCAN: 120000, // 2 minutes
|
|
73
|
-
VERIFY: 180000, // 3 minutes
|
|
74
|
-
REALITY: 300000, // 5 minutes
|
|
75
|
-
PROVE: 600000, // 10 minutes
|
|
76
|
-
AUTOPILOT: 300000, // 5 minutes
|
|
77
|
-
},
|
|
78
|
-
MAX_BUFFER: 10 * 1024 * 1024, // 10MB
|
|
79
|
-
// Hardening limits
|
|
80
|
-
LIMITS: {
|
|
81
|
-
MAX_OUTPUT_LENGTH: 500000, // 500KB max output text
|
|
82
|
-
MAX_PATH_LENGTH: 4096, // Max path string length
|
|
83
|
-
MAX_URL_LENGTH: 2048, // Max URL length
|
|
84
|
-
MAX_STRING_ARG: 10000, // Max string argument length
|
|
85
|
-
MAX_ARRAY_ITEMS: 100, // Max array items in args
|
|
86
|
-
RATE_LIMIT_WINDOW_MS: 60000, // 1 minute window
|
|
87
|
-
RATE_LIMIT_MAX_CALLS: 120, // Max calls per window
|
|
88
|
-
},
|
|
89
|
-
// Sensitive patterns to redact from output
|
|
90
|
-
SENSITIVE_PATTERNS: [
|
|
91
|
-
/(?:sk_live_|sk_test_)[a-zA-Z0-9]{24,}/g, // Stripe keys
|
|
92
|
-
/(?:AKIA|ASIA)[A-Z0-9]{16}/g, // AWS keys
|
|
93
|
-
/ghp_[a-zA-Z0-9]{36}/g, // GitHub tokens
|
|
94
|
-
/xox[baprs]-[0-9A-Za-z\-]{10,}/g, // Slack tokens
|
|
95
|
-
/eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g, // JWTs
|
|
96
|
-
/(?:password|secret|token|apikey|api_key)["']?\s*[:=]\s*["'][^"']{8,}["']/gi, // Generic secrets
|
|
97
|
-
],
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// ============================================================================
|
|
101
|
-
// HARDENING: Input Validation & Sanitization Utilities
|
|
102
|
-
// ============================================================================
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Validate and sanitize a URL
|
|
106
|
-
* @param {string} url - URL to validate
|
|
107
|
-
* @returns {{ valid: boolean, url?: string, error?: string }}
|
|
108
|
-
*/
|
|
109
|
-
function validateUrl(url) {
|
|
110
|
-
if (!url || typeof url !== 'string') {
|
|
111
|
-
return { valid: false, error: 'URL is required and must be a string' };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const trimmed = url.trim();
|
|
115
|
-
|
|
116
|
-
if (trimmed.length > CONFIG.LIMITS.MAX_URL_LENGTH) {
|
|
117
|
-
return { valid: false, error: `URL exceeds maximum length of ${CONFIG.LIMITS.MAX_URL_LENGTH} characters` };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const parsed = new URL(trimmed);
|
|
122
|
-
|
|
123
|
-
// Only allow http/https protocols
|
|
124
|
-
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
125
|
-
return { valid: false, error: 'URL must use http or https protocol' };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Block localhost/internal IPs in production contexts (can be overridden)
|
|
129
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
130
|
-
if (process.env.VIBECHECK_BLOCK_INTERNAL !== 'false') {
|
|
131
|
-
// Allow localhost for development
|
|
132
|
-
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
|
133
|
-
// This is OK for local testing
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return { valid: true, url: trimmed };
|
|
138
|
-
} catch {
|
|
139
|
-
return { valid: false, error: 'Invalid URL format' };
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Sanitize a file path to prevent path traversal.
|
|
145
|
-
*
|
|
146
|
-
* SECURITY FIX: Previous implementation used path.sep which differs between
|
|
147
|
-
* Windows (\) and Unix (/). Attackers could bypass the check on Windows by
|
|
148
|
-
* using forward slashes in paths. Now we normalize all separators before comparing.
|
|
149
|
-
*
|
|
150
|
-
* @param {string} inputPath - Path to sanitize
|
|
151
|
-
* @param {string} projectRoot - Project root directory
|
|
152
|
-
* @returns {{ valid: boolean, path?: string, error?: string }}
|
|
153
|
-
*/
|
|
154
|
-
function sanitizePath(inputPath, projectRoot) {
|
|
155
|
-
if (!inputPath || typeof inputPath !== 'string') {
|
|
156
|
-
return { valid: false, error: 'Path is required and must be a string' };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (inputPath.length > CONFIG.LIMITS.MAX_PATH_LENGTH) {
|
|
160
|
-
return { valid: false, error: `Path exceeds maximum length of ${CONFIG.LIMITS.MAX_PATH_LENGTH} characters` };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Reject null bytes which can truncate paths in some systems
|
|
164
|
-
if (inputPath.includes('\0')) {
|
|
165
|
-
return { valid: false, error: 'Path contains invalid characters (null byte)' };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
const resolvedRoot = path.resolve(projectRoot);
|
|
170
|
-
const resolvedPath = path.resolve(projectRoot, inputPath);
|
|
171
|
-
|
|
172
|
-
// SECURITY: Normalize path separators for cross-platform comparison
|
|
173
|
-
// On Windows, path.sep is '\' but paths can use '/' which would bypass the check
|
|
174
|
-
const normalizedRoot = resolvedRoot.replace(/\\/g, '/').toLowerCase();
|
|
175
|
-
const normalizedPath = resolvedPath.replace(/\\/g, '/').toLowerCase();
|
|
176
|
-
|
|
177
|
-
// Ensure the resolved path is within or equal to the project root
|
|
178
|
-
// Use normalized paths for comparison, add trailing slash to prevent
|
|
179
|
-
// prefix attacks (e.g., /project-malicious matching /project)
|
|
180
|
-
const rootWithSep = normalizedRoot.endsWith('/') ? normalizedRoot : normalizedRoot + '/';
|
|
181
|
-
|
|
182
|
-
if (!normalizedPath.startsWith(rootWithSep) && normalizedPath !== normalizedRoot) {
|
|
183
|
-
return { valid: false, error: 'Path traversal detected - path must be within project root' };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return { valid: true, path: resolvedPath };
|
|
187
|
-
} catch (err) {
|
|
188
|
-
return { valid: false, error: `Invalid path format: ${err.message}` };
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Validate and sanitize string arguments
|
|
194
|
-
* @param {*} value - Value to validate
|
|
195
|
-
* @param {number} maxLength - Maximum allowed length
|
|
196
|
-
* @returns {string}
|
|
197
|
-
*/
|
|
198
|
-
function sanitizeString(value, maxLength = CONFIG.LIMITS.MAX_STRING_ARG) {
|
|
199
|
-
if (value === null || value === undefined) {
|
|
200
|
-
return '';
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const str = String(value);
|
|
204
|
-
return str.length > maxLength ? str.slice(0, maxLength) + '...[truncated]' : str;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Validate array arguments
|
|
209
|
-
* @param {*} arr - Array to validate
|
|
210
|
-
* @param {number} maxItems - Maximum allowed items
|
|
211
|
-
* @returns {any[]}
|
|
212
|
-
*/
|
|
213
|
-
function sanitizeArray(arr, maxItems = CONFIG.LIMITS.MAX_ARRAY_ITEMS) {
|
|
214
|
-
if (!Array.isArray(arr)) {
|
|
215
|
-
return [];
|
|
216
|
-
}
|
|
217
|
-
return arr.slice(0, maxItems);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Validate numeric arguments
|
|
222
|
-
* @param {*} value - Value to validate
|
|
223
|
-
* @param {number} min - Minimum value
|
|
224
|
-
* @param {number} max - Maximum value
|
|
225
|
-
* @param {number} defaultValue - Default if invalid
|
|
226
|
-
* @returns {number}
|
|
227
|
-
*/
|
|
228
|
-
function sanitizeNumber(value, min, max, defaultValue) {
|
|
229
|
-
const num = Number(value);
|
|
230
|
-
if (isNaN(num) || num < min || num > max) {
|
|
231
|
-
return defaultValue;
|
|
232
|
-
}
|
|
233
|
-
return num;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Redact sensitive information from output
|
|
238
|
-
* @param {string} text - Text to redact
|
|
239
|
-
* @returns {string}
|
|
240
|
-
*/
|
|
241
|
-
function redactSensitive(text) {
|
|
242
|
-
if (!text || typeof text !== 'string') {
|
|
243
|
-
return text;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
let result = text;
|
|
247
|
-
for (const pattern of CONFIG.SENSITIVE_PATTERNS) {
|
|
248
|
-
result = result.replace(pattern, '[REDACTED]');
|
|
249
|
-
}
|
|
250
|
-
return result;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Truncate output to prevent memory issues
|
|
255
|
-
* @param {string} text - Text to truncate
|
|
256
|
-
* @param {number} maxLength - Maximum length
|
|
257
|
-
* @returns {string}
|
|
258
|
-
*/
|
|
259
|
-
function truncateOutput(text, maxLength = CONFIG.LIMITS.MAX_OUTPUT_LENGTH) {
|
|
260
|
-
if (!text || typeof text !== 'string') {
|
|
261
|
-
return '';
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (text.length <= maxLength) {
|
|
265
|
-
return text;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const truncated = text.slice(0, maxLength);
|
|
269
|
-
return truncated + `\n\n...[Output truncated at ${maxLength} characters]`;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// ============================================================================
|
|
273
|
-
// HARDENING: Rate Limiting (Per-API-Key)
|
|
274
|
-
//
|
|
275
|
-
// SECURITY FIX: Previous implementation used a global rate limit, allowing
|
|
276
|
-
// a single attacker to exhaust the rate limit budget for ALL users.
|
|
277
|
-
// Now we rate limit per-API-key hash to isolate abuse.
|
|
278
|
-
// ============================================================================
|
|
279
|
-
|
|
280
|
-
const rateLimitState = {
|
|
281
|
-
callsByKey: new Map(), // Map<keyHash, timestamp[]>
|
|
282
|
-
globalCalls: [], // Fallback for unauthenticated requests
|
|
283
|
-
cleanupInterval: null,
|
|
284
|
-
maxKeys: 10000, // Prevent unbounded growth
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Hash API key for rate limit tracking (don't store raw keys)
|
|
289
|
-
*/
|
|
290
|
-
function hashKeyForRateLimit(apiKey) {
|
|
291
|
-
if (!apiKey) return '__anonymous__';
|
|
292
|
-
const crypto = require('crypto');
|
|
293
|
-
return crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 16);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Cleanup expired rate limit entries
|
|
298
|
-
*/
|
|
299
|
-
function cleanupRateLimitState() {
|
|
300
|
-
const now = Date.now();
|
|
301
|
-
const windowStart = now - CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS;
|
|
302
|
-
|
|
303
|
-
// Clean per-key entries
|
|
304
|
-
for (const [keyHash, calls] of rateLimitState.callsByKey) {
|
|
305
|
-
const validCalls = calls.filter(t => t > windowStart);
|
|
306
|
-
if (validCalls.length === 0) {
|
|
307
|
-
rateLimitState.callsByKey.delete(keyHash);
|
|
308
|
-
} else {
|
|
309
|
-
rateLimitState.callsByKey.set(keyHash, validCalls);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Clean global calls
|
|
314
|
-
rateLimitState.globalCalls = rateLimitState.globalCalls.filter(t => t > windowStart);
|
|
315
|
-
|
|
316
|
-
// Enforce max keys to prevent memory exhaustion
|
|
317
|
-
if (rateLimitState.callsByKey.size > rateLimitState.maxKeys) {
|
|
318
|
-
// Evict oldest keys (those with oldest last call)
|
|
319
|
-
const entries = Array.from(rateLimitState.callsByKey.entries());
|
|
320
|
-
entries.sort((a, b) => Math.max(...(a[1] || [0])) - Math.max(...(b[1] || [0])));
|
|
321
|
-
const toDelete = entries.slice(0, rateLimitState.callsByKey.size - rateLimitState.maxKeys);
|
|
322
|
-
for (const [key] of toDelete) {
|
|
323
|
-
rateLimitState.callsByKey.delete(key);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Check rate limit for a specific API key
|
|
330
|
-
*
|
|
331
|
-
* @param {string} apiKey - The API key (optional, falls back to global limit)
|
|
332
|
-
* @returns {{ allowed: boolean, remaining: number, resetIn: number, keyHash?: string }}
|
|
333
|
-
*/
|
|
334
|
-
function checkRateLimit(apiKey = null) {
|
|
335
|
-
const now = Date.now();
|
|
336
|
-
const windowStart = now - CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS;
|
|
337
|
-
const keyHash = hashKeyForRateLimit(apiKey);
|
|
338
|
-
|
|
339
|
-
// Get or create call array for this key
|
|
340
|
-
let calls = rateLimitState.callsByKey.get(keyHash);
|
|
341
|
-
if (!calls) {
|
|
342
|
-
calls = [];
|
|
343
|
-
rateLimitState.callsByKey.set(keyHash, calls);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Clean old entries for this key
|
|
347
|
-
const validCalls = calls.filter(t => t > windowStart);
|
|
348
|
-
rateLimitState.callsByKey.set(keyHash, validCalls);
|
|
349
|
-
|
|
350
|
-
// Determine limit based on authentication
|
|
351
|
-
// Anonymous gets stricter limit (10/min), authenticated gets full limit
|
|
352
|
-
const maxCalls = apiKey ? CONFIG.LIMITS.RATE_LIMIT_MAX_CALLS : Math.min(10, CONFIG.LIMITS.RATE_LIMIT_MAX_CALLS);
|
|
353
|
-
const remaining = maxCalls - validCalls.length;
|
|
354
|
-
|
|
355
|
-
if (remaining <= 0) {
|
|
356
|
-
const oldestCall = Math.min(...validCalls);
|
|
357
|
-
const resetIn = Math.max(0, (oldestCall + CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS) - now);
|
|
358
|
-
return { allowed: false, remaining: 0, resetIn, keyHash };
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Record this call
|
|
362
|
-
validCalls.push(now);
|
|
363
|
-
|
|
364
|
-
// Periodic cleanup (every ~100 calls)
|
|
365
|
-
if (Math.random() < 0.01) {
|
|
366
|
-
cleanupRateLimitState();
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return { allowed: true, remaining: remaining - 1, resetIn: CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS, keyHash };
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// ============================================================================
|
|
373
|
-
// HARDENING: Circuit Breaker for API Integration
|
|
374
|
-
// ============================================================================
|
|
375
|
-
|
|
376
|
-
const circuitBreakerState = {
|
|
377
|
-
failures: 0,
|
|
378
|
-
lastFailureTime: 0,
|
|
379
|
-
state: 'CLOSED', // CLOSED = normal, OPEN = failing, HALF_OPEN = testing
|
|
380
|
-
failureThreshold: 5,
|
|
381
|
-
resetTimeout: 60000, // 1 minute
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Check if API calls should be allowed
|
|
386
|
-
* @returns {{ allowed: boolean, reason?: string }}
|
|
387
|
-
*/
|
|
388
|
-
function checkCircuitBreaker() {
|
|
389
|
-
const now = Date.now();
|
|
390
|
-
|
|
391
|
-
// If circuit is open, check if we should attempt reset
|
|
392
|
-
if (circuitBreakerState.state === 'OPEN') {
|
|
393
|
-
if (now - circuitBreakerState.lastFailureTime >= circuitBreakerState.resetTimeout) {
|
|
394
|
-
circuitBreakerState.state = 'HALF_OPEN';
|
|
395
|
-
console.error('[MCP] Circuit breaker entering HALF_OPEN state - testing API');
|
|
396
|
-
} else {
|
|
397
|
-
return {
|
|
398
|
-
allowed: false,
|
|
399
|
-
reason: `Circuit breaker OPEN - API calls disabled for ${Math.ceil((circuitBreakerState.resetTimeout - (now - circuitBreakerState.lastFailureTime)) / 1000)}s`
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return { allowed: true };
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Record API call result
|
|
409
|
-
* @param {boolean} success - Whether the API call succeeded
|
|
410
|
-
*/
|
|
411
|
-
function recordApiResult(success) {
|
|
412
|
-
if (success) {
|
|
413
|
-
// Reset on success
|
|
414
|
-
if (circuitBreakerState.state === 'HALF_OPEN') {
|
|
415
|
-
console.error('[MCP] Circuit breaker CLOSED - API recovered');
|
|
416
|
-
}
|
|
417
|
-
circuitBreakerState.failures = 0;
|
|
418
|
-
circuitBreakerState.state = 'CLOSED';
|
|
419
|
-
} else {
|
|
420
|
-
circuitBreakerState.failures++;
|
|
421
|
-
circuitBreakerState.lastFailureTime = Date.now();
|
|
422
|
-
|
|
423
|
-
if (circuitBreakerState.failures >= circuitBreakerState.failureThreshold) {
|
|
424
|
-
circuitBreakerState.state = 'OPEN';
|
|
425
|
-
console.error(`[MCP] Circuit breaker OPEN after ${circuitBreakerState.failures} failures - disabling API calls`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// ============================================================================
|
|
431
|
-
// HARDENING: Safe JSON Parsing
|
|
432
|
-
// ============================================================================
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Safely parse JSON with size limits
|
|
436
|
-
* @param {string} text - JSON string to parse
|
|
437
|
-
* @param {number} maxSize - Maximum allowed size in bytes
|
|
438
|
-
* @returns {{ success: boolean, data?: any, error?: string }}
|
|
439
|
-
*/
|
|
440
|
-
function safeJsonParse(text, maxSize = 5 * 1024 * 1024) {
|
|
441
|
-
if (!text || typeof text !== 'string') {
|
|
442
|
-
return { success: false, error: 'Invalid input' };
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (text.length > maxSize) {
|
|
446
|
-
return { success: false, error: `JSON exceeds maximum size of ${maxSize} bytes` };
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
try {
|
|
450
|
-
const data = JSON.parse(text);
|
|
451
|
-
return { success: true, data };
|
|
452
|
-
} catch (e) {
|
|
453
|
-
return { success: false, error: `JSON parse error: ${e.message}` };
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const VERSION = CONFIG.VERSION;
|
|
37
|
+
const VERSION = "2.1.0";
|
|
458
38
|
|
|
459
39
|
// Import intelligence tools
|
|
460
40
|
import {
|
|
@@ -480,12 +60,6 @@ import {
|
|
|
480
60
|
handleArchitectTool,
|
|
481
61
|
} from "./architect-tools.js";
|
|
482
62
|
|
|
483
|
-
// Import authority system tools
|
|
484
|
-
import {
|
|
485
|
-
AUTHORITY_TOOLS,
|
|
486
|
-
handleAuthorityTool,
|
|
487
|
-
} from "./authority-tools.js";
|
|
488
|
-
|
|
489
63
|
// Import codebase architect tools
|
|
490
64
|
import {
|
|
491
65
|
CODEBASE_ARCHITECT_TOOLS,
|
|
@@ -517,779 +91,827 @@ import {
|
|
|
517
91
|
TRUTH_FIREWALL_TOOLS,
|
|
518
92
|
handleTruthFirewallTool,
|
|
519
93
|
hasRecentClaimValidation,
|
|
520
|
-
getContextAttribution,
|
|
521
94
|
} from "./truth-firewall-tools.js";
|
|
522
95
|
|
|
523
|
-
// Context attribution message
|
|
524
|
-
const CONTEXT_ATTRIBUTION = "š§ Context enhanced by vibecheck";
|
|
525
|
-
|
|
526
96
|
// Import Consolidated Tools (15 focused tools - recommended surface)
|
|
527
97
|
import { CONSOLIDATED_TOOLS, handleConsolidatedTool } from "./consolidated-tools.js";
|
|
528
98
|
|
|
529
|
-
// Import v3 Tools (10 focused tools - STARTER+ only, no free tools)
|
|
530
|
-
import { MCP_TOOLS_V3, handleToolV3, TOOL_TIERS as V3_TOOL_TIERS } from "./tools-v3.js";
|
|
531
|
-
|
|
532
99
|
// Import tier auth for entitlement checking
|
|
533
|
-
import {
|
|
100
|
+
import { checkFeatureAccess } from "./tier-auth.js";
|
|
534
101
|
|
|
535
|
-
// Import Agent Firewall Interceptor - ENABLED BY DEFAULT
|
|
536
|
-
// The Agent Firewall is the core gatekeeper that validates AI changes against reality
|
|
537
|
-
import {
|
|
538
|
-
AGENT_FIREWALL_TOOL,
|
|
539
|
-
handleAgentFirewallIntercept,
|
|
540
|
-
} from "./agent-firewall-interceptor.js";
|
|
541
|
-
|
|
542
|
-
// Import Conductor tools - Multi-Agent Coordination (Phase 2)
|
|
543
|
-
import {
|
|
544
|
-
CONDUCTOR_TOOLS,
|
|
545
|
-
handleConductorToolCall,
|
|
546
|
-
getConductorTools,
|
|
547
|
-
} from "./conductor/tools.js";
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* TRUTH FIREWALL CONFIGURATION
|
|
551
|
-
*
|
|
552
|
-
* Tools that make assertions or change code MUST have recent claim validation.
|
|
553
|
-
* Policy modes: strict (default for agents), balanced, permissive
|
|
554
|
-
*/
|
|
555
102
|
const STRICT_GUARDRAIL_TOOLS = new Set([
|
|
556
103
|
"vibecheck.scan",
|
|
557
104
|
"vibecheck.ship",
|
|
558
105
|
"vibecheck.ctx",
|
|
559
|
-
"vibecheck.fix",
|
|
560
|
-
"vibecheck.prove",
|
|
561
|
-
"vibecheck.autopilot_apply",
|
|
562
106
|
]);
|
|
563
107
|
|
|
564
|
-
// Tools that modify code or make assertions - require truth firewall
|
|
565
|
-
const CODE_CHANGING_TOOLS = new Set([
|
|
566
|
-
"vibecheck.fix",
|
|
567
|
-
"vibecheck.autopilot_apply",
|
|
568
|
-
"vibecheck.propose_patch",
|
|
569
|
-
]);
|
|
570
|
-
|
|
571
|
-
// Policy thresholds (aligned with proof-context.js EVIDENCE_SCHEMA)
|
|
572
|
-
const POLICY_THRESHOLDS = {
|
|
573
|
-
strict: { minConfidence: 0.8, allowUnknown: false, requireValidation: true },
|
|
574
|
-
balanced: { minConfidence: 0.6, allowUnknown: false, requireValidation: true },
|
|
575
|
-
permissive: { minConfidence: 0.4, allowUnknown: true, requireValidation: false },
|
|
576
|
-
};
|
|
577
|
-
|
|
578
108
|
function getTruthPolicy(args) {
|
|
579
|
-
|
|
580
|
-
return POLICY_THRESHOLDS[policy] ? policy : "strict";
|
|
109
|
+
return args?.policy || "strict";
|
|
581
110
|
}
|
|
582
111
|
|
|
583
|
-
function getPolicyConfig(policy) {
|
|
584
|
-
return POLICY_THRESHOLDS[policy] || POLICY_THRESHOLDS.strict;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* Emit guardrail metric to audit log.
|
|
589
|
-
*
|
|
590
|
-
* SECURITY FIX: Previous implementation silently ignored all failures.
|
|
591
|
-
* Now we log failures to stderr for security monitoring - an attacker
|
|
592
|
-
* filling disk or manipulating permissions would have gone undetected.
|
|
593
|
-
*/
|
|
594
112
|
async function emitGuardrailMetric(projectPath, metric) {
|
|
595
113
|
try {
|
|
596
114
|
const auditDir = path.join(projectPath, ".vibecheck", "audit");
|
|
597
115
|
await fs.mkdir(auditDir, { recursive: true });
|
|
598
116
|
const record = JSON.stringify({ ...metric, timestamp: new Date().toISOString() });
|
|
599
117
|
await fs.appendFile(path.join(auditDir, "guardrail-metrics.jsonl"), `${record}\n`);
|
|
600
|
-
} catch
|
|
601
|
-
//
|
|
602
|
-
// (e.g., attacker fills disk to prevent audit logging)
|
|
603
|
-
console.error(`[SECURITY] Guardrail metric write failed: ${err.message}`);
|
|
604
|
-
console.error(`[SECURITY] Failed metric: ${JSON.stringify(metric)}`);
|
|
605
|
-
|
|
606
|
-
// Attempt fallback to stderr-only logging for critical metrics
|
|
607
|
-
if (metric.event === 'truth_firewall_block' || metric.event === 'security_violation') {
|
|
608
|
-
console.error(`[SECURITY-CRITICAL] ${metric.event}: ${JSON.stringify(metric)}`);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Check if a code-changing tool should be blocked due to missing validation.
|
|
615
|
-
* Returns { blocked: boolean, reason?: string, suggestion?: string }
|
|
616
|
-
*/
|
|
617
|
-
function checkTruthFirewallBlock(toolName, args, projectPath) {
|
|
618
|
-
const policy = getTruthPolicy(args);
|
|
619
|
-
const policyConfig = getPolicyConfig(policy);
|
|
620
|
-
|
|
621
|
-
// Skip validation check if permissive mode and validation not required
|
|
622
|
-
if (!policyConfig.requireValidation) {
|
|
623
|
-
return { blocked: false };
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Check if this is a code-changing tool that requires validation
|
|
627
|
-
if (!CODE_CHANGING_TOOLS.has(toolName) && !STRICT_GUARDRAIL_TOOLS.has(toolName)) {
|
|
628
|
-
return { blocked: false };
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Check for recent claim validation
|
|
632
|
-
if (!hasRecentClaimValidation(projectPath)) {
|
|
633
|
-
return {
|
|
634
|
-
blocked: true,
|
|
635
|
-
reason: `Truth firewall requires claim validation before ${toolName}`,
|
|
636
|
-
code: "TRUTH_FIREWALL_REQUIRED",
|
|
637
|
-
suggestion: "Call vibecheck.validate_claim or vibecheck.get_truthpack before proceeding",
|
|
638
|
-
nextSteps: [
|
|
639
|
-
"Call vibecheck.get_truthpack with refresh=true for current evidence",
|
|
640
|
-
"Call vibecheck.validate_claim for critical assumptions",
|
|
641
|
-
`Re-run ${toolName} after validation`,
|
|
642
|
-
],
|
|
643
|
-
};
|
|
118
|
+
} catch {
|
|
119
|
+
// ignore metrics write failures
|
|
644
120
|
}
|
|
645
|
-
|
|
646
|
-
return { blocked: false };
|
|
647
121
|
}
|
|
648
122
|
|
|
649
123
|
// ============================================================================
|
|
650
124
|
// TOOL DEFINITIONS - Public Tools (Clean Product Surface)
|
|
651
125
|
// ============================================================================
|
|
652
126
|
|
|
653
|
-
//
|
|
654
|
-
//
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
//
|
|
127
|
+
// RECOMMENDED: Use consolidated tools (15 focused, evidence-backed tools)
|
|
128
|
+
// These map directly to CLI commands and return file/line citations
|
|
129
|
+
const USE_CONSOLIDATED_TOOLS = process.env.VIBECHECK_MCP_CONSOLIDATED !== 'false';
|
|
130
|
+
|
|
131
|
+
const TOOLS = USE_CONSOLIDATED_TOOLS ? [
|
|
132
|
+
// Curated tools for agents
|
|
133
|
+
...CONSOLIDATED_TOOLS,
|
|
134
|
+
] : [
|
|
135
|
+
// Legacy: Full tool set (50+ tools) - for backward compatibility
|
|
136
|
+
// PRIORITY: Truth Firewall tools (Hallucination Stopper) - agents MUST use these
|
|
137
|
+
...TRUTH_FIREWALL_TOOLS, // vibecheck.get_truthpack, vibecheck.validate_claim, vibecheck.compile_context, etc.
|
|
138
|
+
|
|
139
|
+
// Truth Context tools (Evidence-Backed AI)
|
|
140
|
+
...TRUTH_CONTEXT_TOOLS, // vibecheck.ctx, vibecheck.verify_claim, vibecheck.evidence
|
|
141
|
+
|
|
142
|
+
...INTELLIGENCE_TOOLS, // Add all intelligence suite tools
|
|
143
|
+
...VIBECHECK_TOOLS, // Add AI vibecheck tools (verify, quality, smells, etc.)
|
|
144
|
+
...AGENT_CHECKPOINT_TOOLS, // Add agent checkpoint tools
|
|
145
|
+
...ARCHITECT_TOOLS, // Add architect review/suggest tools
|
|
146
|
+
...CODEBASE_ARCHITECT_TOOLS, // Add codebase-aware architect tools
|
|
147
|
+
...VIBECHECK_2_TOOLS, // Add vibecheck 2.0 consolidated tools
|
|
148
|
+
...intentDriftTools, // Add intent drift guard tools
|
|
149
|
+
mdcGeneratorTool, // Add MDC generator tool
|
|
150
|
+
// 1. SHIP - Quick health check (vibe coder friendly)
|
|
151
|
+
{
|
|
152
|
+
name: "vibecheck.ship",
|
|
153
|
+
description:
|
|
154
|
+
"š Quick health check ā 'Is my app ready?' Plain English, traffic light score",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
projectPath: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "Path to project root",
|
|
161
|
+
default: ".",
|
|
162
|
+
},
|
|
163
|
+
fix: {
|
|
164
|
+
type: "boolean",
|
|
165
|
+
description: "Auto-fix problems where possible",
|
|
166
|
+
default: false,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
659
171
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
172
|
+
// 2. SCAN - Deep technical analysis
|
|
173
|
+
{
|
|
174
|
+
name: "vibecheck.scan",
|
|
175
|
+
description:
|
|
176
|
+
"š Deep scan ā technical analysis of secrets, auth, mocks, routes (detailed output)",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
projectPath: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Path to project root",
|
|
183
|
+
default: ".",
|
|
184
|
+
},
|
|
185
|
+
profile: {
|
|
186
|
+
type: "string",
|
|
187
|
+
enum: ["quick", "full", "ship", "ci", "security", "compliance", "ai"],
|
|
188
|
+
description:
|
|
189
|
+
"Check profile: quick, full, ship, ci, security, compliance, ai",
|
|
190
|
+
default: "quick",
|
|
191
|
+
},
|
|
192
|
+
only: {
|
|
193
|
+
type: "array",
|
|
194
|
+
items: { type: "string" },
|
|
195
|
+
description:
|
|
196
|
+
"Run only specific checks: integrity, security, hygiene, contracts, auth, routes, mocks, compliance, ai",
|
|
197
|
+
},
|
|
198
|
+
format: {
|
|
199
|
+
type: "string",
|
|
200
|
+
enum: ["text", "json", "html", "sarif"],
|
|
201
|
+
description: "Output format",
|
|
202
|
+
default: "text",
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
665
207
|
|
|
666
|
-
//
|
|
667
|
-
|
|
208
|
+
// 3. VERIFY - Runtime verification with Playwright
|
|
209
|
+
{
|
|
210
|
+
name: "vibecheck.verify",
|
|
211
|
+
description:
|
|
212
|
+
"š§Ŗ Runtime Verify ā clicks buttons, fills forms, finds Dead UI with Playwright",
|
|
213
|
+
inputSchema: {
|
|
214
|
+
type: "object",
|
|
215
|
+
properties: {
|
|
216
|
+
url: {
|
|
217
|
+
type: "string",
|
|
218
|
+
description: "Target URL to test (required)",
|
|
219
|
+
},
|
|
220
|
+
auth: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Auth credentials (email:password)",
|
|
223
|
+
},
|
|
224
|
+
flows: {
|
|
225
|
+
type: "array",
|
|
226
|
+
items: { type: "string" },
|
|
227
|
+
description: "Flow packs to test: auth, ui, forms, billing",
|
|
228
|
+
},
|
|
229
|
+
headed: {
|
|
230
|
+
type: "boolean",
|
|
231
|
+
description: "Run browser in visible mode",
|
|
232
|
+
default: false,
|
|
233
|
+
},
|
|
234
|
+
record: {
|
|
235
|
+
type: "boolean",
|
|
236
|
+
description: "Record video of test run",
|
|
237
|
+
default: false,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
required: ["url"],
|
|
241
|
+
},
|
|
242
|
+
},
|
|
668
243
|
|
|
244
|
+
// 3b. REALITY v2 - Two-Pass Auth Verification + Dead UI Crawler
|
|
245
|
+
{
|
|
246
|
+
name: "vibecheck.reality",
|
|
247
|
+
description:
|
|
248
|
+
"š§Ŗ Reality Mode v2 ā Two-pass auth verification: crawl anon, then auth. Finds Dead UI, HTTP errors, auth coverage gaps.",
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: "object",
|
|
251
|
+
properties: {
|
|
252
|
+
url: {
|
|
253
|
+
type: "string",
|
|
254
|
+
description: "Target URL to test (required)",
|
|
255
|
+
},
|
|
256
|
+
auth: {
|
|
257
|
+
type: "string",
|
|
258
|
+
description: "Auth credentials (email:password) for login attempt",
|
|
259
|
+
},
|
|
260
|
+
verifyAuth: {
|
|
261
|
+
type: "boolean",
|
|
262
|
+
description: "Enable two-pass auth verification (anon + auth)",
|
|
263
|
+
default: false,
|
|
264
|
+
},
|
|
265
|
+
storageState: {
|
|
266
|
+
type: "string",
|
|
267
|
+
description: "Path to Playwright storageState.json for pre-authenticated session",
|
|
268
|
+
},
|
|
269
|
+
saveStorageState: {
|
|
270
|
+
type: "string",
|
|
271
|
+
description: "Path to save storageState after successful login",
|
|
272
|
+
},
|
|
273
|
+
truthpack: {
|
|
274
|
+
type: "string",
|
|
275
|
+
description: "Path to truthpack.json for auth matcher verification",
|
|
276
|
+
},
|
|
277
|
+
headed: {
|
|
278
|
+
type: "boolean",
|
|
279
|
+
description: "Run browser in visible mode",
|
|
280
|
+
default: false,
|
|
281
|
+
},
|
|
282
|
+
maxPages: {
|
|
283
|
+
type: "number",
|
|
284
|
+
description: "Max pages to visit per pass (default: 18)",
|
|
285
|
+
default: 18,
|
|
286
|
+
},
|
|
287
|
+
maxDepth: {
|
|
288
|
+
type: "number",
|
|
289
|
+
description: "Max link depth to crawl (default: 2)",
|
|
290
|
+
default: 2,
|
|
291
|
+
},
|
|
292
|
+
danger: {
|
|
293
|
+
type: "boolean",
|
|
294
|
+
description: "Allow clicking risky buttons (delete, cancel, etc.)",
|
|
295
|
+
default: false,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
required: ["url"],
|
|
299
|
+
},
|
|
300
|
+
},
|
|
669
301
|
|
|
302
|
+
// 4. AI-TEST - AI Agent testing
|
|
303
|
+
{
|
|
304
|
+
name: "vibecheckai.dev-test",
|
|
305
|
+
description:
|
|
306
|
+
"š¤ AI Agent ā autonomous testing that explores your app and generates fix prompts",
|
|
307
|
+
inputSchema: {
|
|
308
|
+
type: "object",
|
|
309
|
+
properties: {
|
|
310
|
+
url: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: "Target URL to test (required)",
|
|
313
|
+
},
|
|
314
|
+
goal: {
|
|
315
|
+
type: "string",
|
|
316
|
+
description: "Natural language goal for the AI agent",
|
|
317
|
+
default: "Test all features and find issues",
|
|
318
|
+
},
|
|
319
|
+
headed: {
|
|
320
|
+
type: "boolean",
|
|
321
|
+
description: "Run browser in visible mode",
|
|
322
|
+
default: false,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
required: ["url"],
|
|
326
|
+
},
|
|
327
|
+
},
|
|
670
328
|
|
|
671
|
-
//
|
|
672
|
-
|
|
673
|
-
|
|
329
|
+
// 3. GATE - Enforce truth in CI
|
|
330
|
+
{
|
|
331
|
+
name: "vibecheck.gate",
|
|
332
|
+
description: "š¦ Enforce truth in CI ā fail builds on policy violations (STARTER tier)",
|
|
333
|
+
inputSchema: {
|
|
334
|
+
type: "object",
|
|
335
|
+
properties: {
|
|
336
|
+
projectPath: {
|
|
337
|
+
type: "string",
|
|
338
|
+
default: ".",
|
|
339
|
+
},
|
|
340
|
+
policy: {
|
|
341
|
+
type: "string",
|
|
342
|
+
enum: ["default", "strict", "ci"],
|
|
343
|
+
description: "Policy strictness level",
|
|
344
|
+
default: "strict",
|
|
345
|
+
},
|
|
346
|
+
sarif: {
|
|
347
|
+
type: "boolean",
|
|
348
|
+
description: "Generate SARIF for GitHub Code Scanning",
|
|
349
|
+
default: true,
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
},
|
|
674
354
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
355
|
+
// 5. FIX - Fix Missions v1
|
|
356
|
+
{
|
|
357
|
+
name: "vibecheck.fix",
|
|
358
|
+
description:
|
|
359
|
+
"š§ Fix Missions v1 ā AI-powered surgical fixes with proof verification loop. --apply and --autopilot require PRO tier ($99/mo).",
|
|
360
|
+
inputSchema: {
|
|
361
|
+
type: "object",
|
|
362
|
+
properties: {
|
|
363
|
+
projectPath: {
|
|
364
|
+
type: "string",
|
|
365
|
+
default: ".",
|
|
366
|
+
},
|
|
367
|
+
promptOnly: {
|
|
368
|
+
type: "boolean",
|
|
369
|
+
description: "Generate mission prompts only (no edits)",
|
|
370
|
+
default: false,
|
|
371
|
+
},
|
|
372
|
+
apply: {
|
|
373
|
+
type: "boolean",
|
|
374
|
+
description: "Apply patches returned by the model",
|
|
375
|
+
default: false,
|
|
376
|
+
},
|
|
377
|
+
autopilot: {
|
|
378
|
+
type: "boolean",
|
|
379
|
+
description: "Loop: fix ā verify ā fix until SHIP or stuck",
|
|
380
|
+
default: false,
|
|
381
|
+
},
|
|
382
|
+
share: {
|
|
383
|
+
type: "boolean",
|
|
384
|
+
description: "Generate share bundle for review",
|
|
385
|
+
default: false,
|
|
386
|
+
},
|
|
387
|
+
maxMissions: {
|
|
388
|
+
type: "number",
|
|
389
|
+
description: "Max missions to plan (default: 8)",
|
|
390
|
+
default: 8,
|
|
391
|
+
},
|
|
392
|
+
maxSteps: {
|
|
393
|
+
type: "number",
|
|
394
|
+
description: "Max autopilot steps (default: 10)",
|
|
395
|
+
default: 10,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
},
|
|
684
400
|
|
|
685
|
-
//
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
addHandler("vibecheck_conductor_acquire_lock", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_acquire_lock", args, projectPath));
|
|
709
|
-
addHandler("vibecheck_conductor_release_lock", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_release_lock", args, projectPath));
|
|
710
|
-
addHandler("vibecheck_conductor_propose", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_propose", args, projectPath));
|
|
711
|
-
addHandler("vibecheck_conductor_status", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_status", args, projectPath));
|
|
712
|
-
addHandler("vibecheck_conductor_terminate", (projectPath, args) => handleConductorToolCall("vibecheck_conductor_terminate", args, projectPath));
|
|
713
|
-
|
|
714
|
-
// Core CLI tools
|
|
715
|
-
addHandler("vibecheck.ship", this.handleShip.bind(this));
|
|
716
|
-
addHandler("vibecheck.scan", this.handleScan.bind(this));
|
|
717
|
-
addHandler("vibecheck.verify", this.handleVerify.bind(this));
|
|
718
|
-
addHandler("vibecheck.reality", this.handleReality.bind(this));
|
|
719
|
-
addHandler("vibecheckai.dev-test", this.handleAITest.bind(this));
|
|
720
|
-
addHandler("vibecheck.gate", this.handleGate.bind(this));
|
|
721
|
-
addHandler("vibecheck.fix", this.handleFix.bind(this));
|
|
722
|
-
addHandler("vibecheck.share", this.handleShare.bind(this));
|
|
723
|
-
addHandler("vibecheck.ctx", this.handleCtx.bind(this));
|
|
724
|
-
addHandler("vibecheck.prove", this.handleProve.bind(this));
|
|
725
|
-
addHandler("vibecheck.proof", this.handleProof.bind(this));
|
|
726
|
-
addHandler("vibecheck.validate", this.handleValidate.bind(this));
|
|
727
|
-
addHandler("vibecheck.report", this.handleReport.bind(this));
|
|
728
|
-
addHandler("vibecheck.status", this.handleStatus.bind(this));
|
|
729
|
-
addHandler("vibecheck.autopilot", this.handleAutopilot.bind(this));
|
|
730
|
-
addHandler("vibecheck.autopilot_plan", this.handleAutopilotPlan.bind(this));
|
|
731
|
-
addHandler("vibecheck.autopilot_apply", this.handleAutopilotApply.bind(this));
|
|
732
|
-
addHandler("vibecheck.badge", this.handleBadge.bind(this));
|
|
733
|
-
addHandler("vibecheck.context", this.handleContext.bind(this));
|
|
734
|
-
|
|
735
|
-
console.error(`[MCP] Tool registry built with ${Object.keys(registry).length} handlers`);
|
|
736
|
-
return registry;
|
|
737
|
-
}
|
|
401
|
+
// 6. SHARE - Generate share bundle from fix missions
|
|
402
|
+
{
|
|
403
|
+
name: "vibecheck.share",
|
|
404
|
+
description: "š¦ Share Bundle ā generate PR comment / review bundle from latest fix missions",
|
|
405
|
+
inputSchema: {
|
|
406
|
+
type: "object",
|
|
407
|
+
properties: {
|
|
408
|
+
projectPath: {
|
|
409
|
+
type: "string",
|
|
410
|
+
default: ".",
|
|
411
|
+
},
|
|
412
|
+
prComment: {
|
|
413
|
+
type: "boolean",
|
|
414
|
+
description: "Output GitHub PR comment format",
|
|
415
|
+
default: false,
|
|
416
|
+
},
|
|
417
|
+
out: {
|
|
418
|
+
type: "string",
|
|
419
|
+
description: "Write output to file path",
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
738
424
|
|
|
739
|
-
//
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
425
|
+
// 7. PROVE - One Command Reality Proof (orchestrates ctx ā reality ā ship ā fix)
|
|
426
|
+
{
|
|
427
|
+
name: "vibecheck.prove",
|
|
428
|
+
description: "š¬ One Command Reality Proof ā orchestrates ctx ā reality ā ship ā fix loop until SHIP or stuck (PRO tier)",
|
|
429
|
+
inputSchema: {
|
|
430
|
+
type: "object",
|
|
431
|
+
properties: {
|
|
432
|
+
projectPath: {
|
|
433
|
+
type: "string",
|
|
434
|
+
default: ".",
|
|
435
|
+
},
|
|
436
|
+
url: {
|
|
437
|
+
type: "string",
|
|
438
|
+
description: "Base URL for runtime testing",
|
|
439
|
+
},
|
|
440
|
+
auth: {
|
|
441
|
+
type: "string",
|
|
442
|
+
description: "Auth credentials (email:password)",
|
|
443
|
+
},
|
|
444
|
+
storageState: {
|
|
445
|
+
type: "string",
|
|
446
|
+
description: "Path to Playwright storageState.json",
|
|
447
|
+
},
|
|
448
|
+
maxFixRounds: {
|
|
449
|
+
type: "number",
|
|
450
|
+
description: "Max auto-fix attempts (default: 3)",
|
|
451
|
+
default: 3,
|
|
452
|
+
},
|
|
453
|
+
skipReality: {
|
|
454
|
+
type: "boolean",
|
|
455
|
+
description: "Skip runtime crawling (static only)",
|
|
456
|
+
default: false,
|
|
457
|
+
},
|
|
458
|
+
skipFix: {
|
|
459
|
+
type: "boolean",
|
|
460
|
+
description: "Don't auto-fix, just diagnose",
|
|
461
|
+
default: false,
|
|
462
|
+
},
|
|
463
|
+
headed: {
|
|
464
|
+
type: "boolean",
|
|
465
|
+
description: "Run browser in visible mode",
|
|
466
|
+
default: false,
|
|
467
|
+
},
|
|
468
|
+
danger: {
|
|
469
|
+
type: "boolean",
|
|
470
|
+
description: "Allow clicking risky buttons",
|
|
471
|
+
default: false,
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
},
|
|
768
476
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
477
|
+
// 8. CTX - Truth Pack Generator
|
|
478
|
+
{
|
|
479
|
+
name: "vibecheck.ctx",
|
|
480
|
+
description: "š¦ Truth Pack ā generate ground truth for AI agents (routes, env, auth, billing)",
|
|
481
|
+
inputSchema: {
|
|
482
|
+
type: "object",
|
|
483
|
+
properties: {
|
|
484
|
+
projectPath: {
|
|
485
|
+
type: "string",
|
|
486
|
+
default: ".",
|
|
487
|
+
},
|
|
488
|
+
snapshot: {
|
|
489
|
+
type: "boolean",
|
|
490
|
+
description: "Save timestamped snapshot",
|
|
491
|
+
default: false,
|
|
492
|
+
},
|
|
493
|
+
json: {
|
|
494
|
+
type: "boolean",
|
|
495
|
+
description: "Output raw JSON",
|
|
496
|
+
default: false,
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
},
|
|
776
501
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
502
|
+
// 9. PROOF - Premium verification
|
|
503
|
+
{
|
|
504
|
+
name: "vibecheck.proof",
|
|
505
|
+
description:
|
|
506
|
+
"š¬ Premium verification ā mocks (static) or reality (runtime with Playwright)",
|
|
507
|
+
inputSchema: {
|
|
508
|
+
type: "object",
|
|
509
|
+
properties: {
|
|
510
|
+
projectPath: {
|
|
511
|
+
type: "string",
|
|
512
|
+
default: ".",
|
|
513
|
+
},
|
|
514
|
+
mode: {
|
|
515
|
+
type: "string",
|
|
516
|
+
enum: ["mocks", "reality"],
|
|
517
|
+
description:
|
|
518
|
+
"Proof mode: mocks (import graph + fake domains) or reality (Playwright runtime)",
|
|
519
|
+
},
|
|
520
|
+
url: {
|
|
521
|
+
type: "string",
|
|
522
|
+
description: "Base URL for reality mode",
|
|
523
|
+
default: "http://localhost:3000",
|
|
524
|
+
},
|
|
525
|
+
flow: {
|
|
526
|
+
type: "string",
|
|
527
|
+
enum: ["auth", "checkout", "dashboard"],
|
|
528
|
+
description: "Flow to test in reality mode",
|
|
529
|
+
default: "auth",
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
required: ["mode"],
|
|
533
|
+
},
|
|
534
|
+
},
|
|
792
535
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
536
|
+
// 5. REPORT - Access artifacts
|
|
537
|
+
{
|
|
538
|
+
name: "vibecheck.validate",
|
|
539
|
+
description:
|
|
540
|
+
"š¤ Validate AI-generated code. Checks for hallucinations, intent mismatch, and quality issues.",
|
|
541
|
+
inputSchema: {
|
|
542
|
+
type: "object",
|
|
543
|
+
properties: {
|
|
544
|
+
code: { type: "string", description: "The code content to validate" },
|
|
545
|
+
intent: {
|
|
546
|
+
type: "string",
|
|
547
|
+
description: "The user's original request/intent",
|
|
548
|
+
},
|
|
549
|
+
projectPath: { type: "string", default: "." },
|
|
550
|
+
},
|
|
551
|
+
required: ["code"],
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
// 10. REPORT - Access scan artifacts
|
|
555
|
+
{
|
|
556
|
+
name: "vibecheck.report",
|
|
557
|
+
description:
|
|
558
|
+
"š Access scan artifacts ā summary, full report, SARIF export",
|
|
559
|
+
inputSchema: {
|
|
560
|
+
type: "object",
|
|
561
|
+
properties: {
|
|
562
|
+
projectPath: {
|
|
563
|
+
type: "string",
|
|
564
|
+
default: ".",
|
|
565
|
+
},
|
|
566
|
+
type: {
|
|
567
|
+
type: "string",
|
|
568
|
+
enum: ["summary", "full", "sarif", "html"],
|
|
569
|
+
description: "Report type to retrieve",
|
|
570
|
+
default: "summary",
|
|
571
|
+
},
|
|
572
|
+
runId: {
|
|
573
|
+
type: "string",
|
|
574
|
+
description: "Specific run ID (defaults to last run)",
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
},
|
|
807
579
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
stderr: redactSensitive(truncateOutput(stderr || '')),
|
|
823
|
-
success: true
|
|
824
|
-
};
|
|
825
|
-
} catch (error) {
|
|
826
|
-
// Attach partial output for graceful degradation (sanitized)
|
|
827
|
-
error.partialOutput = redactSensitive(truncateOutput(error.stdout || ''));
|
|
828
|
-
error.partialStderr = redactSensitive(truncateOutput(error.stderr || ''));
|
|
829
|
-
|
|
830
|
-
// Add helpful error code for timeout
|
|
831
|
-
if (error.killed && error.signal === 'SIGTERM') {
|
|
832
|
-
error.code = 'TIMEOUT';
|
|
833
|
-
error.message = `Command timed out after ${boundedTimeout}ms`;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
throw error;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
580
|
+
// 11. STATUS - Health and config
|
|
581
|
+
{
|
|
582
|
+
name: "vibecheck.status",
|
|
583
|
+
description: "š Server status ā health, versions, config, last run info",
|
|
584
|
+
inputSchema: {
|
|
585
|
+
type: "object",
|
|
586
|
+
properties: {
|
|
587
|
+
projectPath: {
|
|
588
|
+
type: "string",
|
|
589
|
+
default: ".",
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
},
|
|
839
594
|
|
|
840
|
-
//
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
595
|
+
// 12. AUTOPILOT - Continuous protection
|
|
596
|
+
{
|
|
597
|
+
name: "vibecheck.autopilot",
|
|
598
|
+
description:
|
|
599
|
+
"š¤ Autopilot ā continuous protection with weekly reports, auto-PRs, deploy blocking",
|
|
600
|
+
inputSchema: {
|
|
601
|
+
type: "object",
|
|
602
|
+
properties: {
|
|
603
|
+
projectPath: {
|
|
604
|
+
type: "string",
|
|
605
|
+
default: ".",
|
|
606
|
+
},
|
|
607
|
+
action: {
|
|
608
|
+
type: "string",
|
|
609
|
+
enum: ["status", "enable", "disable", "digest"],
|
|
610
|
+
description: "Autopilot action",
|
|
611
|
+
default: "status",
|
|
612
|
+
},
|
|
613
|
+
slack: {
|
|
614
|
+
type: "string",
|
|
615
|
+
description: "Slack webhook URL for notifications",
|
|
616
|
+
},
|
|
617
|
+
email: {
|
|
618
|
+
type: "string",
|
|
619
|
+
description: "Email for weekly digest",
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
},
|
|
861
624
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
// Silent fail - this is for graceful degradation
|
|
889
|
-
return null;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
625
|
+
// 13. AUTOPILOT PLAN - Generate fix plan (PRO tier)
|
|
626
|
+
{
|
|
627
|
+
name: "vibecheck.autopilot_plan",
|
|
628
|
+
description:
|
|
629
|
+
"š¤ Autopilot Plan ā scan codebase, group issues into fix packs, estimate risk (PRO tier)",
|
|
630
|
+
inputSchema: {
|
|
631
|
+
type: "object",
|
|
632
|
+
properties: {
|
|
633
|
+
projectPath: {
|
|
634
|
+
type: "string",
|
|
635
|
+
default: ".",
|
|
636
|
+
},
|
|
637
|
+
profile: {
|
|
638
|
+
type: "string",
|
|
639
|
+
enum: ["quick", "full", "ship", "ci"],
|
|
640
|
+
description: "Scan profile",
|
|
641
|
+
default: "ship",
|
|
642
|
+
},
|
|
643
|
+
maxFixes: {
|
|
644
|
+
type: "number",
|
|
645
|
+
description: "Max fixes per category",
|
|
646
|
+
default: 10,
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
},
|
|
892
651
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
output += `## ${verdictEmoji} VERDICT: ${verdict}\n\n`;
|
|
930
|
-
output += `**Health Score:** ${score}/100 (${grade})\n\n`;
|
|
931
|
-
|
|
932
|
-
// Summary stats
|
|
933
|
-
output += `### Summary\n`;
|
|
934
|
-
output += `- **Total Issues:** ${findings.length}\n`;
|
|
935
|
-
output += `- **Critical:** ${sevCounts.critical}\n`;
|
|
936
|
-
output += `- **High:** ${sevCounts.high}\n`;
|
|
937
|
-
output += `- **Medium:** ${sevCounts.medium}\n`;
|
|
938
|
-
output += `- **Low:** ${sevCounts.low}\n\n`;
|
|
939
|
-
|
|
940
|
-
// Category breakdown
|
|
941
|
-
if (summary.counts && typeof summary.counts === 'object') {
|
|
942
|
-
output += "### Issue Categories\n\n";
|
|
943
|
-
output += "| Category | Count | Status |\n|----------|-------|--------|\n";
|
|
944
|
-
|
|
945
|
-
const entries = Object.entries(summary.counts).slice(0, 20);
|
|
946
|
-
for (const [key, count] of entries) {
|
|
947
|
-
const safeKey = sanitizeString(key, 50);
|
|
948
|
-
const safeCount = sanitizeNumber(count, 0, 999999, 0);
|
|
949
|
-
const icon = safeCount === 0 ? "ā
" : "ā ļø";
|
|
950
|
-
output += `| ${safeKey} | ${safeCount} | ${icon} |\n`;
|
|
951
|
-
}
|
|
952
|
-
output += '\n';
|
|
953
|
-
}
|
|
652
|
+
// 14. AUTOPILOT APPLY - Apply fixes (PRO tier)
|
|
653
|
+
{
|
|
654
|
+
name: "vibecheck.autopilot_apply",
|
|
655
|
+
description:
|
|
656
|
+
"š§ Autopilot Apply ā apply fix packs with verification, re-scan to confirm (PRO tier)",
|
|
657
|
+
inputSchema: {
|
|
658
|
+
type: "object",
|
|
659
|
+
properties: {
|
|
660
|
+
projectPath: {
|
|
661
|
+
type: "string",
|
|
662
|
+
default: ".",
|
|
663
|
+
},
|
|
664
|
+
profile: {
|
|
665
|
+
type: "string",
|
|
666
|
+
enum: ["quick", "full", "ship", "ci"],
|
|
667
|
+
description: "Scan profile",
|
|
668
|
+
default: "ship",
|
|
669
|
+
},
|
|
670
|
+
maxFixes: {
|
|
671
|
+
type: "number",
|
|
672
|
+
description: "Max fixes per category",
|
|
673
|
+
default: 10,
|
|
674
|
+
},
|
|
675
|
+
verify: {
|
|
676
|
+
type: "boolean",
|
|
677
|
+
description: "Run verification after apply",
|
|
678
|
+
default: true,
|
|
679
|
+
},
|
|
680
|
+
dryRun: {
|
|
681
|
+
type: "boolean",
|
|
682
|
+
description: "Preview changes without applying",
|
|
683
|
+
default: false,
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
},
|
|
954
688
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// Include fix hints if available
|
|
985
|
-
const fixHint = f.fixHints?.[0] || f.fixHint || f.fix;
|
|
986
|
-
if (fixHint) {
|
|
987
|
-
output += ` - **Fix:** ${sanitizeString(fixHint, 200)}\n`;
|
|
988
|
-
}
|
|
989
|
-
output += '\n';
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
if (findings.length > 15) {
|
|
993
|
-
output += `_...and ${findings.length - 15} more issues. See full report for details._\n\n`;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
689
|
+
// 15. BADGE - Generate ship badge
|
|
690
|
+
{
|
|
691
|
+
name: "vibecheck.badge",
|
|
692
|
+
description:
|
|
693
|
+
"š
Ship Badge ā generate a badge for README/PR showing scan status (STARTER tier)",
|
|
694
|
+
inputSchema: {
|
|
695
|
+
type: "object",
|
|
696
|
+
properties: {
|
|
697
|
+
projectPath: {
|
|
698
|
+
type: "string",
|
|
699
|
+
default: ".",
|
|
700
|
+
},
|
|
701
|
+
format: {
|
|
702
|
+
type: "string",
|
|
703
|
+
enum: ["svg", "md", "html"],
|
|
704
|
+
description: "Badge format",
|
|
705
|
+
default: "svg",
|
|
706
|
+
},
|
|
707
|
+
style: {
|
|
708
|
+
type: "string",
|
|
709
|
+
enum: ["flat", "flat-square"],
|
|
710
|
+
description: "Badge style",
|
|
711
|
+
default: "flat",
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
},
|
|
996
716
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
* @param {string} filePath - Path to file
|
|
1021
|
-
* @param {number} maxSize - Maximum file size in bytes
|
|
1022
|
-
* @returns {Promise<string|null>} File contents or null
|
|
1023
|
-
*/
|
|
1024
|
-
async safeReadFile(filePath, maxSize = 10 * 1024 * 1024) {
|
|
1025
|
-
try {
|
|
1026
|
-
const stats = await fs.stat(filePath);
|
|
1027
|
-
|
|
1028
|
-
if (stats.size > maxSize) {
|
|
1029
|
-
console.error(`[MCP] File too large: ${filePath} (${stats.size} bytes)`);
|
|
1030
|
-
return null;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
1034
|
-
return content;
|
|
1035
|
-
} catch (err) {
|
|
1036
|
-
return null;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
717
|
+
// 16. CONTEXT - AI Rules Generator
|
|
718
|
+
{
|
|
719
|
+
name: "vibecheck.context",
|
|
720
|
+
description:
|
|
721
|
+
"š§ AI Context ā generate rules files for Cursor, Windsurf, Copilot to understand your codebase",
|
|
722
|
+
inputSchema: {
|
|
723
|
+
type: "object",
|
|
724
|
+
properties: {
|
|
725
|
+
projectPath: {
|
|
726
|
+
type: "string",
|
|
727
|
+
description: "Path to project root",
|
|
728
|
+
default: ".",
|
|
729
|
+
},
|
|
730
|
+
platform: {
|
|
731
|
+
type: "string",
|
|
732
|
+
enum: ["all", "cursor", "windsurf", "copilot", "claude"],
|
|
733
|
+
description: "Target platform (default: all)",
|
|
734
|
+
default: "all",
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
];
|
|
1039
740
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
tools: TOOLS,
|
|
1044
|
-
}));
|
|
741
|
+
// ============================================================================
|
|
742
|
+
// SERVER IMPLEMENTATION
|
|
743
|
+
// ============================================================================
|
|
1045
744
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
// HARDENING: Input extraction with validation
|
|
1055
|
-
// ====================================================================
|
|
1056
|
-
const params = request?.params;
|
|
1057
|
-
if (!params || typeof params !== 'object') {
|
|
1058
|
-
return this.error('Invalid request: missing params', { code: 'INVALID_REQUEST' });
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
toolName = sanitizeString(params.name, 100);
|
|
1062
|
-
const args = params.arguments && typeof params.arguments === 'object' ? params.arguments : {};
|
|
1063
|
-
|
|
1064
|
-
// Validate tool name
|
|
1065
|
-
if (!toolName || toolName.length < 2) {
|
|
1066
|
-
return this.error('Invalid tool name', { code: 'INVALID_TOOL_NAME' });
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// ====================================================================
|
|
1070
|
-
// HARDENING: Rate limiting (per-API-key)
|
|
1071
|
-
// ====================================================================
|
|
1072
|
-
const apiKey = args?.apiKey || null;
|
|
1073
|
-
const rateCheck = checkRateLimit(apiKey);
|
|
1074
|
-
if (!rateCheck.allowed) {
|
|
1075
|
-
return this.error(`Rate limit exceeded. Try again in ${Math.ceil(rateCheck.resetIn / 1000)} seconds`, {
|
|
1076
|
-
code: 'RATE_LIMIT_EXCEEDED',
|
|
1077
|
-
suggestion: 'Reduce the frequency of tool calls',
|
|
1078
|
-
nextSteps: [`Wait ${Math.ceil(rateCheck.resetIn / 1000)} seconds before retrying`],
|
|
1079
|
-
});
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// ====================================================================
|
|
1083
|
-
// HARDENING: Project path validation
|
|
1084
|
-
// ====================================================================
|
|
1085
|
-
const rawProjectPath = args?.projectPath || '.';
|
|
1086
|
-
const pathValidation = sanitizePath(rawProjectPath, process.cwd());
|
|
1087
|
-
|
|
1088
|
-
if (!pathValidation.valid) {
|
|
1089
|
-
return this.error(pathValidation.error, {
|
|
1090
|
-
code: 'INVALID_PATH',
|
|
1091
|
-
suggestion: 'Provide a valid path within the current working directory',
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
projectPath = pathValidation.path;
|
|
1095
|
-
|
|
1096
|
-
// Emit audit event for tool invocation start
|
|
1097
|
-
// SECURITY: Include apiKey hash for audit trail (never log raw key)
|
|
1098
|
-
try {
|
|
1099
|
-
const crypto = require('crypto');
|
|
1100
|
-
const apiKeyHash = apiKey
|
|
1101
|
-
? crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 16)
|
|
1102
|
-
: 'anonymous';
|
|
1103
|
-
|
|
1104
|
-
emitToolInvoke(toolName, args, "success", {
|
|
1105
|
-
projectPath,
|
|
1106
|
-
apiKeyHash,
|
|
1107
|
-
rateLimit: rateCheck,
|
|
1108
|
-
timestamp: new Date().toISOString(),
|
|
1109
|
-
});
|
|
1110
|
-
} catch {
|
|
1111
|
-
// Audit logging should never break the tool
|
|
1112
|
-
}
|
|
745
|
+
class VibecheckMCP {
|
|
746
|
+
constructor() {
|
|
747
|
+
this.server = new Server(
|
|
748
|
+
{ name: "vibecheck", version: VERSION },
|
|
749
|
+
{ capabilities: { tools: {}, resources: {} } },
|
|
750
|
+
);
|
|
751
|
+
this.setupHandlers();
|
|
752
|
+
}
|
|
1113
753
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
754
|
+
setupHandlers() {
|
|
755
|
+
// List tools
|
|
756
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
757
|
+
tools: TOOLS,
|
|
758
|
+
}));
|
|
759
|
+
|
|
760
|
+
// Call tool
|
|
761
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
762
|
+
const { name, arguments: args } = request.params;
|
|
763
|
+
const projectPath = path.resolve(args?.projectPath || ".");
|
|
764
|
+
const startTime = Date.now();
|
|
765
|
+
|
|
766
|
+
// Emit audit event for tool invocation start
|
|
767
|
+
emitToolInvoke(name, args, "success", { projectPath });
|
|
768
|
+
|
|
769
|
+
try {
|
|
770
|
+
const policy = getTruthPolicy(args);
|
|
771
|
+
if (
|
|
772
|
+
STRICT_GUARDRAIL_TOOLS.has(name) &&
|
|
773
|
+
policy !== "permissive" &&
|
|
774
|
+
!hasRecentClaimValidation(projectPath)
|
|
775
|
+
) {
|
|
776
|
+
await emitGuardrailMetric(projectPath, {
|
|
777
|
+
event: "truth_firewall_block",
|
|
778
|
+
tool: name,
|
|
779
|
+
policy,
|
|
780
|
+
reason: "no_recent_claim_validation",
|
|
1141
781
|
});
|
|
782
|
+
return this.error(
|
|
783
|
+
"Truth firewall requires claim validation before high-impact tools.",
|
|
784
|
+
{
|
|
785
|
+
code: "TRUTH_FIREWALL_REQUIRED",
|
|
786
|
+
suggestion: "Call vibecheck.validate_claim or vibecheck.get_truthpack before proceeding.",
|
|
787
|
+
nextSteps: [
|
|
788
|
+
"Call vibecheck.get_truthpack with refresh=true for current evidence",
|
|
789
|
+
"Call vibecheck.validate_claim for critical assumptions",
|
|
790
|
+
"Re-run the tool after validation",
|
|
791
|
+
],
|
|
792
|
+
},
|
|
793
|
+
);
|
|
1142
794
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
// SECURITY FIX: Never trust client-provided tier - validate from API key
|
|
1147
|
-
// Previous: const userTier = sanitizeString(args?.tier, 20) || ...
|
|
1148
|
-
// This allowed privilege escalation via args.tier = "pro"
|
|
1149
|
-
const { getMcpToolAccess } = await import('./tier-auth.js');
|
|
1150
|
-
const access = await getMcpToolAccess(toolName, apiKey);
|
|
1151
|
-
const userTier = access.tier || 'free';
|
|
1152
|
-
const result = await handleToolV3(toolName, args, { tier: userTier });
|
|
1153
|
-
|
|
1154
|
-
if (result.error) {
|
|
1155
|
-
return this.error(result.error, { tier: result.tier, required: result.required });
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
// Sanitize and truncate output
|
|
1159
|
-
const outputText = truncateOutput(redactSensitive(JSON.stringify(result, null, 2)));
|
|
1160
|
-
return {
|
|
1161
|
-
content: [{ type: "text", text: outputText }],
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
// 1. Check tool registry first (local CLI handlers)
|
|
1166
|
-
if (this.toolRegistry[toolName]) {
|
|
1167
|
-
return await this.toolRegistry[toolName](projectPath, args);
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
// 2. Handle external module tools by prefix/pattern
|
|
1171
|
-
if (toolName.startsWith("vibecheck.intelligence.")) {
|
|
1172
|
-
return await handleIntelligenceTool(toolName, args, __dirname);
|
|
795
|
+
// Handle intelligence tools first
|
|
796
|
+
if (name.startsWith("vibecheck.intelligence.")) {
|
|
797
|
+
return await handleIntelligenceTool(name, args, __dirname);
|
|
1173
798
|
}
|
|
1174
799
|
|
|
1175
|
-
// Handle AI vibecheck tools
|
|
800
|
+
// Handle AI vibecheck tools (verify, quality, smells, hallucination, breaking, mdc, coverage)
|
|
1176
801
|
if (["vibecheck.verify", "vibecheck.quality", "vibecheck.smells",
|
|
1177
802
|
"vibecheck.hallucination", "vibecheck.breaking", "vibecheck.mdc",
|
|
1178
|
-
"vibecheck.coverage"].includes(
|
|
1179
|
-
const result = await handleVibecheckTool(
|
|
1180
|
-
const outputText = truncateOutput(redactSensitive(JSON.stringify(result, null, 2)));
|
|
803
|
+
"vibecheck.coverage"].includes(name)) {
|
|
804
|
+
const result = await handleVibecheckTool(name, args);
|
|
1181
805
|
return {
|
|
1182
|
-
content: [{ type: "text", text:
|
|
806
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1183
807
|
};
|
|
1184
808
|
}
|
|
1185
809
|
|
|
1186
810
|
// Handle agent checkpoint tools
|
|
1187
|
-
if (["vibecheck_checkpoint", "vibecheck_set_strictness", "vibecheck_checkpoint_status"].includes(
|
|
1188
|
-
return await handleCheckpointTool(
|
|
811
|
+
if (["vibecheck_checkpoint", "vibecheck_set_strictness", "vibecheck_checkpoint_status"].includes(name)) {
|
|
812
|
+
return await handleCheckpointTool(name, args);
|
|
1189
813
|
}
|
|
1190
814
|
|
|
1191
815
|
// Handle architect tools
|
|
1192
816
|
if (["vibecheck_architect_review", "vibecheck_architect_suggest",
|
|
1193
|
-
"vibecheck_architect_patterns", "vibecheck_architect_set_strictness"].includes(
|
|
1194
|
-
return await handleArchitectTool(
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
// Handle authority system tools
|
|
1198
|
-
if (["authority.classify", "authority.approve", "authority.list"].includes(toolName)) {
|
|
1199
|
-
const result = await handleAuthorityTool(toolName, args, userTier || "free");
|
|
1200
|
-
const outputText = truncateOutput(redactSensitive(JSON.stringify(result, null, 2)));
|
|
1201
|
-
return {
|
|
1202
|
-
content: [{ type: "text", text: outputText }],
|
|
1203
|
-
};
|
|
817
|
+
"vibecheck_architect_patterns", "vibecheck_architect_set_strictness"].includes(name)) {
|
|
818
|
+
return await handleArchitectTool(name, args);
|
|
1204
819
|
}
|
|
1205
820
|
|
|
1206
821
|
// Handle codebase architect tools
|
|
1207
822
|
if (["vibecheck_architect_context", "vibecheck_architect_guide",
|
|
1208
823
|
"vibecheck_architect_validate", "vibecheck_architect_patterns",
|
|
1209
|
-
"vibecheck_architect_dependencies"].includes(
|
|
1210
|
-
return await handleCodebaseArchitectTool(
|
|
824
|
+
"vibecheck_architect_dependencies"].includes(name)) {
|
|
825
|
+
return await handleCodebaseArchitectTool(name, args);
|
|
1211
826
|
}
|
|
1212
827
|
|
|
1213
828
|
// Handle vibecheck 2.0 tools
|
|
1214
|
-
if (["checkpoint", "check", "ship", "fix", "status", "set_strictness"].includes(
|
|
1215
|
-
return await handleVibecheck2Tool(
|
|
829
|
+
if (["checkpoint", "check", "ship", "fix", "status", "set_strictness"].includes(name)) {
|
|
830
|
+
return await handleVibecheck2Tool(name, args, __dirname);
|
|
1216
831
|
}
|
|
1217
832
|
|
|
1218
833
|
// Handle intent drift tools
|
|
1219
|
-
if (
|
|
1220
|
-
const tool = intentDriftTools.find(t => t.name ===
|
|
834
|
+
if (name.startsWith("vibecheck_intent_")) {
|
|
835
|
+
const tool = intentDriftTools.find(t => t.name === name);
|
|
1221
836
|
if (tool && tool.handler) {
|
|
1222
837
|
const result = await tool.handler(args);
|
|
1223
|
-
const outputText = truncateOutput(redactSensitive(JSON.stringify(result, null, 2)));
|
|
1224
838
|
return {
|
|
1225
|
-
content: [{ type: "text", text:
|
|
839
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1226
840
|
};
|
|
1227
841
|
}
|
|
1228
842
|
}
|
|
1229
843
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
844
|
+
switch (name) {
|
|
845
|
+
case "vibecheck.ship":
|
|
846
|
+
return await this.handleShip(projectPath, args);
|
|
847
|
+
case "vibecheck.scan":
|
|
848
|
+
return await this.handleScan(projectPath, args);
|
|
849
|
+
case "vibecheck.verify":
|
|
850
|
+
return await this.handleVerify(projectPath, args);
|
|
851
|
+
case "vibecheck.reality":
|
|
852
|
+
return await this.handleReality(projectPath, args);
|
|
853
|
+
case "vibecheckai.dev-test":
|
|
854
|
+
return await this.handleAITest(projectPath, args);
|
|
855
|
+
case "vibecheck.gate":
|
|
856
|
+
return await this.handleGate(projectPath, args);
|
|
857
|
+
case "vibecheck.fix":
|
|
858
|
+
return await this.handleFix(projectPath, args);
|
|
859
|
+
case "vibecheck.share":
|
|
860
|
+
return await this.handleShare(projectPath, args);
|
|
861
|
+
case "vibecheck.ctx":
|
|
862
|
+
return await this.handleCtx(projectPath, args);
|
|
863
|
+
case "vibecheck.prove":
|
|
864
|
+
return await this.handleProve(projectPath, args);
|
|
865
|
+
case "vibecheck.proof":
|
|
866
|
+
return await this.handleProof(projectPath, args);
|
|
867
|
+
case "vibecheck.validate":
|
|
868
|
+
return await this.handleValidate(projectPath, args);
|
|
869
|
+
case "vibecheck.report":
|
|
870
|
+
return await this.handleReport(projectPath, args);
|
|
871
|
+
case "vibecheck.status":
|
|
872
|
+
return await this.handleStatus(projectPath, args);
|
|
873
|
+
case "vibecheck.autopilot":
|
|
874
|
+
return await this.handleAutopilot(projectPath, args);
|
|
875
|
+
case "vibecheck.autopilot_plan":
|
|
876
|
+
return await this.handleAutopilotPlan(projectPath, args);
|
|
877
|
+
case "vibecheck.autopilot_apply":
|
|
878
|
+
return await this.handleAutopilotApply(projectPath, args);
|
|
879
|
+
case "vibecheck.badge":
|
|
880
|
+
return await this.handleBadge(projectPath, args);
|
|
881
|
+
case "vibecheck.context":
|
|
882
|
+
return await this.handleContext(projectPath, args);
|
|
883
|
+
case "generate_mdc":
|
|
884
|
+
return await handleMDCGeneration(args);
|
|
885
|
+
// Truth Context tools (Evidence Pack / Truth Pack)
|
|
886
|
+
case "vibecheck.verify_claim":
|
|
887
|
+
case "vibecheck.evidence":
|
|
888
|
+
return await handleTruthContextTool(name, args);
|
|
889
|
+
// Truth Firewall tools (Hallucination Stopper)
|
|
890
|
+
case "vibecheck.get_truthpack":
|
|
891
|
+
case "vibecheck.validate_claim":
|
|
892
|
+
case "vibecheck.compile_context":
|
|
893
|
+
case "vibecheck.search_evidence":
|
|
894
|
+
case "vibecheck.find_counterexamples":
|
|
895
|
+
case "vibecheck.propose_patch":
|
|
896
|
+
case "vibecheck.check_invariants":
|
|
897
|
+
case "vibecheck.add_assumption":
|
|
898
|
+
return await handleTruthFirewallTool(name, args, projectPath);
|
|
899
|
+
default:
|
|
900
|
+
return this.error(`Unknown tool: ${name}`);
|
|
1245
901
|
}
|
|
1246
|
-
|
|
1247
|
-
return this.error(`Unknown tool: ${toolName}`, {
|
|
1248
|
-
code: 'UNKNOWN_TOOL',
|
|
1249
|
-
suggestion: 'Check the tool name and try again',
|
|
1250
|
-
nextSteps: ['Use vibecheck.status to see available tools'],
|
|
1251
|
-
});
|
|
1252
902
|
} catch (err) {
|
|
1253
|
-
//
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
const errorMessage = sanitizeString(err?.message || 'Unknown error', 500);
|
|
1258
|
-
|
|
1259
|
-
// Emit audit event for tool error (safely)
|
|
1260
|
-
try {
|
|
1261
|
-
emitToolComplete(toolName, "error", {
|
|
1262
|
-
errorMessage: redactSensitive(errorMessage),
|
|
1263
|
-
durationMs,
|
|
1264
|
-
});
|
|
1265
|
-
} catch {
|
|
1266
|
-
// Audit logging should never break the response
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
// Log error details to stderr (not stdout - preserves MCP protocol)
|
|
1270
|
-
console.error(`[MCP] Tool ${toolName} failed after ${durationMs}ms: ${errorMessage}`);
|
|
1271
|
-
|
|
1272
|
-
return this.error(`${toolName} failed: ${redactSensitive(errorMessage)}`, {
|
|
1273
|
-
code: err?.code || 'TOOL_ERROR',
|
|
1274
|
-
suggestion: 'Check the error message and try again',
|
|
1275
|
-
nextSteps: [
|
|
1276
|
-
'Verify the tool arguments are correct',
|
|
1277
|
-
'Check that the project path is valid',
|
|
1278
|
-
'Try running with simpler arguments first',
|
|
1279
|
-
],
|
|
903
|
+
// Emit audit event for tool error
|
|
904
|
+
emitToolComplete(name, "error", {
|
|
905
|
+
errorMessage: err.message,
|
|
906
|
+
durationMs: Date.now() - startTime
|
|
1280
907
|
});
|
|
908
|
+
return this.error(`${name} failed: ${err.message}`);
|
|
1281
909
|
}
|
|
1282
910
|
});
|
|
1283
911
|
|
|
1284
912
|
// Resources
|
|
1285
913
|
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
1286
914
|
resources: [
|
|
1287
|
-
{
|
|
1288
|
-
uri: "vibecheck://status",
|
|
1289
|
-
name: "Server Status",
|
|
1290
|
-
description: "Current MCP server health, version, and rate limits",
|
|
1291
|
-
mimeType: "application/json",
|
|
1292
|
-
},
|
|
1293
915
|
{
|
|
1294
916
|
uri: "vibecheck://config",
|
|
1295
917
|
name: "vibecheck Config",
|
|
@@ -1338,53 +960,21 @@ class VibecheckMCP {
|
|
|
1338
960
|
description: "Last prove loop results (ctx ā reality ā ship ā fix)",
|
|
1339
961
|
mimeType: "application/json",
|
|
1340
962
|
},
|
|
1341
|
-
{
|
|
1342
|
-
uri: "vibecheck://registry",
|
|
1343
|
-
name: "Command Registry",
|
|
1344
|
-
description: "Available commands and their tiers",
|
|
1345
|
-
mimeType: "application/json",
|
|
1346
|
-
},
|
|
1347
963
|
],
|
|
1348
964
|
}));
|
|
1349
965
|
|
|
1350
966
|
this.server.setRequestHandler(
|
|
1351
967
|
ReadResourceRequestSchema,
|
|
1352
968
|
async (request) => {
|
|
1353
|
-
|
|
1354
|
-
// HARDENING: Resource request validation
|
|
1355
|
-
// ====================================================================
|
|
1356
|
-
const uri = sanitizeString(request?.params?.uri, 200);
|
|
1357
|
-
if (!uri || !uri.startsWith('vibecheck://')) {
|
|
1358
|
-
return { contents: [{ uri: uri || '', mimeType: "application/json", text: '{"error": "Invalid resource URI"}' }] };
|
|
1359
|
-
}
|
|
1360
|
-
|
|
969
|
+
const { uri } = request.params;
|
|
1361
970
|
const projectPath = process.cwd();
|
|
1362
|
-
|
|
1363
|
-
// Helper to safely read and return JSON resource
|
|
1364
|
-
const safeReadResource = async (filePath, defaultMessage) => {
|
|
1365
|
-
try {
|
|
1366
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
1367
|
-
// Validate JSON and sanitize
|
|
1368
|
-
const parsed = safeJsonParse(content);
|
|
1369
|
-
if (!parsed.success) {
|
|
1370
|
-
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ error: "Invalid JSON in resource file" }) }] };
|
|
1371
|
-
}
|
|
1372
|
-
// Redact any sensitive data and truncate
|
|
1373
|
-
const sanitized = redactSensitive(truncateOutput(content, CONFIG.LIMITS.MAX_OUTPUT_LENGTH));
|
|
1374
|
-
return { contents: [{ uri, mimeType: "application/json", text: sanitized }] };
|
|
1375
|
-
} catch {
|
|
1376
|
-
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ message: defaultMessage }) }] };
|
|
1377
|
-
}
|
|
1378
|
-
};
|
|
1379
971
|
|
|
1380
972
|
if (uri === "vibecheck://config") {
|
|
1381
973
|
const configPath = path.join(projectPath, "vibecheck.config.json");
|
|
1382
974
|
try {
|
|
1383
975
|
const content = await fs.readFile(configPath, "utf-8");
|
|
1384
|
-
// Redact sensitive config values
|
|
1385
|
-
const sanitized = redactSensitive(truncateOutput(content, CONFIG.LIMITS.MAX_OUTPUT_LENGTH));
|
|
1386
976
|
return {
|
|
1387
|
-
contents: [{ uri, mimeType: "application/json", text:
|
|
977
|
+
contents: [{ uri, mimeType: "application/json", text: content }],
|
|
1388
978
|
};
|
|
1389
979
|
} catch {
|
|
1390
980
|
return {
|
|
@@ -1394,237 +984,138 @@ class VibecheckMCP {
|
|
|
1394
984
|
}
|
|
1395
985
|
|
|
1396
986
|
if (uri === "vibecheck://summary") {
|
|
1397
|
-
const summaryPath = path.join(
|
|
1398
|
-
|
|
987
|
+
const summaryPath = path.join(
|
|
988
|
+
projectPath,
|
|
989
|
+
".vibecheck",
|
|
990
|
+
"summary.json",
|
|
991
|
+
);
|
|
992
|
+
try {
|
|
993
|
+
const content = await fs.readFile(summaryPath, "utf-8");
|
|
994
|
+
return {
|
|
995
|
+
contents: [{ uri, mimeType: "application/json", text: content }],
|
|
996
|
+
};
|
|
997
|
+
} catch {
|
|
998
|
+
return {
|
|
999
|
+
contents: [
|
|
1000
|
+
{
|
|
1001
|
+
uri,
|
|
1002
|
+
mimeType: "application/json",
|
|
1003
|
+
text: '{"message": "No scan found. Run vibecheck.scan first."}',
|
|
1004
|
+
},
|
|
1005
|
+
],
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1399
1008
|
}
|
|
1400
1009
|
|
|
1401
1010
|
if (uri === "vibecheck://truthpack") {
|
|
1402
1011
|
const truthpackPath = path.join(projectPath, ".vibecheck", "truth", "truthpack.json");
|
|
1403
|
-
|
|
1012
|
+
try {
|
|
1013
|
+
const content = await fs.readFile(truthpackPath, "utf-8");
|
|
1014
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1015
|
+
} catch {
|
|
1016
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No truthpack. Run vibecheck.ctx first."}' }] };
|
|
1017
|
+
}
|
|
1404
1018
|
}
|
|
1405
1019
|
|
|
1406
1020
|
if (uri === "vibecheck://missions") {
|
|
1407
1021
|
const missionsDir = path.join(projectPath, ".vibecheck", "missions");
|
|
1408
1022
|
try {
|
|
1409
|
-
// HARDENING: Validate directory read
|
|
1410
1023
|
const dirs = await fs.readdir(missionsDir);
|
|
1411
|
-
const
|
|
1412
|
-
const latest = safeDirs.sort().reverse()[0];
|
|
1413
|
-
|
|
1024
|
+
const latest = dirs.sort().reverse()[0];
|
|
1414
1025
|
if (latest) {
|
|
1415
1026
|
const missionPath = path.join(missionsDir, latest, "missions.json");
|
|
1416
|
-
|
|
1027
|
+
const content = await fs.readFile(missionPath, "utf-8");
|
|
1028
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1417
1029
|
}
|
|
1418
|
-
} catch
|
|
1419
|
-
console.error(`[MCP] Error reading missions: ${err.message}`);
|
|
1420
|
-
}
|
|
1030
|
+
} catch {}
|
|
1421
1031
|
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No missions. Run vibecheck.fix first."}' }] };
|
|
1422
1032
|
}
|
|
1423
1033
|
|
|
1424
1034
|
if (uri === "vibecheck://reality") {
|
|
1425
1035
|
const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
|
|
1426
|
-
|
|
1036
|
+
try {
|
|
1037
|
+
const content = await fs.readFile(realityPath, "utf-8");
|
|
1038
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1039
|
+
} catch {
|
|
1040
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No reality results. Run vibecheck verify first."}' }] };
|
|
1041
|
+
}
|
|
1427
1042
|
}
|
|
1428
1043
|
|
|
1429
1044
|
if (uri === "vibecheck://findings") {
|
|
1430
1045
|
const findingsPath = path.join(projectPath, ".vibecheck", "findings.json");
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
// HARDENING: Try summary.json as fallback with size limits
|
|
1443
|
-
const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
|
|
1444
|
-
const summaryContent = await this.safeReadFile(summaryPath, 10 * 1024 * 1024);
|
|
1445
|
-
|
|
1446
|
-
if (summaryContent) {
|
|
1447
|
-
const parsed = safeJsonParse(summaryContent);
|
|
1448
|
-
if (parsed.success && parsed.data.findings) {
|
|
1449
|
-
const findings = sanitizeArray(parsed.data.findings, 1000);
|
|
1046
|
+
try {
|
|
1047
|
+
const content = await fs.readFile(findingsPath, "utf-8");
|
|
1048
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1049
|
+
} catch {
|
|
1050
|
+
// Try summary.json as fallback
|
|
1051
|
+
const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
|
|
1052
|
+
try {
|
|
1053
|
+
const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
|
|
1054
|
+
const findings = summary.findings || [];
|
|
1450
1055
|
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ findings }, null, 2) }] };
|
|
1056
|
+
} catch {
|
|
1057
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No findings. Run vibecheck.scan first."}' }] };
|
|
1451
1058
|
}
|
|
1452
1059
|
}
|
|
1453
|
-
|
|
1454
|
-
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No findings. Run vibecheck.scan first."}' }] };
|
|
1455
1060
|
}
|
|
1456
1061
|
|
|
1457
1062
|
if (uri === "vibecheck://share") {
|
|
1458
1063
|
const missionsDir = path.join(projectPath, ".vibecheck", "missions");
|
|
1459
1064
|
try {
|
|
1460
|
-
// HARDENING: Safe directory read
|
|
1461
1065
|
const dirs = await fs.readdir(missionsDir);
|
|
1462
|
-
const
|
|
1463
|
-
const latest = safeDirs.sort().reverse()[0];
|
|
1464
|
-
|
|
1066
|
+
const latest = dirs.sort().reverse()[0];
|
|
1465
1067
|
if (latest) {
|
|
1466
1068
|
const sharePath = path.join(missionsDir, latest, "share", "share.json");
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
}
|
|
1069
|
+
try {
|
|
1070
|
+
const content = await fs.readFile(sharePath, "utf-8");
|
|
1071
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1072
|
+
} catch {
|
|
1073
|
+
// Fallback to missions.json
|
|
1074
|
+
const missionPath = path.join(missionsDir, latest, "missions.json");
|
|
1075
|
+
const content = await fs.readFile(missionPath, "utf-8");
|
|
1076
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1476
1077
|
}
|
|
1477
|
-
|
|
1478
|
-
// HARDENING: Fallback to missions.json with safe read
|
|
1479
|
-
const missionPath = path.join(missionsDir, latest, "missions.json");
|
|
1480
|
-
return await safeReadResource(missionPath, "No share data available in latest mission.");
|
|
1481
1078
|
}
|
|
1482
|
-
} catch
|
|
1483
|
-
console.error(`[MCP] Error reading share pack: ${err.message}`);
|
|
1484
|
-
}
|
|
1079
|
+
} catch {}
|
|
1485
1080
|
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No share pack. Run vibecheck.fix --share first."}' }] };
|
|
1486
1081
|
}
|
|
1487
1082
|
|
|
1488
1083
|
if (uri === "vibecheck://prove") {
|
|
1489
1084
|
const provePath = path.join(projectPath, ".vibecheck", "prove", "last_prove.json");
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
const circuitCheck = checkCircuitBreaker();
|
|
1497
|
-
|
|
1498
|
-
const status = {
|
|
1499
|
-
server: {
|
|
1500
|
-
version: CONFIG.VERSION,
|
|
1501
|
-
uptime: Math.floor(process.uptime()),
|
|
1502
|
-
transport: 'stdio',
|
|
1503
|
-
nodeVersion: process.version,
|
|
1504
|
-
},
|
|
1505
|
-
health: {
|
|
1506
|
-
status: circuitCheck.allowed ? 'healthy' : 'degraded',
|
|
1507
|
-
circuitBreaker: circuitBreakerState.state,
|
|
1508
|
-
failureCount: circuitBreakerState.failures,
|
|
1509
|
-
},
|
|
1510
|
-
rateLimit: {
|
|
1511
|
-
remaining: rateCheck.remaining,
|
|
1512
|
-
resetIn: rateCheck.resetIn || 0,
|
|
1513
|
-
limit: CONFIG.LIMITS.RATE_LIMIT_MAX_CALLS,
|
|
1514
|
-
window: CONFIG.LIMITS.RATE_LIMIT_WINDOW_MS,
|
|
1515
|
-
},
|
|
1516
|
-
features: {
|
|
1517
|
-
hardening: true,
|
|
1518
|
-
auditLogging: true,
|
|
1519
|
-
secretRedaction: true,
|
|
1520
|
-
pathSecurity: true,
|
|
1521
|
-
},
|
|
1522
|
-
timestamp: new Date().toISOString(),
|
|
1523
|
-
};
|
|
1524
|
-
|
|
1525
|
-
return {
|
|
1526
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(status, null, 2) }],
|
|
1527
|
-
};
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
// Registry resource - available commands and tiers
|
|
1531
|
-
if (uri === "vibecheck://registry") {
|
|
1532
|
-
const registry = {
|
|
1533
|
-
version: CONFIG.VERSION,
|
|
1534
|
-
tiers: {
|
|
1535
|
-
free: {
|
|
1536
|
-
name: 'FREE',
|
|
1537
|
-
price: '$0',
|
|
1538
|
-
description: 'Inspect & Observe',
|
|
1539
|
-
},
|
|
1540
|
-
pro: {
|
|
1541
|
-
name: 'PRO',
|
|
1542
|
-
price: '$69/mo',
|
|
1543
|
-
description: 'Fix, Prove & Enforce',
|
|
1544
|
-
},
|
|
1545
|
-
},
|
|
1546
|
-
tools: Object.entries(V3_TOOL_TIERS).map(([name, tier]) => ({
|
|
1547
|
-
name,
|
|
1548
|
-
tier,
|
|
1549
|
-
})),
|
|
1550
|
-
resources: [
|
|
1551
|
-
'vibecheck://status',
|
|
1552
|
-
'vibecheck://config',
|
|
1553
|
-
'vibecheck://summary',
|
|
1554
|
-
'vibecheck://truthpack',
|
|
1555
|
-
'vibecheck://missions',
|
|
1556
|
-
'vibecheck://reality',
|
|
1557
|
-
'vibecheck://findings',
|
|
1558
|
-
'vibecheck://share',
|
|
1559
|
-
'vibecheck://prove',
|
|
1560
|
-
'vibecheck://registry',
|
|
1561
|
-
],
|
|
1562
|
-
};
|
|
1563
|
-
|
|
1564
|
-
return {
|
|
1565
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(registry, null, 2) }],
|
|
1566
|
-
};
|
|
1085
|
+
try {
|
|
1086
|
+
const content = await fs.readFile(provePath, "utf-8");
|
|
1087
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1088
|
+
} catch {
|
|
1089
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No prove results. Run vibecheck.prove first."}' }] };
|
|
1090
|
+
}
|
|
1567
1091
|
}
|
|
1568
1092
|
|
|
1569
|
-
return {
|
|
1570
|
-
contents: [{
|
|
1571
|
-
uri,
|
|
1572
|
-
mimeType: "application/json",
|
|
1573
|
-
text: JSON.stringify({ error: "Unknown resource URI" })
|
|
1574
|
-
}]
|
|
1575
|
-
};
|
|
1093
|
+
return { contents: [] };
|
|
1576
1094
|
},
|
|
1577
1095
|
);
|
|
1578
1096
|
}
|
|
1579
1097
|
|
|
1580
|
-
//
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
/**
|
|
1585
|
-
* Return a successful response with sanitization
|
|
1586
|
-
* @param {string} text - Response text
|
|
1587
|
-
* @param {boolean} includeAttribution - Include vibecheck attribution
|
|
1588
|
-
* @returns {object} MCP response
|
|
1589
|
-
*/
|
|
1590
|
-
success(text, includeAttribution = true) {
|
|
1591
|
-
// Sanitize output: redact secrets and truncate
|
|
1592
|
-
let sanitized = redactSensitive(sanitizeString(text, CONFIG.LIMITS.MAX_OUTPUT_LENGTH));
|
|
1593
|
-
|
|
1594
|
-
const finalText = includeAttribution
|
|
1595
|
-
? `${sanitized}\n\n---\n_${CONTEXT_ATTRIBUTION}_`
|
|
1596
|
-
: sanitized;
|
|
1597
|
-
|
|
1598
|
-
return { content: [{ type: "text", text: finalText }] };
|
|
1098
|
+
// Helpers
|
|
1099
|
+
success(text) {
|
|
1100
|
+
return { content: [{ type: "text", text }] };
|
|
1599
1101
|
}
|
|
1600
1102
|
|
|
1601
|
-
/**
|
|
1602
|
-
* Return an error response with sanitization
|
|
1603
|
-
* @param {string} text - Error message
|
|
1604
|
-
* @param {object} options - Additional options
|
|
1605
|
-
* @returns {object} MCP error response
|
|
1606
|
-
*/
|
|
1607
1103
|
error(text, options = {}) {
|
|
1608
1104
|
const { code, suggestion, nextSteps = [] } = options;
|
|
1609
1105
|
|
|
1610
|
-
|
|
1611
|
-
const sanitizedText = redactSensitive(sanitizeString(text, 1000));
|
|
1612
|
-
const sanitizedSuggestion = suggestion ? redactSensitive(sanitizeString(suggestion, 500)) : null;
|
|
1613
|
-
const sanitizedSteps = sanitizeArray(nextSteps, 10).map(s => sanitizeString(s, 200));
|
|
1614
|
-
|
|
1615
|
-
let errorText = `ā ${sanitizedText}`;
|
|
1106
|
+
let errorText = `ā ${text}`;
|
|
1616
1107
|
|
|
1617
1108
|
if (code) {
|
|
1618
|
-
errorText += `\n\n**Error Code:** \`${
|
|
1109
|
+
errorText += `\n\n**Error Code:** \`${code}\``;
|
|
1619
1110
|
}
|
|
1620
1111
|
|
|
1621
|
-
if (
|
|
1622
|
-
errorText += `\n\nš” **Suggestion:** ${
|
|
1112
|
+
if (suggestion) {
|
|
1113
|
+
errorText += `\n\nš” **Suggestion:** ${suggestion}`;
|
|
1623
1114
|
}
|
|
1624
1115
|
|
|
1625
|
-
if (
|
|
1116
|
+
if (nextSteps.length > 0) {
|
|
1626
1117
|
errorText += `\n\n**Next Steps:**\n`;
|
|
1627
|
-
|
|
1118
|
+
nextSteps.forEach((step, i) => {
|
|
1628
1119
|
errorText += `${i + 1}. ${step}\n`;
|
|
1629
1120
|
});
|
|
1630
1121
|
}
|
|
@@ -1632,10 +1123,10 @@ class VibecheckMCP {
|
|
|
1632
1123
|
return { content: [{ type: "text", text: errorText }], isError: true };
|
|
1633
1124
|
}
|
|
1634
1125
|
|
|
1635
|
-
// Validate project path exists and is accessible
|
|
1126
|
+
// Validate project path exists and is accessible
|
|
1636
1127
|
validateProjectPath(projectPath) {
|
|
1637
1128
|
try {
|
|
1638
|
-
const stats =
|
|
1129
|
+
const stats = require("fs").statSync(projectPath);
|
|
1639
1130
|
if (!stats.isDirectory()) {
|
|
1640
1131
|
return {
|
|
1641
1132
|
valid: false,
|
|
@@ -1677,157 +1168,47 @@ class VibecheckMCP {
|
|
|
1677
1168
|
});
|
|
1678
1169
|
}
|
|
1679
1170
|
|
|
1680
|
-
|
|
1681
|
-
const
|
|
1682
|
-
const
|
|
1683
|
-
|
|
1684
|
-
// HARDENING: Sanitize only array
|
|
1685
|
-
const only = sanitizeArray(args?.only, 20).map(item => sanitizeString(item, 50));
|
|
1686
|
-
|
|
1687
|
-
// Initialize API integration with timeout and circuit breaker
|
|
1688
|
-
let apiScan = null;
|
|
1689
|
-
let apiConnected = false;
|
|
1690
|
-
|
|
1691
|
-
// HARDENING: Check circuit breaker before attempting API calls
|
|
1692
|
-
const circuitCheck = checkCircuitBreaker();
|
|
1693
|
-
|
|
1694
|
-
// Try to connect to API for dashboard integration
|
|
1695
|
-
if (circuitCheck.allowed) {
|
|
1696
|
-
try {
|
|
1697
|
-
// HARDENING: Add timeout to API availability check
|
|
1698
|
-
const apiCheckPromise = isApiAvailable();
|
|
1699
|
-
const timeoutPromise = new Promise((_, reject) =>
|
|
1700
|
-
setTimeout(() => reject(new Error('API check timeout')), 5000)
|
|
1701
|
-
);
|
|
1702
|
-
|
|
1703
|
-
apiConnected = await Promise.race([apiCheckPromise, timeoutPromise]);
|
|
1704
|
-
|
|
1705
|
-
if (apiConnected) {
|
|
1706
|
-
// Create scan record in dashboard
|
|
1707
|
-
const createScanPromise = createScan({
|
|
1708
|
-
localPath: sanitizeString(projectPath, 500),
|
|
1709
|
-
branch: sanitizeString(args?.branch, 100) || 'main',
|
|
1710
|
-
enableLLM: false,
|
|
1711
|
-
});
|
|
1712
|
-
const scanTimeoutPromise = new Promise((_, reject) =>
|
|
1713
|
-
setTimeout(() => reject(new Error('Create scan timeout')), 10000)
|
|
1714
|
-
);
|
|
1715
|
-
|
|
1716
|
-
apiScan = await Promise.race([createScanPromise, scanTimeoutPromise]);
|
|
1717
|
-
console.error(`[MCP] Connected to dashboard (Scan ID: ${apiScan.scanId})`);
|
|
1718
|
-
recordApiResult(true); // Record success
|
|
1719
|
-
}
|
|
1720
|
-
} catch (err) {
|
|
1721
|
-
// API connection is optional, continue without it
|
|
1722
|
-
console.error(`[MCP] Dashboard integration unavailable: ${err.message}`);
|
|
1723
|
-
recordApiResult(false); // Record failure
|
|
1724
|
-
}
|
|
1725
|
-
} else {
|
|
1726
|
-
console.error(`[MCP] ${circuitCheck.reason}`);
|
|
1727
|
-
}
|
|
1171
|
+
const profile = args?.profile || "quick";
|
|
1172
|
+
const format = args?.format || "text";
|
|
1173
|
+
const only = args?.only;
|
|
1728
1174
|
|
|
1729
1175
|
let output = "# š vibecheck Scan\n\n";
|
|
1730
1176
|
output += `**Profile:** ${profile}\n`;
|
|
1731
1177
|
output += `**Path:** ${projectPath}\n\n`;
|
|
1732
1178
|
|
|
1733
|
-
// Build CLI arguments array (secure - no injection possible)
|
|
1734
|
-
const cliArgs = [`--profile=${profile}`, "--json"];
|
|
1735
|
-
if (only.length > 0) {
|
|
1736
|
-
cliArgs.push(`--only=${only.join(",")}`);
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
1179
|
try {
|
|
1740
|
-
|
|
1180
|
+
// Build CLI command
|
|
1181
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" scan`;
|
|
1182
|
+
cmd += ` --profile=${profile}`;
|
|
1183
|
+
if (only?.length) cmd += ` --only=${only.join(",")}`;
|
|
1184
|
+
cmd += ` --json`;
|
|
1185
|
+
|
|
1186
|
+
const result = execSync(cmd, {
|
|
1187
|
+
cwd: projectPath,
|
|
1188
|
+
encoding: "utf8",
|
|
1189
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1190
|
+
});
|
|
1741
1191
|
|
|
1742
|
-
// Read summary
|
|
1743
|
-
const
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
filesScanned: sanitizeNumber(summary.stats?.filesScanned, 0, 1000000, 0),
|
|
1756
|
-
linesScanned: sanitizeNumber(summary.stats?.linesScanned, 0, 100000000, 0),
|
|
1757
|
-
durationMs: sanitizeNumber(summary.timings?.total, 0, 3600000, 0),
|
|
1758
|
-
metadata: {
|
|
1759
|
-
profile,
|
|
1760
|
-
source: 'mcp-server',
|
|
1761
|
-
version: CONFIG.VERSION,
|
|
1762
|
-
},
|
|
1763
|
-
});
|
|
1764
|
-
const submitTimeout = new Promise((_, reject) =>
|
|
1765
|
-
setTimeout(() => reject(new Error('Submit timeout')), 10000)
|
|
1766
|
-
);
|
|
1767
|
-
|
|
1768
|
-
await Promise.race([submitPromise, submitTimeout]);
|
|
1769
|
-
console.error(`[MCP] Results sent to dashboard`);
|
|
1770
|
-
} catch (err) {
|
|
1771
|
-
console.error(`[MCP] Failed to send results to dashboard: ${err.message}`);
|
|
1772
|
-
}
|
|
1192
|
+
// Read summary
|
|
1193
|
+
const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
|
|
1194
|
+
const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
|
|
1195
|
+
|
|
1196
|
+
output += `## Score: ${summary.score}/100 (${summary.grade})\n\n`;
|
|
1197
|
+
output += `**Verdict:** ${summary.canShip ? "ā
SHIP" : "š« NO-SHIP"}\n\n`;
|
|
1198
|
+
|
|
1199
|
+
if (summary.counts) {
|
|
1200
|
+
output += "### Checks\n\n";
|
|
1201
|
+
output += "| Category | Issues |\n|----------|--------|\n";
|
|
1202
|
+
for (const [key, count] of Object.entries(summary.counts)) {
|
|
1203
|
+
const icon = count === 0 ? "ā
" : "ā ļø";
|
|
1204
|
+
output += `| ${icon} ${key} | ${count} |\n`;
|
|
1773
1205
|
}
|
|
1774
1206
|
}
|
|
1207
|
+
|
|
1208
|
+
output += `\nš **Report:** .vibecheck/report.html\n`;
|
|
1775
1209
|
output += "\n---\n_Context Enhanced by vibecheck AI_\n";
|
|
1776
1210
|
return this.success(output);
|
|
1777
1211
|
} catch (err) {
|
|
1778
|
-
// Graceful degradation: check if scan completed but found issues (exit code 1)
|
|
1779
|
-
const summary = await this.parseSummaryFromDisk(projectPath);
|
|
1780
|
-
if (summary) {
|
|
1781
|
-
output += this.formatScanOutput(summary, projectPath);
|
|
1782
|
-
|
|
1783
|
-
// Submit results to dashboard if connected
|
|
1784
|
-
if (apiConnected && apiScan) {
|
|
1785
|
-
try {
|
|
1786
|
-
// HARDENING: Add timeout to error case submission
|
|
1787
|
-
const submitPromise = submitScanResults(apiScan.scanId, {
|
|
1788
|
-
verdict: sanitizeString(summary.verdict, 50) || 'UNKNOWN',
|
|
1789
|
-
score: sanitizeNumber(summary.score?.overall, 0, 100, 0),
|
|
1790
|
-
findings: sanitizeArray(summary.findings, 1000) || [],
|
|
1791
|
-
filesScanned: sanitizeNumber(summary.stats?.filesScanned, 0, 1000000, 0),
|
|
1792
|
-
linesScanned: sanitizeNumber(summary.stats?.linesScanned, 0, 100000000, 0),
|
|
1793
|
-
durationMs: sanitizeNumber(summary.timings?.total, 0, 3600000, 0),
|
|
1794
|
-
metadata: {
|
|
1795
|
-
profile,
|
|
1796
|
-
source: 'mcp-server',
|
|
1797
|
-
version: CONFIG.VERSION,
|
|
1798
|
-
error: sanitizeString(err.message, 500),
|
|
1799
|
-
},
|
|
1800
|
-
});
|
|
1801
|
-
const submitTimeout = new Promise((_, reject) =>
|
|
1802
|
-
setTimeout(() => reject(new Error('Submit timeout')), 10000)
|
|
1803
|
-
);
|
|
1804
|
-
|
|
1805
|
-
await Promise.race([submitPromise, submitTimeout]);
|
|
1806
|
-
console.error(`[MCP] Results sent to dashboard (with error)`);
|
|
1807
|
-
} catch (apiErr) {
|
|
1808
|
-
console.error(`[MCP] Failed to send results to dashboard: ${apiErr.message}`);
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
output += `\nā ļø Scan completed with findings (exit code ${err.code || 1})\n`;
|
|
1812
|
-
return this.success(output);
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
// Report error to dashboard if connected
|
|
1816
|
-
if (apiConnected && apiScan) {
|
|
1817
|
-
try {
|
|
1818
|
-
// HARDENING: Add timeout to error reporting
|
|
1819
|
-
const reportPromise = reportScanError(apiScan.scanId, err);
|
|
1820
|
-
const reportTimeout = new Promise((_, reject) =>
|
|
1821
|
-
setTimeout(() => reject(new Error('Report timeout')), 10000)
|
|
1822
|
-
);
|
|
1823
|
-
|
|
1824
|
-
await Promise.race([reportPromise, reportTimeout]);
|
|
1825
|
-
console.error(`[MCP] Error reported to dashboard`);
|
|
1826
|
-
} catch (apiErr) {
|
|
1827
|
-
console.error(`[MCP] Failed to report error to dashboard: ${apiErr.message}`);
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
1212
|
return this.error(`Scan failed: ${err.message}`, {
|
|
1832
1213
|
code: "SCAN_ERROR",
|
|
1833
1214
|
suggestion: "Check that the project path is valid and contains scanable code",
|
|
@@ -1846,7 +1227,7 @@ class VibecheckMCP {
|
|
|
1846
1227
|
// ============================================================================
|
|
1847
1228
|
async handleGate(projectPath, args) {
|
|
1848
1229
|
// Check tier access (STARTER tier required)
|
|
1849
|
-
const access = await
|
|
1230
|
+
const access = await checkFeatureAccess("gate", args?.apiKey);
|
|
1850
1231
|
if (!access.hasAccess) {
|
|
1851
1232
|
return {
|
|
1852
1233
|
content: [{
|
|
@@ -1862,12 +1243,16 @@ class VibecheckMCP {
|
|
|
1862
1243
|
let output = "# š¦ vibecheck Gate\n\n";
|
|
1863
1244
|
output += `**Policy:** ${policy}\n\n`;
|
|
1864
1245
|
|
|
1865
|
-
// Build CLI arguments array (secure)
|
|
1866
|
-
const cliArgs = [`--policy=${policy}`];
|
|
1867
|
-
if (args?.sarif) cliArgs.push("--sarif");
|
|
1868
|
-
|
|
1869
1246
|
try {
|
|
1870
|
-
|
|
1247
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" gate`;
|
|
1248
|
+
cmd += ` --policy=${policy}`;
|
|
1249
|
+
if (args?.sarif) cmd += ` --sarif`;
|
|
1250
|
+
|
|
1251
|
+
execSync(cmd, {
|
|
1252
|
+
cwd: projectPath,
|
|
1253
|
+
encoding: "utf8",
|
|
1254
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1255
|
+
});
|
|
1871
1256
|
|
|
1872
1257
|
output += "## ā
GATE PASSED\n\n";
|
|
1873
1258
|
output += "All checks passed. Clear to merge.\n";
|
|
@@ -1886,7 +1271,7 @@ class VibecheckMCP {
|
|
|
1886
1271
|
async handleFix(projectPath, args) {
|
|
1887
1272
|
// Check tier access for --apply and --autopilot (PRO tier required)
|
|
1888
1273
|
if (args?.apply || args?.autopilot) {
|
|
1889
|
-
const access = await
|
|
1274
|
+
const access = await checkFeatureAccess("fix.apply_patches", args?.apiKey);
|
|
1890
1275
|
if (!access.hasAccess) {
|
|
1891
1276
|
return {
|
|
1892
1277
|
content: [{
|
|
@@ -1900,35 +1285,35 @@ class VibecheckMCP {
|
|
|
1900
1285
|
|
|
1901
1286
|
const mode = args?.autopilot ? "Autopilot" :
|
|
1902
1287
|
args?.apply ? "Apply" :
|
|
1903
|
-
args?.promptOnly ? "Prompt Only" :
|
|
1904
|
-
args?.dryRun ? "Dry Run" : "Plan";
|
|
1288
|
+
args?.promptOnly ? "Prompt Only" : "Plan";
|
|
1905
1289
|
|
|
1906
1290
|
let output = "# š vibecheck Fix Missions v1\n\n";
|
|
1907
1291
|
output += `**Mode:** ${mode}\n`;
|
|
1908
1292
|
output += `**Max Missions:** ${args?.maxMissions || 8}\n`;
|
|
1909
1293
|
if (args?.autopilot) output += `**Max Steps:** ${args?.maxSteps || 10}\n`;
|
|
1910
|
-
if (args?.dryRun) output += `**Dry Run:** Yes (no changes will be made)\n`;
|
|
1911
1294
|
output += "\n";
|
|
1912
1295
|
|
|
1913
|
-
// Build CLI arguments array (secure)
|
|
1914
|
-
const cliArgs = [];
|
|
1915
|
-
if (args?.promptOnly) cliArgs.push("--prompt-only");
|
|
1916
|
-
if (args?.apply) cliArgs.push("--apply");
|
|
1917
|
-
if (args?.autopilot) cliArgs.push("--autopilot");
|
|
1918
|
-
if (args?.share) cliArgs.push("--share");
|
|
1919
|
-
if (args?.dryRun) cliArgs.push("--dry-run");
|
|
1920
|
-
if (args?.maxMissions) cliArgs.push("--max-missions", String(args.maxMissions));
|
|
1921
|
-
if (args?.maxSteps) cliArgs.push("--max-steps", String(args.maxSteps));
|
|
1922
|
-
|
|
1923
1296
|
try {
|
|
1924
|
-
|
|
1925
|
-
|
|
1297
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" fix`;
|
|
1298
|
+
if (args?.promptOnly) cmd += ` --prompt-only`;
|
|
1299
|
+
if (args?.apply) cmd += ` --apply`;
|
|
1300
|
+
if (args?.autopilot) cmd += ` --autopilot`;
|
|
1301
|
+
if (args?.share) cmd += ` --share`;
|
|
1302
|
+
if (args?.maxMissions) cmd += ` --max-missions ${args.maxMissions}`;
|
|
1303
|
+
if (args?.maxSteps) cmd += ` --max-steps ${args.maxSteps}`;
|
|
1304
|
+
|
|
1305
|
+
const result = execSync(cmd, {
|
|
1306
|
+
cwd: projectPath,
|
|
1307
|
+
encoding: "utf8",
|
|
1308
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1309
|
+
timeout: 300000, // 5 min timeout for autopilot
|
|
1310
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1926
1311
|
});
|
|
1927
1312
|
|
|
1928
|
-
output +=
|
|
1313
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
|
|
1929
1314
|
|
|
1930
1315
|
// Read mission pack if available
|
|
1931
|
-
const missionsDir = path.join(projectPath,
|
|
1316
|
+
const missionsDir = path.join(projectPath, ".vibecheck", "missions");
|
|
1932
1317
|
try {
|
|
1933
1318
|
const dirs = await fs.readdir(missionsDir);
|
|
1934
1319
|
const latest = dirs.sort().reverse()[0];
|
|
@@ -1942,12 +1327,12 @@ class VibecheckMCP {
|
|
|
1942
1327
|
const m = missions.missions[i];
|
|
1943
1328
|
output += `| ${i + 1} | ${m.title} | ${m.targetFindingIds.length} |\n`;
|
|
1944
1329
|
}
|
|
1945
|
-
output += `\nš **Mission Pack:**
|
|
1330
|
+
output += `\nš **Mission Pack:** .vibecheck/missions/${latest}\n`;
|
|
1946
1331
|
}
|
|
1947
1332
|
} catch {}
|
|
1948
1333
|
} catch (err) {
|
|
1949
1334
|
output += `\nā ļø Fix error: ${err.message}\n`;
|
|
1950
|
-
if (err.
|
|
1335
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
1951
1336
|
}
|
|
1952
1337
|
|
|
1953
1338
|
output += "\n---\n_Fix Missions v1 ā Reality Firewall Protected_\n";
|
|
@@ -1960,18 +1345,22 @@ class VibecheckMCP {
|
|
|
1960
1345
|
async handleShare(projectPath, args) {
|
|
1961
1346
|
let output = "# š¦ vibecheck Share Bundle\n\n";
|
|
1962
1347
|
|
|
1963
|
-
// Build CLI arguments array (secure)
|
|
1964
|
-
const cliArgs = [];
|
|
1965
|
-
if (args?.prComment) cliArgs.push("--pr-comment");
|
|
1966
|
-
if (args?.out) cliArgs.push("--out", args.out);
|
|
1967
|
-
|
|
1968
1348
|
try {
|
|
1969
|
-
|
|
1349
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" share`;
|
|
1350
|
+
if (args?.prComment) cmd += ` --pr-comment`;
|
|
1351
|
+
if (args?.out) cmd += ` --out "${args.out}"`;
|
|
1352
|
+
|
|
1353
|
+
const result = execSync(cmd, {
|
|
1354
|
+
cwd: projectPath,
|
|
1355
|
+
encoding: "utf8",
|
|
1356
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1357
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1358
|
+
});
|
|
1970
1359
|
|
|
1971
|
-
output +=
|
|
1360
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1972
1361
|
|
|
1973
1362
|
// Read share pack if available
|
|
1974
|
-
const missionsDir = path.join(projectPath,
|
|
1363
|
+
const missionsDir = path.join(projectPath, ".vibecheck", "missions");
|
|
1975
1364
|
try {
|
|
1976
1365
|
const dirs = await fs.readdir(missionsDir);
|
|
1977
1366
|
const latest = dirs.sort().reverse()[0];
|
|
@@ -1994,14 +1383,14 @@ class VibecheckMCP {
|
|
|
1994
1383
|
}
|
|
1995
1384
|
}
|
|
1996
1385
|
|
|
1997
|
-
output += `\nš **Share Pack:**
|
|
1998
|
-
output += `š **PR Comment:**
|
|
1386
|
+
output += `\nš **Share Pack:** .vibecheck/missions/${latest}/share/\n`;
|
|
1387
|
+
output += `š **PR Comment:** .vibecheck/missions/${latest}/share/pr_comment.md\n`;
|
|
1999
1388
|
} catch {}
|
|
2000
1389
|
}
|
|
2001
1390
|
} catch {}
|
|
2002
1391
|
} catch (err) {
|
|
2003
1392
|
output += `\nā ļø Share error: ${err.message}\n`;
|
|
2004
|
-
if (err.
|
|
1393
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
2005
1394
|
}
|
|
2006
1395
|
|
|
2007
1396
|
output += "\n---\n_Fix Missions Share Bundle_\n";
|
|
@@ -2015,23 +1404,28 @@ class VibecheckMCP {
|
|
|
2015
1404
|
let output = "# š¦ vibecheck Truth Pack\n\n";
|
|
2016
1405
|
output += `**Path:** ${projectPath}\n\n`;
|
|
2017
1406
|
|
|
2018
|
-
// Build CLI arguments array (secure)
|
|
2019
|
-
const cliArgs = [];
|
|
2020
|
-
if (args?.snapshot) cliArgs.push("--snapshot");
|
|
2021
|
-
if (args?.json) cliArgs.push("--json");
|
|
2022
|
-
|
|
2023
1407
|
try {
|
|
2024
|
-
|
|
1408
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ctx`;
|
|
1409
|
+
if (args?.snapshot) cmd += ` --snapshot`;
|
|
1410
|
+
if (args?.json) cmd += ` --json`;
|
|
1411
|
+
|
|
1412
|
+
const result = execSync(cmd, {
|
|
1413
|
+
cwd: projectPath,
|
|
1414
|
+
encoding: "utf8",
|
|
1415
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1416
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1417
|
+
});
|
|
2025
1418
|
|
|
2026
1419
|
if (args?.json) {
|
|
2027
1420
|
// Return raw JSON
|
|
2028
|
-
|
|
1421
|
+
output = result;
|
|
1422
|
+
return this.success(output);
|
|
2029
1423
|
}
|
|
2030
1424
|
|
|
2031
|
-
output +=
|
|
1425
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
|
|
2032
1426
|
|
|
2033
1427
|
// Read truthpack summary
|
|
2034
|
-
const truthpackPath = path.join(projectPath,
|
|
1428
|
+
const truthpackPath = path.join(projectPath, ".vibecheck", "truth", "truthpack.json");
|
|
2035
1429
|
try {
|
|
2036
1430
|
const truthpack = JSON.parse(await fs.readFile(truthpackPath, "utf-8"));
|
|
2037
1431
|
|
|
@@ -2046,7 +1440,7 @@ class VibecheckMCP {
|
|
|
2046
1440
|
output += `| Stripe Detected | ${truthpack.billing?.hasStripe ? "ā
" : "ā"} |\n`;
|
|
2047
1441
|
output += `| Webhooks | ${truthpack.billing?.summary?.webhookHandlersFound || 0} |\n`;
|
|
2048
1442
|
|
|
2049
|
-
output += `\nš **Saved:**
|
|
1443
|
+
output += `\nš **Saved:** .vibecheck/truth/truthpack.json\n`;
|
|
2050
1444
|
} catch {}
|
|
2051
1445
|
} catch (err) {
|
|
2052
1446
|
output += `\nā ļø Truth pack error: ${err.message}\n`;
|
|
@@ -2061,7 +1455,7 @@ class VibecheckMCP {
|
|
|
2061
1455
|
// ============================================================================
|
|
2062
1456
|
async handleProve(projectPath, args) {
|
|
2063
1457
|
// Check tier access (PRO tier required)
|
|
2064
|
-
const access = await
|
|
1458
|
+
const access = await checkFeatureAccess("prove", args?.apiKey);
|
|
2065
1459
|
if (!access.hasAccess) {
|
|
2066
1460
|
return {
|
|
2067
1461
|
content: [{
|
|
@@ -2076,24 +1470,27 @@ class VibecheckMCP {
|
|
|
2076
1470
|
output += `**URL:** ${args?.url || "(static only)"}\n`;
|
|
2077
1471
|
output += `**Max Fix Rounds:** ${args?.maxFixRounds || 3}\n\n`;
|
|
2078
1472
|
|
|
2079
|
-
// Build CLI arguments array (secure)
|
|
2080
|
-
const cliArgs = [];
|
|
2081
|
-
if (args?.url) cliArgs.push("--url", args.url);
|
|
2082
|
-
if (args?.auth) cliArgs.push("--auth", args.auth);
|
|
2083
|
-
if (args?.storageState) cliArgs.push("--storage-state", args.storageState);
|
|
2084
|
-
if (args?.skipReality) cliArgs.push("--skip-reality");
|
|
2085
|
-
if (args?.skipFix) cliArgs.push("--skip-fix");
|
|
2086
|
-
if (args?.maxFixRounds) cliArgs.push("--max-fix-rounds", String(args.maxFixRounds));
|
|
2087
|
-
|
|
2088
1473
|
try {
|
|
2089
|
-
|
|
2090
|
-
|
|
1474
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" prove`;
|
|
1475
|
+
if (args?.url) cmd += ` --url ${args.url}`;
|
|
1476
|
+
if (args?.auth) cmd += ` --auth ${args.auth}`;
|
|
1477
|
+
if (args?.storageState) cmd += ` --storage-state ${args.storageState}`;
|
|
1478
|
+
if (args?.skipReality) cmd += ` --skip-reality`;
|
|
1479
|
+
if (args?.skipFix) cmd += ` --skip-fix`;
|
|
1480
|
+
if (args?.maxFixRounds) cmd += ` --max-fix-rounds ${args.maxFixRounds}`;
|
|
1481
|
+
|
|
1482
|
+
const result = execSync(cmd, {
|
|
1483
|
+
cwd: projectPath,
|
|
1484
|
+
encoding: "utf8",
|
|
1485
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1486
|
+
timeout: 600000, // 10 min timeout for full prove loop
|
|
1487
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
2091
1488
|
});
|
|
2092
1489
|
|
|
2093
|
-
output +=
|
|
1490
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2094
1491
|
|
|
2095
1492
|
// Read prove report
|
|
2096
|
-
const provePath = path.join(projectPath,
|
|
1493
|
+
const provePath = path.join(projectPath, ".vibecheck", "prove", "last_prove.json");
|
|
2097
1494
|
try {
|
|
2098
1495
|
const report = JSON.parse(await fs.readFile(provePath, "utf-8"));
|
|
2099
1496
|
|
|
@@ -2108,7 +1505,7 @@ class VibecheckMCP {
|
|
|
2108
1505
|
} catch {}
|
|
2109
1506
|
} catch (err) {
|
|
2110
1507
|
output += `\nā ļø Prove error: ${err.message}\n`;
|
|
2111
|
-
if (err.
|
|
1508
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
2112
1509
|
}
|
|
2113
1510
|
|
|
2114
1511
|
output += "\n---\n_One command to make it real_\n";
|
|
@@ -2127,18 +1524,19 @@ class VibecheckMCP {
|
|
|
2127
1524
|
|
|
2128
1525
|
let output = `# š¬ vibecheck Proof: ${mode.toUpperCase()}\n\n`;
|
|
2129
1526
|
|
|
2130
|
-
// Build CLI arguments array (secure)
|
|
2131
|
-
const cliArgs = [mode];
|
|
2132
|
-
if (mode === "reality" && args?.url) cliArgs.push(`--url=${args.url}`);
|
|
2133
|
-
if (mode === "reality" && args?.flow) cliArgs.push(`--flow=${args.flow}`);
|
|
2134
|
-
|
|
2135
1527
|
try {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
1528
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" proof ${mode}`;
|
|
1529
|
+
if (mode === "reality" && args?.url) cmd += ` --url=${args.url}`;
|
|
1530
|
+
if (mode === "reality" && args?.flow) cmd += ` --flow=${args.flow}`;
|
|
1531
|
+
|
|
1532
|
+
const result = execSync(cmd, {
|
|
1533
|
+
cwd: projectPath,
|
|
1534
|
+
encoding: "utf8",
|
|
1535
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1536
|
+
timeout: 120000, // 2 min timeout for reality mode
|
|
2139
1537
|
});
|
|
2140
1538
|
|
|
2141
|
-
output += result
|
|
1539
|
+
output += result;
|
|
2142
1540
|
} catch (err) {
|
|
2143
1541
|
if (mode === "mocks") {
|
|
2144
1542
|
output += "## š« MOCKPROOF: FAIL\n\n";
|
|
@@ -2147,7 +1545,7 @@ class VibecheckMCP {
|
|
|
2147
1545
|
output += "## š« REALITY MODE: FAIL\n\n";
|
|
2148
1546
|
output += "Fake data or mock services detected at runtime.\n";
|
|
2149
1547
|
}
|
|
2150
|
-
output += `\n${err.
|
|
1548
|
+
output += `\n${err.stdout || err.message}\n`;
|
|
2151
1549
|
}
|
|
2152
1550
|
|
|
2153
1551
|
return this.success(output);
|
|
@@ -2264,30 +1662,24 @@ class VibecheckMCP {
|
|
|
2264
1662
|
// SHIP - Quick health check
|
|
2265
1663
|
// ============================================================================
|
|
2266
1664
|
async handleShip(projectPath, args) {
|
|
2267
|
-
// HARDENING: Validate project path
|
|
2268
|
-
const validation = this.validateProjectPath(projectPath);
|
|
2269
|
-
if (!validation.valid) {
|
|
2270
|
-
return this.error(validation.error, {
|
|
2271
|
-
code: validation.code || "INVALID_PATH",
|
|
2272
|
-
suggestion: validation.suggestion,
|
|
2273
|
-
nextSteps: validation.nextSteps || [],
|
|
2274
|
-
});
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
1665
|
let output = "# š vibecheck Ship\n\n";
|
|
2278
1666
|
output += `**Path:** ${projectPath}\n\n`;
|
|
2279
1667
|
|
|
2280
|
-
// Build CLI arguments array (secure)
|
|
2281
|
-
const cliArgs = [];
|
|
2282
|
-
if (args?.fix) cliArgs.push("--fix");
|
|
2283
|
-
|
|
2284
1668
|
try {
|
|
2285
|
-
|
|
1669
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ship`;
|
|
1670
|
+
if (args?.fix) cmd += ` --fix`;
|
|
2286
1671
|
|
|
2287
|
-
|
|
1672
|
+
const result = execSync(cmd, {
|
|
1673
|
+
cwd: projectPath,
|
|
1674
|
+
encoding: "utf8",
|
|
1675
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1676
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1677
|
+
});
|
|
1678
|
+
|
|
1679
|
+
// Parse the output for key information
|
|
1680
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
|
|
2288
1681
|
} catch (err) {
|
|
2289
1682
|
output += `\nā ļø Ship check failed: ${err.message}\n`;
|
|
2290
|
-
if (err.partialOutput) output += `\n${this.stripAnsi(err.partialOutput)}\n`;
|
|
2291
1683
|
}
|
|
2292
1684
|
|
|
2293
1685
|
output += "\n---\n_Context Enhanced by vibecheck AI_\n";
|
|
@@ -2298,46 +1690,35 @@ class VibecheckMCP {
|
|
|
2298
1690
|
// VERIFY - Runtime browser testing
|
|
2299
1691
|
// ============================================================================
|
|
2300
1692
|
async handleVerify(projectPath, args) {
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
if (!urlValidation.valid) {
|
|
2304
|
-
return this.error(urlValidation.error, {
|
|
2305
|
-
code: 'INVALID_URL',
|
|
2306
|
-
suggestion: 'Provide a valid HTTP/HTTPS URL',
|
|
2307
|
-
nextSteps: ['Check the URL format', 'Ensure the URL is accessible'],
|
|
2308
|
-
});
|
|
2309
|
-
}
|
|
2310
|
-
const url = urlValidation.url;
|
|
1693
|
+
const url = args?.url;
|
|
1694
|
+
if (!url) return this.error("URL is required");
|
|
2311
1695
|
|
|
2312
1696
|
let output = "# š§Ŗ vibecheck Verify\n\n";
|
|
2313
1697
|
output += `**URL:** ${url}\n`;
|
|
2314
|
-
|
|
2315
|
-
// HARDENING: Sanitize array inputs
|
|
2316
|
-
const flows = sanitizeArray(args?.flows, 10);
|
|
2317
|
-
if (flows.length) output += `**Flows:** ${flows.join(", ")}\n`;
|
|
1698
|
+
if (args?.flows?.length) output += `**Flows:** ${args.flows.join(", ")}\n`;
|
|
2318
1699
|
if (args?.headed) output += `**Mode:** Headed (visible browser)\n`;
|
|
2319
1700
|
if (args?.record) output += `**Recording:** Enabled\n`;
|
|
2320
1701
|
output += "\n";
|
|
2321
1702
|
|
|
2322
|
-
// Build CLI arguments array (secure)
|
|
2323
|
-
const cliArgs = ["--url", url];
|
|
2324
|
-
// HARDENING: Sanitize auth - don't log full credentials
|
|
2325
|
-
if (args?.auth && typeof args.auth === 'string') {
|
|
2326
|
-
cliArgs.push("--auth", sanitizeString(args.auth, 200));
|
|
2327
|
-
}
|
|
2328
|
-
if (flows.length) cliArgs.push("--flows", flows.join(","));
|
|
2329
|
-
if (args?.headed) cliArgs.push("--headed");
|
|
2330
|
-
if (args?.record) cliArgs.push("--record");
|
|
2331
|
-
|
|
2332
1703
|
try {
|
|
2333
|
-
|
|
2334
|
-
|
|
1704
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" verify --url "${url}"`;
|
|
1705
|
+
if (args?.auth) cmd += ` --auth "${args.auth}"`;
|
|
1706
|
+
if (args?.flows?.length) cmd += ` --flows ${args.flows.join(",")}`;
|
|
1707
|
+
if (args?.headed) cmd += ` --headed`;
|
|
1708
|
+
if (args?.record) cmd += ` --record`;
|
|
1709
|
+
|
|
1710
|
+
const result = execSync(cmd, {
|
|
1711
|
+
cwd: projectPath,
|
|
1712
|
+
encoding: "utf8",
|
|
1713
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1714
|
+
timeout: 180000, // 3 min timeout
|
|
1715
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
2335
1716
|
});
|
|
2336
1717
|
|
|
2337
|
-
output +=
|
|
1718
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2338
1719
|
|
|
2339
1720
|
// Try to read reality results
|
|
2340
|
-
const realityPath = path.join(projectPath,
|
|
1721
|
+
const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
|
|
2341
1722
|
try {
|
|
2342
1723
|
const reality = JSON.parse(await fs.readFile(realityPath, "utf-8"));
|
|
2343
1724
|
|
|
@@ -2360,11 +1741,11 @@ class VibecheckMCP {
|
|
|
2360
1741
|
}
|
|
2361
1742
|
}
|
|
2362
1743
|
|
|
2363
|
-
output += `\nš **Full Report:**
|
|
1744
|
+
output += `\nš **Full Report:** .vibecheck/reality/last_reality.json\n`;
|
|
2364
1745
|
} catch {}
|
|
2365
1746
|
} catch (err) {
|
|
2366
1747
|
output += `\nā ļø Verify failed: ${err.message}\n`;
|
|
2367
|
-
if (err.
|
|
1748
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
2368
1749
|
}
|
|
2369
1750
|
|
|
2370
1751
|
output += "\n---\n_Runtime Verification by vibecheck_\n";
|
|
@@ -2375,78 +1756,42 @@ class VibecheckMCP {
|
|
|
2375
1756
|
// REALITY v2 - Two-Pass Auth Verification + Dead UI Crawler
|
|
2376
1757
|
// ============================================================================
|
|
2377
1758
|
async handleReality(projectPath, args) {
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
if (!urlValidation.valid) {
|
|
2381
|
-
return this.error(urlValidation.error, {
|
|
2382
|
-
code: 'INVALID_URL',
|
|
2383
|
-
suggestion: 'Provide a valid HTTP/HTTPS URL',
|
|
2384
|
-
nextSteps: ['Check the URL format', 'Ensure the URL is accessible'],
|
|
2385
|
-
});
|
|
2386
|
-
}
|
|
2387
|
-
const url = urlValidation.url;
|
|
1759
|
+
const url = args?.url;
|
|
1760
|
+
if (!url) return this.error("URL is required");
|
|
2388
1761
|
|
|
2389
1762
|
let output = "# š§Ŗ vibecheck Reality v2\n\n";
|
|
2390
1763
|
output += `**URL:** ${url}\n`;
|
|
2391
1764
|
output += `**Two-Pass Auth:** ${args?.verifyAuth ? "Yes" : "No"}\n`;
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
if (args?.auth && typeof args.auth === 'string') {
|
|
2395
|
-
const authParts = args.auth.split(":");
|
|
2396
|
-
const maskedAuth = authParts[0] ? `${authParts[0].slice(0, 20)}:***` : '***';
|
|
2397
|
-
output += `**Auth:** ${maskedAuth}\n`;
|
|
2398
|
-
}
|
|
2399
|
-
if (args?.storageState) output += `**Storage State:** ${sanitizeString(args.storageState, 100)}\n`;
|
|
1765
|
+
if (args?.auth) output += `**Auth:** ${args.auth.split(":")[0]}:***\n`;
|
|
1766
|
+
if (args?.storageState) output += `**Storage State:** ${args.storageState}\n`;
|
|
2400
1767
|
if (args?.headed) output += `**Mode:** Headed (visible browser)\n`;
|
|
2401
1768
|
if (args?.danger) output += `**Danger Mode:** Enabled (risky clicks allowed)\n`;
|
|
2402
1769
|
output += "\n";
|
|
2403
1770
|
|
|
2404
|
-
// Build CLI arguments array (secure)
|
|
2405
|
-
const cliArgs = ["--url", url];
|
|
2406
|
-
if (args?.auth && typeof args.auth === 'string') {
|
|
2407
|
-
cliArgs.push("--auth", sanitizeString(args.auth, 200));
|
|
2408
|
-
}
|
|
2409
|
-
if (args?.verifyAuth) cliArgs.push("--verify-auth");
|
|
2410
|
-
|
|
2411
|
-
// HARDENING: Validate path arguments
|
|
2412
|
-
if (args?.storageState) {
|
|
2413
|
-
const pathCheck = sanitizePath(args.storageState, projectPath);
|
|
2414
|
-
if (pathCheck.valid) {
|
|
2415
|
-
cliArgs.push("--storage-state", pathCheck.path);
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
if (args?.saveStorageState) {
|
|
2419
|
-
const pathCheck = sanitizePath(args.saveStorageState, projectPath);
|
|
2420
|
-
if (pathCheck.valid) {
|
|
2421
|
-
cliArgs.push("--save-storage-state", pathCheck.path);
|
|
2422
|
-
}
|
|
2423
|
-
}
|
|
2424
|
-
if (args?.truthpack) {
|
|
2425
|
-
const pathCheck = sanitizePath(args.truthpack, projectPath);
|
|
2426
|
-
if (pathCheck.valid) {
|
|
2427
|
-
cliArgs.push("--truthpack", pathCheck.path);
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
if (args?.headed) cliArgs.push("--headed");
|
|
2431
|
-
|
|
2432
|
-
// HARDENING: Bound numeric arguments
|
|
2433
|
-
if (args?.maxPages) {
|
|
2434
|
-
cliArgs.push("--max-pages", String(sanitizeNumber(args.maxPages, 1, 100, 18)));
|
|
2435
|
-
}
|
|
2436
|
-
if (args?.maxDepth) {
|
|
2437
|
-
cliArgs.push("--max-depth", String(sanitizeNumber(args.maxDepth, 1, 10, 2)));
|
|
2438
|
-
}
|
|
2439
|
-
if (args?.danger) cliArgs.push("--danger");
|
|
2440
|
-
|
|
2441
1771
|
try {
|
|
2442
|
-
|
|
2443
|
-
|
|
1772
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" reality --url "${url}"`;
|
|
1773
|
+
if (args?.auth) cmd += ` --auth "${args.auth}"`;
|
|
1774
|
+
if (args?.verifyAuth) cmd += ` --verify-auth`;
|
|
1775
|
+
if (args?.storageState) cmd += ` --storage-state "${args.storageState}"`;
|
|
1776
|
+
if (args?.saveStorageState) cmd += ` --save-storage-state "${args.saveStorageState}"`;
|
|
1777
|
+
if (args?.truthpack) cmd += ` --truthpack "${args.truthpack}"`;
|
|
1778
|
+
if (args?.headed) cmd += ` --headed`;
|
|
1779
|
+
if (args?.maxPages) cmd += ` --max-pages ${args.maxPages}`;
|
|
1780
|
+
if (args?.maxDepth) cmd += ` --max-depth ${args.maxDepth}`;
|
|
1781
|
+
if (args?.danger) cmd += ` --danger`;
|
|
1782
|
+
|
|
1783
|
+
const result = execSync(cmd, {
|
|
1784
|
+
cwd: projectPath,
|
|
1785
|
+
encoding: "utf8",
|
|
1786
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1787
|
+
timeout: 300000, // 5 min timeout for two-pass
|
|
1788
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
2444
1789
|
});
|
|
2445
1790
|
|
|
2446
|
-
output +=
|
|
1791
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2447
1792
|
|
|
2448
1793
|
// Read reality results
|
|
2449
|
-
const realityPath = path.join(projectPath,
|
|
1794
|
+
const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
|
|
2450
1795
|
try {
|
|
2451
1796
|
const reality = JSON.parse(await fs.readFile(realityPath, "utf-8"));
|
|
2452
1797
|
|
|
@@ -2497,7 +1842,7 @@ class VibecheckMCP {
|
|
|
2497
1842
|
const verdict = blocks.length > 0 ? "š BLOCK" : warns.length > 0 ? "ā ļø WARN" : "ā
CLEAN";
|
|
2498
1843
|
output += `\n## Verdict: ${verdict}\n`;
|
|
2499
1844
|
|
|
2500
|
-
output += `\nš **Full Report:**
|
|
1845
|
+
output += `\nš **Full Report:** .vibecheck/reality/last_reality.json\n`;
|
|
2501
1846
|
|
|
2502
1847
|
if (reality.meta?.savedStorageState) {
|
|
2503
1848
|
output += `š **Saved Auth State:** ${reality.meta.savedStorageState}\n`;
|
|
@@ -2505,7 +1850,7 @@ class VibecheckMCP {
|
|
|
2505
1850
|
} catch {}
|
|
2506
1851
|
} catch (err) {
|
|
2507
1852
|
output += `\nā ļø Reality failed: ${err.message}\n`;
|
|
2508
|
-
if (err.
|
|
1853
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
2509
1854
|
}
|
|
2510
1855
|
|
|
2511
1856
|
output += "\n---\n_Reality Mode v2 ā Two-Pass Auth Verification_\n";
|
|
@@ -2516,38 +1861,35 @@ class VibecheckMCP {
|
|
|
2516
1861
|
// AI-TEST - AI Agent testing
|
|
2517
1862
|
// ============================================================================
|
|
2518
1863
|
async handleAITest(projectPath, args) {
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
if (!urlValidation.valid) {
|
|
2522
|
-
return this.error(urlValidation.error, {
|
|
2523
|
-
code: 'INVALID_URL',
|
|
2524
|
-
suggestion: 'Provide a valid HTTP/HTTPS URL',
|
|
2525
|
-
nextSteps: ['Check the URL format', 'Ensure the URL is accessible'],
|
|
2526
|
-
});
|
|
2527
|
-
}
|
|
2528
|
-
const url = urlValidation.url;
|
|
2529
|
-
|
|
2530
|
-
// HARDENING: Sanitize goal string
|
|
2531
|
-
const goal = sanitizeString(args?.goal, 500) || "Test all features";
|
|
1864
|
+
const url = args?.url;
|
|
1865
|
+
if (!url) return this.error("URL is required");
|
|
2532
1866
|
|
|
2533
1867
|
let output = "# š¤ vibecheck AI Agent\n\n";
|
|
2534
1868
|
output += `**URL:** ${url}\n`;
|
|
2535
|
-
output += `**Goal:** ${goal}\n\n`;
|
|
2536
|
-
|
|
2537
|
-
// Build CLI arguments array (secure)
|
|
2538
|
-
const cliArgs = ["--url", url];
|
|
2539
|
-
if (goal) cliArgs.push("--goal", goal);
|
|
2540
|
-
if (args?.headed) cliArgs.push("--headed");
|
|
1869
|
+
output += `**Goal:** ${args?.goal || "Test all features"}\n\n`;
|
|
2541
1870
|
|
|
2542
1871
|
try {
|
|
2543
|
-
|
|
2544
|
-
|
|
1872
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ai-test --url "${url}"`;
|
|
1873
|
+
if (args?.goal) cmd += ` --goal "${args.goal}"`;
|
|
1874
|
+
if (args?.headed) cmd += ` --headed`;
|
|
1875
|
+
|
|
1876
|
+
const result = execSync(cmd, {
|
|
1877
|
+
cwd: projectPath,
|
|
1878
|
+
encoding: "utf8",
|
|
1879
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1880
|
+
timeout: 180000,
|
|
1881
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
2545
1882
|
});
|
|
2546
1883
|
|
|
2547
|
-
output +=
|
|
1884
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2548
1885
|
|
|
2549
1886
|
// Try to read fix prompts
|
|
2550
|
-
const promptPath = path.join(
|
|
1887
|
+
const promptPath = path.join(
|
|
1888
|
+
projectPath,
|
|
1889
|
+
".vibecheck",
|
|
1890
|
+
"ai-agent",
|
|
1891
|
+
"fix-prompt.md",
|
|
1892
|
+
);
|
|
2551
1893
|
try {
|
|
2552
1894
|
const prompts = await fs.readFile(promptPath, "utf-8");
|
|
2553
1895
|
output += "\n## Fix Prompts Generated\n\n";
|
|
@@ -2571,15 +1913,19 @@ class VibecheckMCP {
|
|
|
2571
1913
|
let output = "# š¤ vibecheck Autopilot\n\n";
|
|
2572
1914
|
output += `**Action:** ${action}\n\n`;
|
|
2573
1915
|
|
|
2574
|
-
// Build CLI arguments array (secure - no injection possible)
|
|
2575
|
-
const cliArgs = [action];
|
|
2576
|
-
if (args?.slack) cliArgs.push("--slack", args.slack);
|
|
2577
|
-
if (args?.email) cliArgs.push("--email", args.email);
|
|
2578
|
-
|
|
2579
1916
|
try {
|
|
2580
|
-
|
|
1917
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot ${action}`;
|
|
1918
|
+
if (args?.slack) cmd += ` --slack="${args.slack}"`;
|
|
1919
|
+
if (args?.email) cmd += ` --email="${args.email}"`;
|
|
1920
|
+
|
|
1921
|
+
const result = execSync(cmd, {
|
|
1922
|
+
cwd: projectPath,
|
|
1923
|
+
encoding: "utf8",
|
|
1924
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1925
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1926
|
+
});
|
|
2581
1927
|
|
|
2582
|
-
output +=
|
|
1928
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2583
1929
|
} catch (err) {
|
|
2584
1930
|
output += `\nā ļø Autopilot failed: ${err.message}\n`;
|
|
2585
1931
|
}
|
|
@@ -2592,7 +1938,7 @@ class VibecheckMCP {
|
|
|
2592
1938
|
// ============================================================================
|
|
2593
1939
|
async handleAutopilotPlan(projectPath, args) {
|
|
2594
1940
|
// Check tier access (PRO tier required)
|
|
2595
|
-
const access = await
|
|
1941
|
+
const access = await checkFeatureAccess("fix.apply_patches", args?.apiKey);
|
|
2596
1942
|
if (!access.hasAccess) {
|
|
2597
1943
|
return {
|
|
2598
1944
|
content: [{
|
|
@@ -2616,17 +1962,20 @@ class VibecheckMCP {
|
|
|
2616
1962
|
const core = await import(corePath);
|
|
2617
1963
|
runAutopilot = core.runAutopilot;
|
|
2618
1964
|
} catch {
|
|
2619
|
-
// Fallback to CLI
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
1965
|
+
// Fallback to CLI
|
|
1966
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot plan`;
|
|
1967
|
+
cmd += ` --profile ${args?.profile || "ship"}`;
|
|
1968
|
+
cmd += ` --max-fixes ${args?.maxFixes || 10}`;
|
|
1969
|
+
cmd += ` --json`;
|
|
1970
|
+
|
|
1971
|
+
const result = execSync(cmd, {
|
|
1972
|
+
cwd: projectPath,
|
|
1973
|
+
encoding: "utf8",
|
|
1974
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1975
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1976
|
+
});
|
|
2628
1977
|
|
|
2629
|
-
const jsonResult = JSON.parse(result
|
|
1978
|
+
const jsonResult = JSON.parse(result);
|
|
2630
1979
|
output += `## Scan Results\n\n`;
|
|
2631
1980
|
output += `- **Total findings:** ${jsonResult.totalFindings}\n`;
|
|
2632
1981
|
output += `- **Fixable:** ${jsonResult.fixableFindings}\n`;
|
|
@@ -2679,7 +2028,7 @@ class VibecheckMCP {
|
|
|
2679
2028
|
// ============================================================================
|
|
2680
2029
|
async handleAutopilotApply(projectPath, args) {
|
|
2681
2030
|
// Check tier access (PRO tier required)
|
|
2682
|
-
const access = await
|
|
2031
|
+
const access = await checkFeatureAccess("fix.apply_patches", args?.apiKey);
|
|
2683
2032
|
if (!access.hasAccess) {
|
|
2684
2033
|
return {
|
|
2685
2034
|
content: [{
|
|
@@ -2695,22 +2044,24 @@ class VibecheckMCP {
|
|
|
2695
2044
|
output += `**Profile:** ${args?.profile || "ship"}\n`;
|
|
2696
2045
|
output += `**Dry Run:** ${args?.dryRun ? "Yes" : "No"}\n\n`;
|
|
2697
2046
|
|
|
2698
|
-
// Build CLI arguments array (secure)
|
|
2699
|
-
const cliArgs = [
|
|
2700
|
-
"apply",
|
|
2701
|
-
"--profile", args?.profile || "ship",
|
|
2702
|
-
"--max-fixes", String(args?.maxFixes || 10),
|
|
2703
|
-
"--json"
|
|
2704
|
-
];
|
|
2705
|
-
if (args?.verify === false) cliArgs.push("--no-verify");
|
|
2706
|
-
if (args?.dryRun) cliArgs.push("--dry-run");
|
|
2707
|
-
|
|
2708
2047
|
try {
|
|
2709
|
-
|
|
2710
|
-
|
|
2048
|
+
// Fallback to CLI
|
|
2049
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot apply`;
|
|
2050
|
+
cmd += ` --profile ${args?.profile || "ship"}`;
|
|
2051
|
+
cmd += ` --max-fixes ${args?.maxFixes || 10}`;
|
|
2052
|
+
if (args?.verify === false) cmd += ` --no-verify`;
|
|
2053
|
+
if (args?.dryRun) cmd += ` --dry-run`;
|
|
2054
|
+
cmd += ` --json`;
|
|
2055
|
+
|
|
2056
|
+
const result = execSync(cmd, {
|
|
2057
|
+
cwd: projectPath,
|
|
2058
|
+
encoding: "utf8",
|
|
2059
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2060
|
+
timeout: 300000, // 5 min timeout
|
|
2061
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
2711
2062
|
});
|
|
2712
2063
|
|
|
2713
|
-
const jsonResult = JSON.parse(result
|
|
2064
|
+
const jsonResult = JSON.parse(result);
|
|
2714
2065
|
|
|
2715
2066
|
output += `## Results\n\n`;
|
|
2716
2067
|
output += `- **Packs attempted:** ${jsonResult.packsAttempted}\n`;
|
|
@@ -2740,7 +2091,7 @@ class VibecheckMCP {
|
|
|
2740
2091
|
// ============================================================================
|
|
2741
2092
|
async handleBadge(projectPath, args) {
|
|
2742
2093
|
// Check tier access (STARTER tier required)
|
|
2743
|
-
const access = await
|
|
2094
|
+
const access = await checkFeatureAccess("badge", args?.apiKey);
|
|
2744
2095
|
if (!access.hasAccess) {
|
|
2745
2096
|
return {
|
|
2746
2097
|
content: [{
|
|
@@ -2755,17 +2106,26 @@ class VibecheckMCP {
|
|
|
2755
2106
|
|
|
2756
2107
|
let output = "# š
vibecheck Badge\n\n";
|
|
2757
2108
|
|
|
2758
|
-
// Build CLI arguments array (secure)
|
|
2759
|
-
const cliArgs = ["--format", format];
|
|
2760
|
-
if (args?.style) cliArgs.push("--style", args.style);
|
|
2761
|
-
|
|
2762
2109
|
try {
|
|
2763
|
-
|
|
2110
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" badge --format ${format}`;
|
|
2111
|
+
if (args?.style) cmd += ` --style ${args.style}`;
|
|
2112
|
+
|
|
2113
|
+
const result = execSync(cmd, {
|
|
2114
|
+
cwd: projectPath,
|
|
2115
|
+
encoding: "utf8",
|
|
2116
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2117
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
2118
|
+
});
|
|
2764
2119
|
|
|
2765
|
-
output +=
|
|
2120
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2766
2121
|
|
|
2767
2122
|
// Read the badge file
|
|
2768
|
-
const badgePath = path.join(
|
|
2123
|
+
const badgePath = path.join(
|
|
2124
|
+
projectPath,
|
|
2125
|
+
".vibecheck",
|
|
2126
|
+
"badges",
|
|
2127
|
+
`badge.${format}`,
|
|
2128
|
+
);
|
|
2769
2129
|
try {
|
|
2770
2130
|
const badge = await fs.readFile(badgePath, "utf-8");
|
|
2771
2131
|
if (format === "md") {
|
|
@@ -2793,15 +2153,19 @@ class VibecheckMCP {
|
|
|
2793
2153
|
output += `**Project:** ${path.basename(projectPath)}\n`;
|
|
2794
2154
|
output += `**Platform:** ${platform}\n\n`;
|
|
2795
2155
|
|
|
2796
|
-
// Build CLI arguments array (secure)
|
|
2797
|
-
const cliArgs = [];
|
|
2798
|
-
if (platform !== "all") cliArgs.push(`--platform=${platform}`);
|
|
2799
|
-
|
|
2800
2156
|
try {
|
|
2801
|
-
|
|
2157
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" context`;
|
|
2158
|
+
if (platform !== "all") cmd += ` --platform=${platform}`;
|
|
2159
|
+
|
|
2160
|
+
execSync(cmd, {
|
|
2161
|
+
cwd: projectPath,
|
|
2162
|
+
encoding: "utf8",
|
|
2163
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2164
|
+
});
|
|
2802
2165
|
|
|
2803
2166
|
output += "## ā
Context Generated\n\n";
|
|
2804
|
-
output +=
|
|
2167
|
+
output +=
|
|
2168
|
+
"Your AI coding assistants now have full project awareness.\n\n";
|
|
2805
2169
|
|
|
2806
2170
|
output += "### Generated Files\n\n";
|
|
2807
2171
|
|
|
@@ -2822,8 +2186,8 @@ class VibecheckMCP {
|
|
|
2822
2186
|
}
|
|
2823
2187
|
|
|
2824
2188
|
output += "**Universal (MCP):**\n";
|
|
2825
|
-
output +=
|
|
2826
|
-
output +=
|
|
2189
|
+
output += "- `.vibecheck/context.json` - Full context\n";
|
|
2190
|
+
output += "- `.vibecheck/project-map.json` - Project analysis\n\n";
|
|
2827
2191
|
|
|
2828
2192
|
output += "### What Your AI Now Knows\n\n";
|
|
2829
2193
|
output += "- Project architecture and structure\n";
|
|
@@ -2832,7 +2196,8 @@ class VibecheckMCP {
|
|
|
2832
2196
|
output += "- Coding conventions and patterns\n";
|
|
2833
2197
|
output += "- Dependencies and tech stack\n\n";
|
|
2834
2198
|
|
|
2835
|
-
output +=
|
|
2199
|
+
output +=
|
|
2200
|
+
"> **Tip:** Regenerate after major codebase changes with `vibecheck context`\n";
|
|
2836
2201
|
} catch (err) {
|
|
2837
2202
|
output += `\nā ļø Context generation failed: ${err.message}\n`;
|
|
2838
2203
|
}
|
|
@@ -2896,57 +2261,15 @@ class VibecheckMCP {
|
|
|
2896
2261
|
}
|
|
2897
2262
|
|
|
2898
2263
|
// ============================================================================
|
|
2899
|
-
// RUN
|
|
2264
|
+
// RUN
|
|
2900
2265
|
// ============================================================================
|
|
2901
2266
|
async run() {
|
|
2902
2267
|
const transport = new StdioServerTransport();
|
|
2903
|
-
|
|
2904
|
-
// ========================================================================
|
|
2905
|
-
// HARDENING: Graceful shutdown handling
|
|
2906
|
-
// ========================================================================
|
|
2907
|
-
const shutdown = async (signal) => {
|
|
2908
|
-
console.error(`\n[MCP] Received ${signal}, shutting down gracefully...`);
|
|
2909
|
-
try {
|
|
2910
|
-
// Clear rate limit state to prevent memory leaks
|
|
2911
|
-
rateLimitState.calls = [];
|
|
2912
|
-
|
|
2913
|
-
// Close server connection
|
|
2914
|
-
await this.server.close();
|
|
2915
|
-
console.error('[MCP] Server closed successfully');
|
|
2916
|
-
} catch (err) {
|
|
2917
|
-
console.error(`[MCP] Error during shutdown: ${err.message}`);
|
|
2918
|
-
}
|
|
2919
|
-
process.exit(0);
|
|
2920
|
-
};
|
|
2921
|
-
|
|
2922
|
-
// Handle termination signals
|
|
2923
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
2924
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
2925
|
-
|
|
2926
|
-
// Handle uncaught errors gracefully
|
|
2927
|
-
process.on('uncaughtException', (err) => {
|
|
2928
|
-
console.error(`[MCP] Uncaught exception: ${err.message}`);
|
|
2929
|
-
console.error(err.stack);
|
|
2930
|
-
// Don't exit - try to keep running
|
|
2931
|
-
});
|
|
2932
|
-
|
|
2933
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
2934
|
-
console.error(`[MCP] Unhandled rejection at:`, promise);
|
|
2935
|
-
console.error(`[MCP] Reason:`, reason);
|
|
2936
|
-
// Don't exit - try to keep running
|
|
2937
|
-
});
|
|
2938
|
-
|
|
2939
2268
|
await this.server.connect(transport);
|
|
2940
|
-
console.error(
|
|
2269
|
+
console.error("vibecheck MCP Server v2.0 running on stdio");
|
|
2941
2270
|
}
|
|
2942
2271
|
}
|
|
2943
2272
|
|
|
2944
|
-
//
|
|
2945
|
-
// MAIN - with error handling
|
|
2946
|
-
// ============================================================================
|
|
2273
|
+
// Main
|
|
2947
2274
|
const server = new VibecheckMCP();
|
|
2948
|
-
server.run().catch(
|
|
2949
|
-
console.error(`[MCP] Fatal error starting server: ${err.message}`);
|
|
2950
|
-
console.error(err.stack);
|
|
2951
|
-
process.exit(1);
|
|
2952
|
-
});
|
|
2275
|
+
server.run().catch(console.error);
|