@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,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox - Path Security for MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Enforces that all tool requests resolve within an allowed workspace root.
|
|
5
|
+
* Prevents path traversal attacks, absolute path escapes, and symlink escapes.
|
|
6
|
+
*
|
|
7
|
+
* @module mcp-server/lib/sandbox
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default excluded directory patterns.
|
|
15
|
+
* These are enforced at the MCP level even if CLI forgets.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_EXCLUSIONS = [
|
|
18
|
+
'node_modules/**',
|
|
19
|
+
'venv/**',
|
|
20
|
+
'.venv/**',
|
|
21
|
+
'dist/**',
|
|
22
|
+
'build/**',
|
|
23
|
+
'.next/**',
|
|
24
|
+
'out/**',
|
|
25
|
+
'coverage/**',
|
|
26
|
+
'.git/**',
|
|
27
|
+
'.cache/**',
|
|
28
|
+
'.turbo/**',
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Third-party directories (can be included via config)
|
|
33
|
+
*/
|
|
34
|
+
export const THIRD_PARTY_DIRS = ['node_modules/**', 'venv/**', '.venv/**'] as const;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generated/build directories (can be included via config)
|
|
38
|
+
*/
|
|
39
|
+
export const GENERATED_DIRS = ['dist/**', 'build/**', '.next/**', 'out/**'] as const;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Configuration options for sandbox path resolution
|
|
43
|
+
*/
|
|
44
|
+
export interface SandboxConfig {
|
|
45
|
+
/** Workspace root directory (absolute path) */
|
|
46
|
+
workspaceRoot: string;
|
|
47
|
+
/** Include third-party directories (node_modules, venv, etc.) */
|
|
48
|
+
includeThirdParty?: boolean;
|
|
49
|
+
/** Include generated/build directories (dist, build, .next, out) */
|
|
50
|
+
includeGenerated?: boolean;
|
|
51
|
+
/** Additional paths to exclude (glob patterns) */
|
|
52
|
+
additionalExclusions?: string[];
|
|
53
|
+
/** Paths to explicitly allow (overrides exclusions) */
|
|
54
|
+
allowlist?: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Result of a sandbox path validation
|
|
59
|
+
*/
|
|
60
|
+
export interface SandboxResult {
|
|
61
|
+
/** Whether the path is valid and safe */
|
|
62
|
+
valid: boolean;
|
|
63
|
+
/** The resolved absolute path (if valid) */
|
|
64
|
+
resolvedPath?: string;
|
|
65
|
+
/** Error message (if invalid) */
|
|
66
|
+
error?: string;
|
|
67
|
+
/** Error code for programmatic handling */
|
|
68
|
+
errorCode?:
|
|
69
|
+
| 'TRAVERSAL_DETECTED'
|
|
70
|
+
| 'ABSOLUTE_PATH_OUTSIDE_ROOT'
|
|
71
|
+
| 'SYMLINK_ESCAPE'
|
|
72
|
+
| 'EXCLUDED_PATH'
|
|
73
|
+
| 'INVALID_ROOT';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Checks if a path matches any of the given glob patterns.
|
|
78
|
+
* Simple glob matching supporting ** and * wildcards.
|
|
79
|
+
*/
|
|
80
|
+
function matchesGlob(filePath: string, patterns: readonly string[]): boolean {
|
|
81
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
82
|
+
|
|
83
|
+
for (const pattern of patterns) {
|
|
84
|
+
const normalizedPattern = pattern.replace(/\\/g, '/');
|
|
85
|
+
|
|
86
|
+
// Convert glob pattern to regex
|
|
87
|
+
const regexPattern = normalizedPattern
|
|
88
|
+
.replace(/\*\*/g, '<<<GLOBSTAR>>>')
|
|
89
|
+
.replace(/\*/g, '[^/]*')
|
|
90
|
+
.replace(/<<<GLOBSTAR>>>/g, '.*')
|
|
91
|
+
.replace(/\//g, '\\/');
|
|
92
|
+
|
|
93
|
+
const regex = new RegExp(`^${regexPattern}$|^${regexPattern}\\/|\\/${regexPattern}$|\\/${regexPattern}\\/`);
|
|
94
|
+
|
|
95
|
+
if (regex.test(normalizedPath)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Also check if the path starts with the pattern's directory
|
|
100
|
+
const patternDir = normalizedPattern.replace(/\/?\*\*.*$/, '');
|
|
101
|
+
if (patternDir && normalizedPath.startsWith(patternDir + '/')) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (patternDir && normalizedPath === patternDir) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Detects path traversal attempts (../ sequences)
|
|
114
|
+
*/
|
|
115
|
+
function hasTraversalSequence(inputPath: string): boolean {
|
|
116
|
+
const normalized = inputPath.replace(/\\/g, '/');
|
|
117
|
+
|
|
118
|
+
// Check for .. sequences
|
|
119
|
+
if (normalized.includes('..')) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check for URL-encoded traversal
|
|
124
|
+
if (normalized.includes('%2e%2e') || normalized.includes('%2E%2E')) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for null byte injection
|
|
129
|
+
if (normalized.includes('\0') || normalized.includes('%00')) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Checks if a path is an absolute path
|
|
138
|
+
*/
|
|
139
|
+
function isAbsolutePath(inputPath: string): boolean {
|
|
140
|
+
// Windows absolute paths: C:\, D:\, etc.
|
|
141
|
+
if (/^[a-zA-Z]:[/\\]/.test(inputPath)) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Unix absolute paths: /
|
|
146
|
+
if (inputPath.startsWith('/')) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// UNC paths: \\server\share
|
|
151
|
+
if (inputPath.startsWith('\\\\')) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Gets the active exclusion patterns based on config
|
|
160
|
+
*/
|
|
161
|
+
export function getActiveExclusions(config: SandboxConfig): string[] {
|
|
162
|
+
const exclusions: string[] = [];
|
|
163
|
+
|
|
164
|
+
// Start with defaults that are always excluded
|
|
165
|
+
exclusions.push('coverage/**', '.git/**', '.cache/**', '.turbo/**');
|
|
166
|
+
|
|
167
|
+
// Add third-party dirs unless explicitly included
|
|
168
|
+
if (!config.includeThirdParty) {
|
|
169
|
+
exclusions.push(...THIRD_PARTY_DIRS);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Add generated dirs unless explicitly included
|
|
173
|
+
if (!config.includeGenerated) {
|
|
174
|
+
exclusions.push(...GENERATED_DIRS);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add any additional exclusions
|
|
178
|
+
if (config.additionalExclusions) {
|
|
179
|
+
exclusions.push(...config.additionalExclusions);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return exclusions;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Validates and resolves a path within the sandbox.
|
|
187
|
+
*
|
|
188
|
+
* @param inputPath - The path to validate (can be relative or absolute)
|
|
189
|
+
* @param config - Sandbox configuration
|
|
190
|
+
* @returns SandboxResult with validation status and resolved path
|
|
191
|
+
*/
|
|
192
|
+
export function resolveSandboxPath(inputPath: string, config: SandboxConfig): SandboxResult {
|
|
193
|
+
// Validate workspace root exists and is absolute
|
|
194
|
+
if (!config.workspaceRoot || !isAbsolutePath(config.workspaceRoot)) {
|
|
195
|
+
return {
|
|
196
|
+
valid: false,
|
|
197
|
+
error: 'Invalid workspace root: must be an absolute path',
|
|
198
|
+
errorCode: 'INVALID_ROOT',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Normalize workspace root
|
|
203
|
+
const normalizedRoot = path.resolve(config.workspaceRoot);
|
|
204
|
+
|
|
205
|
+
// Check for traversal sequences in the input
|
|
206
|
+
if (hasTraversalSequence(inputPath)) {
|
|
207
|
+
return {
|
|
208
|
+
valid: false,
|
|
209
|
+
error: `Path traversal detected in: ${inputPath}`,
|
|
210
|
+
errorCode: 'TRAVERSAL_DETECTED',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Determine the target path
|
|
215
|
+
let targetPath: string;
|
|
216
|
+
|
|
217
|
+
if (isAbsolutePath(inputPath)) {
|
|
218
|
+
// Absolute path - check if it's within root
|
|
219
|
+
targetPath = path.resolve(inputPath);
|
|
220
|
+
} else {
|
|
221
|
+
// Relative path - resolve from workspace root
|
|
222
|
+
targetPath = path.resolve(normalizedRoot, inputPath);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Normalize both paths for comparison
|
|
226
|
+
const normalizedTarget = path.normalize(targetPath);
|
|
227
|
+
const normalizedRootWithSep = normalizedRoot.endsWith(path.sep)
|
|
228
|
+
? normalizedRoot
|
|
229
|
+
: normalizedRoot + path.sep;
|
|
230
|
+
|
|
231
|
+
// Check if target is within workspace root
|
|
232
|
+
if (!normalizedTarget.startsWith(normalizedRootWithSep) && normalizedTarget !== normalizedRoot) {
|
|
233
|
+
return {
|
|
234
|
+
valid: false,
|
|
235
|
+
error: `Path escapes workspace root: ${inputPath}`,
|
|
236
|
+
errorCode: 'ABSOLUTE_PATH_OUTSIDE_ROOT',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check for symlink escape (if the path exists)
|
|
241
|
+
try {
|
|
242
|
+
if (fs.existsSync(targetPath)) {
|
|
243
|
+
const realPath = fs.realpathSync(targetPath);
|
|
244
|
+
const normalizedRealPath = path.normalize(realPath);
|
|
245
|
+
|
|
246
|
+
if (
|
|
247
|
+
!normalizedRealPath.startsWith(normalizedRootWithSep) &&
|
|
248
|
+
normalizedRealPath !== normalizedRoot
|
|
249
|
+
) {
|
|
250
|
+
return {
|
|
251
|
+
valid: false,
|
|
252
|
+
error: `Symlink escape detected: ${inputPath} resolves to ${realPath}`,
|
|
253
|
+
errorCode: 'SYMLINK_ESCAPE',
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
// If we can't check realpath, allow it (file may not exist yet)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Get relative path for exclusion checking
|
|
262
|
+
const relativePath = path.relative(normalizedRoot, normalizedTarget);
|
|
263
|
+
|
|
264
|
+
// Check against allowlist first (allowlist overrides exclusions)
|
|
265
|
+
if (config.allowlist && matchesGlob(relativePath, config.allowlist)) {
|
|
266
|
+
return {
|
|
267
|
+
valid: true,
|
|
268
|
+
resolvedPath: normalizedTarget,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Check against exclusions
|
|
273
|
+
const activeExclusions = getActiveExclusions(config);
|
|
274
|
+
if (matchesGlob(relativePath, activeExclusions)) {
|
|
275
|
+
return {
|
|
276
|
+
valid: false,
|
|
277
|
+
error: `Path is excluded: ${relativePath}`,
|
|
278
|
+
errorCode: 'EXCLUDED_PATH',
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
valid: true,
|
|
284
|
+
resolvedPath: normalizedTarget,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Validates multiple paths at once.
|
|
290
|
+
*
|
|
291
|
+
* @param inputPaths - Array of paths to validate
|
|
292
|
+
* @param config - Sandbox configuration
|
|
293
|
+
* @returns Object with overall validity and individual results
|
|
294
|
+
*/
|
|
295
|
+
export function validateSandboxPaths(
|
|
296
|
+
inputPaths: string[],
|
|
297
|
+
config: SandboxConfig
|
|
298
|
+
): {
|
|
299
|
+
valid: boolean;
|
|
300
|
+
results: Map<string, SandboxResult>;
|
|
301
|
+
errors: string[];
|
|
302
|
+
} {
|
|
303
|
+
const results = new Map<string, SandboxResult>();
|
|
304
|
+
const errors: string[] = [];
|
|
305
|
+
|
|
306
|
+
for (const inputPath of inputPaths) {
|
|
307
|
+
const result = resolveSandboxPath(inputPath, config);
|
|
308
|
+
results.set(inputPath, result);
|
|
309
|
+
|
|
310
|
+
if (!result.valid && result.error) {
|
|
311
|
+
errors.push(result.error);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
valid: errors.length === 0,
|
|
317
|
+
results,
|
|
318
|
+
errors,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Creates a sandbox-safe path resolver bound to a specific config.
|
|
324
|
+
* Useful for creating a resolver that can be reused multiple times.
|
|
325
|
+
*
|
|
326
|
+
* @param config - Sandbox configuration
|
|
327
|
+
* @returns A function that resolves paths within the sandbox
|
|
328
|
+
*/
|
|
329
|
+
export function createSandboxResolver(config: SandboxConfig) {
|
|
330
|
+
return (inputPath: string): SandboxResult => {
|
|
331
|
+
return resolveSandboxPath(inputPath, config);
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* RunRequest interface for config overrides.
|
|
337
|
+
* This matches the expected interface from tool requests.
|
|
338
|
+
*/
|
|
339
|
+
export interface RunRequest {
|
|
340
|
+
/** Path to resolve */
|
|
341
|
+
path?: string;
|
|
342
|
+
/** Paths to resolve */
|
|
343
|
+
paths?: string[];
|
|
344
|
+
/** Project path / workspace root */
|
|
345
|
+
projectPath?: string;
|
|
346
|
+
/** Include third-party directories (node_modules, venv) */
|
|
347
|
+
includeThirdParty?: boolean;
|
|
348
|
+
/** Include generated directories (dist, build, .next) */
|
|
349
|
+
includeGenerated?: boolean;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Creates a SandboxConfig from a RunRequest.
|
|
354
|
+
*
|
|
355
|
+
* @param request - The run request with optional overrides
|
|
356
|
+
* @param defaultRoot - Default workspace root if not in request
|
|
357
|
+
* @returns SandboxConfig ready for use
|
|
358
|
+
*/
|
|
359
|
+
export function configFromRunRequest(request: RunRequest, defaultRoot: string): SandboxConfig {
|
|
360
|
+
return {
|
|
361
|
+
workspaceRoot: request.projectPath || defaultRoot,
|
|
362
|
+
includeThirdParty: request.includeThirdParty ?? false,
|
|
363
|
+
includeGenerated: request.includeGenerated ?? false,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Validates a path from a RunRequest, applying config overrides.
|
|
369
|
+
*
|
|
370
|
+
* @param request - The run request containing path and overrides
|
|
371
|
+
* @param defaultRoot - Default workspace root
|
|
372
|
+
* @returns SandboxResult
|
|
373
|
+
*/
|
|
374
|
+
export function validateRunRequest(request: RunRequest, defaultRoot: string): SandboxResult {
|
|
375
|
+
const config = configFromRunRequest(request, defaultRoot);
|
|
376
|
+
|
|
377
|
+
if (request.path) {
|
|
378
|
+
return resolveSandboxPath(request.path, config);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (request.paths && request.paths.length > 0) {
|
|
382
|
+
const multiResult = validateSandboxPaths(request.paths, config);
|
|
383
|
+
if (!multiResult.valid) {
|
|
384
|
+
return {
|
|
385
|
+
valid: false,
|
|
386
|
+
error: multiResult.errors.join('; '),
|
|
387
|
+
errorCode: 'TRAVERSAL_DETECTED', // Use first error type
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return { valid: true };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// No path specified
|
|
394
|
+
return { valid: true };
|
|
395
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for MCP tool handler system
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
6
|
+
// REQUEST/RESPONSE TYPES
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
8
|
+
|
|
9
|
+
export interface RunRequest {
|
|
10
|
+
/** Unique request identifier for tracing */
|
|
11
|
+
requestId: string;
|
|
12
|
+
/** Optional trace ID for distributed tracing */
|
|
13
|
+
traceId?: string;
|
|
14
|
+
/** Tool name to execute */
|
|
15
|
+
tool: string;
|
|
16
|
+
/** Tool arguments */
|
|
17
|
+
args: Record<string, unknown>;
|
|
18
|
+
/** User context */
|
|
19
|
+
context?: RequestContext;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RequestContext {
|
|
23
|
+
/** User's subscription tier */
|
|
24
|
+
tier?: "free" | "pro";
|
|
25
|
+
/** User ID for audit */
|
|
26
|
+
userId?: string;
|
|
27
|
+
/** Project root path */
|
|
28
|
+
projectRoot?: string;
|
|
29
|
+
/** API key (hashed) */
|
|
30
|
+
apiKeyHash?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RunResponse {
|
|
34
|
+
/** Request ID echo */
|
|
35
|
+
requestId: string;
|
|
36
|
+
/** Trace ID echo */
|
|
37
|
+
traceId?: string;
|
|
38
|
+
/** Success indicator */
|
|
39
|
+
ok: boolean;
|
|
40
|
+
/** Result data (on success) */
|
|
41
|
+
data?: ToolResult;
|
|
42
|
+
/** Error envelope (on failure) */
|
|
43
|
+
error?: ErrorEnvelope;
|
|
44
|
+
/** Execution metadata */
|
|
45
|
+
metadata: ResponseMetadata;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ResponseMetadata {
|
|
49
|
+
/** Execution start time (ISO) */
|
|
50
|
+
startedAt: string;
|
|
51
|
+
/** Execution end time (ISO) */
|
|
52
|
+
completedAt: string;
|
|
53
|
+
/** Duration in milliseconds */
|
|
54
|
+
durationMs: number;
|
|
55
|
+
/** Tool that was executed */
|
|
56
|
+
tool: string;
|
|
57
|
+
/** CLI command that was executed (if applicable) */
|
|
58
|
+
cliCommand?: string;
|
|
59
|
+
/** Exit code from CLI */
|
|
60
|
+
exitCode?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
64
|
+
// ERROR ENVELOPE
|
|
65
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
66
|
+
|
|
67
|
+
export interface ErrorEnvelope {
|
|
68
|
+
/** Error code for programmatic handling */
|
|
69
|
+
code: ErrorCode;
|
|
70
|
+
/** Human-readable message */
|
|
71
|
+
message: string;
|
|
72
|
+
/** Suggested next steps */
|
|
73
|
+
nextSteps?: string[];
|
|
74
|
+
/** Evidence/receipt (file:line) */
|
|
75
|
+
receipt?: string;
|
|
76
|
+
/** Validation errors (for INPUT_VALIDATION) */
|
|
77
|
+
validationErrors?: ValidationError[];
|
|
78
|
+
/** Request ID for support */
|
|
79
|
+
requestId?: string;
|
|
80
|
+
/** User action to resolve (for tier errors) */
|
|
81
|
+
userAction?: string;
|
|
82
|
+
/** Whether the operation can be retried */
|
|
83
|
+
retryable?: boolean;
|
|
84
|
+
/** Current user tier (for tier errors) */
|
|
85
|
+
tier?: string;
|
|
86
|
+
/** Required tier (for tier errors) */
|
|
87
|
+
required?: string;
|
|
88
|
+
/** Tool name (for tier errors) */
|
|
89
|
+
tool?: string;
|
|
90
|
+
/** Blocked option (for option-level gates) */
|
|
91
|
+
option?: string;
|
|
92
|
+
/** Upgrade URL */
|
|
93
|
+
upgradeUrl?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type ErrorCode =
|
|
97
|
+
| "INPUT_VALIDATION"
|
|
98
|
+
| "TOOL_NOT_FOUND"
|
|
99
|
+
| "TIER_REQUIRED"
|
|
100
|
+
| "NOT_ENTITLED" // Tool requires higher tier
|
|
101
|
+
| "OPTION_NOT_ENTITLED" // Specific option requires higher tier
|
|
102
|
+
| "PATH_VIOLATION"
|
|
103
|
+
| "EXECUTOR_FAILED"
|
|
104
|
+
| "OUTPUT_PARSE_ERROR"
|
|
105
|
+
| "OUTPUT_VALIDATION"
|
|
106
|
+
| "TIMEOUT"
|
|
107
|
+
| "INTERNAL_ERROR"
|
|
108
|
+
| "INVALID_API_KEY"
|
|
109
|
+
| "RATE_LIMITED";
|
|
110
|
+
|
|
111
|
+
export interface ValidationError {
|
|
112
|
+
/** JSON path to the invalid field */
|
|
113
|
+
path: string;
|
|
114
|
+
/** Error message */
|
|
115
|
+
message: string;
|
|
116
|
+
/** Expected value/type */
|
|
117
|
+
expected?: string;
|
|
118
|
+
/** Actual value/type */
|
|
119
|
+
actual?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
123
|
+
// TOOL REGISTRY
|
|
124
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
125
|
+
|
|
126
|
+
export interface ToolDefinition {
|
|
127
|
+
/** Tool name (unique identifier) */
|
|
128
|
+
name: string;
|
|
129
|
+
/** Human-readable description */
|
|
130
|
+
description: string;
|
|
131
|
+
/** Subscription tier required */
|
|
132
|
+
tier: "free" | "pro";
|
|
133
|
+
/** Tool category */
|
|
134
|
+
category: ToolCategory;
|
|
135
|
+
/** JSON Schema for input validation */
|
|
136
|
+
inputSchema: JsonSchema;
|
|
137
|
+
/** JSON Schema for output validation */
|
|
138
|
+
outputSchema: JsonSchema;
|
|
139
|
+
/** CLI command mapping */
|
|
140
|
+
cli: CliMapping;
|
|
141
|
+
/** Tool aliases */
|
|
142
|
+
aliases?: string[];
|
|
143
|
+
/** Related tools */
|
|
144
|
+
related?: string[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type ToolCategory =
|
|
148
|
+
| "scan"
|
|
149
|
+
| "proof"
|
|
150
|
+
| "authority"
|
|
151
|
+
| "report"
|
|
152
|
+
| "setup"
|
|
153
|
+
| "account"
|
|
154
|
+
| "conductor"
|
|
155
|
+
| "firewall";
|
|
156
|
+
|
|
157
|
+
export interface CliMapping {
|
|
158
|
+
/** CLI command to execute */
|
|
159
|
+
command: string;
|
|
160
|
+
/** Argument mapping: { inputField: "--flag" } */
|
|
161
|
+
argMap: Record<string, string>;
|
|
162
|
+
/** Fixed flags always passed */
|
|
163
|
+
fixedFlags?: string[];
|
|
164
|
+
/** Timeout in milliseconds */
|
|
165
|
+
timeoutMs?: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface JsonSchema {
|
|
169
|
+
type: string;
|
|
170
|
+
properties?: Record<string, JsonSchemaProperty>;
|
|
171
|
+
required?: string[];
|
|
172
|
+
additionalProperties?: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface JsonSchemaProperty {
|
|
176
|
+
type: string;
|
|
177
|
+
description?: string;
|
|
178
|
+
default?: unknown;
|
|
179
|
+
enum?: unknown[];
|
|
180
|
+
items?: JsonSchemaProperty;
|
|
181
|
+
properties?: Record<string, JsonSchemaProperty>;
|
|
182
|
+
required?: string[];
|
|
183
|
+
minimum?: number;
|
|
184
|
+
maximum?: number;
|
|
185
|
+
pattern?: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
189
|
+
// TOOL RESULT (CANONICAL OUTPUT)
|
|
190
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
191
|
+
|
|
192
|
+
export interface ToolResult {
|
|
193
|
+
/** Verdict (for scan/ship tools) */
|
|
194
|
+
verdict?: "SHIP" | "WARN" | "BLOCK" | "PASS" | "FAIL";
|
|
195
|
+
/** Findings list */
|
|
196
|
+
findings?: Finding[];
|
|
197
|
+
/** Summary statistics */
|
|
198
|
+
summary?: ResultSummary;
|
|
199
|
+
/** Raw output (if applicable) */
|
|
200
|
+
raw?: unknown;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface Finding {
|
|
204
|
+
/** Stable finding ID: sha256(rule_id + path + line + message) */
|
|
205
|
+
id: string;
|
|
206
|
+
/** Rule that generated this finding */
|
|
207
|
+
ruleId: string;
|
|
208
|
+
/** Severity level */
|
|
209
|
+
severity: "critical" | "high" | "medium" | "low" | "info";
|
|
210
|
+
/** Finding message */
|
|
211
|
+
message: string;
|
|
212
|
+
/** File path (relative to project root) */
|
|
213
|
+
path: string;
|
|
214
|
+
/** Line number (1-based) */
|
|
215
|
+
line: number;
|
|
216
|
+
/** Column number (1-based, optional) */
|
|
217
|
+
column?: number;
|
|
218
|
+
/** Code snippet */
|
|
219
|
+
snippet?: string;
|
|
220
|
+
/** Finding category */
|
|
221
|
+
category?: string;
|
|
222
|
+
/** Suggested fix */
|
|
223
|
+
fix?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface ResultSummary {
|
|
227
|
+
/** Total findings count */
|
|
228
|
+
total: number;
|
|
229
|
+
/** Findings by severity */
|
|
230
|
+
bySeverity: Record<string, number>;
|
|
231
|
+
/** Findings by category */
|
|
232
|
+
byCategory?: Record<string, number>;
|
|
233
|
+
/** Files scanned */
|
|
234
|
+
filesScanned?: number;
|
|
235
|
+
/** Duration of scan */
|
|
236
|
+
scanDurationMs?: number;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
240
|
+
// EXECUTOR TYPES
|
|
241
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
242
|
+
|
|
243
|
+
export interface ExecutorOptions {
|
|
244
|
+
/** Working directory */
|
|
245
|
+
cwd: string;
|
|
246
|
+
/** Timeout in milliseconds */
|
|
247
|
+
timeoutMs: number;
|
|
248
|
+
/** Environment variables to pass */
|
|
249
|
+
env?: Record<string, string>;
|
|
250
|
+
/** Request ID for logging */
|
|
251
|
+
requestId: string;
|
|
252
|
+
/** Trace ID for logging */
|
|
253
|
+
traceId?: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface ExecutorResult {
|
|
257
|
+
/** Exit code */
|
|
258
|
+
exitCode: number;
|
|
259
|
+
/** Stdout content */
|
|
260
|
+
stdout: string;
|
|
261
|
+
/** Stderr content */
|
|
262
|
+
stderr: string;
|
|
263
|
+
/** Whether command timed out */
|
|
264
|
+
timedOut: boolean;
|
|
265
|
+
/** Execution duration in ms */
|
|
266
|
+
durationMs: number;
|
|
267
|
+
}
|
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibecheck-mcp-server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"description": "Professional MCP server for vibecheck - Intelligent development environment vibechecks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node index.js",
|
|
12
|
-
"dev": "VIBECHECK_DEBUG=true node index.js"
|
|
12
|
+
"dev": "VIBECHECK_DEBUG=true node index.js",
|
|
13
|
+
"build": "tsc -p tsconfig.json",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest"
|
|
13
16
|
},
|
|
14
17
|
"keywords": [
|
|
15
18
|
"mcp",
|
|
@@ -21,7 +24,13 @@
|
|
|
21
24
|
"architecture"
|
|
22
25
|
],
|
|
23
26
|
"dependencies": {
|
|
24
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
28
|
+
"ajv": "^8.12.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.11.0",
|
|
32
|
+
"typescript": "^5.3.0",
|
|
33
|
+
"vitest": "^1.2.0"
|
|
25
34
|
},
|
|
26
35
|
"engines": {
|
|
27
36
|
"node": ">=20.11"
|