@vibecheckai/cli 3.0.4 → 3.0.7

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 (108) hide show
  1. package/bin/dev/run-v2-torture.js +30 -0
  2. package/bin/runners/context/index.js +1 -1
  3. package/bin/runners/lib/analyzers.js +38 -0
  4. package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
  5. package/bin/runners/lib/contracts/auth-contract.js +8 -0
  6. package/bin/runners/lib/contracts/env-contract.js +3 -0
  7. package/bin/runners/lib/contracts/external-contract.js +10 -2
  8. package/bin/runners/lib/contracts/route-contract.js +7 -0
  9. package/bin/runners/lib/contracts.js +804 -0
  10. package/bin/runners/lib/detectors-v2.js +703 -0
  11. package/bin/runners/lib/drift.js +425 -0
  12. package/bin/runners/lib/entitlements-v2.js +3 -1
  13. package/bin/runners/lib/entitlements.js +11 -3
  14. package/bin/runners/lib/env-resolver.js +417 -0
  15. package/bin/runners/lib/extractors/client-calls.js +990 -0
  16. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
  17. package/bin/runners/lib/extractors/fastify-routes.js +426 -0
  18. package/bin/runners/lib/extractors/index.js +363 -0
  19. package/bin/runners/lib/extractors/next-routes.js +524 -0
  20. package/bin/runners/lib/extractors/proof-graph.js +431 -0
  21. package/bin/runners/lib/extractors/route-matcher.js +451 -0
  22. package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
  23. package/bin/runners/lib/extractors/ui-bindings.js +547 -0
  24. package/bin/runners/lib/findings-schema.js +281 -0
  25. package/bin/runners/lib/html-report.js +650 -0
  26. package/bin/runners/lib/missions/templates.js +45 -0
  27. package/bin/runners/lib/policy.js +295 -0
  28. package/bin/runners/lib/reality/correlation-detectors.js +359 -0
  29. package/bin/runners/lib/reality/index.js +318 -0
  30. package/bin/runners/lib/reality/request-hashing.js +416 -0
  31. package/bin/runners/lib/reality/request-mapper.js +453 -0
  32. package/bin/runners/lib/reality/safety-rails.js +463 -0
  33. package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
  34. package/bin/runners/lib/reality/toast-detector.js +393 -0
  35. package/bin/runners/lib/report-html.js +5 -0
  36. package/bin/runners/lib/report-templates.js +5 -0
  37. package/bin/runners/lib/report.js +135 -0
  38. package/bin/runners/lib/route-truth.js +10 -10
  39. package/bin/runners/lib/schema-validator.js +350 -0
  40. package/bin/runners/lib/schemas/contracts.schema.json +160 -0
  41. package/bin/runners/lib/schemas/finding.schema.json +100 -0
  42. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
  43. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
  44. package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
  45. package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
  46. package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
  47. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
  48. package/bin/runners/lib/schemas/validator.js +438 -0
  49. package/bin/runners/lib/ui.js +562 -0
  50. package/bin/runners/lib/verdict-engine.js +628 -0
  51. package/bin/runners/runAIAgent.js +228 -1
  52. package/bin/runners/runBadge.js +181 -1
  53. package/bin/runners/runCtx.js +7 -2
  54. package/bin/runners/runCtxDiff.js +301 -0
  55. package/bin/runners/runGuard.js +168 -0
  56. package/bin/runners/runInitGha.js +78 -15
  57. package/bin/runners/runLabs.js +341 -0
  58. package/bin/runners/runLaunch.js +180 -1
  59. package/bin/runners/runMdc.js +203 -1
  60. package/bin/runners/runProof.zip +0 -0
  61. package/bin/runners/runProve.js +23 -0
  62. package/bin/runners/runReplay.js +114 -84
  63. package/bin/runners/runScan.js +111 -32
  64. package/bin/runners/runShip.js +23 -2
  65. package/bin/runners/runTruthpack.js +9 -7
  66. package/bin/runners/runValidate.js +161 -1
  67. package/bin/vibecheck.js +416 -770
  68. package/mcp-server/.guardrail/audit/audit.log.jsonl +2 -0
  69. package/mcp-server/.specs/architecture.mdc +90 -0
  70. package/mcp-server/.specs/security.mdc +30 -0
  71. package/mcp-server/README.md +252 -0
  72. package/mcp-server/agent-checkpoint.js +364 -0
  73. package/mcp-server/architect-tools.js +707 -0
  74. package/mcp-server/audit-mcp.js +206 -0
  75. package/mcp-server/codebase-architect-tools.js +838 -0
  76. package/mcp-server/consolidated-tools.js +804 -0
  77. package/mcp-server/hygiene-tools.js +428 -0
  78. package/mcp-server/index-v1.js +698 -0
  79. package/mcp-server/index.js +2092 -0
  80. package/mcp-server/index.old.js +4137 -0
  81. package/mcp-server/intelligence-tools.js +664 -0
  82. package/mcp-server/intent-drift-tools.js +873 -0
  83. package/mcp-server/mdc-generator.js +298 -0
  84. package/mcp-server/package-lock.json +165 -0
  85. package/mcp-server/package.json +47 -0
  86. package/mcp-server/premium-tools.js +1275 -0
  87. package/mcp-server/test-mcp.js +108 -0
  88. package/mcp-server/test-tools.js +36 -0
  89. package/mcp-server/tier-auth.js +147 -0
  90. package/mcp-server/tools/index.js +72 -0
  91. package/mcp-server/tools-reorganized.ts +244 -0
  92. package/mcp-server/truth-context.js +581 -0
  93. package/mcp-server/truth-firewall-tools.js +1500 -0
  94. package/mcp-server/vibecheck-2.0-tools.js +748 -0
  95. package/mcp-server/vibecheck-tools.js +1075 -0
  96. package/package.json +10 -8
  97. package/bin/guardrail.js +0 -834
  98. package/bin/runners/runAudit.js +0 -2
  99. package/bin/runners/runAutopilot.js +0 -2
  100. package/bin/runners/runCertify.js +0 -2
  101. package/bin/runners/runDashboard.js +0 -10
  102. package/bin/runners/runEnhancedShip.js +0 -2
  103. package/bin/runners/runFixPacks.js +0 -2
  104. package/bin/runners/runNaturalLanguage.js +0 -3
  105. package/bin/runners/runProof.js +0 -2
  106. package/bin/runners/runRealitySniff.js +0 -2
  107. package/bin/runners/runUpgrade.js +0 -2
  108. package/bin/runners/runVerifyAgentOutput.js +0 -2
@@ -0,0 +1,804 @@
1
+ /**
2
+ * Vibecheck MCP Consolidated Tools
3
+ *
4
+ * Reduced from 50+ tools to 15 focused tools that map to CLI commands.
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. Utilities (2) - status, doctor
12
+ */
13
+
14
+ import { execSync, spawn } from 'child_process';
15
+ import fs from 'fs';
16
+ import path from 'path';
17
+
18
+ // ============================================================================
19
+ // TOOL DEFINITIONS (15 Core Tools)
20
+ // ============================================================================
21
+
22
+ export const CONSOLIDATED_TOOLS = [
23
+ // === CORE COMMANDS (5) ===
24
+ {
25
+ name: "vibecheck.ship",
26
+ description: "Get ship verdict: SHIP/WARN/BLOCK with evidence. Returns top blockers and fix suggestions.",
27
+ inputSchema: {
28
+ type: "object",
29
+ properties: {
30
+ projectPath: { type: "string", description: "Path to project root", default: "." },
31
+ strict: { type: "boolean", description: "Treat warnings as blockers", default: false },
32
+ json: { type: "boolean", description: "Return raw JSON output", default: false }
33
+ }
34
+ }
35
+ },
36
+ {
37
+ name: "vibecheck.scan",
38
+ description: "Deep scan for ship-killers: secrets, auth gaps, fake success, dead UI, billing bypasses.",
39
+ inputSchema: {
40
+ type: "object",
41
+ properties: {
42
+ projectPath: { type: "string", description: "Path to project root", default: "." },
43
+ profile: {
44
+ type: "string",
45
+ enum: ["quick", "full", "security", "billing"],
46
+ description: "Scan profile",
47
+ default: "quick"
48
+ }
49
+ }
50
+ }
51
+ },
52
+ {
53
+ name: "vibecheck.fix",
54
+ description: "Generate fix plan or apply patches for findings. Returns evidence-backed fix suggestions.",
55
+ inputSchema: {
56
+ type: "object",
57
+ properties: {
58
+ projectPath: { type: "string", description: "Path to project root", default: "." },
59
+ apply: { type: "boolean", description: "Apply patches (requires clean git)", default: false },
60
+ promptOnly: { type: "boolean", description: "Generate prompts only, no patches", default: false }
61
+ }
62
+ }
63
+ },
64
+ {
65
+ name: "vibecheck.verify",
66
+ description: "Runtime verification using Playwright. Tests UI behavior, auth flows, dead buttons.",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: {
70
+ url: { type: "string", description: "Base URL to verify (required)" },
71
+ auth: { type: "string", description: "Login credentials as email:password" },
72
+ paths: { type: "string", description: "Comma-separated paths to test" },
73
+ budget: { type: "string", enum: ["2m", "5m", "10m"], description: "Time budget", default: "5m" }
74
+ },
75
+ required: ["url"]
76
+ }
77
+ },
78
+ {
79
+ name: "vibecheck.ctx",
80
+ description: "Generate Truth Pack with routes, env, auth, billing facts. Evidence-backed context.",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ projectPath: { type: "string", description: "Path to project root", default: "." },
85
+ snapshot: { type: "boolean", description: "Save timestamped snapshot", default: false }
86
+ }
87
+ }
88
+ },
89
+
90
+ // === TRUTH QUERIES (5) ===
91
+ {
92
+ name: "vibecheck.get_truthpack",
93
+ description: "Get the full Truth Pack for the project. Use this BEFORE making claims about the codebase.",
94
+ inputSchema: {
95
+ type: "object",
96
+ properties: {
97
+ projectPath: { type: "string", description: "Path to project root", default: "." },
98
+ refresh: { type: "boolean", description: "Force regenerate truthpack", default: false }
99
+ }
100
+ }
101
+ },
102
+ {
103
+ name: "vibecheck.get_routes",
104
+ description: "Get all server and client routes with file/line evidence.",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: {
108
+ projectPath: { type: "string", description: "Path to project root", default: "." },
109
+ type: { type: "string", enum: ["server", "client", "all"], default: "all" }
110
+ }
111
+ }
112
+ },
113
+ {
114
+ name: "vibecheck.get_env",
115
+ description: "Get env vars: declared, used, mismatches. Evidence-backed.",
116
+ inputSchema: {
117
+ type: "object",
118
+ properties: {
119
+ projectPath: { type: "string", description: "Path to project root", default: "." }
120
+ }
121
+ }
122
+ },
123
+ {
124
+ name: "vibecheck.get_auth",
125
+ description: "Get auth model: guards, protected routes, unprotected routes, gaps.",
126
+ inputSchema: {
127
+ type: "object",
128
+ properties: {
129
+ projectPath: { type: "string", description: "Path to project root", default: "." }
130
+ }
131
+ }
132
+ },
133
+ {
134
+ name: "vibecheck.get_billing",
135
+ description: "Get billing model: gates, paid features, bypasses, enforcement gaps.",
136
+ inputSchema: {
137
+ type: "object",
138
+ properties: {
139
+ projectPath: { type: "string", description: "Path to project root", default: "." }
140
+ }
141
+ }
142
+ },
143
+
144
+ // === EVIDENCE (3) ===
145
+ {
146
+ name: "vibecheck.validate_claim",
147
+ description: "Validate a claim about the codebase. Returns true/false/unknown with evidence.",
148
+ inputSchema: {
149
+ type: "object",
150
+ properties: {
151
+ projectPath: { type: "string", description: "Path to project root", default: "." },
152
+ claim: { type: "string", description: "The claim to validate (required)" },
153
+ type: {
154
+ type: "string",
155
+ enum: ["route_exists", "env_var_used", "auth_enforced", "file_exists", "function_exists"],
156
+ description: "Type of claim"
157
+ }
158
+ },
159
+ required: ["claim"]
160
+ }
161
+ },
162
+ {
163
+ name: "vibecheck.get_evidence",
164
+ description: "Get file/line evidence for a specific finding or claim.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ projectPath: { type: "string", description: "Path to project root", default: "." },
169
+ findingId: { type: "string", description: "Finding ID to get evidence for" },
170
+ file: { type: "string", description: "File path to extract evidence from" },
171
+ pattern: { type: "string", description: "Regex pattern to search for" }
172
+ }
173
+ }
174
+ },
175
+ {
176
+ name: "vibecheck.get_proof_graph",
177
+ description: "Get the proof graph: claims -> evidence -> gaps -> risk score.",
178
+ inputSchema: {
179
+ type: "object",
180
+ properties: {
181
+ projectPath: { type: "string", description: "Path to project root", default: "." }
182
+ }
183
+ }
184
+ },
185
+
186
+ // === SPEC-REQUIRED TOOLS (4) ===
187
+ {
188
+ name: "vibecheck.get_contracts",
189
+ description: "Get context contracts (routes, env, auth, external). Use with validate_plan to stop hallucinations.",
190
+ inputSchema: {
191
+ type: "object",
192
+ properties: {
193
+ projectPath: { type: "string", description: "Path to project root", default: "." }
194
+ }
195
+ }
196
+ },
197
+ {
198
+ name: "vibecheck.validate_plan",
199
+ description: "Validate an AI-generated plan against contracts. Returns violations for invented routes, missing env vars, auth mismatches.",
200
+ inputSchema: {
201
+ type: "object",
202
+ properties: {
203
+ projectPath: { type: "string", description: "Path to project root", default: "." },
204
+ plan: {
205
+ type: "array",
206
+ description: "Array of plan actions to validate",
207
+ items: {
208
+ type: "object",
209
+ properties: {
210
+ action: { type: "string" },
211
+ target: { type: "string" },
212
+ details: { type: "object" }
213
+ }
214
+ }
215
+ }
216
+ },
217
+ required: ["plan"]
218
+ }
219
+ },
220
+ {
221
+ name: "vibecheck.share",
222
+ description: "Build share pack from latest mission. Generates sanitized share.json, share.md, pr_comment.md.",
223
+ inputSchema: {
224
+ type: "object",
225
+ properties: {
226
+ projectPath: { type: "string", description: "Path to project root", default: "." },
227
+ missionDir: { type: "string", description: "Specific mission directory (default: latest)" }
228
+ }
229
+ }
230
+ },
231
+ {
232
+ name: "vibecheck.pr_comment",
233
+ description: "Render PR comment markdown from last ship report. Ready to paste into GitHub PR.",
234
+ inputSchema: {
235
+ type: "object",
236
+ properties: {
237
+ projectPath: { type: "string", description: "Path to project root", default: "." },
238
+ maxFindings: { type: "number", description: "Max findings to include", default: 12 }
239
+ }
240
+ }
241
+ },
242
+
243
+ // === UTILITIES (2) ===
244
+ {
245
+ name: "vibecheck.status",
246
+ description: "Get current vibecheck status: last verdict, findings count, truthpack freshness.",
247
+ inputSchema: {
248
+ type: "object",
249
+ properties: {
250
+ projectPath: { type: "string", description: "Path to project root", default: "." }
251
+ }
252
+ }
253
+ },
254
+ {
255
+ name: "vibecheck.doctor",
256
+ description: "Environment health check. Verifies dependencies, configs, permissions.",
257
+ inputSchema: {
258
+ type: "object",
259
+ properties: {
260
+ projectPath: { type: "string", description: "Path to project root", default: "." }
261
+ }
262
+ }
263
+ }
264
+ ];
265
+
266
+ // ============================================================================
267
+ // TOOL HANDLERS
268
+ // ============================================================================
269
+
270
+ export async function handleConsolidatedTool(name, args) {
271
+ const projectPath = args.projectPath || process.cwd();
272
+
273
+ switch (name) {
274
+ // Core Commands
275
+ case "vibecheck.ship":
276
+ return await runCliCommand("ship", projectPath, args);
277
+ case "vibecheck.scan":
278
+ return await runCliCommand("scan", projectPath, args);
279
+ case "vibecheck.fix":
280
+ return await runCliCommand("fix", projectPath, args);
281
+ case "vibecheck.verify":
282
+ return await runCliCommand("verify", projectPath, args);
283
+ case "vibecheck.ctx":
284
+ return await runCliCommand("ctx", projectPath, args);
285
+
286
+ // Truth Queries
287
+ case "vibecheck.get_truthpack":
288
+ return await getTruthpack(projectPath, args.refresh);
289
+ case "vibecheck.get_routes":
290
+ return await getRoutes(projectPath, args.type);
291
+ case "vibecheck.get_env":
292
+ return await getEnv(projectPath);
293
+ case "vibecheck.get_auth":
294
+ return await getAuth(projectPath);
295
+ case "vibecheck.get_billing":
296
+ return await getBilling(projectPath);
297
+
298
+ // Evidence
299
+ case "vibecheck.validate_claim":
300
+ return await validateClaim(projectPath, args.claim, args.type);
301
+ case "vibecheck.get_evidence":
302
+ return await getEvidence(projectPath, args);
303
+ case "vibecheck.get_proof_graph":
304
+ return await getProofGraph(projectPath);
305
+
306
+ // Utilities
307
+ case "vibecheck.status":
308
+ return await getStatus(projectPath);
309
+ case "vibecheck.doctor":
310
+ return await runCliCommand("doctor", projectPath, args);
311
+
312
+ // Spec-required tools
313
+ case "vibecheck.get_contracts":
314
+ return await getContracts(projectPath);
315
+ case "vibecheck.validate_plan":
316
+ return await validatePlan(projectPath, args.plan);
317
+ case "vibecheck.share":
318
+ return await buildShare(projectPath, args.missionDir);
319
+ case "vibecheck.pr_comment":
320
+ return await renderPRComment(projectPath, args.maxFindings);
321
+
322
+ default:
323
+ return { error: `Unknown tool: ${name}`, available: CONSOLIDATED_TOOLS.map(t => t.name) };
324
+ }
325
+ }
326
+
327
+ // ============================================================================
328
+ // IMPLEMENTATION HELPERS
329
+ // ============================================================================
330
+
331
+ async function runCliCommand(cmd, projectPath, args) {
332
+ const binPath = path.join(__dirname, '..', 'bin', 'vibecheck.js');
333
+ let cmdArgs = [cmd];
334
+
335
+ // Add flags based on args
336
+ if (args.strict) cmdArgs.push('--strict');
337
+ if (args.json) cmdArgs.push('--json');
338
+ if (args.apply) cmdArgs.push('--apply');
339
+ if (args.promptOnly) cmdArgs.push('--prompt-only');
340
+ if (args.snapshot) cmdArgs.push('--snapshot');
341
+ if (args.url) cmdArgs.push('--url', args.url);
342
+ if (args.auth) cmdArgs.push('--auth', args.auth);
343
+ if (args.paths) cmdArgs.push('--paths', args.paths);
344
+ if (args.budget) cmdArgs.push('--budget', args.budget);
345
+ if (args.profile) cmdArgs.push('--profile', args.profile);
346
+
347
+ try {
348
+ const result = execSync(`node "${binPath}" ${cmdArgs.join(' ')}`, {
349
+ cwd: projectPath,
350
+ encoding: 'utf8',
351
+ timeout: 120000
352
+ });
353
+
354
+ // Try to parse JSON output
355
+ try {
356
+ return JSON.parse(result);
357
+ } catch {
358
+ return { output: result, success: true };
359
+ }
360
+ } catch (error) {
361
+ return {
362
+ error: error.message,
363
+ exitCode: error.status,
364
+ output: error.stdout || error.stderr
365
+ };
366
+ }
367
+ }
368
+
369
+ async function getTruthpack(projectPath, refresh = false) {
370
+ // Spec path: .vibecheck/truthpack.json
371
+ const specPath = path.join(projectPath, '.vibecheck', 'truthpack.json');
372
+ // Legacy path: .vibecheck/truth/truthpack.json
373
+ const legacyPath = path.join(projectPath, '.vibecheck', 'truth', 'truthpack.json');
374
+
375
+ const truthpackPath = fs.existsSync(specPath) ? specPath : legacyPath;
376
+
377
+ if (refresh || !fs.existsSync(truthpackPath)) {
378
+ await runCliCommand('ctx', projectPath, {});
379
+ }
380
+
381
+ // Try spec path first after potential regeneration
382
+ for (const tpPath of [specPath, legacyPath]) {
383
+ try {
384
+ const truthpack = JSON.parse(fs.readFileSync(tpPath, 'utf8'));
385
+ return {
386
+ data: truthpack,
387
+ evidence: [{ file: tpPath.replace(projectPath, '.'), line: 1 }],
388
+ confidence: 1.0,
389
+ freshness: truthpack.generatedAt
390
+ };
391
+ } catch {}
392
+ }
393
+
394
+ return { error: 'Truthpack not found. Run vibecheck ctx first.', gaps: ['truthpack_missing'] };
395
+ }
396
+
397
+ async function getRoutes(projectPath, type = 'all') {
398
+ const truthpack = await getTruthpack(projectPath);
399
+ if (truthpack.error) return truthpack;
400
+
401
+ const routes = truthpack.data.routes || {};
402
+
403
+ if (type === 'server') {
404
+ return { data: routes.server || [], evidence: [], confidence: 0.9 };
405
+ } else if (type === 'client') {
406
+ return { data: routes.clientRefs || [], evidence: [], confidence: 0.85 };
407
+ }
408
+
409
+ return {
410
+ data: {
411
+ server: routes.server || [],
412
+ client: routes.clientRefs || [],
413
+ gaps: routes.gaps || []
414
+ },
415
+ evidence: [],
416
+ confidence: 0.9
417
+ };
418
+ }
419
+
420
+ async function getEnv(projectPath) {
421
+ const truthpack = await getTruthpack(projectPath);
422
+ if (truthpack.error) return truthpack;
423
+
424
+ return {
425
+ data: truthpack.data.env || {},
426
+ evidence: [],
427
+ confidence: 0.95
428
+ };
429
+ }
430
+
431
+ async function getAuth(projectPath) {
432
+ const truthpack = await getTruthpack(projectPath);
433
+ if (truthpack.error) return truthpack;
434
+
435
+ return {
436
+ data: truthpack.data.auth || {},
437
+ evidence: [],
438
+ confidence: 0.85
439
+ };
440
+ }
441
+
442
+ async function getBilling(projectPath) {
443
+ const truthpack = await getTruthpack(projectPath);
444
+ if (truthpack.error) return truthpack;
445
+
446
+ return {
447
+ data: truthpack.data.billing || {},
448
+ evidence: [],
449
+ confidence: 0.8
450
+ };
451
+ }
452
+
453
+ async function validateClaim(projectPath, claim, type) {
454
+ // Use the claim verifier runner
455
+ const binPath = path.join(__dirname, '..', 'bin', 'runners', 'runClaimVerifier.js');
456
+
457
+ try {
458
+ const { validateClaim: verify } = require(binPath);
459
+ const result = await verify(projectPath, { claim, type });
460
+ return result;
461
+ } catch (error) {
462
+ // Fallback to simple validation
463
+ const truthpack = await getTruthpack(projectPath);
464
+ if (truthpack.error) return { valid: 'unknown', reason: truthpack.error };
465
+
466
+ // Simple claim validation based on type
467
+ if (type === 'route_exists') {
468
+ const routes = truthpack.data.routes?.server || [];
469
+ const exists = routes.some(r => r.path === claim || r.path.includes(claim));
470
+ return {
471
+ valid: exists,
472
+ confidence: exists ? 0.9 : 0.7,
473
+ evidence: exists ? routes.filter(r => r.path.includes(claim)).slice(0, 3) : [],
474
+ gaps: exists ? [] : [`Route ${claim} not found in server routes`]
475
+ };
476
+ }
477
+
478
+ if (type === 'env_var_used') {
479
+ const vars = truthpack.data.env?.vars || [];
480
+ const found = vars.find(v => v.name === claim);
481
+ return {
482
+ valid: !!found,
483
+ confidence: found ? 0.95 : 0.8,
484
+ evidence: found ? [{ file: found.file, line: found.line }] : [],
485
+ gaps: found ? [] : [`Env var ${claim} not found in codebase`]
486
+ };
487
+ }
488
+
489
+ if (type === 'file_exists') {
490
+ const filePath = path.join(projectPath, claim);
491
+ const exists = fs.existsSync(filePath);
492
+ return {
493
+ valid: exists,
494
+ confidence: 1.0,
495
+ evidence: exists ? [{ file: claim, line: 1 }] : [],
496
+ gaps: exists ? [] : [`File ${claim} does not exist`]
497
+ };
498
+ }
499
+
500
+ return { valid: 'unknown', reason: 'Claim type not supported', type };
501
+ }
502
+ }
503
+
504
+ async function getEvidence(projectPath, args) {
505
+ if (args.findingId) {
506
+ // Get evidence for a specific finding from last ship report
507
+ const shipPath = path.join(projectPath, '.vibecheck', 'last_ship.json');
508
+ try {
509
+ const report = JSON.parse(fs.readFileSync(shipPath, 'utf8'));
510
+ const finding = report.findings?.find(f => f.id === args.findingId);
511
+ if (finding) {
512
+ return {
513
+ data: finding.evidence || [],
514
+ finding,
515
+ confidence: 0.9
516
+ };
517
+ }
518
+ } catch {}
519
+ return { error: 'Finding not found', findingId: args.findingId };
520
+ }
521
+
522
+ if (args.file && args.pattern) {
523
+ // Search for pattern in file
524
+ const filePath = path.join(projectPath, args.file);
525
+ try {
526
+ const content = fs.readFileSync(filePath, 'utf8');
527
+ const regex = new RegExp(args.pattern, 'gi');
528
+ const matches = [];
529
+ let match;
530
+ while ((match = regex.exec(content)) !== null) {
531
+ const line = content.substring(0, match.index).split('\n').length;
532
+ matches.push({
533
+ file: args.file,
534
+ line,
535
+ snippet: match[0],
536
+ confidence: 0.95
537
+ });
538
+ }
539
+ return { data: matches, confidence: 0.9 };
540
+ } catch (error) {
541
+ return { error: error.message };
542
+ }
543
+ }
544
+
545
+ return { error: 'Provide either findingId or file+pattern' };
546
+ }
547
+
548
+ async function getProofGraph(projectPath) {
549
+ const proofPath = path.join(projectPath, '.vibecheck', 'proof-graph.json');
550
+
551
+ try {
552
+ const proofGraph = JSON.parse(fs.readFileSync(proofPath, 'utf8'));
553
+ return {
554
+ data: proofGraph,
555
+ confidence: 0.9
556
+ };
557
+ } catch {
558
+ // Generate if not exists
559
+ await runCliCommand('ship', projectPath, {});
560
+ try {
561
+ const proofGraph = JSON.parse(fs.readFileSync(proofPath, 'utf8'));
562
+ return { data: proofGraph, confidence: 0.9 };
563
+ } catch {
564
+ return { error: 'Proof graph not available. Run vibecheck ship first.' };
565
+ }
566
+ }
567
+ }
568
+
569
+ async function getStatus(projectPath) {
570
+ const shipPath = path.join(projectPath, '.vibecheck', 'last_ship.json');
571
+ // Check both spec path and legacy path
572
+ const truthpackPath = path.join(projectPath, '.vibecheck', 'truthpack.json');
573
+ const legacyTruthpackPath = path.join(projectPath, '.vibecheck', 'truth', 'truthpack.json');
574
+
575
+ const status = {
576
+ hasShipReport: false,
577
+ hasTruthpack: false,
578
+ lastVerdict: null,
579
+ findingsCount: 0,
580
+ truthpackAge: null
581
+ };
582
+
583
+ try {
584
+ const report = JSON.parse(fs.readFileSync(shipPath, 'utf8'));
585
+ status.hasShipReport = true;
586
+ status.lastVerdict = report.meta?.verdict;
587
+ status.findingsCount = report.findings?.length || 0;
588
+ } catch {}
589
+
590
+ // Try spec path first, then legacy
591
+ for (const tpPath of [truthpackPath, legacyTruthpackPath]) {
592
+ try {
593
+ const truthpack = JSON.parse(fs.readFileSync(tpPath, 'utf8'));
594
+ status.hasTruthpack = true;
595
+ status.truthpackAge = truthpack.generatedAt;
596
+ break;
597
+ } catch {}
598
+ }
599
+
600
+ return { data: status, confidence: 1.0 };
601
+ }
602
+
603
+ // ============================================================================
604
+ // SPEC-REQUIRED TOOL IMPLEMENTATIONS
605
+ // ============================================================================
606
+
607
+ async function getContracts(projectPath) {
608
+ const contractDir = path.join(projectPath, '.vibecheck', 'contracts');
609
+ const contracts = {};
610
+
611
+ const files = ['routes.json', 'env.json', 'auth.json', 'external.json'];
612
+
613
+ for (const file of files) {
614
+ const filePath = path.join(contractDir, file);
615
+ try {
616
+ contracts[file.replace('.json', '')] = JSON.parse(fs.readFileSync(filePath, 'utf8'));
617
+ } catch {}
618
+ }
619
+
620
+ if (Object.keys(contracts).length === 0) {
621
+ return {
622
+ error: 'No contracts found. Run vibecheck ctx sync first.',
623
+ hint: 'vibecheck ctx sync generates contracts from your truthpack'
624
+ };
625
+ }
626
+
627
+ return {
628
+ data: contracts,
629
+ files: Object.keys(contracts).map(k => `.vibecheck/contracts/${k}.json`),
630
+ confidence: 1.0
631
+ };
632
+ }
633
+
634
+ async function validatePlan(projectPath, plan) {
635
+ if (!plan || !Array.isArray(plan)) {
636
+ return { error: 'Plan must be an array of actions' };
637
+ }
638
+
639
+ // Load contracts
640
+ const contractsResult = await getContracts(projectPath);
641
+ if (contractsResult.error) {
642
+ return {
643
+ valid: 'unknown',
644
+ reason: contractsResult.error,
645
+ hint: 'Generate contracts first with: vibecheck ctx sync'
646
+ };
647
+ }
648
+
649
+ const contracts = contractsResult.data;
650
+ const violations = [];
651
+ const requiredContractEdits = [];
652
+
653
+ for (const action of plan) {
654
+ // Check route references
655
+ if (action.action === 'fetch' || action.action === 'api_call' || action.target?.startsWith('/api/')) {
656
+ const routeTarget = action.target || action.details?.endpoint;
657
+ if (routeTarget && contracts.routes?.routes) {
658
+ const routeExists = contracts.routes.routes.some(r =>
659
+ r.path === routeTarget ||
660
+ r.path.replace(/:\w+/g, '[^/]+').match(new RegExp(`^${routeTarget.replace(/:\w+/g, '[^/]+')}$`))
661
+ );
662
+ if (!routeExists) {
663
+ violations.push({
664
+ type: 'route_not_in_contract',
665
+ action: action.action,
666
+ target: routeTarget,
667
+ message: `Route ${routeTarget} not found in routes contract`,
668
+ severity: 'BLOCK'
669
+ });
670
+ requiredContractEdits.push({
671
+ contract: 'routes',
672
+ action: 'add_route',
673
+ data: { path: routeTarget, method: action.details?.method || 'GET' }
674
+ });
675
+ }
676
+ }
677
+ }
678
+
679
+ // Check env var references
680
+ if (action.action === 'use_env' || action.details?.env) {
681
+ const envVar = action.target || action.details?.env;
682
+ if (envVar && contracts.env?.vars) {
683
+ const envExists = contracts.env.vars.some(v => v.name === envVar);
684
+ if (!envExists) {
685
+ violations.push({
686
+ type: 'env_not_in_contract',
687
+ action: action.action,
688
+ target: envVar,
689
+ message: `Env var ${envVar} not declared in env contract`,
690
+ severity: 'WARN'
691
+ });
692
+ requiredContractEdits.push({
693
+ contract: 'env',
694
+ action: 'add_var',
695
+ data: { name: envVar, required: true }
696
+ });
697
+ }
698
+ }
699
+ }
700
+ }
701
+
702
+ return {
703
+ valid: violations.length === 0,
704
+ violations,
705
+ requiredContractEdits,
706
+ checkedActions: plan.length,
707
+ confidence: 0.9
708
+ };
709
+ }
710
+
711
+ async function buildShare(projectPath, missionDir) {
712
+ const binPath = path.join(__dirname, '..', 'bin', 'vibecheck.js');
713
+ let cmdArgs = ['share'];
714
+ if (missionDir) cmdArgs.push('--mission-dir', missionDir);
715
+
716
+ try {
717
+ const result = execSync(`node "${binPath}" ${cmdArgs.join(' ')}`, {
718
+ cwd: projectPath,
719
+ encoding: 'utf8',
720
+ timeout: 30000
721
+ });
722
+
723
+ // Read generated files
724
+ const shareDir = path.join(projectPath, '.vibecheck', 'missions');
725
+ const files = {};
726
+
727
+ for (const file of ['share.json', 'share.md', 'pr_comment.md']) {
728
+ // Find latest mission dir
729
+ try {
730
+ const dirs = fs.readdirSync(shareDir).filter(d => d.match(/^\d+$/)).sort().reverse();
731
+ if (dirs.length > 0) {
732
+ const latestShare = path.join(shareDir, dirs[0], 'share', file);
733
+ if (fs.existsSync(latestShare)) {
734
+ files[file] = fs.readFileSync(latestShare, 'utf8');
735
+ }
736
+ }
737
+ } catch {}
738
+ }
739
+
740
+ return {
741
+ success: true,
742
+ output: result,
743
+ files,
744
+ confidence: 1.0
745
+ };
746
+ } catch (error) {
747
+ return { error: error.message };
748
+ }
749
+ }
750
+
751
+ async function renderPRComment(projectPath, maxFindings = 12) {
752
+ const shipPath = path.join(projectPath, '.vibecheck', 'last_ship.json');
753
+
754
+ try {
755
+ const report = JSON.parse(fs.readFileSync(shipPath, 'utf8'));
756
+ const verdict = report.meta?.verdict || 'unknown';
757
+ const findings = report.findings || [];
758
+
759
+ // Build PR comment markdown
760
+ const severityCounts = { BLOCK: 0, WARN: 0, INFO: 0 };
761
+ for (const f of findings) {
762
+ severityCounts[f.severity] = (severityCounts[f.severity] || 0) + 1;
763
+ }
764
+
765
+ const verdictEmoji = verdict === 'SHIP' ? '✅' : verdict === 'WARN' ? '⚠️' : '🛑';
766
+
767
+ let md = `## vibecheck — ${verdictEmoji} ${verdict}\n\n`;
768
+ md += `**Reality summary:** BLOCK=${severityCounts.BLOCK} • WARN=${severityCounts.WARN}\n\n`;
769
+
770
+ const topFindings = findings
771
+ .filter(f => f.severity === 'BLOCK' || f.severity === 'WARN')
772
+ .slice(0, maxFindings);
773
+
774
+ if (topFindings.length === 0) {
775
+ md += `✅ No blockers. Ship it.\n`;
776
+ } else {
777
+ md += `### Top findings\n`;
778
+ for (const f of topFindings) {
779
+ md += `- **${f.severity}** \`${f.id}\` — ${f.title}\n`;
780
+ const ev = (f.evidence || [])[0];
781
+ if (ev?.file) {
782
+ md += ` - Evidence: \`${ev.file}:${ev.lines}\` (${ev.reason})\n`;
783
+ }
784
+ const hint = (f.fixHints || [])[0];
785
+ if (hint) md += ` - Fix: ${hint}\n`;
786
+ }
787
+
788
+ if (findings.length > topFindings.length) {
789
+ md += `\n_…and ${findings.length - topFindings.length} more finding(s). See \`.vibecheck/last_ship.json\` for full details._\n`;
790
+ }
791
+ }
792
+
793
+ return {
794
+ markdown: md,
795
+ verdict,
796
+ findingsCount: findings.length,
797
+ confidence: 1.0
798
+ };
799
+ } catch (error) {
800
+ return { error: 'No ship report found. Run vibecheck ship first.' };
801
+ }
802
+ }
803
+
804
+ export default { CONSOLIDATED_TOOLS, handleConsolidatedTool };