@vibecheckai/cli 3.5.1 → 3.5.2
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 +406 -154
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/mcp.js +15 -13
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/proof-context.js +248 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +488 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +303 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +127 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +213 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +90 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +103 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +451 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +86 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +162 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +189 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +137 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analysis-core.js +220 -182
- package/bin/runners/lib/analyzers.js +2145 -224
- package/bin/runners/lib/api-client.js +269 -0
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/cli-output.js +242 -210
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/detectors-v2.js +547 -785
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/entitlements-v2.js +152 -446
- package/bin/runners/lib/error-handler.js +60 -12
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/fingerprint.js +377 -0
- package/bin/runners/lib/global-flags.js +37 -0
- package/bin/runners/lib/help-formatter.js +413 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/route-truth.js +1167 -322
- package/bin/runners/lib/scan-output.js +504 -463
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/ship-output.js +328 -31
- package/bin/runners/lib/terminal-ui.js +234 -731
- package/bin/runners/lib/truth.js +1332 -308
- package/bin/runners/lib/unified-cli-output.js +604 -0
- package/bin/runners/lib/unified-output.js +163 -155
- package/bin/runners/lib/upsell.js +104 -204
- package/bin/runners/runAgent.d.ts +5 -0
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runAllowlist.js +166 -101
- package/bin/runners/runApprove.js +1200 -0
- package/bin/runners/runAuth.js +373 -95
- package/bin/runners/runCheckpoint.js +59 -21
- package/bin/runners/runClassify.js +926 -0
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runContext.js +136 -24
- package/bin/runners/runDoctor.js +115 -67
- package/bin/runners/runEvidencePack.js +239 -96
- package/bin/runners/runFirewall.d.ts +5 -0
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.d.ts +5 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runFix.js +6 -5
- package/bin/runners/runGuard.js +212 -118
- package/bin/runners/runInit.js +66 -21
- package/bin/runners/runLabs.js +204 -121
- package/bin/runners/runMcp.js +131 -60
- package/bin/runners/runPolish.d.ts +4 -0
- package/bin/runners/runPolish.js +43 -20
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +15 -5
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runReport.js +36 -4
- package/bin/runners/runScan.js +689 -91
- package/bin/runners/runShip.js +96 -40
- package/bin/runners/runTruth.d.ts +5 -0
- package/bin/runners/runTruth.js +101 -0
- package/bin/runners/runValidate.js +21 -4
- package/bin/runners/runWatch.js +118 -54
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +297 -52
- package/mcp-server/HARDENING_SUMMARY.md +299 -0
- package/mcp-server/agent-firewall-interceptor.js +500 -0
- package/mcp-server/authority-tools.js +569 -0
- package/mcp-server/conductor/conflict-resolver.js +588 -0
- package/mcp-server/conductor/execution-planner.js +544 -0
- package/mcp-server/conductor/index.js +377 -0
- package/mcp-server/conductor/lock-manager.js +615 -0
- package/mcp-server/conductor/request-queue.js +550 -0
- package/mcp-server/conductor/session-manager.js +500 -0
- package/mcp-server/conductor/tools.js +510 -0
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +474 -591
- package/mcp-server/index.js +1748 -1099
- package/mcp-server/lib/api-client.cjs +13 -0
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +428 -721
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/logger.cjs +30 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +342 -284
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/logger.js +173 -0
- package/mcp-server/package.json +11 -27
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +507 -378
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +492 -347
- package/mcp-server/tools-v3.js +950 -0
- package/mcp-server/truth-context.js +131 -90
- package/mcp-server/truth-firewall-tools.js +1612 -1001
- package/mcp-server/tsconfig.json +8 -5
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
- package/mcp-server/vibecheck-tools.js +2 -2
- package/package.json +4 -3
- package/bin/runners/runInstall.js +0 -281
- package/mcp-server/ARCHITECTURE.md +0 -339
- package/mcp-server/__tests__/cache.test.ts +0 -313
- package/mcp-server/__tests__/executor.test.ts +0 -239
- package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +0 -1
- package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +0 -3
- package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +0 -3
- package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +0 -3
- package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +0 -3
- package/mcp-server/__tests__/fixtures/exclusion-test/package.json +0 -5
- package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +0 -5
- package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +0 -4
- package/mcp-server/__tests__/ids.test.ts +0 -345
- package/mcp-server/__tests__/integration/tools.test.ts +0 -410
- package/mcp-server/__tests__/registry.test.ts +0 -365
- package/mcp-server/__tests__/sandbox.test.ts +0 -323
- package/mcp-server/__tests__/schemas.test.ts +0 -372
- package/mcp-server/benchmarks/run-benchmarks.ts +0 -304
- package/mcp-server/examples/doctor.request.json +0 -14
- package/mcp-server/examples/doctor.response.json +0 -53
- package/mcp-server/examples/error.response.json +0 -15
- package/mcp-server/examples/scan.request.json +0 -14
- package/mcp-server/examples/scan.response.json +0 -108
- package/mcp-server/index-v3.ts +0 -293
- package/mcp-server/index.old.js +0 -4137
- package/mcp-server/lib/cache.ts +0 -341
- package/mcp-server/lib/errors.ts +0 -346
- package/mcp-server/lib/ids.ts +0 -238
- package/mcp-server/lib/logger.ts +0 -368
- package/mcp-server/lib/metrics.ts +0 -365
- package/mcp-server/lib/validator.ts +0 -229
- package/mcp-server/package-lock.json +0 -165
- package/mcp-server/schemas/error-envelope.schema.json +0 -125
- package/mcp-server/schemas/finding.schema.json +0 -167
- package/mcp-server/schemas/report-artifact.schema.json +0 -88
- package/mcp-server/schemas/run-request.schema.json +0 -75
- package/mcp-server/schemas/verdict.schema.json +0 -168
- package/mcp-server/tier-auth.d.ts +0 -71
- package/mcp-server/vitest.config.ts +0 -16
|
@@ -1,671 +1,554 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Universal Tool Handler
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Handles:
|
|
6
|
-
* - Schema validation
|
|
7
|
-
* - Tier checking
|
|
8
|
-
* - Execution routing
|
|
9
|
-
* - Response normalization
|
|
10
|
-
* - Error handling
|
|
4
|
+
* Registry-driven dispatcher for all MCP tools.
|
|
11
5
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
6
|
+
* Pipeline:
|
|
7
|
+
* 1) Load tool definition from registry
|
|
8
|
+
* 2) Validate input schema
|
|
9
|
+
* 3) Sandbox path validation
|
|
10
|
+
* 4) Execute CLI command
|
|
11
|
+
* 5) Parse output into canonical JSON
|
|
12
|
+
* 6) Validate output schema
|
|
13
|
+
* 7) Return response with error envelope
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
import * as fs from "fs";
|
|
17
|
+
import * as path from "path";
|
|
18
|
+
import Ajv from "ajv";
|
|
19
|
+
import type {
|
|
20
|
+
RunRequest,
|
|
21
|
+
RunResponse,
|
|
22
|
+
ErrorEnvelope,
|
|
23
|
+
ErrorCode,
|
|
24
|
+
ToolDefinition,
|
|
25
|
+
ToolResult,
|
|
26
|
+
ValidationError,
|
|
27
|
+
Finding,
|
|
28
|
+
} from "../lib/types";
|
|
29
|
+
import { PathSandbox } from "../lib/sandbox";
|
|
30
|
+
import { CliExecutor, parseCliOutput, sortFindings, buildCliArgs } from "../lib/executor";
|
|
31
|
+
|
|
32
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
33
|
+
// REGISTRY
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
35
|
+
|
|
36
|
+
interface ToolRegistry {
|
|
37
|
+
version: string;
|
|
38
|
+
tools: Record<string, ToolDefinition>;
|
|
39
|
+
$defs?: Record<string, unknown>;
|
|
40
|
+
}
|
|
29
41
|
|
|
30
|
-
|
|
42
|
+
let registryCache: ToolRegistry | null = null;
|
|
31
43
|
|
|
32
44
|
/**
|
|
33
|
-
* Load tool registry
|
|
45
|
+
* Load tool registry (cached)
|
|
34
46
|
*/
|
|
35
|
-
function loadRegistry() {
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
function loadRegistry(): ToolRegistry {
|
|
48
|
+
if (registryCache) return registryCache;
|
|
49
|
+
|
|
50
|
+
const registryPath = path.join(__dirname, "../registry/tools.json");
|
|
51
|
+
const content = fs.readFileSync(registryPath, "utf-8");
|
|
52
|
+
registryCache = JSON.parse(content) as ToolRegistry;
|
|
53
|
+
return registryCache;
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
/**
|
|
41
|
-
*
|
|
57
|
+
* Get tool definition by name (supports aliases)
|
|
42
58
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
function getToolDefinition(toolName: string): ToolDefinition | null {
|
|
60
|
+
const registry = loadRegistry();
|
|
61
|
+
|
|
62
|
+
// Direct match
|
|
63
|
+
if (registry.tools[toolName]) {
|
|
64
|
+
return registry.tools[toolName];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Search aliases
|
|
68
|
+
for (const tool of Object.values(registry.tools)) {
|
|
69
|
+
if (tool.aliases?.includes(toolName)) {
|
|
70
|
+
return tool;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
54
75
|
}
|
|
55
76
|
|
|
77
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
78
|
+
// VALIDATION
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
80
|
+
|
|
81
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
82
|
+
|
|
56
83
|
/**
|
|
57
|
-
*
|
|
84
|
+
* Validate data against JSON schema
|
|
58
85
|
*/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
86
|
+
function validateSchema(
|
|
87
|
+
data: unknown,
|
|
88
|
+
schema: unknown,
|
|
89
|
+
schemaName: string
|
|
90
|
+
): ValidationError[] {
|
|
91
|
+
const validate = ajv.compile(schema as object);
|
|
92
|
+
const valid = validate(data);
|
|
93
|
+
|
|
94
|
+
if (valid) return [];
|
|
95
|
+
|
|
96
|
+
return (validate.errors || []).map((err) => ({
|
|
97
|
+
path: err.instancePath || "/",
|
|
98
|
+
message: err.message || "Validation failed",
|
|
99
|
+
expected: err.params?.allowedValues?.join(", "),
|
|
100
|
+
actual: String(err.data),
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
105
|
+
// ERROR HELPERS
|
|
106
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
107
|
+
|
|
108
|
+
const ERROR_NEXT_STEPS: Record<ErrorCode, string[]> = {
|
|
109
|
+
INPUT_VALIDATION: [
|
|
110
|
+
"Check the tool's inputSchema for required fields",
|
|
111
|
+
"Ensure all values match expected types",
|
|
112
|
+
"Review the validation errors for specifics",
|
|
113
|
+
],
|
|
114
|
+
TOOL_NOT_FOUND: [
|
|
115
|
+
"Check the tool name is spelled correctly",
|
|
116
|
+
"Use 'vibecheck.scan' format for tool names",
|
|
117
|
+
"List available tools with the registry",
|
|
118
|
+
],
|
|
119
|
+
TIER_REQUIRED: [
|
|
120
|
+
"This tool requires a Pro subscription ($69/mo)",
|
|
121
|
+
"Upgrade at https://vibecheckai.dev/pricing",
|
|
122
|
+
"Some tools have free alternatives",
|
|
123
|
+
],
|
|
124
|
+
NOT_ENTITLED: [
|
|
125
|
+
"This tool requires a Pro subscription ($69/mo)",
|
|
126
|
+
"Upgrade at https://vibecheckai.dev/pricing",
|
|
127
|
+
"Run: vibecheck upgrade",
|
|
128
|
+
],
|
|
129
|
+
OPTION_NOT_ENTITLED: [
|
|
130
|
+
"This option requires a Pro subscription ($69/mo)",
|
|
131
|
+
"The base tool is available on FREE tier",
|
|
132
|
+
"Upgrade at https://vibecheckai.dev/pricing",
|
|
133
|
+
],
|
|
134
|
+
PATH_VIOLATION: [
|
|
135
|
+
"Ensure paths are within the project directory",
|
|
136
|
+
"Do not use absolute paths or path traversal (..)",
|
|
137
|
+
"Check for symlinks pointing outside the project",
|
|
138
|
+
],
|
|
139
|
+
EXECUTOR_FAILED: [
|
|
140
|
+
"Check the CLI command output for details",
|
|
141
|
+
"Run 'vibecheck doctor' to diagnose issues",
|
|
142
|
+
"Ensure dependencies are installed",
|
|
143
|
+
],
|
|
144
|
+
OUTPUT_PARSE_ERROR: [
|
|
145
|
+
"The CLI output was not valid JSON",
|
|
146
|
+
"This may indicate a CLI bug",
|
|
147
|
+
"Try running the command directly for debugging",
|
|
148
|
+
],
|
|
149
|
+
OUTPUT_VALIDATION: [
|
|
150
|
+
"The CLI output didn't match expected schema",
|
|
151
|
+
"This may indicate a version mismatch",
|
|
152
|
+
"Please report this issue",
|
|
153
|
+
],
|
|
154
|
+
TIMEOUT: [
|
|
155
|
+
"The command timed out",
|
|
156
|
+
"Try a smaller scope (fewer files/categories)",
|
|
157
|
+
"Increase timeout if running intensive operations",
|
|
158
|
+
],
|
|
159
|
+
INTERNAL_ERROR: [
|
|
160
|
+
"An unexpected error occurred",
|
|
161
|
+
"Please report this issue with the requestId",
|
|
162
|
+
"https://github.com/vibecheckai/vibecheck/issues",
|
|
163
|
+
],
|
|
164
|
+
INVALID_API_KEY: [
|
|
165
|
+
"The API key is invalid or expired",
|
|
166
|
+
"Run: vibecheck login",
|
|
167
|
+
"Check your credentials at https://vibecheckai.dev/dashboard",
|
|
168
|
+
],
|
|
169
|
+
RATE_LIMITED: [
|
|
170
|
+
"Too many requests - please wait and try again",
|
|
171
|
+
"Consider upgrading for higher limits",
|
|
172
|
+
"https://vibecheckai.dev/pricing",
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
function createErrorEnvelope(
|
|
177
|
+
code: ErrorCode,
|
|
178
|
+
message: string,
|
|
179
|
+
requestId: string,
|
|
180
|
+
extra?: Partial<ErrorEnvelope>
|
|
181
|
+
): ErrorEnvelope {
|
|
182
|
+
return {
|
|
183
|
+
code,
|
|
184
|
+
message,
|
|
185
|
+
nextSteps: ERROR_NEXT_STEPS[code],
|
|
186
|
+
requestId,
|
|
187
|
+
...extra,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
192
|
+
// MAIN HANDLER
|
|
193
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
62
194
|
|
|
63
195
|
/**
|
|
64
|
-
*
|
|
196
|
+
* Handle a tool execution request
|
|
65
197
|
*/
|
|
66
|
-
export async function
|
|
198
|
+
export async function handleToolRequest(request: RunRequest): Promise<RunResponse> {
|
|
199
|
+
const startedAt = new Date().toISOString();
|
|
67
200
|
const startTime = Date.now();
|
|
68
|
-
const requestId = request.requestId ?? generateRequestId();
|
|
69
|
-
const logger = createRequestLogger(requestId);
|
|
70
|
-
const metrics = getMetricsCollector();
|
|
71
|
-
|
|
72
|
-
logger.info('Tool request received', { tool: request.tool, projectPath: request.projectPath });
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
// 1. Load registry and find tool
|
|
76
|
-
const registry = loadRegistry();
|
|
77
|
-
const toolDef = registry.tools.find((t: { name: string }) => t.name === request.tool);
|
|
78
|
-
|
|
79
|
-
// Check aliases
|
|
80
|
-
if (!toolDef && registry.aliases[request.tool]) {
|
|
81
|
-
const aliasTarget = registry.aliases[request.tool];
|
|
82
|
-
logger.warn('Using deprecated alias', { alias: request.tool, target: aliasTarget });
|
|
83
|
-
request.tool = aliasTarget;
|
|
84
|
-
}
|
|
85
201
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
tool: request.tool,
|
|
93
|
-
replacement: deprecated.replacement,
|
|
94
|
-
removeIn: deprecated.removeIn
|
|
95
|
-
});
|
|
96
|
-
}
|
|
202
|
+
const baseMetadata = {
|
|
203
|
+
startedAt,
|
|
204
|
+
completedAt: "",
|
|
205
|
+
durationMs: 0,
|
|
206
|
+
tool: request.tool,
|
|
207
|
+
};
|
|
97
208
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
209
|
+
try {
|
|
210
|
+
// 1) Load tool definition
|
|
211
|
+
const toolDef = getToolDefinition(request.tool);
|
|
212
|
+
if (!toolDef) {
|
|
213
|
+
return buildErrorResponse(
|
|
214
|
+
request,
|
|
215
|
+
createErrorEnvelope("TOOL_NOT_FOUND", `Unknown tool: ${request.tool}`, request.requestId),
|
|
216
|
+
baseMetadata,
|
|
217
|
+
startTime
|
|
218
|
+
);
|
|
104
219
|
}
|
|
105
220
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
221
|
+
// 2) Check tier access (aligned with CLI entitlements-v2.js)
|
|
222
|
+
const userTier = request.context?.tier || "free";
|
|
223
|
+
|
|
224
|
+
// Developer mode bypass (blocked in production environments)
|
|
225
|
+
const isDevProBypassAllowed = (): boolean => {
|
|
226
|
+
if (process.env.NODE_ENV === "production") return false;
|
|
227
|
+
if (process.env.CI === "true" || process.env.CI === "1") return false;
|
|
228
|
+
return process.env.VIBECHECK_DEV_PRO === "1";
|
|
229
|
+
};
|
|
230
|
+
const isDevMode = isDevProBypassAllowed();
|
|
231
|
+
|
|
232
|
+
if (toolDef.tier === "pro" && userTier !== "pro" && !isDevMode) {
|
|
233
|
+
return buildErrorResponse(
|
|
234
|
+
request,
|
|
235
|
+
createErrorEnvelope(
|
|
236
|
+
"NOT_ENTITLED",
|
|
237
|
+
"Requires PRO",
|
|
238
|
+
request.requestId,
|
|
239
|
+
{
|
|
240
|
+
userAction: "Open billing",
|
|
241
|
+
retryable: false,
|
|
242
|
+
tier: userTier,
|
|
243
|
+
required: "pro",
|
|
244
|
+
tool: toolDef.name,
|
|
245
|
+
upgradeUrl: "https://vibecheckai.dev/pricing",
|
|
246
|
+
}
|
|
247
|
+
),
|
|
248
|
+
baseMetadata,
|
|
249
|
+
startTime
|
|
250
|
+
);
|
|
115
251
|
}
|
|
116
252
|
|
|
117
|
-
// 3
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
253
|
+
// 3) Validate input schema
|
|
254
|
+
const inputErrors = validateSchema(request.args, toolDef.inputSchema, "input");
|
|
255
|
+
if (inputErrors.length > 0) {
|
|
256
|
+
return buildErrorResponse(
|
|
257
|
+
request,
|
|
258
|
+
createErrorEnvelope(
|
|
259
|
+
"INPUT_VALIDATION",
|
|
260
|
+
"Input validation failed",
|
|
261
|
+
request.requestId,
|
|
262
|
+
{ validationErrors: inputErrors }
|
|
263
|
+
),
|
|
264
|
+
baseMetadata,
|
|
265
|
+
startTime
|
|
266
|
+
);
|
|
124
267
|
}
|
|
125
268
|
|
|
126
|
-
// 4
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
269
|
+
// 4) Resolve and sandbox project path
|
|
270
|
+
const projectPath = String(request.args.projectPath || request.context?.projectRoot || ".");
|
|
271
|
+
let sandbox: PathSandbox;
|
|
272
|
+
let resolvedProjectPath: string;
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
// Determine project root (use provided or current working directory)
|
|
276
|
+
const baseProjectRoot = request.context?.projectRoot || process.cwd();
|
|
277
|
+
sandbox = new PathSandbox({ projectRoot: baseProjectRoot });
|
|
278
|
+
resolvedProjectPath = sandbox.assertAllowed(projectPath);
|
|
279
|
+
} catch (err) {
|
|
280
|
+
const error = err as Error & { violationType?: string };
|
|
281
|
+
return buildErrorResponse(
|
|
282
|
+
request,
|
|
283
|
+
createErrorEnvelope(
|
|
284
|
+
"PATH_VIOLATION",
|
|
285
|
+
error.message,
|
|
286
|
+
request.requestId,
|
|
287
|
+
{ receipt: `violation: ${error.violationType}` }
|
|
288
|
+
),
|
|
289
|
+
baseMetadata,
|
|
290
|
+
startTime
|
|
291
|
+
);
|
|
133
292
|
}
|
|
134
293
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
294
|
+
// Validate any other path arguments
|
|
295
|
+
const pathArgs = ["outputPath", "file", "filePath"];
|
|
296
|
+
for (const argName of pathArgs) {
|
|
297
|
+
const argValue = request.args[argName];
|
|
298
|
+
if (argValue && typeof argValue === "string") {
|
|
299
|
+
const result = sandbox.validate(argValue);
|
|
300
|
+
if (!result.allowed) {
|
|
301
|
+
return buildErrorResponse(
|
|
302
|
+
request,
|
|
303
|
+
createErrorEnvelope(
|
|
304
|
+
"PATH_VIOLATION",
|
|
305
|
+
`Invalid path in '${argName}': ${result.error}`,
|
|
306
|
+
request.requestId
|
|
307
|
+
),
|
|
308
|
+
baseMetadata,
|
|
309
|
+
startTime
|
|
310
|
+
);
|
|
146
311
|
}
|
|
147
312
|
}
|
|
148
313
|
}
|
|
149
314
|
|
|
150
|
-
// 5
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const cached = cache.get(cacheKey);
|
|
157
|
-
|
|
158
|
-
if (cached.cached) {
|
|
159
|
-
const cacheMaxAge = request.cache?.maxAge ?? tool.cacheMaxAge ?? 300;
|
|
160
|
-
if (cached.age < cacheMaxAge * 1000) {
|
|
161
|
-
logger.info('Cache hit', { tool: tool.name, cacheAge: cached.age });
|
|
162
|
-
return createSuccessEnvelope(cached.data, {
|
|
163
|
-
cached: true,
|
|
164
|
-
cacheAge: cached.age,
|
|
165
|
-
durationMs: Date.now() - startTime,
|
|
166
|
-
requestId,
|
|
167
|
-
}) as ToolResponse;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
315
|
+
// 5) Build CLI arguments
|
|
316
|
+
const cliArgs = buildCliArgs(
|
|
317
|
+
{ ...request.args, projectPath: resolvedProjectPath },
|
|
318
|
+
toolDef.cli.argMap,
|
|
319
|
+
toolDef.cli.fixedFlags
|
|
320
|
+
);
|
|
171
321
|
|
|
172
|
-
// 6
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
322
|
+
// 6) Execute CLI command
|
|
323
|
+
const executor = new CliExecutor({
|
|
324
|
+
cwd: resolvedProjectPath,
|
|
325
|
+
timeoutMs: toolDef.cli.timeoutMs || 300000,
|
|
326
|
+
requestId: request.requestId,
|
|
327
|
+
traceId: request.traceId,
|
|
178
328
|
});
|
|
179
329
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
330
|
+
const execResult = await executor.execute(toolDef.cli.command, cliArgs);
|
|
331
|
+
|
|
332
|
+
// Check for timeout
|
|
333
|
+
if (execResult.timedOut) {
|
|
334
|
+
return buildErrorResponse(
|
|
335
|
+
request,
|
|
336
|
+
createErrorEnvelope(
|
|
337
|
+
"TIMEOUT",
|
|
338
|
+
`Command timed out after ${toolDef.cli.timeoutMs}ms`,
|
|
339
|
+
request.requestId
|
|
340
|
+
),
|
|
341
|
+
{
|
|
342
|
+
...baseMetadata,
|
|
343
|
+
cliCommand: `vibecheck ${toolDef.cli.command}`,
|
|
344
|
+
exitCode: execResult.exitCode,
|
|
345
|
+
},
|
|
346
|
+
startTime
|
|
347
|
+
);
|
|
184
348
|
}
|
|
185
349
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
durationMs,
|
|
204
|
-
requestId,
|
|
205
|
-
}) as ToolResponse;
|
|
206
|
-
} else {
|
|
207
|
-
metrics.recordToolExecution(tool.name, durationMs, false, false);
|
|
208
|
-
return createErrorEnvelope(
|
|
209
|
-
Errors.cliError(result.error?.message ?? 'Unknown error', result.exitCode),
|
|
210
|
-
requestId
|
|
211
|
-
) as ToolResponse;
|
|
350
|
+
// Check for execution failure (exit code > 2 indicates error, not just findings)
|
|
351
|
+
if (execResult.exitCode > 10) {
|
|
352
|
+
return buildErrorResponse(
|
|
353
|
+
request,
|
|
354
|
+
createErrorEnvelope(
|
|
355
|
+
"EXECUTOR_FAILED",
|
|
356
|
+
execResult.stderr || `CLI exited with code ${execResult.exitCode}`,
|
|
357
|
+
request.requestId,
|
|
358
|
+
{ receipt: `exit_code: ${execResult.exitCode}` }
|
|
359
|
+
),
|
|
360
|
+
{
|
|
361
|
+
...baseMetadata,
|
|
362
|
+
cliCommand: `vibecheck ${toolDef.cli.command}`,
|
|
363
|
+
exitCode: execResult.exitCode,
|
|
364
|
+
},
|
|
365
|
+
startTime
|
|
366
|
+
);
|
|
212
367
|
}
|
|
213
368
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
* - Sort findings deterministically
|
|
227
|
-
* - Ensure consistent field ordering
|
|
228
|
-
*/
|
|
229
|
-
function normalizeOutput(data: unknown, toolName: string): unknown {
|
|
230
|
-
if (!data || typeof data !== 'object') {
|
|
231
|
-
return data;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const obj = data as Record<string, unknown>;
|
|
235
|
-
|
|
236
|
-
// Process findings array if present
|
|
237
|
-
if (Array.isArray(obj.findings)) {
|
|
238
|
-
obj.findings = normalizeFindings(obj.findings);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Process blockers array if present
|
|
242
|
-
if (Array.isArray(obj.blockers)) {
|
|
243
|
-
obj.blockers = normalizeFindings(obj.blockers);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Process topIssues array if present
|
|
247
|
-
if (Array.isArray(obj.topIssues)) {
|
|
248
|
-
obj.topIssues = normalizeFindings(obj.topIssues);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return obj;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Normalize findings with stable IDs and sorting
|
|
256
|
-
*/
|
|
257
|
-
function normalizeFindings(findings: unknown[]): unknown[] {
|
|
258
|
-
const normalized = findings.map((finding, index) => {
|
|
259
|
-
if (!finding || typeof finding !== 'object') {
|
|
260
|
-
return finding;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const f = finding as Record<string, unknown>;
|
|
264
|
-
|
|
265
|
-
// Generate stable ID if missing or invalid
|
|
266
|
-
if (!f.id || typeof f.id !== 'string' || !f.id.match(/^[a-z_]+-[a-f0-9]{8}$/)) {
|
|
267
|
-
const category = (f.category as string) || (f.rule_id as string)?.split('.')[0] || 'unknown';
|
|
268
|
-
const evidence = Array.isArray(f.evidence) ? f.evidence[0] : {};
|
|
269
|
-
const evidenceObj = evidence as { file?: string; line?: number };
|
|
270
|
-
|
|
271
|
-
f.id = generateFindingId(
|
|
272
|
-
category,
|
|
369
|
+
// 7) Parse CLI output
|
|
370
|
+
let toolResult: ToolResult;
|
|
371
|
+
try {
|
|
372
|
+
toolResult = parseCliOutput(execResult.stdout, execResult.stderr);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
return buildErrorResponse(
|
|
375
|
+
request,
|
|
376
|
+
createErrorEnvelope(
|
|
377
|
+
"OUTPUT_PARSE_ERROR",
|
|
378
|
+
`Failed to parse CLI output: ${(err as Error).message}`,
|
|
379
|
+
request.requestId
|
|
380
|
+
),
|
|
273
381
|
{
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
382
|
+
...baseMetadata,
|
|
383
|
+
cliCommand: `vibecheck ${toolDef.cli.command}`,
|
|
384
|
+
exitCode: execResult.exitCode,
|
|
385
|
+
},
|
|
386
|
+
startTime
|
|
278
387
|
);
|
|
279
388
|
}
|
|
280
|
-
|
|
281
|
-
return f;
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// Sort deterministically
|
|
285
|
-
return sortFindings(normalized as Array<{
|
|
286
|
-
severity?: string;
|
|
287
|
-
rule_id?: string;
|
|
288
|
-
ruleId?: string;
|
|
289
|
-
evidence?: Array<{ file?: string; line?: number }>;
|
|
290
|
-
title?: string;
|
|
291
|
-
}>);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Execute tool by name
|
|
296
|
-
*/
|
|
297
|
-
async function executeToolByName(
|
|
298
|
-
toolName: string,
|
|
299
|
-
projectPath: string,
|
|
300
|
-
options: Record<string, unknown>,
|
|
301
|
-
execOptions: { timeout: number; requestId: string }
|
|
302
|
-
): Promise<{ ok: boolean; data?: unknown; error?: { message: string }; exitCode?: number }> {
|
|
303
|
-
const baseOptions = {
|
|
304
|
-
projectPath,
|
|
305
|
-
timeout: execOptions.timeout,
|
|
306
|
-
requestId: execOptions.requestId,
|
|
307
|
-
};
|
|
308
389
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
case 'vibecheck.init':
|
|
314
|
-
return ToolExecutors.init({ ...baseOptions, force: options.force as boolean });
|
|
315
|
-
|
|
316
|
-
case 'vibecheck.scan':
|
|
317
|
-
return ToolExecutors.scan({
|
|
318
|
-
...baseOptions,
|
|
319
|
-
profile: options.profile as string,
|
|
320
|
-
since: options.since as string,
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
case 'vibecheck.ship':
|
|
324
|
-
return ToolExecutors.ship({
|
|
325
|
-
...baseOptions,
|
|
326
|
-
strict: options.strict as boolean,
|
|
327
|
-
mockproof: options.mockproof as boolean,
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
case 'vibecheck.prove':
|
|
331
|
-
return ToolExecutors.prove({
|
|
332
|
-
...baseOptions,
|
|
333
|
-
url: options.url as string,
|
|
334
|
-
auth: options.auth as string,
|
|
335
|
-
maxFixRounds: options.maxFixRounds as number,
|
|
336
|
-
skipReality: options.skipReality as boolean,
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
case 'vibecheck.reality':
|
|
340
|
-
return ToolExecutors.reality({
|
|
341
|
-
...baseOptions,
|
|
342
|
-
url: options.url as string,
|
|
343
|
-
auth: options.auth as string,
|
|
344
|
-
verifyAuth: options.verifyAuth as boolean,
|
|
345
|
-
maxPages: options.maxPages as number,
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
case 'vibecheck.fix':
|
|
349
|
-
return ToolExecutors.fix({
|
|
350
|
-
...baseOptions,
|
|
351
|
-
apply: options.apply as boolean,
|
|
352
|
-
promptOnly: options.promptOnly as boolean,
|
|
353
|
-
autopilot: options.autopilot as boolean,
|
|
354
|
-
maxMissions: options.maxMissions as number,
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
case 'vibecheck.guard':
|
|
358
|
-
return ToolExecutors.guard({
|
|
359
|
-
...baseOptions,
|
|
360
|
-
claims: options.claims as boolean,
|
|
361
|
-
hallucinations: options.hallucinations as boolean,
|
|
362
|
-
prompts: options.prompts as boolean,
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
case 'vibecheck.ctx':
|
|
366
|
-
return ToolExecutors.ctx({
|
|
367
|
-
...baseOptions,
|
|
368
|
-
snapshot: options.snapshot as boolean,
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
case 'vibecheck.report':
|
|
372
|
-
return ToolExecutors.report({
|
|
373
|
-
...baseOptions,
|
|
374
|
-
type: options.type as string,
|
|
375
|
-
format: options.format as string,
|
|
376
|
-
output: options.output as string,
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
case 'vibecheck.polish':
|
|
380
|
-
return ToolExecutors.polish({
|
|
381
|
-
...baseOptions,
|
|
382
|
-
category: options.category as string,
|
|
383
|
-
fix: options.fix as boolean,
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
case 'vibecheck.status':
|
|
387
|
-
return ToolExecutors.status(baseOptions);
|
|
388
|
-
|
|
389
|
-
case 'vibecheck.share':
|
|
390
|
-
return ToolExecutors.share({
|
|
391
|
-
...baseOptions,
|
|
392
|
-
missionDir: options.missionDir as string,
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
case 'vibecheck.badge':
|
|
396
|
-
return ToolExecutors.badge({
|
|
397
|
-
...baseOptions,
|
|
398
|
-
format: options.format as string,
|
|
399
|
-
style: options.style as string,
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
// Query tools (non-CLI)
|
|
403
|
-
case 'vibecheck.get_truthpack':
|
|
404
|
-
return handleGetTruthpack(projectPath, options);
|
|
405
|
-
|
|
406
|
-
case 'vibecheck.validate_claim':
|
|
407
|
-
return handleValidateClaim(projectPath, options);
|
|
408
|
-
|
|
409
|
-
case 'vibecheck.search_evidence':
|
|
410
|
-
return handleSearchEvidence(projectPath, options);
|
|
411
|
-
|
|
412
|
-
case 'vibecheck.list_reports':
|
|
413
|
-
return handleListReports(projectPath, options);
|
|
414
|
-
|
|
415
|
-
case 'vibecheck.get_last_verdict':
|
|
416
|
-
return handleGetLastVerdict(projectPath);
|
|
417
|
-
|
|
418
|
-
case 'vibecheck.get_finding':
|
|
419
|
-
return handleGetFinding(projectPath, options);
|
|
420
|
-
|
|
421
|
-
default:
|
|
422
|
-
return { ok: false, error: { message: `No executor for tool: ${toolName}` } };
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Query tool handlers
|
|
428
|
-
*/
|
|
429
|
-
import { readFileSync as readSync, existsSync, readdirSync, statSync } from 'fs';
|
|
430
|
-
|
|
431
|
-
async function handleGetTruthpack(projectPath: string, options: Record<string, unknown>) {
|
|
432
|
-
const truthpackPaths = [
|
|
433
|
-
join(projectPath, '.vibecheck', 'truthpack.json'),
|
|
434
|
-
join(projectPath, '.vibecheck', 'truth', 'truthpack.json'),
|
|
435
|
-
];
|
|
436
|
-
|
|
437
|
-
for (const path of truthpackPaths) {
|
|
438
|
-
if (existsSync(path)) {
|
|
439
|
-
try {
|
|
440
|
-
const data = JSON.parse(readSync(path, 'utf-8'));
|
|
441
|
-
return { ok: true, data };
|
|
442
|
-
} catch {
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
390
|
+
// 8) Sort findings for stable output
|
|
391
|
+
if (toolResult.findings) {
|
|
392
|
+
toolResult.findings = sortFindings(toolResult.findings);
|
|
445
393
|
}
|
|
446
|
-
}
|
|
447
394
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const claim = options.claim as string;
|
|
458
|
-
const type = options.type as string;
|
|
459
|
-
|
|
460
|
-
// Load truthpack
|
|
461
|
-
const truthpackResult = await handleGetTruthpack(projectPath, {});
|
|
462
|
-
if (!truthpackResult.ok) {
|
|
463
|
-
return { ok: false, error: { message: 'Cannot validate claim: truthpack not available' } };
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const truthpack = truthpackResult.data as Record<string, unknown>;
|
|
467
|
-
|
|
468
|
-
// Simple validation logic
|
|
469
|
-
if (type === 'route_exists') {
|
|
470
|
-
const routes = (truthpack.routes as { server?: Array<{ path: string }> })?.server ?? [];
|
|
471
|
-
const found = routes.some(r => r.path === claim || r.path.includes(claim));
|
|
472
|
-
return {
|
|
473
|
-
ok: true,
|
|
474
|
-
data: {
|
|
475
|
-
valid: found,
|
|
476
|
-
claim,
|
|
477
|
-
type,
|
|
478
|
-
evidence: found ? routes.filter(r => r.path.includes(claim)).slice(0, 3) : [],
|
|
479
|
-
},
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (type === 'env_var_used') {
|
|
484
|
-
const vars = (truthpack.env as { vars?: Array<{ name: string }> })?.vars ?? [];
|
|
485
|
-
const found = vars.some(v => v.name === claim);
|
|
486
|
-
return {
|
|
487
|
-
ok: true,
|
|
488
|
-
data: {
|
|
489
|
-
valid: found,
|
|
490
|
-
claim,
|
|
491
|
-
type,
|
|
492
|
-
evidence: found ? vars.filter(v => v.name === claim) : [],
|
|
493
|
-
},
|
|
494
|
-
};
|
|
495
|
-
}
|
|
395
|
+
// 9) Validate output schema (soft validation - log but don't fail)
|
|
396
|
+
const outputErrors = validateSchema(toolResult, toolDef.outputSchema, "output");
|
|
397
|
+
if (outputErrors.length > 0) {
|
|
398
|
+
// Log but don't fail - output schema validation is informational
|
|
399
|
+
console.error(
|
|
400
|
+
`[WARN] Output schema validation errors for ${toolDef.name}:`,
|
|
401
|
+
JSON.stringify(outputErrors)
|
|
402
|
+
);
|
|
403
|
+
}
|
|
496
404
|
|
|
497
|
-
|
|
498
|
-
const
|
|
405
|
+
// 10) Build success response
|
|
406
|
+
const completedAt = new Date().toISOString();
|
|
499
407
|
return {
|
|
408
|
+
requestId: request.requestId,
|
|
409
|
+
traceId: request.traceId,
|
|
500
410
|
ok: true,
|
|
501
|
-
data:
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
411
|
+
data: toolResult,
|
|
412
|
+
metadata: {
|
|
413
|
+
startedAt,
|
|
414
|
+
completedAt,
|
|
415
|
+
durationMs: Date.now() - startTime,
|
|
416
|
+
tool: toolDef.name,
|
|
417
|
+
cliCommand: `vibecheck ${toolDef.cli.command}`,
|
|
418
|
+
exitCode: execResult.exitCode,
|
|
506
419
|
},
|
|
507
420
|
};
|
|
421
|
+
} catch (err) {
|
|
422
|
+
// Catch-all for unexpected errors
|
|
423
|
+
const error = err as Error;
|
|
424
|
+
return buildErrorResponse(
|
|
425
|
+
request,
|
|
426
|
+
createErrorEnvelope(
|
|
427
|
+
"INTERNAL_ERROR",
|
|
428
|
+
"An unexpected error occurred",
|
|
429
|
+
request.requestId,
|
|
430
|
+
{ receipt: error.message }
|
|
431
|
+
),
|
|
432
|
+
baseMetadata,
|
|
433
|
+
startTime
|
|
434
|
+
);
|
|
508
435
|
}
|
|
436
|
+
}
|
|
509
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Build error response
|
|
440
|
+
*/
|
|
441
|
+
function buildErrorResponse(
|
|
442
|
+
request: RunRequest,
|
|
443
|
+
error: ErrorEnvelope,
|
|
444
|
+
metadata: Partial<RunResponse["metadata"]>,
|
|
445
|
+
startTime: number
|
|
446
|
+
): RunResponse {
|
|
447
|
+
const completedAt = new Date().toISOString();
|
|
510
448
|
return {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
449
|
+
requestId: request.requestId,
|
|
450
|
+
traceId: request.traceId,
|
|
451
|
+
ok: false,
|
|
452
|
+
error,
|
|
453
|
+
metadata: {
|
|
454
|
+
startedAt: metadata.startedAt || completedAt,
|
|
455
|
+
completedAt,
|
|
456
|
+
durationMs: Date.now() - startTime,
|
|
457
|
+
tool: metadata.tool || request.tool,
|
|
458
|
+
cliCommand: metadata.cliCommand,
|
|
459
|
+
exitCode: metadata.exitCode,
|
|
517
460
|
},
|
|
518
461
|
};
|
|
519
462
|
}
|
|
520
463
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const limit = options.limit as number ?? 10;
|
|
464
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
465
|
+
// REGISTRY UTILITIES (exported for testing)
|
|
466
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
525
467
|
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
468
|
+
export function getAllTools(): ToolDefinition[] {
|
|
469
|
+
const registry = loadRegistry();
|
|
470
|
+
return Object.values(registry.tools);
|
|
471
|
+
}
|
|
531
472
|
|
|
532
|
-
|
|
533
|
-
|
|
473
|
+
export function getToolByName(name: string): ToolDefinition | null {
|
|
474
|
+
return getToolDefinition(name);
|
|
475
|
+
}
|
|
534
476
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (route.path.includes(query)) {
|
|
540
|
-
results.push({ type: 'route', match: route, confidence: 0.9 });
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
477
|
+
export function listToolNames(): string[] {
|
|
478
|
+
const registry = loadRegistry();
|
|
479
|
+
return Object.keys(registry.tools);
|
|
480
|
+
}
|
|
544
481
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
for (const v of vars) {
|
|
549
|
-
if (v.name.includes(query)) {
|
|
550
|
-
results.push({ type: 'env_var', match: v, confidence: 0.95 });
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
482
|
+
export function getToolsByTier(tier: "free" | "pro"): ToolDefinition[] {
|
|
483
|
+
return getAllTools().filter((t) => t.tier === tier);
|
|
484
|
+
}
|
|
554
485
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
data: {
|
|
558
|
-
query,
|
|
559
|
-
type,
|
|
560
|
-
results: results.slice(0, limit),
|
|
561
|
-
total: results.length,
|
|
562
|
-
},
|
|
563
|
-
};
|
|
486
|
+
export function getToolsByCategory(category: string): ToolDefinition[] {
|
|
487
|
+
return getAllTools().filter((t) => t.category === category);
|
|
564
488
|
}
|
|
565
489
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
490
|
+
/**
|
|
491
|
+
* Validate the entire registry
|
|
492
|
+
*/
|
|
493
|
+
export function validateRegistry(): { valid: boolean; errors: string[] } {
|
|
494
|
+
const errors: string[] = [];
|
|
495
|
+
const registry = loadRegistry();
|
|
571
496
|
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
{ type: 'verdict', path: 'last_ship.json' },
|
|
577
|
-
{ type: 'truthpack', path: 'truthpack.json' },
|
|
578
|
-
{ type: 'truthpack', path: 'truth/truthpack.json' },
|
|
579
|
-
{ type: 'summary', path: 'summary.json' },
|
|
580
|
-
{ type: 'sarif', path: 'results.sarif' },
|
|
581
|
-
{ type: 'html', path: 'report.html' },
|
|
582
|
-
];
|
|
583
|
-
|
|
584
|
-
for (const { type, path } of reportFiles) {
|
|
585
|
-
const fullPath = join(vibecheckDir, path);
|
|
586
|
-
if (existsSync(fullPath)) {
|
|
587
|
-
const stat = statSync(fullPath);
|
|
588
|
-
reports.push({ type, path: `.vibecheck/${path}`, created: stat.mtimeMs });
|
|
497
|
+
for (const [name, tool] of Object.entries(registry.tools)) {
|
|
498
|
+
// Check name matches key
|
|
499
|
+
if (tool.name !== name) {
|
|
500
|
+
errors.push(`Tool '${name}' has mismatched name property: '${tool.name}'`);
|
|
589
501
|
}
|
|
590
|
-
}
|
|
591
502
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
503
|
+
// Check required fields
|
|
504
|
+
if (!tool.tier) errors.push(`Tool '${name}' missing tier`);
|
|
505
|
+
if (!tool.category) errors.push(`Tool '${name}' missing category`);
|
|
506
|
+
if (!tool.inputSchema) errors.push(`Tool '${name}' missing inputSchema`);
|
|
507
|
+
if (!tool.outputSchema) errors.push(`Tool '${name}' missing outputSchema`);
|
|
508
|
+
if (!tool.cli) errors.push(`Tool '${name}' missing cli mapping`);
|
|
509
|
+
if (!tool.cli?.command) errors.push(`Tool '${name}' missing cli.command`);
|
|
510
|
+
if (!tool.cli?.argMap) errors.push(`Tool '${name}' missing cli.argMap`);
|
|
511
|
+
|
|
512
|
+
// Validate tier
|
|
513
|
+
if (tool.tier && !["free", "pro"].includes(tool.tier)) {
|
|
514
|
+
errors.push(`Tool '${name}' has invalid tier: '${tool.tier}'`);
|
|
602
515
|
}
|
|
603
|
-
}
|
|
604
516
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
async function handleGetLastVerdict(projectPath: string) {
|
|
614
|
-
const verdictPath = join(projectPath, '.vibecheck', 'last_ship.json');
|
|
615
|
-
|
|
616
|
-
if (!existsSync(verdictPath)) {
|
|
617
|
-
return { ok: false, error: { message: 'No verdict found. Run vibecheck.ship first.' } };
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
try {
|
|
621
|
-
const data = JSON.parse(readSync(verdictPath, 'utf-8'));
|
|
622
|
-
return { ok: true, data };
|
|
623
|
-
} catch {
|
|
624
|
-
return { ok: false, error: { message: 'Failed to read verdict file' } };
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
async function handleGetFinding(projectPath: string, options: Record<string, unknown>) {
|
|
629
|
-
const findingId = options.findingId as string;
|
|
630
|
-
|
|
631
|
-
// Load last ship report
|
|
632
|
-
const verdictResult = await handleGetLastVerdict(projectPath);
|
|
633
|
-
if (!verdictResult.ok) {
|
|
634
|
-
return verdictResult;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const verdict = verdictResult.data as { findings?: Array<{ id: string }> };
|
|
638
|
-
const finding = verdict.findings?.find(f => f.id === findingId);
|
|
517
|
+
// Validate input schema is valid JSON Schema
|
|
518
|
+
if (tool.inputSchema) {
|
|
519
|
+
try {
|
|
520
|
+
ajv.compile(tool.inputSchema);
|
|
521
|
+
} catch (err) {
|
|
522
|
+
errors.push(`Tool '${name}' has invalid inputSchema: ${(err as Error).message}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
639
525
|
|
|
640
|
-
|
|
641
|
-
|
|
526
|
+
// Validate output schema is valid JSON Schema
|
|
527
|
+
if (tool.outputSchema) {
|
|
528
|
+
try {
|
|
529
|
+
ajv.compile(tool.outputSchema);
|
|
530
|
+
} catch (err) {
|
|
531
|
+
errors.push(`Tool '${name}' has invalid outputSchema: ${(err as Error).message}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
642
534
|
}
|
|
643
535
|
|
|
644
|
-
return {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
* Cancel a running tool execution
|
|
649
|
-
*/
|
|
650
|
-
export function cancelTool(requestId: string): boolean {
|
|
651
|
-
return cancelExecution(requestId);
|
|
536
|
+
return {
|
|
537
|
+
valid: errors.length === 0,
|
|
538
|
+
errors,
|
|
539
|
+
};
|
|
652
540
|
}
|
|
653
541
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
export function listTools(): Array<{ name: string; description: string; tier: string; category: string }> {
|
|
658
|
-
const registry = loadRegistry();
|
|
659
|
-
return registry.tools.map((t: { name: string; description: string; tier: string; category: string }) => ({
|
|
660
|
-
name: t.name,
|
|
661
|
-
description: t.description,
|
|
662
|
-
tier: t.tier,
|
|
663
|
-
category: t.category,
|
|
664
|
-
}));
|
|
665
|
-
}
|
|
542
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
543
|
+
// DEFAULT EXPORT
|
|
544
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
666
545
|
|
|
667
546
|
export default {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
547
|
+
handleToolRequest,
|
|
548
|
+
getAllTools,
|
|
549
|
+
getToolByName,
|
|
550
|
+
listToolNames,
|
|
551
|
+
getToolsByTier,
|
|
552
|
+
getToolsByCategory,
|
|
553
|
+
validateRegistry,
|
|
671
554
|
};
|