@vibecheckai/cli 3.9.1 → 4.0.0
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/README.md +1 -1
- package/bin/runners/context/generators/cursor-enhanced.js +99 -13
- package/mcp-server/.eslintrc.json +24 -0
- package/mcp-server/README.md +425 -135
- package/mcp-server/SPEC.md +583 -0
- package/mcp-server/configs/README.md +172 -0
- package/mcp-server/configs/claude-desktop-pro.json +31 -0
- package/mcp-server/configs/claude-desktop-with-workspace.json +25 -0
- package/mcp-server/configs/claude-desktop.json +19 -0
- package/mcp-server/configs/cursor-mcp.json +21 -0
- package/mcp-server/configs/windsurf-mcp.json +17 -0
- package/mcp-server/mcp-config.example.json +9 -0
- package/mcp-server/package.json +49 -34
- package/mcp-server/src/cli.ts +185 -0
- package/mcp-server/src/index.ts +85 -0
- package/mcp-server/src/server.ts +1933 -0
- package/mcp-server/src/services/cache-service.ts +466 -0
- package/mcp-server/src/services/cli-service.ts +345 -0
- package/mcp-server/src/services/context-manager.ts +717 -0
- package/mcp-server/src/services/firewall-service.ts +662 -0
- package/mcp-server/src/services/git-service.ts +671 -0
- package/mcp-server/src/services/index.ts +52 -0
- package/mcp-server/src/services/prompt-builder-service.ts +1031 -0
- package/mcp-server/src/services/session-service.ts +550 -0
- package/mcp-server/src/services/tier-service.ts +470 -0
- package/mcp-server/src/types.ts +351 -0
- package/mcp-server/tsconfig.json +16 -27
- package/package.json +6 -6
- package/mcp-server/.guardrail/audit/audit.log.jsonl +0 -2
- package/mcp-server/.specs/architecture.mdc +0 -90
- package/mcp-server/.specs/security.mdc +0 -30
- package/mcp-server/HARDENING_SUMMARY.md +0 -299
- package/mcp-server/agent-checkpoint.js +0 -364
- package/mcp-server/agent-firewall-interceptor.js +0 -500
- package/mcp-server/architect-tools.js +0 -707
- package/mcp-server/audit-mcp.js +0 -206
- package/mcp-server/authority-tools.js +0 -569
- package/mcp-server/codebase-architect-tools.js +0 -838
- package/mcp-server/conductor/conflict-resolver.js +0 -588
- package/mcp-server/conductor/execution-planner.js +0 -544
- package/mcp-server/conductor/index.js +0 -377
- package/mcp-server/conductor/lock-manager.js +0 -615
- package/mcp-server/conductor/request-queue.js +0 -550
- package/mcp-server/conductor/session-manager.js +0 -500
- package/mcp-server/conductor/tools.js +0 -510
- package/mcp-server/consolidated-tools.js +0 -1170
- package/mcp-server/deprecation-middleware.js +0 -282
- package/mcp-server/handlers/index.ts +0 -15
- package/mcp-server/handlers/tool-handler.ts +0 -593
- package/mcp-server/hygiene-tools.js +0 -428
- package/mcp-server/index-v1.js +0 -698
- package/mcp-server/index.js +0 -2940
- package/mcp-server/intelligence-tools.js +0 -664
- package/mcp-server/intent-drift-tools.js +0 -873
- package/mcp-server/intent-firewall-interceptor.js +0 -529
- package/mcp-server/lib/api-client.cjs +0 -13
- package/mcp-server/lib/cache-wrapper.cjs +0 -383
- package/mcp-server/lib/error-envelope.js +0 -138
- package/mcp-server/lib/executor.ts +0 -499
- package/mcp-server/lib/index.ts +0 -29
- package/mcp-server/lib/logger.cjs +0 -30
- package/mcp-server/lib/rate-limiter.js +0 -166
- package/mcp-server/lib/sandbox.test.ts +0 -519
- package/mcp-server/lib/sandbox.ts +0 -395
- package/mcp-server/lib/types.ts +0 -267
- package/mcp-server/logger.js +0 -173
- package/mcp-server/manifest.json +0 -473
- package/mcp-server/mdc-generator.js +0 -298
- package/mcp-server/premium-tools.js +0 -1275
- package/mcp-server/proof-tools.js +0 -571
- package/mcp-server/registry/tool-registry.js +0 -586
- package/mcp-server/registry/tools.json +0 -619
- package/mcp-server/registry.test.ts +0 -340
- package/mcp-server/test-mcp.js +0 -108
- package/mcp-server/test-tools.js +0 -36
- package/mcp-server/tests/tier-gating.test.js +0 -297
- package/mcp-server/tier-auth.js +0 -767
- package/mcp-server/tools/index.js +0 -72
- package/mcp-server/tools-reorganized.ts +0 -244
- package/mcp-server/tools-v3.js +0 -1004
- package/mcp-server/truth-context.js +0 -622
- package/mcp-server/truth-firewall-tools.js +0 -2183
- package/mcp-server/vibecheck-2.0-tools.js +0 -761
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
- package/mcp-server/vibecheck-tools.js +0 -1075
|
@@ -1,1170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vibecheck MCP Consolidated Tools
|
|
3
|
-
*
|
|
4
|
-
* Reduced from 50+ tools to a curated set for agents.
|
|
5
|
-
* Each tool returns evidence-backed responses with file/line citations.
|
|
6
|
-
*
|
|
7
|
-
* Tool Categories:
|
|
8
|
-
* 1. Core Commands (5) - ship, scan, fix, verify, ctx
|
|
9
|
-
* 2. Truth Queries (5) - truthpack, routes, env, auth, billing
|
|
10
|
-
* 3. Evidence (3) - validate_claim, evidence, proof_graph
|
|
11
|
-
* 4. Proof Artifacts (2) - evidence_pack, allowlist
|
|
12
|
-
* 5. Utilities (2) - status, doctor
|
|
13
|
-
*
|
|
14
|
-
* Response Format (Standardized):
|
|
15
|
-
* {
|
|
16
|
-
* ok: boolean,
|
|
17
|
-
* data: any,
|
|
18
|
-
* evidence: Array<{ file, line, snippet, confidence }>,
|
|
19
|
-
* metadata: { timestamp, cached, projectFingerprint }
|
|
20
|
-
* }
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { execSync, spawn } from 'child_process';
|
|
24
|
-
import fs from 'fs';
|
|
25
|
-
import path from 'path';
|
|
26
|
-
import { handleTruthFirewallTool } from './truth-firewall-tools.js';
|
|
27
|
-
import { PROOF_TOOLS, handleProofTool } from './proof-tools.js';
|
|
28
|
-
|
|
29
|
-
// ============================================================================
|
|
30
|
-
// STANDARDIZED RESPONSE WRAPPER
|
|
31
|
-
// ============================================================================
|
|
32
|
-
|
|
33
|
-
function wrapResponse(data, options = {}) {
|
|
34
|
-
const { evidence = [], cached = false, error = null } = options;
|
|
35
|
-
|
|
36
|
-
if (error) {
|
|
37
|
-
return {
|
|
38
|
-
ok: false,
|
|
39
|
-
error: typeof error === 'string' ? error : error.message,
|
|
40
|
-
data: null,
|
|
41
|
-
evidence: [],
|
|
42
|
-
metadata: {
|
|
43
|
-
timestamp: new Date().toISOString(),
|
|
44
|
-
cached: false
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
ok: true,
|
|
51
|
-
data,
|
|
52
|
-
evidence: evidence.map(e => ({
|
|
53
|
-
file: e.file || null,
|
|
54
|
-
line: e.line || e.lines || null,
|
|
55
|
-
snippet: e.snippet || e.code || null,
|
|
56
|
-
confidence: e.confidence || 0.9,
|
|
57
|
-
reason: e.reason || null
|
|
58
|
-
})),
|
|
59
|
-
metadata: {
|
|
60
|
-
timestamp: new Date().toISOString(),
|
|
61
|
-
cached
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ============================================================================
|
|
67
|
-
// TOOL DEFINITIONS (Curated for Agents)
|
|
68
|
-
// ============================================================================
|
|
69
|
-
|
|
70
|
-
const ALL_TOOLS = [
|
|
71
|
-
// === CORE COMMANDS (5) ===
|
|
72
|
-
{
|
|
73
|
-
name: "vibecheck.ship",
|
|
74
|
-
description: "Get ship verdict: SHIP/WARN/BLOCK with evidence. Returns top blockers and fix suggestions.",
|
|
75
|
-
inputSchema: {
|
|
76
|
-
type: "object",
|
|
77
|
-
properties: {
|
|
78
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
79
|
-
strict: { type: "boolean", description: "Treat warnings as blockers", default: false },
|
|
80
|
-
json: { type: "boolean", description: "Return raw JSON output", default: false }
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: "vibecheck.scan",
|
|
86
|
-
description: "Deep scan for ship-killers: secrets, auth gaps, fake success, dead UI, billing bypasses.",
|
|
87
|
-
inputSchema: {
|
|
88
|
-
type: "object",
|
|
89
|
-
properties: {
|
|
90
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
91
|
-
profile: {
|
|
92
|
-
type: "string",
|
|
93
|
-
enum: ["quick", "full", "security", "billing"],
|
|
94
|
-
description: "Scan profile",
|
|
95
|
-
default: "quick"
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: "vibecheck.fix",
|
|
102
|
-
description: "Generate fix plan or apply patches for findings. Returns evidence-backed fix suggestions.",
|
|
103
|
-
inputSchema: {
|
|
104
|
-
type: "object",
|
|
105
|
-
properties: {
|
|
106
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
107
|
-
apply: { type: "boolean", description: "Apply patches (requires clean git)", default: false },
|
|
108
|
-
promptOnly: { type: "boolean", description: "Generate prompts only, no patches", default: false }
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: "vibecheck.verify",
|
|
114
|
-
description: "Runtime verification using Playwright. Tests UI behavior, auth flows, dead buttons.",
|
|
115
|
-
inputSchema: {
|
|
116
|
-
type: "object",
|
|
117
|
-
properties: {
|
|
118
|
-
url: { type: "string", description: "Base URL to verify (required)" },
|
|
119
|
-
auth: { type: "string", description: "Login credentials as email:password" },
|
|
120
|
-
paths: { type: "string", description: "Comma-separated paths to test" },
|
|
121
|
-
budget: { type: "string", enum: ["2m", "5m", "10m"], description: "Time budget", default: "5m" }
|
|
122
|
-
},
|
|
123
|
-
required: ["url"]
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: "vibecheck.ctx",
|
|
128
|
-
description: "Generate Truth Pack with routes, env, auth, billing facts. Evidence-backed context.",
|
|
129
|
-
inputSchema: {
|
|
130
|
-
type: "object",
|
|
131
|
-
properties: {
|
|
132
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
133
|
-
snapshot: { type: "boolean", description: "Save timestamped snapshot", default: false }
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
// === TRUTH QUERIES (5) ===
|
|
139
|
-
{
|
|
140
|
-
name: "vibecheck.get_truthpack",
|
|
141
|
-
description: "Get the full Truth Pack for the project. Use this BEFORE making claims about the codebase.",
|
|
142
|
-
inputSchema: {
|
|
143
|
-
type: "object",
|
|
144
|
-
properties: {
|
|
145
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
146
|
-
refresh: { type: "boolean", description: "Force regenerate truthpack", default: false }
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
name: "vibecheck.get_routes",
|
|
152
|
-
description: "Get all server and client routes with file/line evidence.",
|
|
153
|
-
inputSchema: {
|
|
154
|
-
type: "object",
|
|
155
|
-
properties: {
|
|
156
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
157
|
-
type: { type: "string", enum: ["server", "client", "all"], default: "all" }
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
name: "vibecheck.get_env",
|
|
163
|
-
description: "Get env vars: declared, used, mismatches. Evidence-backed.",
|
|
164
|
-
inputSchema: {
|
|
165
|
-
type: "object",
|
|
166
|
-
properties: {
|
|
167
|
-
projectPath: { type: "string", description: "Path to project root", default: "." }
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
name: "vibecheck.get_auth",
|
|
173
|
-
description: "Get auth model: guards, protected routes, unprotected routes, gaps.",
|
|
174
|
-
inputSchema: {
|
|
175
|
-
type: "object",
|
|
176
|
-
properties: {
|
|
177
|
-
projectPath: { type: "string", description: "Path to project root", default: "." }
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
name: "vibecheck.get_billing",
|
|
183
|
-
description: "Get billing model: gates, paid features, bypasses, enforcement gaps.",
|
|
184
|
-
inputSchema: {
|
|
185
|
-
type: "object",
|
|
186
|
-
properties: {
|
|
187
|
-
projectPath: { type: "string", description: "Path to project root", default: "." }
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
// === EVIDENCE (3) ===
|
|
193
|
-
{
|
|
194
|
-
name: "vibecheck.validate_claim",
|
|
195
|
-
description: "Validate a claim about the codebase. Returns true/false/unknown with evidence.",
|
|
196
|
-
inputSchema: {
|
|
197
|
-
type: "object",
|
|
198
|
-
properties: {
|
|
199
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
200
|
-
claim: { type: "string", description: "The claim to validate (required)" },
|
|
201
|
-
type: {
|
|
202
|
-
type: "string",
|
|
203
|
-
enum: ["route_exists", "env_var_used", "auth_enforced", "file_exists", "function_exists"],
|
|
204
|
-
description: "Type of claim"
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
required: ["claim"]
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: "vibecheck.compile_context",
|
|
212
|
-
description: "Get task-focused context with invariants and policy controls.",
|
|
213
|
-
inputSchema: {
|
|
214
|
-
type: "object",
|
|
215
|
-
properties: {
|
|
216
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
217
|
-
task: { type: "string", description: "Task description" },
|
|
218
|
-
policy: { type: "string", enum: ["strict", "balanced", "permissive"], default: "strict" }
|
|
219
|
-
},
|
|
220
|
-
required: ["task"]
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
name: "vibecheck.search_evidence",
|
|
225
|
-
description: "Search for evidence with file/line citations.",
|
|
226
|
-
inputSchema: {
|
|
227
|
-
type: "object",
|
|
228
|
-
properties: {
|
|
229
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
230
|
-
query: { type: "string", description: "What to search for" },
|
|
231
|
-
type: { type: "string", enum: ["route", "handler", "middleware", "component", "env_var", "model", "any"], default: "any" },
|
|
232
|
-
limit: { type: "number", default: 10 }
|
|
233
|
-
},
|
|
234
|
-
required: ["query"]
|
|
235
|
-
}
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
name: "vibecheck.find_counterexamples",
|
|
239
|
-
description: "Find counterexamples that falsify auth/billing claims.",
|
|
240
|
-
inputSchema: {
|
|
241
|
-
type: "object",
|
|
242
|
-
properties: {
|
|
243
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
244
|
-
claim: { type: "string", enum: ["auth_enforced", "billing_gate_exists", "route_guarded", "no_bypass"] },
|
|
245
|
-
subject: { type: "object" }
|
|
246
|
-
},
|
|
247
|
-
required: ["claim", "subject"]
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
name: "vibecheck.check_invariants",
|
|
252
|
-
description: "Check invariants (auth/billing/ux/api) for ship-killers.",
|
|
253
|
-
inputSchema: {
|
|
254
|
-
type: "object",
|
|
255
|
-
properties: {
|
|
256
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
257
|
-
category: { type: "string", enum: ["all", "auth", "billing", "security", "ux", "api"], default: "all" }
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
name: "vibecheck.get_evidence",
|
|
263
|
-
description: "Get file/line evidence for a specific finding or claim.",
|
|
264
|
-
inputSchema: {
|
|
265
|
-
type: "object",
|
|
266
|
-
properties: {
|
|
267
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
268
|
-
findingId: { type: "string", description: "Finding ID to get evidence for" },
|
|
269
|
-
file: { type: "string", description: "File path to extract evidence from" },
|
|
270
|
-
pattern: { type: "string", description: "Regex pattern to search for" }
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
name: "vibecheck.get_proof_graph",
|
|
276
|
-
description: "Get the proof graph: claims -> evidence -> gaps -> risk score.",
|
|
277
|
-
inputSchema: {
|
|
278
|
-
type: "object",
|
|
279
|
-
properties: {
|
|
280
|
-
projectPath: { type: "string", description: "Path to project root", default: "." }
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
|
|
285
|
-
// === SPEC-REQUIRED TOOLS (4) ===
|
|
286
|
-
{
|
|
287
|
-
name: "vibecheck.get_contracts",
|
|
288
|
-
description: "Get context contracts (routes, env, auth, external). Use with validate_plan to stop hallucinations.",
|
|
289
|
-
inputSchema: {
|
|
290
|
-
type: "object",
|
|
291
|
-
properties: {
|
|
292
|
-
projectPath: { type: "string", description: "Path to project root", default: "." }
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
name: "vibecheck.validate_plan",
|
|
298
|
-
description: "Validate an AI-generated plan against contracts. Returns violations for invented routes, missing env vars, auth mismatches.",
|
|
299
|
-
inputSchema: {
|
|
300
|
-
type: "object",
|
|
301
|
-
properties: {
|
|
302
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
303
|
-
plan: {
|
|
304
|
-
type: "array",
|
|
305
|
-
description: "Array of plan actions to validate",
|
|
306
|
-
items: {
|
|
307
|
-
type: "object",
|
|
308
|
-
properties: {
|
|
309
|
-
action: { type: "string" },
|
|
310
|
-
target: { type: "string" },
|
|
311
|
-
details: { type: "object" }
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
required: ["plan"]
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
name: "vibecheck.share",
|
|
321
|
-
description: "Build share pack from latest mission. Generates sanitized share.json, share.md, pr_comment.md.",
|
|
322
|
-
inputSchema: {
|
|
323
|
-
type: "object",
|
|
324
|
-
properties: {
|
|
325
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
326
|
-
missionDir: { type: "string", description: "Specific mission directory (default: latest)" }
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
name: "vibecheck.pr_comment",
|
|
332
|
-
description: "Render PR comment markdown from last ship report. Ready to paste into GitHub PR.",
|
|
333
|
-
inputSchema: {
|
|
334
|
-
type: "object",
|
|
335
|
-
properties: {
|
|
336
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
337
|
-
maxFindings: { type: "number", description: "Max findings to include", default: 12 }
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
},
|
|
341
|
-
|
|
342
|
-
// === PROOF ARTIFACTS (2) ===
|
|
343
|
-
{
|
|
344
|
-
name: "vibecheck.evidence_pack",
|
|
345
|
-
description: "Build shareable evidence pack with videos, traces, screenshots. Returns zip path.",
|
|
346
|
-
inputSchema: {
|
|
347
|
-
type: "object",
|
|
348
|
-
properties: {
|
|
349
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
350
|
-
includeVideos: { type: "boolean", description: "Include recorded videos", default: true },
|
|
351
|
-
includeTraces: { type: "boolean", description: "Include Playwright traces", default: true },
|
|
352
|
-
includeScreenshots: { type: "boolean", description: "Include screenshots", default: true },
|
|
353
|
-
applyAllowlist: { type: "boolean", description: "Filter by allowlist", default: true }
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
name: "vibecheck.allowlist",
|
|
359
|
-
description: "Manage finding allowlist. Add entries to suppress known false positives.",
|
|
360
|
-
inputSchema: {
|
|
361
|
-
type: "object",
|
|
362
|
-
properties: {
|
|
363
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
364
|
-
action: {
|
|
365
|
-
type: "string",
|
|
366
|
-
enum: ["list", "add", "remove", "check"],
|
|
367
|
-
description: "Action to perform",
|
|
368
|
-
default: "list"
|
|
369
|
-
},
|
|
370
|
-
findingId: { type: "string", description: "Finding ID to add/remove (for add/remove)" },
|
|
371
|
-
pattern: { type: "string", description: "Pattern to match (for add)" },
|
|
372
|
-
reason: { type: "string", description: "Reason for allowlisting (for add)" },
|
|
373
|
-
scope: { type: "string", enum: ["global", "file", "line"], default: "global" }
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
},
|
|
377
|
-
|
|
378
|
-
// === UTILITIES (2) ===
|
|
379
|
-
{
|
|
380
|
-
name: "vibecheck.status",
|
|
381
|
-
description: "Get current vibecheck status: last verdict, findings count, truthpack freshness.",
|
|
382
|
-
inputSchema: {
|
|
383
|
-
type: "object",
|
|
384
|
-
properties: {
|
|
385
|
-
projectPath: { type: "string", description: "Path to project root", default: "." }
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
name: "vibecheck.doctor",
|
|
391
|
-
description: "Environment health check. Verifies dependencies, configs, permissions.",
|
|
392
|
-
inputSchema: {
|
|
393
|
-
type: "object",
|
|
394
|
-
properties: {
|
|
395
|
-
projectPath: { type: "string", description: "Path to project root", default: "." }
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
|
|
400
|
-
// === RUNTIME (1) ===
|
|
401
|
-
{
|
|
402
|
-
name: "vibecheck.reality",
|
|
403
|
-
description: "Run runtime browser verification with optional video/trace recording.",
|
|
404
|
-
inputSchema: {
|
|
405
|
-
type: "object",
|
|
406
|
-
properties: {
|
|
407
|
-
url: { type: "string", description: "Base URL to verify (required)" },
|
|
408
|
-
projectPath: { type: "string", description: "Path to project root", default: "." },
|
|
409
|
-
auth: { type: "string", description: "Login credentials as email:password" },
|
|
410
|
-
recordVideo: { type: "boolean", description: "Record video of session", default: false },
|
|
411
|
-
recordTrace: { type: "boolean", description: "Record Playwright trace", default: false },
|
|
412
|
-
maxPages: { type: "number", description: "Max pages to crawl", default: 18 }
|
|
413
|
-
},
|
|
414
|
-
required: ["url"]
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
];
|
|
418
|
-
|
|
419
|
-
const ALLOWED_TOOL_NAMES = new Set([
|
|
420
|
-
// Core
|
|
421
|
-
"vibecheck.ship",
|
|
422
|
-
"vibecheck.scan",
|
|
423
|
-
"vibecheck.ctx",
|
|
424
|
-
// Truth
|
|
425
|
-
"vibecheck.get_truthpack",
|
|
426
|
-
"vibecheck.validate_claim",
|
|
427
|
-
"vibecheck.compile_context",
|
|
428
|
-
"vibecheck.search_evidence",
|
|
429
|
-
"vibecheck.find_counterexamples",
|
|
430
|
-
"vibecheck.check_invariants",
|
|
431
|
-
// Proof artifacts
|
|
432
|
-
"vibecheck.evidence_pack",
|
|
433
|
-
"vibecheck.allowlist",
|
|
434
|
-
// Runtime
|
|
435
|
-
"vibecheck.reality",
|
|
436
|
-
// Proof tools (new)
|
|
437
|
-
"vibecheck.prove",
|
|
438
|
-
"vibecheck.prove_status",
|
|
439
|
-
"vibecheck.get_evidence",
|
|
440
|
-
"vibecheck.check_flaky",
|
|
441
|
-
"vibecheck.allowlist_add",
|
|
442
|
-
"vibecheck.get_proof_graph",
|
|
443
|
-
// Utilities
|
|
444
|
-
"vibecheck.status",
|
|
445
|
-
]);
|
|
446
|
-
|
|
447
|
-
// Combine base tools with proof tools
|
|
448
|
-
const COMBINED_TOOLS = [...ALL_TOOLS, ...PROOF_TOOLS];
|
|
449
|
-
|
|
450
|
-
export const CONSOLIDATED_TOOLS = COMBINED_TOOLS.filter((tool) =>
|
|
451
|
-
ALLOWED_TOOL_NAMES.has(tool.name),
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
// ============================================================================
|
|
455
|
-
// TOOL HANDLERS
|
|
456
|
-
// ============================================================================
|
|
457
|
-
|
|
458
|
-
export async function handleConsolidatedTool(name, args) {
|
|
459
|
-
const projectPath = args.projectPath || process.cwd();
|
|
460
|
-
|
|
461
|
-
switch (name) {
|
|
462
|
-
// Core Commands
|
|
463
|
-
case "vibecheck.ship":
|
|
464
|
-
return wrapResponse(await runCliCommand("ship", projectPath, args));
|
|
465
|
-
case "vibecheck.scan":
|
|
466
|
-
return wrapResponse(await runCliCommand("scan", projectPath, args));
|
|
467
|
-
case "vibecheck.ctx":
|
|
468
|
-
return wrapResponse(await runCliCommand("ctx", projectPath, args));
|
|
469
|
-
|
|
470
|
-
// Truth Firewall / Evidence
|
|
471
|
-
case "vibecheck.get_truthpack":
|
|
472
|
-
case "vibecheck.validate_claim":
|
|
473
|
-
case "vibecheck.compile_context":
|
|
474
|
-
case "vibecheck.search_evidence":
|
|
475
|
-
case "vibecheck.find_counterexamples":
|
|
476
|
-
case "vibecheck.check_invariants":
|
|
477
|
-
return await handleTruthFirewallTool(name, args, projectPath);
|
|
478
|
-
|
|
479
|
-
// Proof Artifacts
|
|
480
|
-
case "vibecheck.evidence_pack":
|
|
481
|
-
return await handleEvidencePack(projectPath, args);
|
|
482
|
-
case "vibecheck.allowlist":
|
|
483
|
-
return await handleAllowlist(projectPath, args);
|
|
484
|
-
|
|
485
|
-
// Runtime
|
|
486
|
-
case "vibecheck.reality":
|
|
487
|
-
return await handleReality(projectPath, args);
|
|
488
|
-
|
|
489
|
-
// Proof Tools
|
|
490
|
-
case "vibecheck.prove":
|
|
491
|
-
case "vibecheck.prove_status":
|
|
492
|
-
case "vibecheck.get_evidence":
|
|
493
|
-
case "vibecheck.check_flaky":
|
|
494
|
-
case "vibecheck.allowlist_add":
|
|
495
|
-
case "vibecheck.get_proof_graph":
|
|
496
|
-
return await handleProofTool(name, { ...args, projectPath });
|
|
497
|
-
|
|
498
|
-
// Utilities
|
|
499
|
-
case "vibecheck.status":
|
|
500
|
-
return wrapResponse(await getStatus(projectPath));
|
|
501
|
-
|
|
502
|
-
default:
|
|
503
|
-
return wrapResponse(null, { error: `Unknown tool: ${name}. Available: ${CONSOLIDATED_TOOLS.map(t => t.name).join(', ')}` });
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// ============================================================================
|
|
508
|
-
// IMPLEMENTATION HELPERS
|
|
509
|
-
// ============================================================================
|
|
510
|
-
|
|
511
|
-
async function runCliCommand(cmd, projectPath, args) {
|
|
512
|
-
const binPath = path.join(__dirname, '..', 'bin', 'vibecheck.js');
|
|
513
|
-
let cmdArgs = [cmd];
|
|
514
|
-
|
|
515
|
-
// Add flags based on args
|
|
516
|
-
if (args.strict) cmdArgs.push('--strict');
|
|
517
|
-
if (args.json) cmdArgs.push('--json');
|
|
518
|
-
if (args.apply) cmdArgs.push('--apply');
|
|
519
|
-
if (args.promptOnly) cmdArgs.push('--prompt-only');
|
|
520
|
-
if (args.snapshot) cmdArgs.push('--snapshot');
|
|
521
|
-
if (args.url) cmdArgs.push('--url', args.url);
|
|
522
|
-
if (args.auth) cmdArgs.push('--auth', args.auth);
|
|
523
|
-
if (args.paths) cmdArgs.push('--paths', args.paths);
|
|
524
|
-
if (args.budget) cmdArgs.push('--budget', args.budget);
|
|
525
|
-
if (args.profile) cmdArgs.push('--profile', args.profile);
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
const result = execSync(`node "${binPath}" ${cmdArgs.join(' ')}`, {
|
|
529
|
-
cwd: projectPath,
|
|
530
|
-
encoding: 'utf8',
|
|
531
|
-
timeout: 120000
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
// Try to parse JSON output
|
|
535
|
-
try {
|
|
536
|
-
return JSON.parse(result);
|
|
537
|
-
} catch {
|
|
538
|
-
return { output: result, success: true };
|
|
539
|
-
}
|
|
540
|
-
} catch (error) {
|
|
541
|
-
return {
|
|
542
|
-
error: error.message,
|
|
543
|
-
exitCode: error.status,
|
|
544
|
-
output: error.stdout || error.stderr
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
async function getTruthpack(projectPath, refresh = false) {
|
|
550
|
-
// Spec path: .vibecheck/truthpack.json
|
|
551
|
-
const specPath = path.join(projectPath, '.vibecheck', 'truthpack.json');
|
|
552
|
-
// Legacy path: .vibecheck/truth/truthpack.json
|
|
553
|
-
const legacyPath = path.join(projectPath, '.vibecheck', 'truth', 'truthpack.json');
|
|
554
|
-
|
|
555
|
-
const truthpackPath = fs.existsSync(specPath) ? specPath : legacyPath;
|
|
556
|
-
|
|
557
|
-
if (refresh || !fs.existsSync(truthpackPath)) {
|
|
558
|
-
await runCliCommand('ctx', projectPath, {});
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Try spec path first after potential regeneration
|
|
562
|
-
for (const tpPath of [specPath, legacyPath]) {
|
|
563
|
-
try {
|
|
564
|
-
const truthpack = JSON.parse(fs.readFileSync(tpPath, 'utf8'));
|
|
565
|
-
return {
|
|
566
|
-
data: truthpack,
|
|
567
|
-
evidence: [{ file: tpPath.replace(projectPath, '.'), line: 1 }],
|
|
568
|
-
confidence: 1.0,
|
|
569
|
-
freshness: truthpack.generatedAt
|
|
570
|
-
};
|
|
571
|
-
} catch {}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
return { error: 'Truthpack not found. Run vibecheck ctx first.', gaps: ['truthpack_missing'] };
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
async function getRoutes(projectPath, type = 'all') {
|
|
578
|
-
const truthpack = await getTruthpack(projectPath);
|
|
579
|
-
if (truthpack.error) return truthpack;
|
|
580
|
-
|
|
581
|
-
const routes = truthpack.data.routes || {};
|
|
582
|
-
|
|
583
|
-
if (type === 'server') {
|
|
584
|
-
return { data: routes.server || [], evidence: [], confidence: 0.9 };
|
|
585
|
-
} else if (type === 'client') {
|
|
586
|
-
return { data: routes.clientRefs || [], evidence: [], confidence: 0.85 };
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
return {
|
|
590
|
-
data: {
|
|
591
|
-
server: routes.server || [],
|
|
592
|
-
client: routes.clientRefs || [],
|
|
593
|
-
gaps: routes.gaps || []
|
|
594
|
-
},
|
|
595
|
-
evidence: [],
|
|
596
|
-
confidence: 0.9
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
async function getEnv(projectPath) {
|
|
601
|
-
const truthpack = await getTruthpack(projectPath);
|
|
602
|
-
if (truthpack.error) return truthpack;
|
|
603
|
-
|
|
604
|
-
return {
|
|
605
|
-
data: truthpack.data.env || {},
|
|
606
|
-
evidence: [],
|
|
607
|
-
confidence: 0.95
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
async function getAuth(projectPath) {
|
|
612
|
-
const truthpack = await getTruthpack(projectPath);
|
|
613
|
-
if (truthpack.error) return truthpack;
|
|
614
|
-
|
|
615
|
-
return {
|
|
616
|
-
data: truthpack.data.auth || {},
|
|
617
|
-
evidence: [],
|
|
618
|
-
confidence: 0.85
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
async function getBilling(projectPath) {
|
|
623
|
-
const truthpack = await getTruthpack(projectPath);
|
|
624
|
-
if (truthpack.error) return truthpack;
|
|
625
|
-
|
|
626
|
-
return {
|
|
627
|
-
data: truthpack.data.billing || {},
|
|
628
|
-
evidence: [],
|
|
629
|
-
confidence: 0.8
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
async function validateClaim(projectPath, claim, type) {
|
|
634
|
-
// Use the claim verifier runner
|
|
635
|
-
const binPath = path.join(__dirname, '..', 'bin', 'runners', 'runClaimVerifier.js');
|
|
636
|
-
|
|
637
|
-
try {
|
|
638
|
-
const { validateClaim: verify } = require(binPath);
|
|
639
|
-
const result = await verify(projectPath, { claim, type });
|
|
640
|
-
return result;
|
|
641
|
-
} catch (error) {
|
|
642
|
-
// Fallback to simple validation
|
|
643
|
-
const truthpack = await getTruthpack(projectPath);
|
|
644
|
-
if (truthpack.error) return { valid: 'unknown', reason: truthpack.error };
|
|
645
|
-
|
|
646
|
-
// Simple claim validation based on type
|
|
647
|
-
if (type === 'route_exists') {
|
|
648
|
-
const routes = truthpack.data.routes?.server || [];
|
|
649
|
-
const exists = routes.some(r => r.path === claim || r.path.includes(claim));
|
|
650
|
-
return {
|
|
651
|
-
valid: exists,
|
|
652
|
-
confidence: exists ? 0.9 : 0.7,
|
|
653
|
-
evidence: exists ? routes.filter(r => r.path.includes(claim)).slice(0, 3) : [],
|
|
654
|
-
gaps: exists ? [] : [`Route ${claim} not found in server routes`]
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
if (type === 'env_var_used') {
|
|
659
|
-
const vars = truthpack.data.env?.vars || [];
|
|
660
|
-
const found = vars.find(v => v.name === claim);
|
|
661
|
-
return {
|
|
662
|
-
valid: !!found,
|
|
663
|
-
confidence: found ? 0.95 : 0.8,
|
|
664
|
-
evidence: found ? [{ file: found.file, line: found.line }] : [],
|
|
665
|
-
gaps: found ? [] : [`Env var ${claim} not found in codebase`]
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
if (type === 'file_exists') {
|
|
670
|
-
const filePath = path.join(projectPath, claim);
|
|
671
|
-
const exists = fs.existsSync(filePath);
|
|
672
|
-
return {
|
|
673
|
-
valid: exists,
|
|
674
|
-
confidence: 1.0,
|
|
675
|
-
evidence: exists ? [{ file: claim, line: 1 }] : [],
|
|
676
|
-
gaps: exists ? [] : [`File ${claim} does not exist`]
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
return { valid: 'unknown', reason: 'Claim type not supported', type };
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
async function getEvidence(projectPath, args) {
|
|
685
|
-
if (args.findingId) {
|
|
686
|
-
// Get evidence for a specific finding from last ship report
|
|
687
|
-
const shipPath = path.join(projectPath, '.vibecheck', 'last_ship.json');
|
|
688
|
-
try {
|
|
689
|
-
const report = JSON.parse(fs.readFileSync(shipPath, 'utf8'));
|
|
690
|
-
const finding = report.findings?.find(f => f.id === args.findingId);
|
|
691
|
-
if (finding) {
|
|
692
|
-
return {
|
|
693
|
-
data: finding.evidence || [],
|
|
694
|
-
finding,
|
|
695
|
-
confidence: 0.9
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
} catch {}
|
|
699
|
-
return { error: 'Finding not found', findingId: args.findingId };
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
if (args.file && args.pattern) {
|
|
703
|
-
// Search for pattern in file
|
|
704
|
-
const filePath = path.join(projectPath, args.file);
|
|
705
|
-
try {
|
|
706
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
707
|
-
const regex = new RegExp(args.pattern, 'gi');
|
|
708
|
-
const matches = [];
|
|
709
|
-
let match;
|
|
710
|
-
while ((match = regex.exec(content)) !== null) {
|
|
711
|
-
const line = content.substring(0, match.index).split('\n').length;
|
|
712
|
-
matches.push({
|
|
713
|
-
file: args.file,
|
|
714
|
-
line,
|
|
715
|
-
snippet: match[0],
|
|
716
|
-
confidence: 0.95
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
return { data: matches, confidence: 0.9 };
|
|
720
|
-
} catch (error) {
|
|
721
|
-
return { error: error.message };
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
return { error: 'Provide either findingId or file+pattern' };
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
async function getProofGraph(projectPath) {
|
|
729
|
-
const proofPath = path.join(projectPath, '.vibecheck', 'proof-graph.json');
|
|
730
|
-
|
|
731
|
-
try {
|
|
732
|
-
const proofGraph = JSON.parse(fs.readFileSync(proofPath, 'utf8'));
|
|
733
|
-
return {
|
|
734
|
-
data: proofGraph,
|
|
735
|
-
confidence: 0.9
|
|
736
|
-
};
|
|
737
|
-
} catch {
|
|
738
|
-
// Generate if not exists
|
|
739
|
-
await runCliCommand('ship', projectPath, {});
|
|
740
|
-
try {
|
|
741
|
-
const proofGraph = JSON.parse(fs.readFileSync(proofPath, 'utf8'));
|
|
742
|
-
return { data: proofGraph, confidence: 0.9 };
|
|
743
|
-
} catch {
|
|
744
|
-
return { error: 'Proof graph not available. Run vibecheck ship first.' };
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
async function getStatus(projectPath) {
|
|
750
|
-
const shipPath = path.join(projectPath, '.vibecheck', 'last_ship.json');
|
|
751
|
-
// Check both spec path and legacy path
|
|
752
|
-
const truthpackPath = path.join(projectPath, '.vibecheck', 'truthpack.json');
|
|
753
|
-
const legacyTruthpackPath = path.join(projectPath, '.vibecheck', 'truth', 'truthpack.json');
|
|
754
|
-
|
|
755
|
-
const status = {
|
|
756
|
-
hasShipReport: false,
|
|
757
|
-
hasTruthpack: false,
|
|
758
|
-
lastVerdict: null,
|
|
759
|
-
findingsCount: 0,
|
|
760
|
-
truthpackAge: null
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
try {
|
|
764
|
-
const report = JSON.parse(fs.readFileSync(shipPath, 'utf8'));
|
|
765
|
-
status.hasShipReport = true;
|
|
766
|
-
status.lastVerdict = report.meta?.verdict;
|
|
767
|
-
status.findingsCount = report.findings?.length || 0;
|
|
768
|
-
} catch {}
|
|
769
|
-
|
|
770
|
-
// Try spec path first, then legacy
|
|
771
|
-
for (const tpPath of [truthpackPath, legacyTruthpackPath]) {
|
|
772
|
-
try {
|
|
773
|
-
const truthpack = JSON.parse(fs.readFileSync(tpPath, 'utf8'));
|
|
774
|
-
status.hasTruthpack = true;
|
|
775
|
-
status.truthpackAge = truthpack.generatedAt;
|
|
776
|
-
break;
|
|
777
|
-
} catch {}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
return { data: status, confidence: 1.0 };
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// ============================================================================
|
|
784
|
-
// SPEC-REQUIRED TOOL IMPLEMENTATIONS
|
|
785
|
-
// ============================================================================
|
|
786
|
-
|
|
787
|
-
async function getContracts(projectPath) {
|
|
788
|
-
const contractDir = path.join(projectPath, '.vibecheck', 'contracts');
|
|
789
|
-
const contracts = {};
|
|
790
|
-
|
|
791
|
-
const files = ['routes.json', 'env.json', 'auth.json', 'external.json'];
|
|
792
|
-
|
|
793
|
-
for (const file of files) {
|
|
794
|
-
const filePath = path.join(contractDir, file);
|
|
795
|
-
try {
|
|
796
|
-
contracts[file.replace('.json', '')] = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
797
|
-
} catch {}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
if (Object.keys(contracts).length === 0) {
|
|
801
|
-
return {
|
|
802
|
-
error: 'No contracts found. Run vibecheck ctx sync first.',
|
|
803
|
-
hint: 'vibecheck ctx sync generates contracts from your truthpack'
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
return {
|
|
808
|
-
data: contracts,
|
|
809
|
-
files: Object.keys(contracts).map(k => `.vibecheck/contracts/${k}.json`),
|
|
810
|
-
confidence: 1.0
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
async function validatePlan(projectPath, plan) {
|
|
815
|
-
if (!plan || !Array.isArray(plan)) {
|
|
816
|
-
return { error: 'Plan must be an array of actions' };
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// Load contracts
|
|
820
|
-
const contractsResult = await getContracts(projectPath);
|
|
821
|
-
if (contractsResult.error) {
|
|
822
|
-
return {
|
|
823
|
-
valid: 'unknown',
|
|
824
|
-
reason: contractsResult.error,
|
|
825
|
-
hint: 'Generate contracts first with: vibecheck ctx sync'
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
const contracts = contractsResult.data;
|
|
830
|
-
const violations = [];
|
|
831
|
-
const requiredContractEdits = [];
|
|
832
|
-
|
|
833
|
-
for (const action of plan) {
|
|
834
|
-
// Check route references
|
|
835
|
-
if (action.action === 'fetch' || action.action === 'api_call' || action.target?.startsWith('/api/')) {
|
|
836
|
-
const routeTarget = action.target || action.details?.endpoint;
|
|
837
|
-
if (routeTarget && contracts.routes?.routes) {
|
|
838
|
-
const routeExists = contracts.routes.routes.some(r =>
|
|
839
|
-
r.path === routeTarget ||
|
|
840
|
-
r.path.replace(/:\w+/g, '[^/]+').match(new RegExp(`^${routeTarget.replace(/:\w+/g, '[^/]+')}$`))
|
|
841
|
-
);
|
|
842
|
-
if (!routeExists) {
|
|
843
|
-
violations.push({
|
|
844
|
-
type: 'route_not_in_contract',
|
|
845
|
-
action: action.action,
|
|
846
|
-
target: routeTarget,
|
|
847
|
-
message: `Route ${routeTarget} not found in routes contract`,
|
|
848
|
-
severity: 'BLOCK'
|
|
849
|
-
});
|
|
850
|
-
requiredContractEdits.push({
|
|
851
|
-
contract: 'routes',
|
|
852
|
-
action: 'add_route',
|
|
853
|
-
data: { path: routeTarget, method: action.details?.method || 'GET' }
|
|
854
|
-
});
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
// Check env var references
|
|
860
|
-
if (action.action === 'use_env' || action.details?.env) {
|
|
861
|
-
const envVar = action.target || action.details?.env;
|
|
862
|
-
if (envVar && contracts.env?.vars) {
|
|
863
|
-
const envExists = contracts.env.vars.some(v => v.name === envVar);
|
|
864
|
-
if (!envExists) {
|
|
865
|
-
violations.push({
|
|
866
|
-
type: 'env_not_in_contract',
|
|
867
|
-
action: action.action,
|
|
868
|
-
target: envVar,
|
|
869
|
-
message: `Env var ${envVar} not declared in env contract`,
|
|
870
|
-
severity: 'WARN'
|
|
871
|
-
});
|
|
872
|
-
requiredContractEdits.push({
|
|
873
|
-
contract: 'env',
|
|
874
|
-
action: 'add_var',
|
|
875
|
-
data: { name: envVar, required: true }
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
return {
|
|
883
|
-
valid: violations.length === 0,
|
|
884
|
-
violations,
|
|
885
|
-
requiredContractEdits,
|
|
886
|
-
checkedActions: plan.length,
|
|
887
|
-
confidence: 0.9
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
async function buildShare(projectPath, missionDir) {
|
|
892
|
-
const binPath = path.join(__dirname, '..', 'bin', 'vibecheck.js');
|
|
893
|
-
let cmdArgs = ['share'];
|
|
894
|
-
if (missionDir) cmdArgs.push('--mission-dir', missionDir);
|
|
895
|
-
|
|
896
|
-
try {
|
|
897
|
-
const result = execSync(`node "${binPath}" ${cmdArgs.join(' ')}`, {
|
|
898
|
-
cwd: projectPath,
|
|
899
|
-
encoding: 'utf8',
|
|
900
|
-
timeout: 30000
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
// Read generated files
|
|
904
|
-
const shareDir = path.join(projectPath, '.vibecheck', 'missions');
|
|
905
|
-
const files = {};
|
|
906
|
-
|
|
907
|
-
for (const file of ['share.json', 'share.md', 'pr_comment.md']) {
|
|
908
|
-
// Find latest mission dir
|
|
909
|
-
try {
|
|
910
|
-
const dirs = fs.readdirSync(shareDir).filter(d => d.match(/^\d+$/)).sort().reverse();
|
|
911
|
-
if (dirs.length > 0) {
|
|
912
|
-
const latestShare = path.join(shareDir, dirs[0], 'share', file);
|
|
913
|
-
if (fs.existsSync(latestShare)) {
|
|
914
|
-
files[file] = fs.readFileSync(latestShare, 'utf8');
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
} catch {}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
return {
|
|
921
|
-
success: true,
|
|
922
|
-
output: result,
|
|
923
|
-
files,
|
|
924
|
-
confidence: 1.0
|
|
925
|
-
};
|
|
926
|
-
} catch (error) {
|
|
927
|
-
return { error: error.message };
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
async function renderPRComment(projectPath, maxFindings = 12) {
|
|
932
|
-
const shipPath = path.join(projectPath, '.vibecheck', 'last_ship.json');
|
|
933
|
-
|
|
934
|
-
try {
|
|
935
|
-
const report = JSON.parse(fs.readFileSync(shipPath, 'utf8'));
|
|
936
|
-
const verdict = report.meta?.verdict || 'unknown';
|
|
937
|
-
const findings = report.findings || [];
|
|
938
|
-
|
|
939
|
-
// Build PR comment markdown
|
|
940
|
-
const severityCounts = { BLOCK: 0, WARN: 0, INFO: 0 };
|
|
941
|
-
for (const f of findings) {
|
|
942
|
-
severityCounts[f.severity] = (severityCounts[f.severity] || 0) + 1;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
const verdictEmoji = verdict === 'SHIP' ? '✅' : verdict === 'WARN' ? '⚠️' : '🛑';
|
|
946
|
-
|
|
947
|
-
let md = `## vibecheck — ${verdictEmoji} ${verdict}\n\n`;
|
|
948
|
-
md += `**Reality summary:** BLOCK=${severityCounts.BLOCK} • WARN=${severityCounts.WARN}\n\n`;
|
|
949
|
-
|
|
950
|
-
const topFindings = findings
|
|
951
|
-
.filter(f => f.severity === 'BLOCK' || f.severity === 'WARN')
|
|
952
|
-
.slice(0, maxFindings);
|
|
953
|
-
|
|
954
|
-
if (topFindings.length === 0) {
|
|
955
|
-
md += `✅ No blockers. Ship it.\n`;
|
|
956
|
-
} else {
|
|
957
|
-
md += `### Top findings\n`;
|
|
958
|
-
for (const f of topFindings) {
|
|
959
|
-
md += `- **${f.severity}** \`${f.id}\` — ${f.title}\n`;
|
|
960
|
-
const ev = (f.evidence || [])[0];
|
|
961
|
-
if (ev?.file) {
|
|
962
|
-
md += ` - Evidence: \`${ev.file}:${ev.lines}\` (${ev.reason})\n`;
|
|
963
|
-
}
|
|
964
|
-
const hint = (f.fixHints || [])[0];
|
|
965
|
-
if (hint) md += ` - Fix: ${hint}\n`;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if (findings.length > topFindings.length) {
|
|
969
|
-
md += `\n_…and ${findings.length - topFindings.length} more finding(s). See \`.vibecheck/last_ship.json\` for full details._\n`;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
return {
|
|
974
|
-
markdown: md,
|
|
975
|
-
verdict,
|
|
976
|
-
findingsCount: findings.length,
|
|
977
|
-
confidence: 1.0
|
|
978
|
-
};
|
|
979
|
-
} catch (error) {
|
|
980
|
-
return { error: 'No ship report found. Run vibecheck ship first.' };
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// ============================================================================
|
|
985
|
-
// PROOF ARTIFACT HANDLERS
|
|
986
|
-
// ============================================================================
|
|
987
|
-
|
|
988
|
-
async function handleEvidencePack(projectPath, args) {
|
|
989
|
-
try {
|
|
990
|
-
// Try to import the evidence-pack module
|
|
991
|
-
const evidencePackPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'bin', 'runners', 'lib', 'evidence-pack.js');
|
|
992
|
-
|
|
993
|
-
// For Windows, fix the path
|
|
994
|
-
const normalizedPath = process.platform === 'win32'
|
|
995
|
-
? evidencePackPath.replace(/^\/([A-Za-z]):/, '$1:')
|
|
996
|
-
: evidencePackPath;
|
|
997
|
-
|
|
998
|
-
let evidencePack;
|
|
999
|
-
try {
|
|
1000
|
-
evidencePack = await import(`file://${normalizedPath}`);
|
|
1001
|
-
} catch {
|
|
1002
|
-
// Fall back to CLI command
|
|
1003
|
-
return wrapResponse(await runCliCommand("evidence-pack", projectPath, args));
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
const pack = await evidencePack.buildEvidencePack(projectPath, {
|
|
1007
|
-
includeVideos: args.includeVideos !== false,
|
|
1008
|
-
includeTraces: args.includeTraces !== false,
|
|
1009
|
-
includeScreenshots: args.includeScreenshots !== false,
|
|
1010
|
-
applyAllowlist: args.applyAllowlist !== false
|
|
1011
|
-
});
|
|
1012
|
-
|
|
1013
|
-
return wrapResponse({
|
|
1014
|
-
packId: pack.id,
|
|
1015
|
-
manifestPath: pack.manifestPath,
|
|
1016
|
-
zipPath: pack.zipPath,
|
|
1017
|
-
summary: pack.summary
|
|
1018
|
-
}, {
|
|
1019
|
-
evidence: pack.manifest.findings.slice(0, 5).map(f => ({
|
|
1020
|
-
file: f.where?.file,
|
|
1021
|
-
line: f.where?.line,
|
|
1022
|
-
snippet: f.what,
|
|
1023
|
-
confidence: f.confidence
|
|
1024
|
-
}))
|
|
1025
|
-
});
|
|
1026
|
-
} catch (error) {
|
|
1027
|
-
return wrapResponse(null, { error: error.message });
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
async function handleAllowlist(projectPath, args) {
|
|
1032
|
-
try {
|
|
1033
|
-
const evidencePackPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'bin', 'runners', 'lib', 'evidence-pack.js');
|
|
1034
|
-
const normalizedPath = process.platform === 'win32'
|
|
1035
|
-
? evidencePackPath.replace(/^\/([A-Za-z]):/, '$1:')
|
|
1036
|
-
: evidencePackPath;
|
|
1037
|
-
|
|
1038
|
-
let evidencePack;
|
|
1039
|
-
try {
|
|
1040
|
-
evidencePack = await import(`file://${normalizedPath}`);
|
|
1041
|
-
} catch {
|
|
1042
|
-
return wrapResponse(null, { error: 'Evidence pack module not available' });
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
const action = args.action || 'list';
|
|
1046
|
-
|
|
1047
|
-
switch (action) {
|
|
1048
|
-
case 'list': {
|
|
1049
|
-
const allowlist = evidencePack.loadAllowlist(projectPath);
|
|
1050
|
-
return wrapResponse({
|
|
1051
|
-
entries: allowlist.entries || [],
|
|
1052
|
-
lastUpdated: allowlist.lastUpdated
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
case 'add': {
|
|
1057
|
-
if (!args.findingId && !args.pattern) {
|
|
1058
|
-
return wrapResponse(null, { error: 'Either findingId or pattern is required' });
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
const entry = evidencePack.addToAllowlist(projectPath, {
|
|
1062
|
-
findingId: args.findingId,
|
|
1063
|
-
pattern: args.pattern,
|
|
1064
|
-
reason: args.reason || 'Added via MCP tool',
|
|
1065
|
-
scope: args.scope || 'global',
|
|
1066
|
-
addedBy: 'mcp'
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
return wrapResponse({
|
|
1070
|
-
added: entry,
|
|
1071
|
-
message: `Added allowlist entry: ${entry.id}`
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
case 'remove': {
|
|
1076
|
-
if (!args.findingId) {
|
|
1077
|
-
return wrapResponse(null, { error: 'findingId is required for remove action' });
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
const allowlist = evidencePack.loadAllowlist(projectPath);
|
|
1081
|
-
const before = allowlist.entries.length;
|
|
1082
|
-
allowlist.entries = allowlist.entries.filter(e => e.id !== args.findingId && e.findingId !== args.findingId);
|
|
1083
|
-
evidencePack.saveAllowlist(projectPath, allowlist);
|
|
1084
|
-
|
|
1085
|
-
return wrapResponse({
|
|
1086
|
-
removed: before - allowlist.entries.length,
|
|
1087
|
-
remaining: allowlist.entries.length
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
case 'check': {
|
|
1092
|
-
if (!args.findingId) {
|
|
1093
|
-
return wrapResponse(null, { error: 'findingId is required for check action' });
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
const allowlist = evidencePack.loadAllowlist(projectPath);
|
|
1097
|
-
const result = evidencePack.isAllowlisted({ id: args.findingId }, allowlist);
|
|
1098
|
-
|
|
1099
|
-
return wrapResponse({
|
|
1100
|
-
allowed: result.allowed,
|
|
1101
|
-
reason: result.reason,
|
|
1102
|
-
entryId: result.entry?.id
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
default:
|
|
1107
|
-
return wrapResponse(null, { error: `Unknown action: ${action}` });
|
|
1108
|
-
}
|
|
1109
|
-
} catch (error) {
|
|
1110
|
-
return wrapResponse(null, { error: error.message });
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
async function handleReality(projectPath, args) {
|
|
1115
|
-
try {
|
|
1116
|
-
if (!args.url) {
|
|
1117
|
-
return wrapResponse(null, { error: 'url is required' });
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
const cmdArgs = {
|
|
1121
|
-
url: args.url,
|
|
1122
|
-
projectPath,
|
|
1123
|
-
json: true
|
|
1124
|
-
};
|
|
1125
|
-
|
|
1126
|
-
if (args.auth) cmdArgs.auth = args.auth;
|
|
1127
|
-
if (args.recordVideo) cmdArgs['--video'] = true;
|
|
1128
|
-
if (args.recordTrace) cmdArgs['--trace'] = true;
|
|
1129
|
-
if (args.maxPages) cmdArgs.maxPages = args.maxPages;
|
|
1130
|
-
|
|
1131
|
-
// Build CLI command
|
|
1132
|
-
const binPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'bin', 'vibecheck.js');
|
|
1133
|
-
const normalizedBinPath = process.platform === 'win32'
|
|
1134
|
-
? binPath.replace(/^\/([A-Za-z]):/, '$1:')
|
|
1135
|
-
: binPath;
|
|
1136
|
-
|
|
1137
|
-
let cliArgs = ['reality', '--url', args.url, '--json'];
|
|
1138
|
-
if (args.auth) cliArgs.push('--auth', args.auth);
|
|
1139
|
-
if (args.recordVideo) cliArgs.push('--video');
|
|
1140
|
-
if (args.recordTrace) cliArgs.push('--trace');
|
|
1141
|
-
if (args.maxPages) cliArgs.push('--max-pages', String(args.maxPages));
|
|
1142
|
-
|
|
1143
|
-
const result = execSync(`node "${normalizedBinPath}" ${cliArgs.join(' ')}`, {
|
|
1144
|
-
cwd: projectPath,
|
|
1145
|
-
encoding: 'utf8',
|
|
1146
|
-
timeout: 300000 // 5 minutes for runtime tests
|
|
1147
|
-
});
|
|
1148
|
-
|
|
1149
|
-
try {
|
|
1150
|
-
const parsed = JSON.parse(result);
|
|
1151
|
-
return wrapResponse(parsed, {
|
|
1152
|
-
evidence: (parsed.findings || []).slice(0, 5).map(f => ({
|
|
1153
|
-
file: f.file,
|
|
1154
|
-
line: f.line,
|
|
1155
|
-
snippet: f.title,
|
|
1156
|
-
confidence: f.confidence || 0.8
|
|
1157
|
-
}))
|
|
1158
|
-
});
|
|
1159
|
-
} catch {
|
|
1160
|
-
return wrapResponse({ output: result });
|
|
1161
|
-
}
|
|
1162
|
-
} catch (error) {
|
|
1163
|
-
return wrapResponse(null, {
|
|
1164
|
-
error: error.message,
|
|
1165
|
-
output: error.stdout || error.stderr
|
|
1166
|
-
});
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
export default { CONSOLIDATED_TOOLS, handleConsolidatedTool, wrapResponse };
|