@vibecheckai/cli 3.5.0 → 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 +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- 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 +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- 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 +73 -373
- 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/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- 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/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/scan-output.js +91 -76
- 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 +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -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 +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- 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/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/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- 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/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- 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/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- 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.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/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/intelligence/cross-repo-intelligence.js +0 -817
- 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/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/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Tool Handler
|
|
3
|
+
*
|
|
4
|
+
* Registry-driven dispatcher for all MCP tools.
|
|
5
|
+
*
|
|
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
|
+
*/
|
|
15
|
+
|
|
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
|
+
}
|
|
41
|
+
|
|
42
|
+
let registryCache: ToolRegistry | null = null;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load tool registry (cached)
|
|
46
|
+
*/
|
|
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;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get tool definition by name (supports aliases)
|
|
58
|
+
*/
|
|
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;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
78
|
+
// VALIDATION
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
80
|
+
|
|
81
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate data against JSON schema
|
|
85
|
+
*/
|
|
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
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Handle a tool execution request
|
|
197
|
+
*/
|
|
198
|
+
export async function handleToolRequest(request: RunRequest): Promise<RunResponse> {
|
|
199
|
+
const startedAt = new Date().toISOString();
|
|
200
|
+
const startTime = Date.now();
|
|
201
|
+
|
|
202
|
+
const baseMetadata = {
|
|
203
|
+
startedAt,
|
|
204
|
+
completedAt: "",
|
|
205
|
+
durationMs: 0,
|
|
206
|
+
tool: request.tool,
|
|
207
|
+
};
|
|
208
|
+
|
|
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
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
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
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
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
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
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
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
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
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 5) Build CLI arguments
|
|
316
|
+
const cliArgs = buildCliArgs(
|
|
317
|
+
{ ...request.args, projectPath: resolvedProjectPath },
|
|
318
|
+
toolDef.cli.argMap,
|
|
319
|
+
toolDef.cli.fixedFlags
|
|
320
|
+
);
|
|
321
|
+
|
|
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,
|
|
328
|
+
});
|
|
329
|
+
|
|
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
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
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
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
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
|
+
),
|
|
381
|
+
{
|
|
382
|
+
...baseMetadata,
|
|
383
|
+
cliCommand: `vibecheck ${toolDef.cli.command}`,
|
|
384
|
+
exitCode: execResult.exitCode,
|
|
385
|
+
},
|
|
386
|
+
startTime
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 8) Sort findings for stable output
|
|
391
|
+
if (toolResult.findings) {
|
|
392
|
+
toolResult.findings = sortFindings(toolResult.findings);
|
|
393
|
+
}
|
|
394
|
+
|
|
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
|
+
}
|
|
404
|
+
|
|
405
|
+
// 10) Build success response
|
|
406
|
+
const completedAt = new Date().toISOString();
|
|
407
|
+
return {
|
|
408
|
+
requestId: request.requestId,
|
|
409
|
+
traceId: request.traceId,
|
|
410
|
+
ok: true,
|
|
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,
|
|
419
|
+
},
|
|
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
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
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();
|
|
448
|
+
return {
|
|
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,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
465
|
+
// REGISTRY UTILITIES (exported for testing)
|
|
466
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
467
|
+
|
|
468
|
+
export function getAllTools(): ToolDefinition[] {
|
|
469
|
+
const registry = loadRegistry();
|
|
470
|
+
return Object.values(registry.tools);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export function getToolByName(name: string): ToolDefinition | null {
|
|
474
|
+
return getToolDefinition(name);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export function listToolNames(): string[] {
|
|
478
|
+
const registry = loadRegistry();
|
|
479
|
+
return Object.keys(registry.tools);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export function getToolsByTier(tier: "free" | "pro"): ToolDefinition[] {
|
|
483
|
+
return getAllTools().filter((t) => t.tier === tier);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export function getToolsByCategory(category: string): ToolDefinition[] {
|
|
487
|
+
return getAllTools().filter((t) => t.category === category);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Validate the entire registry
|
|
492
|
+
*/
|
|
493
|
+
export function validateRegistry(): { valid: boolean; errors: string[] } {
|
|
494
|
+
const errors: string[] = [];
|
|
495
|
+
const registry = loadRegistry();
|
|
496
|
+
|
|
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}'`);
|
|
501
|
+
}
|
|
502
|
+
|
|
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}'`);
|
|
515
|
+
}
|
|
516
|
+
|
|
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
|
+
}
|
|
525
|
+
|
|
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
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
valid: errors.length === 0,
|
|
538
|
+
errors,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
543
|
+
// DEFAULT EXPORT
|
|
544
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
545
|
+
|
|
546
|
+
export default {
|
|
547
|
+
handleToolRequest,
|
|
548
|
+
getAllTools,
|
|
549
|
+
getToolByName,
|
|
550
|
+
listToolNames,
|
|
551
|
+
getToolsByTier,
|
|
552
|
+
getToolsByCategory,
|
|
553
|
+
validateRegistry,
|
|
554
|
+
};
|