@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.
Files changed (85) hide show
  1. package/README.md +1 -1
  2. package/bin/runners/context/generators/cursor-enhanced.js +99 -13
  3. package/mcp-server/.eslintrc.json +24 -0
  4. package/mcp-server/README.md +425 -135
  5. package/mcp-server/SPEC.md +583 -0
  6. package/mcp-server/configs/README.md +172 -0
  7. package/mcp-server/configs/claude-desktop-pro.json +31 -0
  8. package/mcp-server/configs/claude-desktop-with-workspace.json +25 -0
  9. package/mcp-server/configs/claude-desktop.json +19 -0
  10. package/mcp-server/configs/cursor-mcp.json +21 -0
  11. package/mcp-server/configs/windsurf-mcp.json +17 -0
  12. package/mcp-server/mcp-config.example.json +9 -0
  13. package/mcp-server/package.json +49 -34
  14. package/mcp-server/src/cli.ts +185 -0
  15. package/mcp-server/src/index.ts +85 -0
  16. package/mcp-server/src/server.ts +1933 -0
  17. package/mcp-server/src/services/cache-service.ts +466 -0
  18. package/mcp-server/src/services/cli-service.ts +345 -0
  19. package/mcp-server/src/services/context-manager.ts +717 -0
  20. package/mcp-server/src/services/firewall-service.ts +662 -0
  21. package/mcp-server/src/services/git-service.ts +671 -0
  22. package/mcp-server/src/services/index.ts +52 -0
  23. package/mcp-server/src/services/prompt-builder-service.ts +1031 -0
  24. package/mcp-server/src/services/session-service.ts +550 -0
  25. package/mcp-server/src/services/tier-service.ts +470 -0
  26. package/mcp-server/src/types.ts +351 -0
  27. package/mcp-server/tsconfig.json +16 -27
  28. package/package.json +6 -6
  29. package/mcp-server/.guardrail/audit/audit.log.jsonl +0 -2
  30. package/mcp-server/.specs/architecture.mdc +0 -90
  31. package/mcp-server/.specs/security.mdc +0 -30
  32. package/mcp-server/HARDENING_SUMMARY.md +0 -299
  33. package/mcp-server/agent-checkpoint.js +0 -364
  34. package/mcp-server/agent-firewall-interceptor.js +0 -500
  35. package/mcp-server/architect-tools.js +0 -707
  36. package/mcp-server/audit-mcp.js +0 -206
  37. package/mcp-server/authority-tools.js +0 -569
  38. package/mcp-server/codebase-architect-tools.js +0 -838
  39. package/mcp-server/conductor/conflict-resolver.js +0 -588
  40. package/mcp-server/conductor/execution-planner.js +0 -544
  41. package/mcp-server/conductor/index.js +0 -377
  42. package/mcp-server/conductor/lock-manager.js +0 -615
  43. package/mcp-server/conductor/request-queue.js +0 -550
  44. package/mcp-server/conductor/session-manager.js +0 -500
  45. package/mcp-server/conductor/tools.js +0 -510
  46. package/mcp-server/consolidated-tools.js +0 -1170
  47. package/mcp-server/deprecation-middleware.js +0 -282
  48. package/mcp-server/handlers/index.ts +0 -15
  49. package/mcp-server/handlers/tool-handler.ts +0 -593
  50. package/mcp-server/hygiene-tools.js +0 -428
  51. package/mcp-server/index-v1.js +0 -698
  52. package/mcp-server/index.js +0 -2940
  53. package/mcp-server/intelligence-tools.js +0 -664
  54. package/mcp-server/intent-drift-tools.js +0 -873
  55. package/mcp-server/intent-firewall-interceptor.js +0 -529
  56. package/mcp-server/lib/api-client.cjs +0 -13
  57. package/mcp-server/lib/cache-wrapper.cjs +0 -383
  58. package/mcp-server/lib/error-envelope.js +0 -138
  59. package/mcp-server/lib/executor.ts +0 -499
  60. package/mcp-server/lib/index.ts +0 -29
  61. package/mcp-server/lib/logger.cjs +0 -30
  62. package/mcp-server/lib/rate-limiter.js +0 -166
  63. package/mcp-server/lib/sandbox.test.ts +0 -519
  64. package/mcp-server/lib/sandbox.ts +0 -395
  65. package/mcp-server/lib/types.ts +0 -267
  66. package/mcp-server/logger.js +0 -173
  67. package/mcp-server/manifest.json +0 -473
  68. package/mcp-server/mdc-generator.js +0 -298
  69. package/mcp-server/premium-tools.js +0 -1275
  70. package/mcp-server/proof-tools.js +0 -571
  71. package/mcp-server/registry/tool-registry.js +0 -586
  72. package/mcp-server/registry/tools.json +0 -619
  73. package/mcp-server/registry.test.ts +0 -340
  74. package/mcp-server/test-mcp.js +0 -108
  75. package/mcp-server/test-tools.js +0 -36
  76. package/mcp-server/tests/tier-gating.test.js +0 -297
  77. package/mcp-server/tier-auth.js +0 -767
  78. package/mcp-server/tools/index.js +0 -72
  79. package/mcp-server/tools-reorganized.ts +0 -244
  80. package/mcp-server/tools-v3.js +0 -1004
  81. package/mcp-server/truth-context.js +0 -622
  82. package/mcp-server/truth-firewall-tools.js +0 -2183
  83. package/mcp-server/vibecheck-2.0-tools.js +0 -761
  84. package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
  85. 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 };