@vibecheckai/cli 3.2.2 → 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- 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/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -1,311 +1,311 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plan Validator
|
|
3
|
-
* Validates AI agent plans against contracts
|
|
4
|
-
* This is the core of the "hallucination stopper"
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
"use strict";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Validate an AI-generated plan against contracts
|
|
11
|
-
* Returns validation result with specific violations
|
|
12
|
-
*/
|
|
13
|
-
function validatePlan(plan, contracts) {
|
|
14
|
-
const result = {
|
|
15
|
-
valid: true,
|
|
16
|
-
violations: [],
|
|
17
|
-
warnings: [],
|
|
18
|
-
suggestions: []
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// Parse plan to extract intended actions
|
|
22
|
-
const actions = parsePlanActions(plan);
|
|
23
|
-
|
|
24
|
-
// Validate route references
|
|
25
|
-
if (contracts.routes) {
|
|
26
|
-
const routeResult = validateRouteActions(actions, contracts.routes);
|
|
27
|
-
result.violations.push(...routeResult.violations);
|
|
28
|
-
result.warnings.push(...routeResult.warnings);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Validate env references
|
|
32
|
-
if (contracts.env) {
|
|
33
|
-
const envResult = validateEnvActions(actions, contracts.env);
|
|
34
|
-
result.violations.push(...envResult.violations);
|
|
35
|
-
result.warnings.push(...envResult.warnings);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Validate auth assumptions
|
|
39
|
-
if (contracts.auth) {
|
|
40
|
-
const authResult = validateAuthActions(actions, contracts.auth);
|
|
41
|
-
result.violations.push(...authResult.violations);
|
|
42
|
-
result.warnings.push(...authResult.warnings);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Validate external service usage
|
|
46
|
-
if (contracts.external) {
|
|
47
|
-
const externalResult = validateExternalActions(actions, contracts.external);
|
|
48
|
-
result.violations.push(...externalResult.violations);
|
|
49
|
-
result.warnings.push(...externalResult.warnings);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
result.valid = result.violations.length === 0;
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Parse plan to extract intended actions
|
|
58
|
-
*/
|
|
59
|
-
function parsePlanActions(plan) {
|
|
60
|
-
const actions = {
|
|
61
|
-
routes: [],
|
|
62
|
-
envVars: [],
|
|
63
|
-
files: [],
|
|
64
|
-
authAssumptions: [],
|
|
65
|
-
externalCalls: []
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// Handle different plan formats
|
|
69
|
-
const planText = typeof plan === "string" ? plan : JSON.stringify(plan);
|
|
70
|
-
|
|
71
|
-
// Extract route references
|
|
72
|
-
const routePatterns = [
|
|
73
|
-
/(?:fetch|axios|api\.)\s*\(\s*['"`]([/][^'"`]+)['"`]/gi,
|
|
74
|
-
/(?:GET|POST|PUT|PATCH|DELETE)\s+([/][^\s]+)/gi,
|
|
75
|
-
/\/api\/[a-z0-9/_-]+/gi
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
for (const pattern of routePatterns) {
|
|
79
|
-
let match;
|
|
80
|
-
while ((match = pattern.exec(planText)) !== null) {
|
|
81
|
-
const path = match[1] || match[0];
|
|
82
|
-
if (path.startsWith("/")) {
|
|
83
|
-
actions.routes.push({
|
|
84
|
-
path: path.replace(/['"`]/g, ""),
|
|
85
|
-
method: inferMethod(match[0])
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Extract env var references
|
|
92
|
-
const envPatterns = [
|
|
93
|
-
/process\.env\.([A-Z_][A-Z0-9_]*)/gi,
|
|
94
|
-
/import\.meta\.env\.([A-Z_][A-Z0-9_]*)/gi,
|
|
95
|
-
/\$\{?([A-Z_][A-Z0-9_]*)\}?/g
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
for (const pattern of envPatterns) {
|
|
99
|
-
let match;
|
|
100
|
-
while ((match = pattern.exec(planText)) !== null) {
|
|
101
|
-
if (match[1] && /^[A-Z]/.test(match[1])) {
|
|
102
|
-
actions.envVars.push(match[1]);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Extract file references
|
|
108
|
-
const filePatterns = [
|
|
109
|
-
/(?:create|modify|edit|update|delete|add to)\s+['"`]?([a-z0-9/_.-]+\.[a-z]+)['"`]?/gi,
|
|
110
|
-
/(?:in|at|file)\s+['"`]([^'"`]+)['"`]/gi
|
|
111
|
-
];
|
|
112
|
-
|
|
113
|
-
for (const pattern of filePatterns) {
|
|
114
|
-
let match;
|
|
115
|
-
while ((match = pattern.exec(planText)) !== null) {
|
|
116
|
-
if (match[1]) {
|
|
117
|
-
actions.files.push(match[1]);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Extract auth assumptions
|
|
123
|
-
const authPatterns = [
|
|
124
|
-
/(?:authenticated|logged in|auth required|protected)/gi,
|
|
125
|
-
/(?:public|no auth|unauthenticated)/gi
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
if (/(?:authenticated|logged in|auth required|protected)/i.test(planText)) {
|
|
129
|
-
actions.authAssumptions.push({ type: "requires_auth" });
|
|
130
|
-
}
|
|
131
|
-
if (/(?:public|no auth|unauthenticated)/i.test(planText)) {
|
|
132
|
-
actions.authAssumptions.push({ type: "no_auth" });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Extract external service references
|
|
136
|
-
const externalPatterns = [
|
|
137
|
-
/stripe\./gi,
|
|
138
|
-
/github\./gi,
|
|
139
|
-
/sendgrid\./gi,
|
|
140
|
-
/twilio\./gi,
|
|
141
|
-
/supabase\./gi
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
for (const pattern of externalPatterns) {
|
|
145
|
-
if (pattern.test(planText)) {
|
|
146
|
-
const service = pattern.source.replace(/\\\./g, "").toLowerCase();
|
|
147
|
-
actions.externalCalls.push({ service });
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return actions;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function inferMethod(text) {
|
|
155
|
-
const upper = text.toUpperCase();
|
|
156
|
-
if (upper.includes("POST")) return "POST";
|
|
157
|
-
if (upper.includes("PUT")) return "PUT";
|
|
158
|
-
if (upper.includes("PATCH")) return "PATCH";
|
|
159
|
-
if (upper.includes("DELETE")) return "DELETE";
|
|
160
|
-
return "GET";
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Validate route actions against contract
|
|
165
|
-
*/
|
|
166
|
-
function validateRouteActions(actions, routeContract) {
|
|
167
|
-
const violations = [];
|
|
168
|
-
const warnings = [];
|
|
169
|
-
const contractRoutes = new Set(routeContract.routes.map(r => r.path));
|
|
170
|
-
|
|
171
|
-
for (const route of actions.routes) {
|
|
172
|
-
// Check exact match
|
|
173
|
-
if (!contractRoutes.has(route.path)) {
|
|
174
|
-
// Check parameterized match
|
|
175
|
-
const match = routeContract.routes.find(r => matchesParameterized(r.path, route.path));
|
|
176
|
-
|
|
177
|
-
if (!match) {
|
|
178
|
-
violations.push({
|
|
179
|
-
type: "invented_route",
|
|
180
|
-
severity: "BLOCK",
|
|
181
|
-
route: route.path,
|
|
182
|
-
method: route.method,
|
|
183
|
-
message: `Plan references route ${route.method} ${route.path} which does not exist in contract`,
|
|
184
|
-
suggestion: `Available routes: ${routeContract.routes.slice(0, 5).map(r => r.path).join(", ")}...`
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return { violations, warnings };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Validate env actions against contract
|
|
195
|
-
*/
|
|
196
|
-
function validateEnvActions(actions, envContract) {
|
|
197
|
-
const violations = [];
|
|
198
|
-
const warnings = [];
|
|
199
|
-
const contractVars = new Set(envContract.vars.map(v => v.name));
|
|
200
|
-
|
|
201
|
-
for (const varName of actions.envVars) {
|
|
202
|
-
if (!contractVars.has(varName)) {
|
|
203
|
-
warnings.push({
|
|
204
|
-
type: "undeclared_env",
|
|
205
|
-
severity: "WARN",
|
|
206
|
-
name: varName,
|
|
207
|
-
message: `Plan uses env var ${varName} which is not in contract`,
|
|
208
|
-
suggestion: "Add to .vibecheck/contracts/env.json or .env.example"
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return { violations, warnings };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Validate auth actions against contract
|
|
218
|
-
*/
|
|
219
|
-
function validateAuthActions(actions, authContract) {
|
|
220
|
-
const violations = [];
|
|
221
|
-
const warnings = [];
|
|
222
|
-
|
|
223
|
-
// Check for auth assumptions that conflict with contract
|
|
224
|
-
for (const assumption of actions.authAssumptions) {
|
|
225
|
-
if (assumption.type === "no_auth") {
|
|
226
|
-
// Plan assumes no auth - check if referenced routes are actually public
|
|
227
|
-
warnings.push({
|
|
228
|
-
type: "auth_assumption",
|
|
229
|
-
severity: "WARN",
|
|
230
|
-
message: "Plan assumes some routes are public - verify against auth contract",
|
|
231
|
-
suggestion: `Protected patterns: ${authContract.protectedPatterns.slice(0, 3).join(", ")}...`
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return { violations, warnings };
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Validate external service actions against contract
|
|
241
|
-
*/
|
|
242
|
-
function validateExternalActions(actions, externalContract) {
|
|
243
|
-
const violations = [];
|
|
244
|
-
const warnings = [];
|
|
245
|
-
const contractServices = new Set(externalContract.services.map(s => s.name));
|
|
246
|
-
|
|
247
|
-
for (const call of actions.externalCalls) {
|
|
248
|
-
if (!contractServices.has(call.service)) {
|
|
249
|
-
warnings.push({
|
|
250
|
-
type: "undeclared_service",
|
|
251
|
-
severity: "WARN",
|
|
252
|
-
service: call.service,
|
|
253
|
-
message: `Plan uses ${call.service} which is not declared in external contract`,
|
|
254
|
-
suggestion: "Add to .vibecheck/contracts/external.json"
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return { violations, warnings };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function matchesParameterized(pattern, actual) {
|
|
263
|
-
const patternParts = pattern.split("/").filter(Boolean);
|
|
264
|
-
const actualParts = actual.split("/").filter(Boolean);
|
|
265
|
-
|
|
266
|
-
if (patternParts.length !== actualParts.length) return false;
|
|
267
|
-
|
|
268
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
269
|
-
const p = patternParts[i];
|
|
270
|
-
if (p.startsWith(":") || p.startsWith("*")) continue;
|
|
271
|
-
if (p !== actualParts[i]) return false;
|
|
272
|
-
}
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Format validation result for display
|
|
278
|
-
*/
|
|
279
|
-
function formatValidationResult(result) {
|
|
280
|
-
const lines = [];
|
|
281
|
-
|
|
282
|
-
if (result.valid) {
|
|
283
|
-
lines.push("✓ Plan validated against contracts");
|
|
284
|
-
} else {
|
|
285
|
-
lines.push("✗ Plan validation failed");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (result.violations.length > 0) {
|
|
289
|
-
lines.push(`\n🛑 VIOLATIONS (${result.violations.length}):`);
|
|
290
|
-
for (const v of result.violations) {
|
|
291
|
-
lines.push(` ✗ ${v.message}`);
|
|
292
|
-
if (v.suggestion) lines.push(` → ${v.suggestion}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (result.warnings.length > 0) {
|
|
297
|
-
lines.push(`\n⚠️ WARNINGS (${result.warnings.length}):`);
|
|
298
|
-
for (const w of result.warnings) {
|
|
299
|
-
lines.push(` ⚠ ${w.message}`);
|
|
300
|
-
if (w.suggestion) lines.push(` → ${w.suggestion}`);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return lines.join("\n");
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
module.exports = {
|
|
308
|
-
validatePlan,
|
|
309
|
-
parsePlanActions,
|
|
310
|
-
formatValidationResult
|
|
311
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Plan Validator
|
|
3
|
+
* Validates AI agent plans against contracts
|
|
4
|
+
* This is the core of the "hallucination stopper"
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate an AI-generated plan against contracts
|
|
11
|
+
* Returns validation result with specific violations
|
|
12
|
+
*/
|
|
13
|
+
function validatePlan(plan, contracts) {
|
|
14
|
+
const result = {
|
|
15
|
+
valid: true,
|
|
16
|
+
violations: [],
|
|
17
|
+
warnings: [],
|
|
18
|
+
suggestions: []
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Parse plan to extract intended actions
|
|
22
|
+
const actions = parsePlanActions(plan);
|
|
23
|
+
|
|
24
|
+
// Validate route references
|
|
25
|
+
if (contracts.routes) {
|
|
26
|
+
const routeResult = validateRouteActions(actions, contracts.routes);
|
|
27
|
+
result.violations.push(...routeResult.violations);
|
|
28
|
+
result.warnings.push(...routeResult.warnings);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate env references
|
|
32
|
+
if (contracts.env) {
|
|
33
|
+
const envResult = validateEnvActions(actions, contracts.env);
|
|
34
|
+
result.violations.push(...envResult.violations);
|
|
35
|
+
result.warnings.push(...envResult.warnings);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate auth assumptions
|
|
39
|
+
if (contracts.auth) {
|
|
40
|
+
const authResult = validateAuthActions(actions, contracts.auth);
|
|
41
|
+
result.violations.push(...authResult.violations);
|
|
42
|
+
result.warnings.push(...authResult.warnings);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Validate external service usage
|
|
46
|
+
if (contracts.external) {
|
|
47
|
+
const externalResult = validateExternalActions(actions, contracts.external);
|
|
48
|
+
result.violations.push(...externalResult.violations);
|
|
49
|
+
result.warnings.push(...externalResult.warnings);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
result.valid = result.violations.length === 0;
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse plan to extract intended actions
|
|
58
|
+
*/
|
|
59
|
+
function parsePlanActions(plan) {
|
|
60
|
+
const actions = {
|
|
61
|
+
routes: [],
|
|
62
|
+
envVars: [],
|
|
63
|
+
files: [],
|
|
64
|
+
authAssumptions: [],
|
|
65
|
+
externalCalls: []
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Handle different plan formats
|
|
69
|
+
const planText = typeof plan === "string" ? plan : JSON.stringify(plan);
|
|
70
|
+
|
|
71
|
+
// Extract route references
|
|
72
|
+
const routePatterns = [
|
|
73
|
+
/(?:fetch|axios|api\.)\s*\(\s*['"`]([/][^'"`]+)['"`]/gi,
|
|
74
|
+
/(?:GET|POST|PUT|PATCH|DELETE)\s+([/][^\s]+)/gi,
|
|
75
|
+
/\/api\/[a-z0-9/_-]+/gi
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
for (const pattern of routePatterns) {
|
|
79
|
+
let match;
|
|
80
|
+
while ((match = pattern.exec(planText)) !== null) {
|
|
81
|
+
const path = match[1] || match[0];
|
|
82
|
+
if (path.startsWith("/")) {
|
|
83
|
+
actions.routes.push({
|
|
84
|
+
path: path.replace(/['"`]/g, ""),
|
|
85
|
+
method: inferMethod(match[0])
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Extract env var references
|
|
92
|
+
const envPatterns = [
|
|
93
|
+
/process\.env\.([A-Z_][A-Z0-9_]*)/gi,
|
|
94
|
+
/import\.meta\.env\.([A-Z_][A-Z0-9_]*)/gi,
|
|
95
|
+
/\$\{?([A-Z_][A-Z0-9_]*)\}?/g
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const pattern of envPatterns) {
|
|
99
|
+
let match;
|
|
100
|
+
while ((match = pattern.exec(planText)) !== null) {
|
|
101
|
+
if (match[1] && /^[A-Z]/.test(match[1])) {
|
|
102
|
+
actions.envVars.push(match[1]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Extract file references
|
|
108
|
+
const filePatterns = [
|
|
109
|
+
/(?:create|modify|edit|update|delete|add to)\s+['"`]?([a-z0-9/_.-]+\.[a-z]+)['"`]?/gi,
|
|
110
|
+
/(?:in|at|file)\s+['"`]([^'"`]+)['"`]/gi
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
for (const pattern of filePatterns) {
|
|
114
|
+
let match;
|
|
115
|
+
while ((match = pattern.exec(planText)) !== null) {
|
|
116
|
+
if (match[1]) {
|
|
117
|
+
actions.files.push(match[1]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Extract auth assumptions
|
|
123
|
+
const authPatterns = [
|
|
124
|
+
/(?:authenticated|logged in|auth required|protected)/gi,
|
|
125
|
+
/(?:public|no auth|unauthenticated)/gi
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
if (/(?:authenticated|logged in|auth required|protected)/i.test(planText)) {
|
|
129
|
+
actions.authAssumptions.push({ type: "requires_auth" });
|
|
130
|
+
}
|
|
131
|
+
if (/(?:public|no auth|unauthenticated)/i.test(planText)) {
|
|
132
|
+
actions.authAssumptions.push({ type: "no_auth" });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Extract external service references
|
|
136
|
+
const externalPatterns = [
|
|
137
|
+
/stripe\./gi,
|
|
138
|
+
/github\./gi,
|
|
139
|
+
/sendgrid\./gi,
|
|
140
|
+
/twilio\./gi,
|
|
141
|
+
/supabase\./gi
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
for (const pattern of externalPatterns) {
|
|
145
|
+
if (pattern.test(planText)) {
|
|
146
|
+
const service = pattern.source.replace(/\\\./g, "").toLowerCase();
|
|
147
|
+
actions.externalCalls.push({ service });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return actions;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function inferMethod(text) {
|
|
155
|
+
const upper = text.toUpperCase();
|
|
156
|
+
if (upper.includes("POST")) return "POST";
|
|
157
|
+
if (upper.includes("PUT")) return "PUT";
|
|
158
|
+
if (upper.includes("PATCH")) return "PATCH";
|
|
159
|
+
if (upper.includes("DELETE")) return "DELETE";
|
|
160
|
+
return "GET";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Validate route actions against contract
|
|
165
|
+
*/
|
|
166
|
+
function validateRouteActions(actions, routeContract) {
|
|
167
|
+
const violations = [];
|
|
168
|
+
const warnings = [];
|
|
169
|
+
const contractRoutes = new Set(routeContract.routes.map(r => r.path));
|
|
170
|
+
|
|
171
|
+
for (const route of actions.routes) {
|
|
172
|
+
// Check exact match
|
|
173
|
+
if (!contractRoutes.has(route.path)) {
|
|
174
|
+
// Check parameterized match
|
|
175
|
+
const match = routeContract.routes.find(r => matchesParameterized(r.path, route.path));
|
|
176
|
+
|
|
177
|
+
if (!match) {
|
|
178
|
+
violations.push({
|
|
179
|
+
type: "invented_route",
|
|
180
|
+
severity: "BLOCK",
|
|
181
|
+
route: route.path,
|
|
182
|
+
method: route.method,
|
|
183
|
+
message: `Plan references route ${route.method} ${route.path} which does not exist in contract`,
|
|
184
|
+
suggestion: `Available routes: ${routeContract.routes.slice(0, 5).map(r => r.path).join(", ")}...`
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { violations, warnings };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Validate env actions against contract
|
|
195
|
+
*/
|
|
196
|
+
function validateEnvActions(actions, envContract) {
|
|
197
|
+
const violations = [];
|
|
198
|
+
const warnings = [];
|
|
199
|
+
const contractVars = new Set(envContract.vars.map(v => v.name));
|
|
200
|
+
|
|
201
|
+
for (const varName of actions.envVars) {
|
|
202
|
+
if (!contractVars.has(varName)) {
|
|
203
|
+
warnings.push({
|
|
204
|
+
type: "undeclared_env",
|
|
205
|
+
severity: "WARN",
|
|
206
|
+
name: varName,
|
|
207
|
+
message: `Plan uses env var ${varName} which is not in contract`,
|
|
208
|
+
suggestion: "Add to .vibecheck/contracts/env.json or .env.example"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { violations, warnings };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Validate auth actions against contract
|
|
218
|
+
*/
|
|
219
|
+
function validateAuthActions(actions, authContract) {
|
|
220
|
+
const violations = [];
|
|
221
|
+
const warnings = [];
|
|
222
|
+
|
|
223
|
+
// Check for auth assumptions that conflict with contract
|
|
224
|
+
for (const assumption of actions.authAssumptions) {
|
|
225
|
+
if (assumption.type === "no_auth") {
|
|
226
|
+
// Plan assumes no auth - check if referenced routes are actually public
|
|
227
|
+
warnings.push({
|
|
228
|
+
type: "auth_assumption",
|
|
229
|
+
severity: "WARN",
|
|
230
|
+
message: "Plan assumes some routes are public - verify against auth contract",
|
|
231
|
+
suggestion: `Protected patterns: ${authContract.protectedPatterns.slice(0, 3).join(", ")}...`
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { violations, warnings };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Validate external service actions against contract
|
|
241
|
+
*/
|
|
242
|
+
function validateExternalActions(actions, externalContract) {
|
|
243
|
+
const violations = [];
|
|
244
|
+
const warnings = [];
|
|
245
|
+
const contractServices = new Set(externalContract.services.map(s => s.name));
|
|
246
|
+
|
|
247
|
+
for (const call of actions.externalCalls) {
|
|
248
|
+
if (!contractServices.has(call.service)) {
|
|
249
|
+
warnings.push({
|
|
250
|
+
type: "undeclared_service",
|
|
251
|
+
severity: "WARN",
|
|
252
|
+
service: call.service,
|
|
253
|
+
message: `Plan uses ${call.service} which is not declared in external contract`,
|
|
254
|
+
suggestion: "Add to .vibecheck/contracts/external.json"
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { violations, warnings };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function matchesParameterized(pattern, actual) {
|
|
263
|
+
const patternParts = pattern.split("/").filter(Boolean);
|
|
264
|
+
const actualParts = actual.split("/").filter(Boolean);
|
|
265
|
+
|
|
266
|
+
if (patternParts.length !== actualParts.length) return false;
|
|
267
|
+
|
|
268
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
269
|
+
const p = patternParts[i];
|
|
270
|
+
if (p.startsWith(":") || p.startsWith("*")) continue;
|
|
271
|
+
if (p !== actualParts[i]) return false;
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Format validation result for display
|
|
278
|
+
*/
|
|
279
|
+
function formatValidationResult(result) {
|
|
280
|
+
const lines = [];
|
|
281
|
+
|
|
282
|
+
if (result.valid) {
|
|
283
|
+
lines.push("✓ Plan validated against contracts");
|
|
284
|
+
} else {
|
|
285
|
+
lines.push("✗ Plan validation failed");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (result.violations.length > 0) {
|
|
289
|
+
lines.push(`\n🛑 VIOLATIONS (${result.violations.length}):`);
|
|
290
|
+
for (const v of result.violations) {
|
|
291
|
+
lines.push(` ✗ ${v.message}`);
|
|
292
|
+
if (v.suggestion) lines.push(` → ${v.suggestion}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (result.warnings.length > 0) {
|
|
297
|
+
lines.push(`\n⚠️ WARNINGS (${result.warnings.length}):`);
|
|
298
|
+
for (const w of result.warnings) {
|
|
299
|
+
lines.push(` ⚠ ${w.message}`);
|
|
300
|
+
if (w.suggestion) lines.push(` → ${w.suggestion}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return lines.join("\n");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
validatePlan,
|
|
309
|
+
parsePlanActions,
|
|
310
|
+
formatValidationResult
|
|
311
|
+
};
|