@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.
- package/bin/dev/run-v2-torture.js +30 -0
- package/bin/runners/context/index.js +1 -1
- package/bin/runners/lib/analyzers.js +38 -0
- package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
- package/bin/runners/lib/contracts/auth-contract.js +8 -0
- package/bin/runners/lib/contracts/env-contract.js +3 -0
- package/bin/runners/lib/contracts/external-contract.js +10 -2
- package/bin/runners/lib/contracts/route-contract.js +7 -0
- package/bin/runners/lib/contracts.js +804 -0
- package/bin/runners/lib/detectors-v2.js +703 -0
- package/bin/runners/lib/drift.js +425 -0
- package/bin/runners/lib/entitlements-v2.js +3 -1
- package/bin/runners/lib/entitlements.js +11 -3
- package/bin/runners/lib/env-resolver.js +417 -0
- package/bin/runners/lib/extractors/client-calls.js +990 -0
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
- package/bin/runners/lib/extractors/fastify-routes.js +426 -0
- package/bin/runners/lib/extractors/index.js +363 -0
- package/bin/runners/lib/extractors/next-routes.js +524 -0
- package/bin/runners/lib/extractors/proof-graph.js +431 -0
- package/bin/runners/lib/extractors/route-matcher.js +451 -0
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
- package/bin/runners/lib/extractors/ui-bindings.js +547 -0
- package/bin/runners/lib/findings-schema.js +281 -0
- package/bin/runners/lib/html-report.js +650 -0
- package/bin/runners/lib/missions/templates.js +45 -0
- package/bin/runners/lib/policy.js +295 -0
- package/bin/runners/lib/reality/correlation-detectors.js +359 -0
- package/bin/runners/lib/reality/index.js +318 -0
- package/bin/runners/lib/reality/request-hashing.js +416 -0
- package/bin/runners/lib/reality/request-mapper.js +453 -0
- package/bin/runners/lib/reality/safety-rails.js +463 -0
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
- package/bin/runners/lib/reality/toast-detector.js +393 -0
- package/bin/runners/lib/report-html.js +5 -0
- package/bin/runners/lib/report-templates.js +5 -0
- package/bin/runners/lib/report.js +135 -0
- package/bin/runners/lib/route-truth.js +10 -10
- package/bin/runners/lib/schema-validator.js +350 -0
- package/bin/runners/lib/schemas/contracts.schema.json +160 -0
- package/bin/runners/lib/schemas/finding.schema.json +100 -0
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
- package/bin/runners/lib/schemas/validator.js +438 -0
- package/bin/runners/lib/ui.js +562 -0
- package/bin/runners/lib/verdict-engine.js +628 -0
- package/bin/runners/runAIAgent.js +228 -1
- package/bin/runners/runBadge.js +181 -1
- package/bin/runners/runCtx.js +7 -2
- package/bin/runners/runCtxDiff.js +301 -0
- package/bin/runners/runGuard.js +168 -0
- package/bin/runners/runInitGha.js +78 -15
- package/bin/runners/runLabs.js +341 -0
- package/bin/runners/runLaunch.js +180 -1
- package/bin/runners/runMdc.js +203 -1
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +23 -0
- package/bin/runners/runReplay.js +114 -84
- package/bin/runners/runScan.js +111 -32
- package/bin/runners/runShip.js +23 -2
- package/bin/runners/runTruthpack.js +9 -7
- package/bin/runners/runValidate.js +161 -1
- package/bin/vibecheck.js +416 -770
- package/mcp-server/.guardrail/audit/audit.log.jsonl +2 -0
- package/mcp-server/.specs/architecture.mdc +90 -0
- package/mcp-server/.specs/security.mdc +30 -0
- package/mcp-server/README.md +252 -0
- package/mcp-server/agent-checkpoint.js +364 -0
- package/mcp-server/architect-tools.js +707 -0
- package/mcp-server/audit-mcp.js +206 -0
- package/mcp-server/codebase-architect-tools.js +838 -0
- package/mcp-server/consolidated-tools.js +804 -0
- package/mcp-server/hygiene-tools.js +428 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +2092 -0
- package/mcp-server/index.old.js +4137 -0
- package/mcp-server/intelligence-tools.js +664 -0
- package/mcp-server/intent-drift-tools.js +873 -0
- package/mcp-server/mdc-generator.js +298 -0
- package/mcp-server/package-lock.json +165 -0
- package/mcp-server/package.json +47 -0
- package/mcp-server/premium-tools.js +1275 -0
- package/mcp-server/test-mcp.js +108 -0
- package/mcp-server/test-tools.js +36 -0
- package/mcp-server/tier-auth.js +147 -0
- package/mcp-server/tools/index.js +72 -0
- package/mcp-server/tools-reorganized.ts +244 -0
- package/mcp-server/truth-context.js +581 -0
- package/mcp-server/truth-firewall-tools.js +1500 -0
- package/mcp-server/vibecheck-2.0-tools.js +748 -0
- package/mcp-server/vibecheck-tools.js +1075 -0
- package/package.json +10 -8
- package/bin/guardrail.js +0 -834
- package/bin/runners/runAudit.js +0 -2
- package/bin/runners/runAutopilot.js +0 -2
- package/bin/runners/runCertify.js +0 -2
- package/bin/runners/runDashboard.js +0 -10
- package/bin/runners/runEnhancedShip.js +0 -2
- package/bin/runners/runFixPacks.js +0 -2
- package/bin/runners/runNaturalLanguage.js +0 -3
- package/bin/runners/runProof.js +0 -2
- package/bin/runners/runRealitySniff.js +0 -2
- package/bin/runners/runUpgrade.js +0 -2
- package/bin/runners/runVerifyAgentOutput.js +0 -2
|
@@ -0,0 +1,2092 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* vibecheck MCP Server v2.0 - Clean Product Surface
|
|
5
|
+
*
|
|
6
|
+
* 6 Public Tools (maps to CLI):
|
|
7
|
+
* vibecheck.scan - Find truth
|
|
8
|
+
* vibecheck.gate - Enforce truth in CI
|
|
9
|
+
* vibecheck.fix - Apply safe patches
|
|
10
|
+
* vibecheck.proof - Premium verification (mocks, reality)
|
|
11
|
+
* vibecheck.report - Access artifacts
|
|
12
|
+
* vibecheck.status - Health and config info
|
|
13
|
+
*
|
|
14
|
+
* Everything else is parameters on these tools.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import {
|
|
20
|
+
CallToolRequestSchema,
|
|
21
|
+
ListToolsRequestSchema,
|
|
22
|
+
ListResourcesRequestSchema,
|
|
23
|
+
ReadResourceRequestSchema,
|
|
24
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
25
|
+
|
|
26
|
+
import fs from "fs/promises";
|
|
27
|
+
import path from "path";
|
|
28
|
+
import { fileURLToPath } from "url";
|
|
29
|
+
import { execSync } from "child_process";
|
|
30
|
+
|
|
31
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
32
|
+
const __dirname = path.dirname(__filename);
|
|
33
|
+
|
|
34
|
+
const VERSION = "2.1.0";
|
|
35
|
+
|
|
36
|
+
// Import intelligence tools
|
|
37
|
+
import {
|
|
38
|
+
INTELLIGENCE_TOOLS,
|
|
39
|
+
handleIntelligenceTool,
|
|
40
|
+
} from "./intelligence-tools.js";
|
|
41
|
+
|
|
42
|
+
// Import AI vibecheck tools
|
|
43
|
+
import {
|
|
44
|
+
VIBECHECK_TOOLS,
|
|
45
|
+
handleVibecheckTool,
|
|
46
|
+
} from "./vibecheck-tools.js";
|
|
47
|
+
|
|
48
|
+
// Import agent checkpoint tools
|
|
49
|
+
import {
|
|
50
|
+
AGENT_CHECKPOINT_TOOLS,
|
|
51
|
+
handleCheckpointTool,
|
|
52
|
+
} from "./agent-checkpoint.js";
|
|
53
|
+
|
|
54
|
+
// Import architect tools
|
|
55
|
+
import {
|
|
56
|
+
ARCHITECT_TOOLS,
|
|
57
|
+
handleArchitectTool,
|
|
58
|
+
} from "./architect-tools.js";
|
|
59
|
+
|
|
60
|
+
// Import codebase architect tools
|
|
61
|
+
import {
|
|
62
|
+
CODEBASE_ARCHITECT_TOOLS,
|
|
63
|
+
handleCodebaseArchitectTool,
|
|
64
|
+
} from "./codebase-architect-tools.js";
|
|
65
|
+
|
|
66
|
+
// Import vibecheck 2.0 tools
|
|
67
|
+
import {
|
|
68
|
+
VIBECHECK_2_TOOLS,
|
|
69
|
+
handleVibecheck2Tool,
|
|
70
|
+
} from "./vibecheck-2.0-tools.js";
|
|
71
|
+
|
|
72
|
+
// Import intent drift tools
|
|
73
|
+
import {
|
|
74
|
+
intentDriftTools,
|
|
75
|
+
} from "./intent-drift-tools.js";
|
|
76
|
+
|
|
77
|
+
// Import audit trail for MCP
|
|
78
|
+
import { emitToolInvoke, emitToolComplete } from "./audit-mcp.js";
|
|
79
|
+
|
|
80
|
+
// Import MDC generator
|
|
81
|
+
import { mdcGeneratorTool, handleMDCGeneration } from "./mdc-generator.js";
|
|
82
|
+
|
|
83
|
+
// Import Truth Context tools (Evidence Pack / Truth Pack)
|
|
84
|
+
import { TRUTH_CONTEXT_TOOLS, handleTruthContextTool } from "./truth-context.js";
|
|
85
|
+
|
|
86
|
+
// Import Truth Firewall tools (Hallucination Stopper)
|
|
87
|
+
import { TRUTH_FIREWALL_TOOLS, handleTruthFirewallTool } from "./truth-firewall-tools.js";
|
|
88
|
+
|
|
89
|
+
// Import Consolidated Tools (15 focused tools - recommended surface)
|
|
90
|
+
import { CONSOLIDATED_TOOLS, handleConsolidatedTool } from "./consolidated-tools.js";
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// TOOL DEFINITIONS - Public Tools (Clean Product Surface)
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
// RECOMMENDED: Use consolidated tools (15 focused, evidence-backed tools)
|
|
97
|
+
// These map directly to CLI commands and return file/line citations
|
|
98
|
+
const USE_CONSOLIDATED_TOOLS = process.env.VIBECHECK_MCP_CONSOLIDATED !== 'false';
|
|
99
|
+
|
|
100
|
+
const TOOLS = USE_CONSOLIDATED_TOOLS ? [
|
|
101
|
+
// 15 Consolidated Tools - recommended for new integrations
|
|
102
|
+
...CONSOLIDATED_TOOLS,
|
|
103
|
+
// Keep Truth Firewall for backward compatibility
|
|
104
|
+
...TRUTH_FIREWALL_TOOLS,
|
|
105
|
+
] : [
|
|
106
|
+
// Legacy: Full tool set (50+ tools) - for backward compatibility
|
|
107
|
+
// PRIORITY: Truth Firewall tools (Hallucination Stopper) - agents MUST use these
|
|
108
|
+
...TRUTH_FIREWALL_TOOLS, // vibecheck.get_truthpack, vibecheck.validate_claim, vibecheck.compile_context, etc.
|
|
109
|
+
|
|
110
|
+
// Truth Context tools (Evidence-Backed AI)
|
|
111
|
+
...TRUTH_CONTEXT_TOOLS, // vibecheck.ctx, vibecheck.verify_claim, vibecheck.evidence
|
|
112
|
+
|
|
113
|
+
...INTELLIGENCE_TOOLS, // Add all intelligence suite tools
|
|
114
|
+
...VIBECHECK_TOOLS, // Add AI vibecheck tools (verify, quality, smells, etc.)
|
|
115
|
+
...AGENT_CHECKPOINT_TOOLS, // Add agent checkpoint tools
|
|
116
|
+
...ARCHITECT_TOOLS, // Add architect review/suggest tools
|
|
117
|
+
...CODEBASE_ARCHITECT_TOOLS, // Add codebase-aware architect tools
|
|
118
|
+
...VIBECHECK_2_TOOLS, // Add vibecheck 2.0 consolidated tools
|
|
119
|
+
...intentDriftTools, // Add intent drift guard tools
|
|
120
|
+
mdcGeneratorTool, // Add MDC generator tool
|
|
121
|
+
// 1. SHIP - Quick health check (vibe coder friendly)
|
|
122
|
+
{
|
|
123
|
+
name: "vibecheck.ship",
|
|
124
|
+
description:
|
|
125
|
+
"🚀 Quick health check — 'Is my app ready?' Plain English, traffic light score",
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {
|
|
129
|
+
projectPath: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Path to project root",
|
|
132
|
+
default: ".",
|
|
133
|
+
},
|
|
134
|
+
fix: {
|
|
135
|
+
type: "boolean",
|
|
136
|
+
description: "Auto-fix problems where possible",
|
|
137
|
+
default: false,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// 2. SCAN - Deep technical analysis
|
|
144
|
+
{
|
|
145
|
+
name: "vibecheck.scan",
|
|
146
|
+
description:
|
|
147
|
+
"🔍 Deep scan — technical analysis of secrets, auth, mocks, routes (detailed output)",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
projectPath: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description: "Path to project root",
|
|
154
|
+
default: ".",
|
|
155
|
+
},
|
|
156
|
+
profile: {
|
|
157
|
+
type: "string",
|
|
158
|
+
enum: ["quick", "full", "ship", "ci", "security", "compliance", "ai"],
|
|
159
|
+
description:
|
|
160
|
+
"Check profile: quick, full, ship, ci, security, compliance, ai",
|
|
161
|
+
default: "quick",
|
|
162
|
+
},
|
|
163
|
+
only: {
|
|
164
|
+
type: "array",
|
|
165
|
+
items: { type: "string" },
|
|
166
|
+
description:
|
|
167
|
+
"Run only specific checks: integrity, security, hygiene, contracts, auth, routes, mocks, compliance, ai",
|
|
168
|
+
},
|
|
169
|
+
format: {
|
|
170
|
+
type: "string",
|
|
171
|
+
enum: ["text", "json", "html", "sarif"],
|
|
172
|
+
description: "Output format",
|
|
173
|
+
default: "text",
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
// 3. VERIFY - Runtime verification with Playwright
|
|
180
|
+
{
|
|
181
|
+
name: "vibecheck.verify",
|
|
182
|
+
description:
|
|
183
|
+
"🧪 Runtime Verify — clicks buttons, fills forms, finds Dead UI with Playwright",
|
|
184
|
+
inputSchema: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
url: {
|
|
188
|
+
type: "string",
|
|
189
|
+
description: "Target URL to test (required)",
|
|
190
|
+
},
|
|
191
|
+
auth: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "Auth credentials (email:password)",
|
|
194
|
+
},
|
|
195
|
+
flows: {
|
|
196
|
+
type: "array",
|
|
197
|
+
items: { type: "string" },
|
|
198
|
+
description: "Flow packs to test: auth, ui, forms, billing",
|
|
199
|
+
},
|
|
200
|
+
headed: {
|
|
201
|
+
type: "boolean",
|
|
202
|
+
description: "Run browser in visible mode",
|
|
203
|
+
default: false,
|
|
204
|
+
},
|
|
205
|
+
record: {
|
|
206
|
+
type: "boolean",
|
|
207
|
+
description: "Record video of test run",
|
|
208
|
+
default: false,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
required: ["url"],
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
// 3b. REALITY v2 - Two-Pass Auth Verification + Dead UI Crawler
|
|
216
|
+
{
|
|
217
|
+
name: "vibecheck.reality",
|
|
218
|
+
description:
|
|
219
|
+
"🧪 Reality Mode v2 — Two-pass auth verification: crawl anon, then auth. Finds Dead UI, HTTP errors, auth coverage gaps.",
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
url: {
|
|
224
|
+
type: "string",
|
|
225
|
+
description: "Target URL to test (required)",
|
|
226
|
+
},
|
|
227
|
+
auth: {
|
|
228
|
+
type: "string",
|
|
229
|
+
description: "Auth credentials (email:password) for login attempt",
|
|
230
|
+
},
|
|
231
|
+
verifyAuth: {
|
|
232
|
+
type: "boolean",
|
|
233
|
+
description: "Enable two-pass auth verification (anon + auth)",
|
|
234
|
+
default: false,
|
|
235
|
+
},
|
|
236
|
+
storageState: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Path to Playwright storageState.json for pre-authenticated session",
|
|
239
|
+
},
|
|
240
|
+
saveStorageState: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Path to save storageState after successful login",
|
|
243
|
+
},
|
|
244
|
+
truthpack: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "Path to truthpack.json for auth matcher verification",
|
|
247
|
+
},
|
|
248
|
+
headed: {
|
|
249
|
+
type: "boolean",
|
|
250
|
+
description: "Run browser in visible mode",
|
|
251
|
+
default: false,
|
|
252
|
+
},
|
|
253
|
+
maxPages: {
|
|
254
|
+
type: "number",
|
|
255
|
+
description: "Max pages to visit per pass (default: 18)",
|
|
256
|
+
default: 18,
|
|
257
|
+
},
|
|
258
|
+
maxDepth: {
|
|
259
|
+
type: "number",
|
|
260
|
+
description: "Max link depth to crawl (default: 2)",
|
|
261
|
+
default: 2,
|
|
262
|
+
},
|
|
263
|
+
danger: {
|
|
264
|
+
type: "boolean",
|
|
265
|
+
description: "Allow clicking risky buttons (delete, cancel, etc.)",
|
|
266
|
+
default: false,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
required: ["url"],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// 4. AI-TEST - AI Agent testing
|
|
274
|
+
{
|
|
275
|
+
name: "vibecheckai.dev-test",
|
|
276
|
+
description:
|
|
277
|
+
"🤖 AI Agent — autonomous testing that explores your app and generates fix prompts",
|
|
278
|
+
inputSchema: {
|
|
279
|
+
type: "object",
|
|
280
|
+
properties: {
|
|
281
|
+
url: {
|
|
282
|
+
type: "string",
|
|
283
|
+
description: "Target URL to test (required)",
|
|
284
|
+
},
|
|
285
|
+
goal: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "Natural language goal for the AI agent",
|
|
288
|
+
default: "Test all features and find issues",
|
|
289
|
+
},
|
|
290
|
+
headed: {
|
|
291
|
+
type: "boolean",
|
|
292
|
+
description: "Run browser in visible mode",
|
|
293
|
+
default: false,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
required: ["url"],
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
// 3. GATE - Enforce truth in CI
|
|
301
|
+
{
|
|
302
|
+
name: "vibecheck.gate",
|
|
303
|
+
description: "🚦 Enforce truth in CI — fail builds on policy violations",
|
|
304
|
+
inputSchema: {
|
|
305
|
+
type: "object",
|
|
306
|
+
properties: {
|
|
307
|
+
projectPath: {
|
|
308
|
+
type: "string",
|
|
309
|
+
default: ".",
|
|
310
|
+
},
|
|
311
|
+
policy: {
|
|
312
|
+
type: "string",
|
|
313
|
+
enum: ["default", "strict", "ci"],
|
|
314
|
+
description: "Policy strictness level",
|
|
315
|
+
default: "strict",
|
|
316
|
+
},
|
|
317
|
+
sarif: {
|
|
318
|
+
type: "boolean",
|
|
319
|
+
description: "Generate SARIF for GitHub Code Scanning",
|
|
320
|
+
default: true,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
// 5. FIX - Fix Missions v1
|
|
327
|
+
{
|
|
328
|
+
name: "vibecheck.fix",
|
|
329
|
+
description:
|
|
330
|
+
"🔧 Fix Missions v1 — AI-powered surgical fixes with proof verification loop",
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: "object",
|
|
333
|
+
properties: {
|
|
334
|
+
projectPath: {
|
|
335
|
+
type: "string",
|
|
336
|
+
default: ".",
|
|
337
|
+
},
|
|
338
|
+
promptOnly: {
|
|
339
|
+
type: "boolean",
|
|
340
|
+
description: "Generate mission prompts only (no edits)",
|
|
341
|
+
default: false,
|
|
342
|
+
},
|
|
343
|
+
apply: {
|
|
344
|
+
type: "boolean",
|
|
345
|
+
description: "Apply patches returned by the model",
|
|
346
|
+
default: false,
|
|
347
|
+
},
|
|
348
|
+
autopilot: {
|
|
349
|
+
type: "boolean",
|
|
350
|
+
description: "Loop: fix → verify → fix until SHIP or stuck",
|
|
351
|
+
default: false,
|
|
352
|
+
},
|
|
353
|
+
share: {
|
|
354
|
+
type: "boolean",
|
|
355
|
+
description: "Generate share bundle for review",
|
|
356
|
+
default: false,
|
|
357
|
+
},
|
|
358
|
+
maxMissions: {
|
|
359
|
+
type: "number",
|
|
360
|
+
description: "Max missions to plan (default: 8)",
|
|
361
|
+
default: 8,
|
|
362
|
+
},
|
|
363
|
+
maxSteps: {
|
|
364
|
+
type: "number",
|
|
365
|
+
description: "Max autopilot steps (default: 10)",
|
|
366
|
+
default: 10,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
// 6. SHARE - Generate share bundle from fix missions
|
|
373
|
+
{
|
|
374
|
+
name: "vibecheck.share",
|
|
375
|
+
description: "📦 Share Bundle — generate PR comment / review bundle from latest fix missions",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
type: "object",
|
|
378
|
+
properties: {
|
|
379
|
+
projectPath: {
|
|
380
|
+
type: "string",
|
|
381
|
+
default: ".",
|
|
382
|
+
},
|
|
383
|
+
prComment: {
|
|
384
|
+
type: "boolean",
|
|
385
|
+
description: "Output GitHub PR comment format",
|
|
386
|
+
default: false,
|
|
387
|
+
},
|
|
388
|
+
out: {
|
|
389
|
+
type: "string",
|
|
390
|
+
description: "Write output to file path",
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
// 7. PROVE - One Command Reality Proof (orchestrates ctx → reality → ship → fix)
|
|
397
|
+
{
|
|
398
|
+
name: "vibecheck.prove",
|
|
399
|
+
description: "🔬 One Command Reality Proof — orchestrates ctx → reality → ship → fix loop until SHIP or stuck",
|
|
400
|
+
inputSchema: {
|
|
401
|
+
type: "object",
|
|
402
|
+
properties: {
|
|
403
|
+
projectPath: {
|
|
404
|
+
type: "string",
|
|
405
|
+
default: ".",
|
|
406
|
+
},
|
|
407
|
+
url: {
|
|
408
|
+
type: "string",
|
|
409
|
+
description: "Base URL for runtime testing",
|
|
410
|
+
},
|
|
411
|
+
auth: {
|
|
412
|
+
type: "string",
|
|
413
|
+
description: "Auth credentials (email:password)",
|
|
414
|
+
},
|
|
415
|
+
storageState: {
|
|
416
|
+
type: "string",
|
|
417
|
+
description: "Path to Playwright storageState.json",
|
|
418
|
+
},
|
|
419
|
+
maxFixRounds: {
|
|
420
|
+
type: "number",
|
|
421
|
+
description: "Max auto-fix attempts (default: 3)",
|
|
422
|
+
default: 3,
|
|
423
|
+
},
|
|
424
|
+
skipReality: {
|
|
425
|
+
type: "boolean",
|
|
426
|
+
description: "Skip runtime crawling (static only)",
|
|
427
|
+
default: false,
|
|
428
|
+
},
|
|
429
|
+
skipFix: {
|
|
430
|
+
type: "boolean",
|
|
431
|
+
description: "Don't auto-fix, just diagnose",
|
|
432
|
+
default: false,
|
|
433
|
+
},
|
|
434
|
+
headed: {
|
|
435
|
+
type: "boolean",
|
|
436
|
+
description: "Run browser in visible mode",
|
|
437
|
+
default: false,
|
|
438
|
+
},
|
|
439
|
+
danger: {
|
|
440
|
+
type: "boolean",
|
|
441
|
+
description: "Allow clicking risky buttons",
|
|
442
|
+
default: false,
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
// 8. CTX - Truth Pack Generator
|
|
449
|
+
{
|
|
450
|
+
name: "vibecheck.ctx",
|
|
451
|
+
description: "📦 Truth Pack — generate ground truth for AI agents (routes, env, auth, billing)",
|
|
452
|
+
inputSchema: {
|
|
453
|
+
type: "object",
|
|
454
|
+
properties: {
|
|
455
|
+
projectPath: {
|
|
456
|
+
type: "string",
|
|
457
|
+
default: ".",
|
|
458
|
+
},
|
|
459
|
+
snapshot: {
|
|
460
|
+
type: "boolean",
|
|
461
|
+
description: "Save timestamped snapshot",
|
|
462
|
+
default: false,
|
|
463
|
+
},
|
|
464
|
+
json: {
|
|
465
|
+
type: "boolean",
|
|
466
|
+
description: "Output raw JSON",
|
|
467
|
+
default: false,
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
// 9. PROOF - Premium verification
|
|
474
|
+
{
|
|
475
|
+
name: "vibecheck.proof",
|
|
476
|
+
description:
|
|
477
|
+
"🎬 Premium verification — mocks (static) or reality (runtime with Playwright)",
|
|
478
|
+
inputSchema: {
|
|
479
|
+
type: "object",
|
|
480
|
+
properties: {
|
|
481
|
+
projectPath: {
|
|
482
|
+
type: "string",
|
|
483
|
+
default: ".",
|
|
484
|
+
},
|
|
485
|
+
mode: {
|
|
486
|
+
type: "string",
|
|
487
|
+
enum: ["mocks", "reality"],
|
|
488
|
+
description:
|
|
489
|
+
"Proof mode: mocks (import graph + fake domains) or reality (Playwright runtime)",
|
|
490
|
+
},
|
|
491
|
+
url: {
|
|
492
|
+
type: "string",
|
|
493
|
+
description: "Base URL for reality mode",
|
|
494
|
+
default: "http://localhost:3000",
|
|
495
|
+
},
|
|
496
|
+
flow: {
|
|
497
|
+
type: "string",
|
|
498
|
+
enum: ["auth", "checkout", "dashboard"],
|
|
499
|
+
description: "Flow to test in reality mode",
|
|
500
|
+
default: "auth",
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
required: ["mode"],
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
// 5. REPORT - Access artifacts
|
|
508
|
+
{
|
|
509
|
+
name: "vibecheck.validate",
|
|
510
|
+
description:
|
|
511
|
+
"🤖 Validate AI-generated code. Checks for hallucinations, intent mismatch, and quality issues.",
|
|
512
|
+
inputSchema: {
|
|
513
|
+
type: "object",
|
|
514
|
+
properties: {
|
|
515
|
+
code: { type: "string", description: "The code content to validate" },
|
|
516
|
+
intent: {
|
|
517
|
+
type: "string",
|
|
518
|
+
description: "The user's original request/intent",
|
|
519
|
+
},
|
|
520
|
+
projectPath: { type: "string", default: "." },
|
|
521
|
+
},
|
|
522
|
+
required: ["code"],
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
// 10. REPORT - Access scan artifacts
|
|
526
|
+
{
|
|
527
|
+
name: "vibecheck.report",
|
|
528
|
+
description:
|
|
529
|
+
"📄 Access scan artifacts — summary, full report, SARIF export",
|
|
530
|
+
inputSchema: {
|
|
531
|
+
type: "object",
|
|
532
|
+
properties: {
|
|
533
|
+
projectPath: {
|
|
534
|
+
type: "string",
|
|
535
|
+
default: ".",
|
|
536
|
+
},
|
|
537
|
+
type: {
|
|
538
|
+
type: "string",
|
|
539
|
+
enum: ["summary", "full", "sarif", "html"],
|
|
540
|
+
description: "Report type to retrieve",
|
|
541
|
+
default: "summary",
|
|
542
|
+
},
|
|
543
|
+
runId: {
|
|
544
|
+
type: "string",
|
|
545
|
+
description: "Specific run ID (defaults to last run)",
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
// 11. STATUS - Health and config
|
|
552
|
+
{
|
|
553
|
+
name: "vibecheck.status",
|
|
554
|
+
description: "📊 Server status — health, versions, config, last run info",
|
|
555
|
+
inputSchema: {
|
|
556
|
+
type: "object",
|
|
557
|
+
properties: {
|
|
558
|
+
projectPath: {
|
|
559
|
+
type: "string",
|
|
560
|
+
default: ".",
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
// 12. AUTOPILOT - Continuous protection
|
|
567
|
+
{
|
|
568
|
+
name: "vibecheck.autopilot",
|
|
569
|
+
description:
|
|
570
|
+
"🤖 Autopilot — continuous protection with weekly reports, auto-PRs, deploy blocking",
|
|
571
|
+
inputSchema: {
|
|
572
|
+
type: "object",
|
|
573
|
+
properties: {
|
|
574
|
+
projectPath: {
|
|
575
|
+
type: "string",
|
|
576
|
+
default: ".",
|
|
577
|
+
},
|
|
578
|
+
action: {
|
|
579
|
+
type: "string",
|
|
580
|
+
enum: ["status", "enable", "disable", "digest"],
|
|
581
|
+
description: "Autopilot action",
|
|
582
|
+
default: "status",
|
|
583
|
+
},
|
|
584
|
+
slack: {
|
|
585
|
+
type: "string",
|
|
586
|
+
description: "Slack webhook URL for notifications",
|
|
587
|
+
},
|
|
588
|
+
email: {
|
|
589
|
+
type: "string",
|
|
590
|
+
description: "Email for weekly digest",
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
|
|
596
|
+
// 13. AUTOPILOT PLAN - Generate fix plan (Pro/Compliance)
|
|
597
|
+
{
|
|
598
|
+
name: "vibecheck.autopilot_plan",
|
|
599
|
+
description:
|
|
600
|
+
"🤖 Autopilot Plan — scan codebase, group issues into fix packs, estimate risk (Pro/Compliance)",
|
|
601
|
+
inputSchema: {
|
|
602
|
+
type: "object",
|
|
603
|
+
properties: {
|
|
604
|
+
projectPath: {
|
|
605
|
+
type: "string",
|
|
606
|
+
default: ".",
|
|
607
|
+
},
|
|
608
|
+
profile: {
|
|
609
|
+
type: "string",
|
|
610
|
+
enum: ["quick", "full", "ship", "ci"],
|
|
611
|
+
description: "Scan profile",
|
|
612
|
+
default: "ship",
|
|
613
|
+
},
|
|
614
|
+
maxFixes: {
|
|
615
|
+
type: "number",
|
|
616
|
+
description: "Max fixes per category",
|
|
617
|
+
default: 10,
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
|
|
623
|
+
// 14. AUTOPILOT APPLY - Apply fixes (Pro/Compliance)
|
|
624
|
+
{
|
|
625
|
+
name: "vibecheck.autopilot_apply",
|
|
626
|
+
description:
|
|
627
|
+
"🔧 Autopilot Apply — apply fix packs with verification, re-scan to confirm (Pro/Compliance)",
|
|
628
|
+
inputSchema: {
|
|
629
|
+
type: "object",
|
|
630
|
+
properties: {
|
|
631
|
+
projectPath: {
|
|
632
|
+
type: "string",
|
|
633
|
+
default: ".",
|
|
634
|
+
},
|
|
635
|
+
profile: {
|
|
636
|
+
type: "string",
|
|
637
|
+
enum: ["quick", "full", "ship", "ci"],
|
|
638
|
+
description: "Scan profile",
|
|
639
|
+
default: "ship",
|
|
640
|
+
},
|
|
641
|
+
maxFixes: {
|
|
642
|
+
type: "number",
|
|
643
|
+
description: "Max fixes per category",
|
|
644
|
+
default: 10,
|
|
645
|
+
},
|
|
646
|
+
verify: {
|
|
647
|
+
type: "boolean",
|
|
648
|
+
description: "Run verification after apply",
|
|
649
|
+
default: true,
|
|
650
|
+
},
|
|
651
|
+
dryRun: {
|
|
652
|
+
type: "boolean",
|
|
653
|
+
description: "Preview changes without applying",
|
|
654
|
+
default: false,
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
|
|
660
|
+
// 15. BADGE - Generate ship badge
|
|
661
|
+
{
|
|
662
|
+
name: "vibecheck.badge",
|
|
663
|
+
description:
|
|
664
|
+
"🏅 Ship Badge — generate a badge for README/PR showing scan status",
|
|
665
|
+
inputSchema: {
|
|
666
|
+
type: "object",
|
|
667
|
+
properties: {
|
|
668
|
+
projectPath: {
|
|
669
|
+
type: "string",
|
|
670
|
+
default: ".",
|
|
671
|
+
},
|
|
672
|
+
format: {
|
|
673
|
+
type: "string",
|
|
674
|
+
enum: ["svg", "md", "html"],
|
|
675
|
+
description: "Badge format",
|
|
676
|
+
default: "svg",
|
|
677
|
+
},
|
|
678
|
+
style: {
|
|
679
|
+
type: "string",
|
|
680
|
+
enum: ["flat", "flat-square"],
|
|
681
|
+
description: "Badge style",
|
|
682
|
+
default: "flat",
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
// 16. CONTEXT - AI Rules Generator
|
|
689
|
+
{
|
|
690
|
+
name: "vibecheck.context",
|
|
691
|
+
description:
|
|
692
|
+
"🧠 AI Context — generate rules files for Cursor, Windsurf, Copilot to understand your codebase",
|
|
693
|
+
inputSchema: {
|
|
694
|
+
type: "object",
|
|
695
|
+
properties: {
|
|
696
|
+
projectPath: {
|
|
697
|
+
type: "string",
|
|
698
|
+
description: "Path to project root",
|
|
699
|
+
default: ".",
|
|
700
|
+
},
|
|
701
|
+
platform: {
|
|
702
|
+
type: "string",
|
|
703
|
+
enum: ["all", "cursor", "windsurf", "copilot", "claude"],
|
|
704
|
+
description: "Target platform (default: all)",
|
|
705
|
+
default: "all",
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
];
|
|
711
|
+
|
|
712
|
+
// ============================================================================
|
|
713
|
+
// SERVER IMPLEMENTATION
|
|
714
|
+
// ============================================================================
|
|
715
|
+
|
|
716
|
+
class VibecheckMCP {
|
|
717
|
+
constructor() {
|
|
718
|
+
this.server = new Server(
|
|
719
|
+
{ name: "vibecheck", version: VERSION },
|
|
720
|
+
{ capabilities: { tools: {}, resources: {} } },
|
|
721
|
+
);
|
|
722
|
+
this.setupHandlers();
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
setupHandlers() {
|
|
726
|
+
// List tools
|
|
727
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
728
|
+
tools: TOOLS,
|
|
729
|
+
}));
|
|
730
|
+
|
|
731
|
+
// Call tool
|
|
732
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
733
|
+
const { name, arguments: args } = request.params;
|
|
734
|
+
const projectPath = path.resolve(args?.projectPath || ".");
|
|
735
|
+
const startTime = Date.now();
|
|
736
|
+
|
|
737
|
+
// Emit audit event for tool invocation start
|
|
738
|
+
emitToolInvoke(name, args, "success", { projectPath });
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
// Handle intelligence tools first
|
|
742
|
+
if (name.startsWith("vibecheck.intelligence.")) {
|
|
743
|
+
return await handleIntelligenceTool(name, args, __dirname);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Handle AI vibecheck tools (verify, quality, smells, hallucination, breaking, mdc, coverage)
|
|
747
|
+
if (["vibecheck.verify", "vibecheck.quality", "vibecheck.smells",
|
|
748
|
+
"vibecheck.hallucination", "vibecheck.breaking", "vibecheck.mdc",
|
|
749
|
+
"vibecheck.coverage"].includes(name)) {
|
|
750
|
+
const result = await handleVibecheckTool(name, args);
|
|
751
|
+
return {
|
|
752
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Handle agent checkpoint tools
|
|
757
|
+
if (["vibecheck_checkpoint", "vibecheck_set_strictness", "vibecheck_checkpoint_status"].includes(name)) {
|
|
758
|
+
return await handleCheckpointTool(name, args);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Handle architect tools
|
|
762
|
+
if (["vibecheck_architect_review", "vibecheck_architect_suggest",
|
|
763
|
+
"vibecheck_architect_patterns", "vibecheck_architect_set_strictness"].includes(name)) {
|
|
764
|
+
return await handleArchitectTool(name, args);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Handle codebase architect tools
|
|
768
|
+
if (["vibecheck_architect_context", "vibecheck_architect_guide",
|
|
769
|
+
"vibecheck_architect_validate", "vibecheck_architect_patterns",
|
|
770
|
+
"vibecheck_architect_dependencies"].includes(name)) {
|
|
771
|
+
return await handleCodebaseArchitectTool(name, args);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Handle vibecheck 2.0 tools
|
|
775
|
+
if (["checkpoint", "check", "ship", "fix", "status", "set_strictness"].includes(name)) {
|
|
776
|
+
return await handleVibecheck2Tool(name, args, __dirname);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Handle intent drift tools
|
|
780
|
+
if (name.startsWith("vibecheck_intent_")) {
|
|
781
|
+
const tool = intentDriftTools.find(t => t.name === name);
|
|
782
|
+
if (tool && tool.handler) {
|
|
783
|
+
const result = await tool.handler(args);
|
|
784
|
+
return {
|
|
785
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
switch (name) {
|
|
791
|
+
case "vibecheck.ship":
|
|
792
|
+
return await this.handleShip(projectPath, args);
|
|
793
|
+
case "vibecheck.scan":
|
|
794
|
+
return await this.handleScan(projectPath, args);
|
|
795
|
+
case "vibecheck.verify":
|
|
796
|
+
return await this.handleVerify(projectPath, args);
|
|
797
|
+
case "vibecheck.reality":
|
|
798
|
+
return await this.handleReality(projectPath, args);
|
|
799
|
+
case "vibecheckai.dev-test":
|
|
800
|
+
return await this.handleAITest(projectPath, args);
|
|
801
|
+
case "vibecheck.gate":
|
|
802
|
+
return await this.handleGate(projectPath, args);
|
|
803
|
+
case "vibecheck.fix":
|
|
804
|
+
return await this.handleFix(projectPath, args);
|
|
805
|
+
case "vibecheck.share":
|
|
806
|
+
return await this.handleShare(projectPath, args);
|
|
807
|
+
case "vibecheck.ctx":
|
|
808
|
+
return await this.handleCtx(projectPath, args);
|
|
809
|
+
case "vibecheck.prove":
|
|
810
|
+
return await this.handleProve(projectPath, args);
|
|
811
|
+
case "vibecheck.proof":
|
|
812
|
+
return await this.handleProof(projectPath, args);
|
|
813
|
+
case "vibecheck.validate":
|
|
814
|
+
return await this.handleValidate(projectPath, args);
|
|
815
|
+
case "vibecheck.report":
|
|
816
|
+
return await this.handleReport(projectPath, args);
|
|
817
|
+
case "vibecheck.status":
|
|
818
|
+
return await this.handleStatus(projectPath, args);
|
|
819
|
+
case "vibecheck.autopilot":
|
|
820
|
+
return await this.handleAutopilot(projectPath, args);
|
|
821
|
+
case "vibecheck.autopilot_plan":
|
|
822
|
+
return await this.handleAutopilotPlan(projectPath, args);
|
|
823
|
+
case "vibecheck.autopilot_apply":
|
|
824
|
+
return await this.handleAutopilotApply(projectPath, args);
|
|
825
|
+
case "vibecheck.badge":
|
|
826
|
+
return await this.handleBadge(projectPath, args);
|
|
827
|
+
case "vibecheck.context":
|
|
828
|
+
return await this.handleContext(projectPath, args);
|
|
829
|
+
case "generate_mdc":
|
|
830
|
+
return await handleMDCGeneration(args);
|
|
831
|
+
// Truth Context tools (Evidence Pack / Truth Pack)
|
|
832
|
+
case "vibecheck.verify_claim":
|
|
833
|
+
case "vibecheck.evidence":
|
|
834
|
+
return await handleTruthContextTool(name, args);
|
|
835
|
+
// Truth Firewall tools (Hallucination Stopper)
|
|
836
|
+
case "vibecheck.get_truthpack":
|
|
837
|
+
case "vibecheck.validate_claim":
|
|
838
|
+
case "vibecheck.compile_context":
|
|
839
|
+
case "vibecheck.search_evidence":
|
|
840
|
+
case "vibecheck.find_counterexamples":
|
|
841
|
+
case "vibecheck.propose_patch":
|
|
842
|
+
case "vibecheck.check_invariants":
|
|
843
|
+
case "vibecheck.add_assumption":
|
|
844
|
+
return await handleTruthFirewallTool(name, args, projectPath);
|
|
845
|
+
default:
|
|
846
|
+
return this.error(`Unknown tool: ${name}`);
|
|
847
|
+
}
|
|
848
|
+
} catch (err) {
|
|
849
|
+
// Emit audit event for tool error
|
|
850
|
+
emitToolComplete(name, "error", {
|
|
851
|
+
errorMessage: err.message,
|
|
852
|
+
durationMs: Date.now() - startTime
|
|
853
|
+
});
|
|
854
|
+
return this.error(`${name} failed: ${err.message}`);
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
// Resources
|
|
859
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
860
|
+
resources: [
|
|
861
|
+
{
|
|
862
|
+
uri: "vibecheck://config",
|
|
863
|
+
name: "vibecheck Config",
|
|
864
|
+
description: "Project vibecheck configuration",
|
|
865
|
+
mimeType: "application/json",
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
uri: "vibecheck://summary",
|
|
869
|
+
name: "Last Scan Summary",
|
|
870
|
+
description: "Most recent scan results and verdict",
|
|
871
|
+
mimeType: "application/json",
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
uri: "vibecheck://truthpack",
|
|
875
|
+
name: "Truth Pack",
|
|
876
|
+
description: "Ground truth: routes, env, auth, billing",
|
|
877
|
+
mimeType: "application/json",
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
uri: "vibecheck://missions",
|
|
881
|
+
name: "Fix Missions",
|
|
882
|
+
description: "Latest mission pack for AI-powered fixes",
|
|
883
|
+
mimeType: "application/json",
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
uri: "vibecheck://reality",
|
|
887
|
+
name: "Reality Results",
|
|
888
|
+
description: "Last runtime verification results",
|
|
889
|
+
mimeType: "application/json",
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
uri: "vibecheck://findings",
|
|
893
|
+
name: "All Findings",
|
|
894
|
+
description: "Complete list of detected issues",
|
|
895
|
+
mimeType: "application/json",
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
uri: "vibecheck://share",
|
|
899
|
+
name: "Share Pack",
|
|
900
|
+
description: "Latest fix missions share bundle for PR/review",
|
|
901
|
+
mimeType: "application/json",
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
uri: "vibecheck://prove",
|
|
905
|
+
name: "Prove Report",
|
|
906
|
+
description: "Last prove loop results (ctx → reality → ship → fix)",
|
|
907
|
+
mimeType: "application/json",
|
|
908
|
+
},
|
|
909
|
+
],
|
|
910
|
+
}));
|
|
911
|
+
|
|
912
|
+
this.server.setRequestHandler(
|
|
913
|
+
ReadResourceRequestSchema,
|
|
914
|
+
async (request) => {
|
|
915
|
+
const { uri } = request.params;
|
|
916
|
+
const projectPath = process.cwd();
|
|
917
|
+
|
|
918
|
+
if (uri === "vibecheck://config") {
|
|
919
|
+
const configPath = path.join(projectPath, "vibecheck.config.json");
|
|
920
|
+
try {
|
|
921
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
922
|
+
return {
|
|
923
|
+
contents: [{ uri, mimeType: "application/json", text: content }],
|
|
924
|
+
};
|
|
925
|
+
} catch {
|
|
926
|
+
return {
|
|
927
|
+
contents: [{ uri, mimeType: "application/json", text: "{}" }],
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (uri === "vibecheck://summary") {
|
|
933
|
+
const summaryPath = path.join(
|
|
934
|
+
projectPath,
|
|
935
|
+
".vibecheck",
|
|
936
|
+
"summary.json",
|
|
937
|
+
);
|
|
938
|
+
try {
|
|
939
|
+
const content = await fs.readFile(summaryPath, "utf-8");
|
|
940
|
+
return {
|
|
941
|
+
contents: [{ uri, mimeType: "application/json", text: content }],
|
|
942
|
+
};
|
|
943
|
+
} catch {
|
|
944
|
+
return {
|
|
945
|
+
contents: [
|
|
946
|
+
{
|
|
947
|
+
uri,
|
|
948
|
+
mimeType: "application/json",
|
|
949
|
+
text: '{"message": "No scan found. Run vibecheck.scan first."}',
|
|
950
|
+
},
|
|
951
|
+
],
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (uri === "vibecheck://truthpack") {
|
|
957
|
+
const truthpackPath = path.join(projectPath, ".vibecheck", "truth", "truthpack.json");
|
|
958
|
+
try {
|
|
959
|
+
const content = await fs.readFile(truthpackPath, "utf-8");
|
|
960
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
961
|
+
} catch {
|
|
962
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No truthpack. Run vibecheck.ctx first."}' }] };
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (uri === "vibecheck://missions") {
|
|
967
|
+
const missionsDir = path.join(projectPath, ".vibecheck", "missions");
|
|
968
|
+
try {
|
|
969
|
+
const dirs = await fs.readdir(missionsDir);
|
|
970
|
+
const latest = dirs.sort().reverse()[0];
|
|
971
|
+
if (latest) {
|
|
972
|
+
const missionPath = path.join(missionsDir, latest, "missions.json");
|
|
973
|
+
const content = await fs.readFile(missionPath, "utf-8");
|
|
974
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
975
|
+
}
|
|
976
|
+
} catch {}
|
|
977
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No missions. Run vibecheck.fix first."}' }] };
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (uri === "vibecheck://reality") {
|
|
981
|
+
const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
|
|
982
|
+
try {
|
|
983
|
+
const content = await fs.readFile(realityPath, "utf-8");
|
|
984
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
985
|
+
} catch {
|
|
986
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No reality results. Run vibecheck verify first."}' }] };
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
if (uri === "vibecheck://findings") {
|
|
991
|
+
const findingsPath = path.join(projectPath, ".vibecheck", "findings.json");
|
|
992
|
+
try {
|
|
993
|
+
const content = await fs.readFile(findingsPath, "utf-8");
|
|
994
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
995
|
+
} catch {
|
|
996
|
+
// Try summary.json as fallback
|
|
997
|
+
const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
|
|
998
|
+
try {
|
|
999
|
+
const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
|
|
1000
|
+
const findings = summary.findings || [];
|
|
1001
|
+
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ findings }, null, 2) }] };
|
|
1002
|
+
} catch {
|
|
1003
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No findings. Run vibecheck.scan first."}' }] };
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (uri === "vibecheck://share") {
|
|
1009
|
+
const missionsDir = path.join(projectPath, ".vibecheck", "missions");
|
|
1010
|
+
try {
|
|
1011
|
+
const dirs = await fs.readdir(missionsDir);
|
|
1012
|
+
const latest = dirs.sort().reverse()[0];
|
|
1013
|
+
if (latest) {
|
|
1014
|
+
const sharePath = path.join(missionsDir, latest, "share", "share.json");
|
|
1015
|
+
try {
|
|
1016
|
+
const content = await fs.readFile(sharePath, "utf-8");
|
|
1017
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1018
|
+
} catch {
|
|
1019
|
+
// Fallback to missions.json
|
|
1020
|
+
const missionPath = path.join(missionsDir, latest, "missions.json");
|
|
1021
|
+
const content = await fs.readFile(missionPath, "utf-8");
|
|
1022
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
} catch {}
|
|
1026
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No share pack. Run vibecheck.fix --share first."}' }] };
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
if (uri === "vibecheck://prove") {
|
|
1030
|
+
const provePath = path.join(projectPath, ".vibecheck", "prove", "last_prove.json");
|
|
1031
|
+
try {
|
|
1032
|
+
const content = await fs.readFile(provePath, "utf-8");
|
|
1033
|
+
return { contents: [{ uri, mimeType: "application/json", text: content }] };
|
|
1034
|
+
} catch {
|
|
1035
|
+
return { contents: [{ uri, mimeType: "application/json", text: '{"message": "No prove results. Run vibecheck.prove first."}' }] };
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
return { contents: [] };
|
|
1040
|
+
},
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Helpers
|
|
1045
|
+
success(text) {
|
|
1046
|
+
return { content: [{ type: "text", text }] };
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
error(text) {
|
|
1050
|
+
return { content: [{ type: "text", text: `❌ ${text}` }], isError: true };
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Validate project path exists and is accessible
|
|
1054
|
+
validateProjectPath(projectPath) {
|
|
1055
|
+
try {
|
|
1056
|
+
const stats = require("fs").statSync(projectPath);
|
|
1057
|
+
if (!stats.isDirectory()) {
|
|
1058
|
+
return { valid: false, error: `Path is not a directory: ${projectPath}` };
|
|
1059
|
+
}
|
|
1060
|
+
return { valid: true };
|
|
1061
|
+
} catch (e) {
|
|
1062
|
+
return { valid: false, error: `Cannot access path: ${projectPath} (${e.code || e.message})` };
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// ============================================================================
|
|
1067
|
+
// SCAN
|
|
1068
|
+
// ============================================================================
|
|
1069
|
+
async handleScan(projectPath, args) {
|
|
1070
|
+
const profile = args?.profile || "quick";
|
|
1071
|
+
const format = args?.format || "text";
|
|
1072
|
+
const only = args?.only;
|
|
1073
|
+
|
|
1074
|
+
let output = "# 🔍 vibecheck Scan\n\n";
|
|
1075
|
+
output += `**Profile:** ${profile}\n`;
|
|
1076
|
+
output += `**Path:** ${projectPath}\n\n`;
|
|
1077
|
+
|
|
1078
|
+
try {
|
|
1079
|
+
// Build CLI command
|
|
1080
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" scan`;
|
|
1081
|
+
cmd += ` --profile=${profile}`;
|
|
1082
|
+
if (only?.length) cmd += ` --only=${only.join(",")}`;
|
|
1083
|
+
cmd += ` --json`;
|
|
1084
|
+
|
|
1085
|
+
const result = execSync(cmd, {
|
|
1086
|
+
cwd: projectPath,
|
|
1087
|
+
encoding: "utf8",
|
|
1088
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
// Read summary
|
|
1092
|
+
const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
|
|
1093
|
+
const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
|
|
1094
|
+
|
|
1095
|
+
output += `## Score: ${summary.score}/100 (${summary.grade})\n\n`;
|
|
1096
|
+
output += `**Verdict:** ${summary.canShip ? "✅ SHIP" : "🚫 NO-SHIP"}\n\n`;
|
|
1097
|
+
|
|
1098
|
+
if (summary.counts) {
|
|
1099
|
+
output += "### Checks\n\n";
|
|
1100
|
+
output += "| Category | Issues |\n|----------|--------|\n";
|
|
1101
|
+
for (const [key, count] of Object.entries(summary.counts)) {
|
|
1102
|
+
const icon = count === 0 ? "✅" : "⚠️";
|
|
1103
|
+
output += `| ${icon} ${key} | ${count} |\n`;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
output += `\n📄 **Report:** .vibecheck/report.html\n`;
|
|
1108
|
+
} catch (err) {
|
|
1109
|
+
output += `\n⚠️ Scan error: ${err.message}\n`;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
output += "\n---\n_Context Enhanced by vibecheck AI_\n";
|
|
1113
|
+
return this.success(output);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// ============================================================================
|
|
1117
|
+
// GATE
|
|
1118
|
+
// ============================================================================
|
|
1119
|
+
async handleGate(projectPath, args) {
|
|
1120
|
+
const policy = args?.policy || "strict";
|
|
1121
|
+
|
|
1122
|
+
let output = "# 🚦 vibecheck Gate\n\n";
|
|
1123
|
+
output += `**Policy:** ${policy}\n\n`;
|
|
1124
|
+
|
|
1125
|
+
try {
|
|
1126
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" gate`;
|
|
1127
|
+
cmd += ` --policy=${policy}`;
|
|
1128
|
+
if (args?.sarif) cmd += ` --sarif`;
|
|
1129
|
+
|
|
1130
|
+
execSync(cmd, {
|
|
1131
|
+
cwd: projectPath,
|
|
1132
|
+
encoding: "utf8",
|
|
1133
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
output += "## ✅ GATE PASSED\n\n";
|
|
1137
|
+
output += "All checks passed. Clear to merge.\n";
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
output += "## 🚫 GATE FAILED\n\n";
|
|
1140
|
+
output += "Build blocked. Fix the issues and re-run.\n\n";
|
|
1141
|
+
output += `Run \`vibecheck fix --plan\` to see recommended fixes.\n`;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
return this.success(output);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// ============================================================================
|
|
1148
|
+
// FIX MISSIONS v1
|
|
1149
|
+
// ============================================================================
|
|
1150
|
+
async handleFix(projectPath, args) {
|
|
1151
|
+
const mode = args?.autopilot ? "Autopilot" :
|
|
1152
|
+
args?.apply ? "Apply" :
|
|
1153
|
+
args?.promptOnly ? "Prompt Only" : "Plan";
|
|
1154
|
+
|
|
1155
|
+
let output = "# 🛠 vibecheck Fix Missions v1\n\n";
|
|
1156
|
+
output += `**Mode:** ${mode}\n`;
|
|
1157
|
+
output += `**Max Missions:** ${args?.maxMissions || 8}\n`;
|
|
1158
|
+
if (args?.autopilot) output += `**Max Steps:** ${args?.maxSteps || 10}\n`;
|
|
1159
|
+
output += "\n";
|
|
1160
|
+
|
|
1161
|
+
try {
|
|
1162
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" fix`;
|
|
1163
|
+
if (args?.promptOnly) cmd += ` --prompt-only`;
|
|
1164
|
+
if (args?.apply) cmd += ` --apply`;
|
|
1165
|
+
if (args?.autopilot) cmd += ` --autopilot`;
|
|
1166
|
+
if (args?.share) cmd += ` --share`;
|
|
1167
|
+
if (args?.maxMissions) cmd += ` --max-missions ${args.maxMissions}`;
|
|
1168
|
+
if (args?.maxSteps) cmd += ` --max-steps ${args.maxSteps}`;
|
|
1169
|
+
|
|
1170
|
+
const result = execSync(cmd, {
|
|
1171
|
+
cwd: projectPath,
|
|
1172
|
+
encoding: "utf8",
|
|
1173
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1174
|
+
timeout: 300000, // 5 min timeout for autopilot
|
|
1175
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
|
|
1179
|
+
|
|
1180
|
+
// Read mission pack if available
|
|
1181
|
+
const missionsDir = path.join(projectPath, ".vibecheck", "missions");
|
|
1182
|
+
try {
|
|
1183
|
+
const dirs = await fs.readdir(missionsDir);
|
|
1184
|
+
const latest = dirs.sort().reverse()[0];
|
|
1185
|
+
if (latest) {
|
|
1186
|
+
const missionPath = path.join(missionsDir, latest, "missions.json");
|
|
1187
|
+
const missions = JSON.parse(await fs.readFile(missionPath, "utf-8"));
|
|
1188
|
+
|
|
1189
|
+
output += "\n## Planned Missions\n\n";
|
|
1190
|
+
output += "| # | Mission | Target Findings |\n|---|---------|----------------|\n";
|
|
1191
|
+
for (let i = 0; i < missions.missions.length; i++) {
|
|
1192
|
+
const m = missions.missions[i];
|
|
1193
|
+
output += `| ${i + 1} | ${m.title} | ${m.targetFindingIds.length} |\n`;
|
|
1194
|
+
}
|
|
1195
|
+
output += `\n📁 **Mission Pack:** .vibecheck/missions/${latest}\n`;
|
|
1196
|
+
}
|
|
1197
|
+
} catch {}
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
output += `\n⚠️ Fix error: ${err.message}\n`;
|
|
1200
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
output += "\n---\n_Fix Missions v1 — Reality Firewall Protected_\n";
|
|
1204
|
+
return this.success(output);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// ============================================================================
|
|
1208
|
+
// SHARE - Generate share bundle from fix missions
|
|
1209
|
+
// ============================================================================
|
|
1210
|
+
async handleShare(projectPath, args) {
|
|
1211
|
+
let output = "# 📦 vibecheck Share Bundle\n\n";
|
|
1212
|
+
|
|
1213
|
+
try {
|
|
1214
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" share`;
|
|
1215
|
+
if (args?.prComment) cmd += ` --pr-comment`;
|
|
1216
|
+
if (args?.out) cmd += ` --out "${args.out}"`;
|
|
1217
|
+
|
|
1218
|
+
const result = execSync(cmd, {
|
|
1219
|
+
cwd: projectPath,
|
|
1220
|
+
encoding: "utf8",
|
|
1221
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1222
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1226
|
+
|
|
1227
|
+
// Read share pack if available
|
|
1228
|
+
const missionsDir = path.join(projectPath, ".vibecheck", "missions");
|
|
1229
|
+
try {
|
|
1230
|
+
const dirs = await fs.readdir(missionsDir);
|
|
1231
|
+
const latest = dirs.sort().reverse()[0];
|
|
1232
|
+
if (latest) {
|
|
1233
|
+
const sharePath = path.join(missionsDir, latest, "share", "share.json");
|
|
1234
|
+
try {
|
|
1235
|
+
const share = JSON.parse(await fs.readFile(sharePath, "utf-8"));
|
|
1236
|
+
|
|
1237
|
+
output += "\n## Share Pack Summary\n\n";
|
|
1238
|
+
output += `| Metric | Value |\n|--------|-------|\n`;
|
|
1239
|
+
output += `| Mission Pack | ${latest} |\n`;
|
|
1240
|
+
output += `| Total Steps | ${share.timeline?.length || 0} |\n`;
|
|
1241
|
+
output += `| Final Verdict | ${share.finalVerdict || "Unknown"} |\n`;
|
|
1242
|
+
|
|
1243
|
+
if (share.timeline?.length > 0) {
|
|
1244
|
+
output += "\n## Timeline\n\n";
|
|
1245
|
+
output += "| Step | Mission | Before | After |\n|------|---------|--------|-------|\n";
|
|
1246
|
+
for (const t of share.timeline.slice(0, 10)) {
|
|
1247
|
+
output += `| ${t.step} | ${t.missionTitle || t.missionId} | ${t.beforeVerdict} | ${t.afterVerdict} |\n`;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
output += `\n📁 **Share Pack:** .vibecheck/missions/${latest}/share/\n`;
|
|
1252
|
+
output += `📄 **PR Comment:** .vibecheck/missions/${latest}/share/pr_comment.md\n`;
|
|
1253
|
+
} catch {}
|
|
1254
|
+
}
|
|
1255
|
+
} catch {}
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
output += `\n⚠️ Share error: ${err.message}\n`;
|
|
1258
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
output += "\n---\n_Fix Missions Share Bundle_\n";
|
|
1262
|
+
return this.success(output);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// ============================================================================
|
|
1266
|
+
// CTX - Truth Pack Generator
|
|
1267
|
+
// ============================================================================
|
|
1268
|
+
async handleCtx(projectPath, args) {
|
|
1269
|
+
let output = "# 📦 vibecheck Truth Pack\n\n";
|
|
1270
|
+
output += `**Path:** ${projectPath}\n\n`;
|
|
1271
|
+
|
|
1272
|
+
try {
|
|
1273
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ctx`;
|
|
1274
|
+
if (args?.snapshot) cmd += ` --snapshot`;
|
|
1275
|
+
if (args?.json) cmd += ` --json`;
|
|
1276
|
+
|
|
1277
|
+
const result = execSync(cmd, {
|
|
1278
|
+
cwd: projectPath,
|
|
1279
|
+
encoding: "utf8",
|
|
1280
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1281
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
if (args?.json) {
|
|
1285
|
+
// Return raw JSON
|
|
1286
|
+
output = result;
|
|
1287
|
+
return this.success(output);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
|
|
1291
|
+
|
|
1292
|
+
// Read truthpack summary
|
|
1293
|
+
const truthpackPath = path.join(projectPath, ".vibecheck", "truth", "truthpack.json");
|
|
1294
|
+
try {
|
|
1295
|
+
const truthpack = JSON.parse(await fs.readFile(truthpackPath, "utf-8"));
|
|
1296
|
+
|
|
1297
|
+
output += "\n## Truth Pack Contents\n\n";
|
|
1298
|
+
output += `| Category | Count |\n|----------|-------|\n`;
|
|
1299
|
+
output += `| Server Routes | ${truthpack.routes?.server?.length || 0} |\n`;
|
|
1300
|
+
output += `| Client Refs | ${truthpack.routes?.clientRefs?.length || 0} |\n`;
|
|
1301
|
+
output += `| Route Gaps | ${truthpack.routes?.gaps?.length || 0} |\n`;
|
|
1302
|
+
output += `| Env Used | ${truthpack.env?.vars?.length || 0} |\n`;
|
|
1303
|
+
output += `| Env Declared | ${truthpack.env?.declared?.length || 0} |\n`;
|
|
1304
|
+
output += `| Auth Middleware | ${truthpack.auth?.nextMiddleware?.length || 0} |\n`;
|
|
1305
|
+
output += `| Stripe Detected | ${truthpack.billing?.hasStripe ? "✅" : "❌"} |\n`;
|
|
1306
|
+
output += `| Webhooks | ${truthpack.billing?.summary?.webhookHandlersFound || 0} |\n`;
|
|
1307
|
+
|
|
1308
|
+
output += `\n📁 **Saved:** .vibecheck/truth/truthpack.json\n`;
|
|
1309
|
+
} catch {}
|
|
1310
|
+
} catch (err) {
|
|
1311
|
+
output += `\n⚠️ Truth pack error: ${err.message}\n`;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
output += "\n---\n_Ground Truth for AI Agents_\n";
|
|
1315
|
+
return this.success(output);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// ============================================================================
|
|
1319
|
+
// PROVE - One Command Reality Proof
|
|
1320
|
+
// ============================================================================
|
|
1321
|
+
async handleProve(projectPath, args) {
|
|
1322
|
+
let output = "# 🔬 vibecheck prove\n\n";
|
|
1323
|
+
output += `**URL:** ${args?.url || "(static only)"}\n`;
|
|
1324
|
+
output += `**Max Fix Rounds:** ${args?.maxFixRounds || 3}\n\n`;
|
|
1325
|
+
|
|
1326
|
+
try {
|
|
1327
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" prove`;
|
|
1328
|
+
if (args?.url) cmd += ` --url ${args.url}`;
|
|
1329
|
+
if (args?.auth) cmd += ` --auth ${args.auth}`;
|
|
1330
|
+
if (args?.storageState) cmd += ` --storage-state ${args.storageState}`;
|
|
1331
|
+
if (args?.skipReality) cmd += ` --skip-reality`;
|
|
1332
|
+
if (args?.skipFix) cmd += ` --skip-fix`;
|
|
1333
|
+
if (args?.maxFixRounds) cmd += ` --max-fix-rounds ${args.maxFixRounds}`;
|
|
1334
|
+
|
|
1335
|
+
const result = execSync(cmd, {
|
|
1336
|
+
cwd: projectPath,
|
|
1337
|
+
encoding: "utf8",
|
|
1338
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1339
|
+
timeout: 600000, // 10 min timeout for full prove loop
|
|
1340
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1344
|
+
|
|
1345
|
+
// Read prove report
|
|
1346
|
+
const provePath = path.join(projectPath, ".vibecheck", "prove", "last_prove.json");
|
|
1347
|
+
try {
|
|
1348
|
+
const report = JSON.parse(await fs.readFile(provePath, "utf-8"));
|
|
1349
|
+
|
|
1350
|
+
output += "\n## Timeline\n\n";
|
|
1351
|
+
output += "| Step | Action | Status |\n|------|--------|--------|\n";
|
|
1352
|
+
for (const t of report.timeline || []) {
|
|
1353
|
+
output += `| ${t.step} | ${t.action} | ${t.status || t.verdict || "ok"} |\n`;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
output += `\n**Final Verdict:** ${report.finalVerdict}\n`;
|
|
1357
|
+
output += `**Duration:** ${report.meta?.durationSeconds}s\n`;
|
|
1358
|
+
} catch {}
|
|
1359
|
+
} catch (err) {
|
|
1360
|
+
output += `\n⚠️ Prove error: ${err.message}\n`;
|
|
1361
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
output += "\n---\n_One command to make it real_\n";
|
|
1365
|
+
return this.success(output);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// ============================================================================
|
|
1369
|
+
// PROOF
|
|
1370
|
+
// ============================================================================
|
|
1371
|
+
async handleProof(projectPath, args) {
|
|
1372
|
+
const mode = args?.mode;
|
|
1373
|
+
|
|
1374
|
+
if (!mode) {
|
|
1375
|
+
return this.error("Mode required: 'mocks' or 'reality'");
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
let output = `# 🎬 vibecheck Proof: ${mode.toUpperCase()}\n\n`;
|
|
1379
|
+
|
|
1380
|
+
try {
|
|
1381
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" proof ${mode}`;
|
|
1382
|
+
if (mode === "reality" && args?.url) cmd += ` --url=${args.url}`;
|
|
1383
|
+
if (mode === "reality" && args?.flow) cmd += ` --flow=${args.flow}`;
|
|
1384
|
+
|
|
1385
|
+
const result = execSync(cmd, {
|
|
1386
|
+
cwd: projectPath,
|
|
1387
|
+
encoding: "utf8",
|
|
1388
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1389
|
+
timeout: 120000, // 2 min timeout for reality mode
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
output += result;
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
if (mode === "mocks") {
|
|
1395
|
+
output += "## 🚫 MOCKPROOF: FAIL\n\n";
|
|
1396
|
+
output += "Mock/demo code detected in production paths.\n";
|
|
1397
|
+
} else {
|
|
1398
|
+
output += "## 🚫 REALITY MODE: FAIL\n\n";
|
|
1399
|
+
output += "Fake data or mock services detected at runtime.\n";
|
|
1400
|
+
}
|
|
1401
|
+
output += `\n${err.stdout || err.message}\n`;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
return this.success(output);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// ============================================================================
|
|
1408
|
+
// VALIDATE
|
|
1409
|
+
// ============================================================================
|
|
1410
|
+
async handleValidate(projectPath, args) {
|
|
1411
|
+
const { code, intent } = args;
|
|
1412
|
+
if (!code) return this.error("Code is required");
|
|
1413
|
+
|
|
1414
|
+
let output = "# 🤖 AI Code Validation\n\n";
|
|
1415
|
+
if (intent) output += `**Intent:** ${intent}\n\n`;
|
|
1416
|
+
|
|
1417
|
+
try {
|
|
1418
|
+
const {
|
|
1419
|
+
runHallucinationCheck,
|
|
1420
|
+
validateIntent,
|
|
1421
|
+
validateQuality,
|
|
1422
|
+
} = require(
|
|
1423
|
+
path.join(__dirname, "..", "bin", "runners", "lib", "ai-bridge.js"),
|
|
1424
|
+
);
|
|
1425
|
+
|
|
1426
|
+
// 1. Hallucinations (checking against project deps + internal logic)
|
|
1427
|
+
// Note: In MCP context, we might want to check the provided code specifically for imports.
|
|
1428
|
+
// The bridge's runHallucinationCheck mostly checks package.json.
|
|
1429
|
+
// But we can check imports in the 'code' snippet if we extract them.
|
|
1430
|
+
// The bridge handles extractImports internally but runHallucinationCheck doesn't expose it directly for a string input.
|
|
1431
|
+
// We will rely on package.json sanity check for now + static analysis of the snippet.
|
|
1432
|
+
|
|
1433
|
+
const hallResult = await runHallucinationCheck(projectPath);
|
|
1434
|
+
|
|
1435
|
+
// 2. Intent
|
|
1436
|
+
let intentResult = { score: 100, issues: [] };
|
|
1437
|
+
if (intent) {
|
|
1438
|
+
intentResult = validateIntent(code, intent);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// 3. Quality
|
|
1442
|
+
const qualityResult = validateQuality(code);
|
|
1443
|
+
|
|
1444
|
+
const allIssues = [
|
|
1445
|
+
...hallResult.issues,
|
|
1446
|
+
...intentResult.issues,
|
|
1447
|
+
...qualityResult.issues,
|
|
1448
|
+
];
|
|
1449
|
+
|
|
1450
|
+
const score = Math.round(
|
|
1451
|
+
(hallResult.score + intentResult.score + qualityResult.score) / 3,
|
|
1452
|
+
);
|
|
1453
|
+
const status = score >= 80 ? "✅ PASSED" : "⚠️ ISSUES FOUND";
|
|
1454
|
+
|
|
1455
|
+
output += `**Status:** ${status} (${score}/100)\n\n`;
|
|
1456
|
+
|
|
1457
|
+
if (allIssues.length > 0) {
|
|
1458
|
+
output += "### Issues\n";
|
|
1459
|
+
for (const issue of allIssues) {
|
|
1460
|
+
const icon =
|
|
1461
|
+
issue.severity === "critical"
|
|
1462
|
+
? "🔴"
|
|
1463
|
+
: issue.severity === "high"
|
|
1464
|
+
? "🟠"
|
|
1465
|
+
: "🟡";
|
|
1466
|
+
output += `- ${icon} **[${issue.type}]** ${issue.message}\n`;
|
|
1467
|
+
}
|
|
1468
|
+
} else {
|
|
1469
|
+
output += "✨ Code looks valid and safe.\n";
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
return this.success(output);
|
|
1473
|
+
} catch (err) {
|
|
1474
|
+
return this.error(`Validation failed: ${err.message}`);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// ============================================================================
|
|
1479
|
+
// REPORT
|
|
1480
|
+
// ============================================================================
|
|
1481
|
+
async handleReport(projectPath, args) {
|
|
1482
|
+
const type = args?.type || "summary";
|
|
1483
|
+
const outputDir = path.join(projectPath, ".vibecheck");
|
|
1484
|
+
|
|
1485
|
+
let output = "# 📄 vibecheck Report\n\n";
|
|
1486
|
+
|
|
1487
|
+
try {
|
|
1488
|
+
if (type === "summary") {
|
|
1489
|
+
const summaryPath = path.join(outputDir, "summary.json");
|
|
1490
|
+
const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
|
|
1491
|
+
|
|
1492
|
+
output += `**Score:** ${summary.score}/100 (${summary.grade})\n`;
|
|
1493
|
+
output += `**Verdict:** ${summary.canShip ? "✅ SHIP" : "🚫 NO-SHIP"}\n`;
|
|
1494
|
+
output += `**Generated:** ${summary.timestamp}\n`;
|
|
1495
|
+
} else if (type === "full") {
|
|
1496
|
+
const reportPath = path.join(outputDir, "summary.md");
|
|
1497
|
+
output += await fs.readFile(reportPath, "utf-8");
|
|
1498
|
+
} else if (type === "sarif") {
|
|
1499
|
+
const sarifPath = path.join(outputDir, "results.sarif");
|
|
1500
|
+
const sarif = await fs.readFile(sarifPath, "utf-8");
|
|
1501
|
+
output += "```json\n" + sarif.substring(0, 2000) + "\n```\n";
|
|
1502
|
+
output += `\n📄 **Full SARIF:** ${sarifPath}\n`;
|
|
1503
|
+
} else if (type === "html") {
|
|
1504
|
+
output += `📄 **HTML Report:** ${path.join(outputDir, "report.html")}\n`;
|
|
1505
|
+
output += "Open in browser to view the full report.\n";
|
|
1506
|
+
}
|
|
1507
|
+
} catch (err) {
|
|
1508
|
+
output += `⚠️ No ${type} report found. Run \`vibecheck.scan\` first.\n`;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
return this.success(output);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// ============================================================================
|
|
1515
|
+
// SHIP - Quick health check
|
|
1516
|
+
// ============================================================================
|
|
1517
|
+
async handleShip(projectPath, args) {
|
|
1518
|
+
let output = "# 🚀 vibecheck Ship\n\n";
|
|
1519
|
+
output += `**Path:** ${projectPath}\n\n`;
|
|
1520
|
+
|
|
1521
|
+
try {
|
|
1522
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ship`;
|
|
1523
|
+
if (args?.fix) cmd += ` --fix`;
|
|
1524
|
+
|
|
1525
|
+
const result = execSync(cmd, {
|
|
1526
|
+
cwd: projectPath,
|
|
1527
|
+
encoding: "utf8",
|
|
1528
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1529
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
// Parse the output for key information
|
|
1533
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, ""); // Strip ANSI codes
|
|
1534
|
+
} catch (err) {
|
|
1535
|
+
output += `\n⚠️ Ship check failed: ${err.message}\n`;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
output += "\n---\n_Context Enhanced by vibecheck AI_\n";
|
|
1539
|
+
return this.success(output);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// ============================================================================
|
|
1543
|
+
// VERIFY - Runtime browser testing
|
|
1544
|
+
// ============================================================================
|
|
1545
|
+
async handleVerify(projectPath, args) {
|
|
1546
|
+
const url = args?.url;
|
|
1547
|
+
if (!url) return this.error("URL is required");
|
|
1548
|
+
|
|
1549
|
+
let output = "# 🧪 vibecheck Verify\n\n";
|
|
1550
|
+
output += `**URL:** ${url}\n`;
|
|
1551
|
+
if (args?.flows?.length) output += `**Flows:** ${args.flows.join(", ")}\n`;
|
|
1552
|
+
if (args?.headed) output += `**Mode:** Headed (visible browser)\n`;
|
|
1553
|
+
if (args?.record) output += `**Recording:** Enabled\n`;
|
|
1554
|
+
output += "\n";
|
|
1555
|
+
|
|
1556
|
+
try {
|
|
1557
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" verify --url "${url}"`;
|
|
1558
|
+
if (args?.auth) cmd += ` --auth "${args.auth}"`;
|
|
1559
|
+
if (args?.flows?.length) cmd += ` --flows ${args.flows.join(",")}`;
|
|
1560
|
+
if (args?.headed) cmd += ` --headed`;
|
|
1561
|
+
if (args?.record) cmd += ` --record`;
|
|
1562
|
+
|
|
1563
|
+
const result = execSync(cmd, {
|
|
1564
|
+
cwd: projectPath,
|
|
1565
|
+
encoding: "utf8",
|
|
1566
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1567
|
+
timeout: 180000, // 3 min timeout
|
|
1568
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1572
|
+
|
|
1573
|
+
// Try to read reality results
|
|
1574
|
+
const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
|
|
1575
|
+
try {
|
|
1576
|
+
const reality = JSON.parse(await fs.readFile(realityPath, "utf-8"));
|
|
1577
|
+
|
|
1578
|
+
output += "\n## Verification Summary\n\n";
|
|
1579
|
+
output += `| Metric | Value |\n|--------|-------|\n`;
|
|
1580
|
+
output += `| Pages Visited | ${reality.meta?.pagesVisited || 0} |\n`;
|
|
1581
|
+
output += `| Buttons Clicked | ${reality.meta?.buttonsClicked || 0} |\n`;
|
|
1582
|
+
output += `| Forms Tested | ${reality.meta?.formsTested || 0} |\n`;
|
|
1583
|
+
output += `| Dead UI Found | ${reality.findings?.length || 0} |\n`;
|
|
1584
|
+
output += `| Console Errors | ${reality.consoleErrors?.length || 0} |\n`;
|
|
1585
|
+
output += `| Duration | ${reality.meta?.durationMs || 0}ms |\n`;
|
|
1586
|
+
|
|
1587
|
+
if (reality.findings?.length > 0) {
|
|
1588
|
+
output += "\n## Dead UI Detected\n\n";
|
|
1589
|
+
for (const f of reality.findings.slice(0, 5)) {
|
|
1590
|
+
output += `- **${f.title}** — ${f.reason}\n`;
|
|
1591
|
+
}
|
|
1592
|
+
if (reality.findings.length > 5) {
|
|
1593
|
+
output += `\n_...and ${reality.findings.length - 5} more_\n`;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
output += `\n📁 **Full Report:** .vibecheck/reality/last_reality.json\n`;
|
|
1598
|
+
} catch {}
|
|
1599
|
+
} catch (err) {
|
|
1600
|
+
output += `\n⚠️ Verify failed: ${err.message}\n`;
|
|
1601
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
output += "\n---\n_Runtime Verification by vibecheck_\n";
|
|
1605
|
+
return this.success(output);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// ============================================================================
|
|
1609
|
+
// REALITY v2 - Two-Pass Auth Verification + Dead UI Crawler
|
|
1610
|
+
// ============================================================================
|
|
1611
|
+
async handleReality(projectPath, args) {
|
|
1612
|
+
const url = args?.url;
|
|
1613
|
+
if (!url) return this.error("URL is required");
|
|
1614
|
+
|
|
1615
|
+
let output = "# 🧪 vibecheck Reality v2\n\n";
|
|
1616
|
+
output += `**URL:** ${url}\n`;
|
|
1617
|
+
output += `**Two-Pass Auth:** ${args?.verifyAuth ? "Yes" : "No"}\n`;
|
|
1618
|
+
if (args?.auth) output += `**Auth:** ${args.auth.split(":")[0]}:***\n`;
|
|
1619
|
+
if (args?.storageState) output += `**Storage State:** ${args.storageState}\n`;
|
|
1620
|
+
if (args?.headed) output += `**Mode:** Headed (visible browser)\n`;
|
|
1621
|
+
if (args?.danger) output += `**Danger Mode:** Enabled (risky clicks allowed)\n`;
|
|
1622
|
+
output += "\n";
|
|
1623
|
+
|
|
1624
|
+
try {
|
|
1625
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" reality --url "${url}"`;
|
|
1626
|
+
if (args?.auth) cmd += ` --auth "${args.auth}"`;
|
|
1627
|
+
if (args?.verifyAuth) cmd += ` --verify-auth`;
|
|
1628
|
+
if (args?.storageState) cmd += ` --storage-state "${args.storageState}"`;
|
|
1629
|
+
if (args?.saveStorageState) cmd += ` --save-storage-state "${args.saveStorageState}"`;
|
|
1630
|
+
if (args?.truthpack) cmd += ` --truthpack "${args.truthpack}"`;
|
|
1631
|
+
if (args?.headed) cmd += ` --headed`;
|
|
1632
|
+
if (args?.maxPages) cmd += ` --max-pages ${args.maxPages}`;
|
|
1633
|
+
if (args?.maxDepth) cmd += ` --max-depth ${args.maxDepth}`;
|
|
1634
|
+
if (args?.danger) cmd += ` --danger`;
|
|
1635
|
+
|
|
1636
|
+
const result = execSync(cmd, {
|
|
1637
|
+
cwd: projectPath,
|
|
1638
|
+
encoding: "utf8",
|
|
1639
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1640
|
+
timeout: 300000, // 5 min timeout for two-pass
|
|
1641
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1645
|
+
|
|
1646
|
+
// Read reality results
|
|
1647
|
+
const realityPath = path.join(projectPath, ".vibecheck", "reality", "last_reality.json");
|
|
1648
|
+
try {
|
|
1649
|
+
const reality = JSON.parse(await fs.readFile(realityPath, "utf-8"));
|
|
1650
|
+
|
|
1651
|
+
output += "\n## Reality Report\n\n";
|
|
1652
|
+
output += `| Metric | Value |\n|--------|-------|\n`;
|
|
1653
|
+
output += `| Anon Pages | ${reality.passes?.anon?.pagesVisited?.length || 0} |\n`;
|
|
1654
|
+
if (reality.passes?.auth) {
|
|
1655
|
+
output += `| Auth Pages | ${reality.passes?.auth?.pagesVisited?.length || 0} |\n`;
|
|
1656
|
+
}
|
|
1657
|
+
output += `| Findings | ${reality.findings?.length || 0} |\n`;
|
|
1658
|
+
output += `| Console Errors | ${reality.consoleErrors?.length || 0} |\n`;
|
|
1659
|
+
output += `| Network Errors | ${reality.networkErrors?.length || 0} |\n`;
|
|
1660
|
+
|
|
1661
|
+
if (reality.coverage) {
|
|
1662
|
+
output += `| UI Coverage | ${reality.coverage.percent}% (${reality.coverage.hit}/${reality.coverage.total}) |\n`;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if (reality.meta?.protectedMatcherCount > 0) {
|
|
1666
|
+
output += `| Auth Matchers | ${reality.meta.protectedMatcherCount} |\n`;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// Show findings by category
|
|
1670
|
+
const blocks = reality.findings?.filter(f => f.severity === "BLOCK") || [];
|
|
1671
|
+
const warns = reality.findings?.filter(f => f.severity === "WARN") || [];
|
|
1672
|
+
|
|
1673
|
+
if (blocks.length > 0) {
|
|
1674
|
+
output += "\n### 🛑 BLOCK Findings\n\n";
|
|
1675
|
+
for (const f of blocks.slice(0, 5)) {
|
|
1676
|
+
output += `- **${f.title}**\n - ${f.reason}\n`;
|
|
1677
|
+
if (f.screenshot) output += ` - Screenshot: ${f.screenshot}\n`;
|
|
1678
|
+
}
|
|
1679
|
+
if (blocks.length > 5) {
|
|
1680
|
+
output += `\n_...and ${blocks.length - 5} more BLOCKs_\n`;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
if (warns.length > 0) {
|
|
1685
|
+
output += "\n### ⚠️ WARN Findings\n\n";
|
|
1686
|
+
for (const f of warns.slice(0, 3)) {
|
|
1687
|
+
output += `- **${f.title}** — ${f.reason}\n`;
|
|
1688
|
+
}
|
|
1689
|
+
if (warns.length > 3) {
|
|
1690
|
+
output += `\n_...and ${warns.length - 3} more WARNs_\n`;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// Verdict
|
|
1695
|
+
const verdict = blocks.length > 0 ? "🛑 BLOCK" : warns.length > 0 ? "⚠️ WARN" : "✅ CLEAN";
|
|
1696
|
+
output += `\n## Verdict: ${verdict}\n`;
|
|
1697
|
+
|
|
1698
|
+
output += `\n📁 **Full Report:** .vibecheck/reality/last_reality.json\n`;
|
|
1699
|
+
|
|
1700
|
+
if (reality.meta?.savedStorageState) {
|
|
1701
|
+
output += `📁 **Saved Auth State:** ${reality.meta.savedStorageState}\n`;
|
|
1702
|
+
}
|
|
1703
|
+
} catch {}
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
output += `\n⚠️ Reality failed: ${err.message}\n`;
|
|
1706
|
+
if (err.stdout) output += `\n${err.stdout.replace(/\x1b\[[0-9;]*m/g, "")}\n`;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
output += "\n---\n_Reality Mode v2 — Two-Pass Auth Verification_\n";
|
|
1710
|
+
return this.success(output);
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// ============================================================================
|
|
1714
|
+
// AI-TEST - AI Agent testing
|
|
1715
|
+
// ============================================================================
|
|
1716
|
+
async handleAITest(projectPath, args) {
|
|
1717
|
+
const url = args?.url;
|
|
1718
|
+
if (!url) return this.error("URL is required");
|
|
1719
|
+
|
|
1720
|
+
let output = "# 🤖 vibecheck AI Agent\n\n";
|
|
1721
|
+
output += `**URL:** ${url}\n`;
|
|
1722
|
+
output += `**Goal:** ${args?.goal || "Test all features"}\n\n`;
|
|
1723
|
+
|
|
1724
|
+
try {
|
|
1725
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" ai-test --url "${url}"`;
|
|
1726
|
+
if (args?.goal) cmd += ` --goal "${args.goal}"`;
|
|
1727
|
+
if (args?.headed) cmd += ` --headed`;
|
|
1728
|
+
|
|
1729
|
+
const result = execSync(cmd, {
|
|
1730
|
+
cwd: projectPath,
|
|
1731
|
+
encoding: "utf8",
|
|
1732
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1733
|
+
timeout: 180000,
|
|
1734
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1738
|
+
|
|
1739
|
+
// Try to read fix prompts
|
|
1740
|
+
const promptPath = path.join(
|
|
1741
|
+
projectPath,
|
|
1742
|
+
".vibecheck",
|
|
1743
|
+
"ai-agent",
|
|
1744
|
+
"fix-prompt.md",
|
|
1745
|
+
);
|
|
1746
|
+
try {
|
|
1747
|
+
const prompts = await fs.readFile(promptPath, "utf-8");
|
|
1748
|
+
output += "\n## Fix Prompts Generated\n\n";
|
|
1749
|
+
output += prompts.substring(0, 2000);
|
|
1750
|
+
if (prompts.length > 2000) output += "\n\n... (truncated)";
|
|
1751
|
+
} catch {}
|
|
1752
|
+
} catch (err) {
|
|
1753
|
+
output += `\n⚠️ AI Agent failed: ${err.message}\n`;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
output += "\n---\n_Context Enhanced by vibecheck AI_\n";
|
|
1757
|
+
return this.success(output);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
// ============================================================================
|
|
1761
|
+
// AUTOPILOT - Continuous protection
|
|
1762
|
+
// ============================================================================
|
|
1763
|
+
async handleAutopilot(projectPath, args) {
|
|
1764
|
+
const action = args?.action || "status";
|
|
1765
|
+
|
|
1766
|
+
let output = "# 🤖 vibecheck Autopilot\n\n";
|
|
1767
|
+
output += `**Action:** ${action}\n\n`;
|
|
1768
|
+
|
|
1769
|
+
try {
|
|
1770
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot ${action}`;
|
|
1771
|
+
if (args?.slack) cmd += ` --slack="${args.slack}"`;
|
|
1772
|
+
if (args?.email) cmd += ` --email="${args.email}"`;
|
|
1773
|
+
|
|
1774
|
+
const result = execSync(cmd, {
|
|
1775
|
+
cwd: projectPath,
|
|
1776
|
+
encoding: "utf8",
|
|
1777
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1778
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1782
|
+
} catch (err) {
|
|
1783
|
+
output += `\n⚠️ Autopilot failed: ${err.message}\n`;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
return this.success(output);
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// ============================================================================
|
|
1790
|
+
// AUTOPILOT PLAN - Generate fix plan (Pro/Compliance)
|
|
1791
|
+
// ============================================================================
|
|
1792
|
+
async handleAutopilotPlan(projectPath, args) {
|
|
1793
|
+
let output = "# 🤖 vibecheck Autopilot Plan\n\n";
|
|
1794
|
+
output += `**Path:** ${projectPath}\n`;
|
|
1795
|
+
output += `**Profile:** ${args?.profile || "ship"}\n\n`;
|
|
1796
|
+
|
|
1797
|
+
try {
|
|
1798
|
+
// Use the core autopilot runner directly
|
|
1799
|
+
const corePath = path.join(__dirname, "..", "packages", "core", "dist", "index.js");
|
|
1800
|
+
let runAutopilot;
|
|
1801
|
+
|
|
1802
|
+
try {
|
|
1803
|
+
const core = await import(corePath);
|
|
1804
|
+
runAutopilot = core.runAutopilot;
|
|
1805
|
+
} catch {
|
|
1806
|
+
// Fallback to CLI
|
|
1807
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot plan`;
|
|
1808
|
+
cmd += ` --profile ${args?.profile || "ship"}`;
|
|
1809
|
+
cmd += ` --max-fixes ${args?.maxFixes || 10}`;
|
|
1810
|
+
cmd += ` --json`;
|
|
1811
|
+
|
|
1812
|
+
const result = execSync(cmd, {
|
|
1813
|
+
cwd: projectPath,
|
|
1814
|
+
encoding: "utf8",
|
|
1815
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1816
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
const jsonResult = JSON.parse(result);
|
|
1820
|
+
output += `## Scan Results\n\n`;
|
|
1821
|
+
output += `- **Total findings:** ${jsonResult.totalFindings}\n`;
|
|
1822
|
+
output += `- **Fixable:** ${jsonResult.fixableFindings}\n`;
|
|
1823
|
+
output += `- **Estimated time:** ${jsonResult.estimatedDuration}\n\n`;
|
|
1824
|
+
|
|
1825
|
+
output += `## Fix Packs\n\n`;
|
|
1826
|
+
for (const pack of jsonResult.packs || []) {
|
|
1827
|
+
const risk = pack.estimatedRisk === "high" ? "🔴" : pack.estimatedRisk === "medium" ? "🟡" : "🟢";
|
|
1828
|
+
output += `### ${risk} ${pack.name}\n`;
|
|
1829
|
+
output += `- Issues: ${pack.findings.length}\n`;
|
|
1830
|
+
output += `- Files: ${pack.impactedFiles.join(", ")}\n\n`;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
output += `\n💡 Run \`vibecheck.autopilot_apply\` to apply these fixes.\n`;
|
|
1834
|
+
return this.success(output);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
if (runAutopilot) {
|
|
1838
|
+
const result = await runAutopilot({
|
|
1839
|
+
projectPath,
|
|
1840
|
+
mode: "plan",
|
|
1841
|
+
profile: args?.profile || "ship",
|
|
1842
|
+
maxFixes: args?.maxFixes || 10,
|
|
1843
|
+
});
|
|
1844
|
+
|
|
1845
|
+
output += `## Scan Results\n\n`;
|
|
1846
|
+
output += `- **Total findings:** ${result.totalFindings}\n`;
|
|
1847
|
+
output += `- **Fixable:** ${result.fixableFindings}\n`;
|
|
1848
|
+
output += `- **Estimated time:** ${result.estimatedDuration}\n\n`;
|
|
1849
|
+
|
|
1850
|
+
output += `## Fix Packs\n\n`;
|
|
1851
|
+
for (const pack of result.packs) {
|
|
1852
|
+
const risk = pack.estimatedRisk === "high" ? "🔴" : pack.estimatedRisk === "medium" ? "🟡" : "🟢";
|
|
1853
|
+
output += `### ${risk} ${pack.name}\n`;
|
|
1854
|
+
output += `- Issues: ${pack.findings.length}\n`;
|
|
1855
|
+
output += `- Files: ${pack.impactedFiles.join(", ")}\n\n`;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
output += `\n💡 Run \`vibecheck.autopilot_apply\` to apply these fixes.\n`;
|
|
1859
|
+
}
|
|
1860
|
+
} catch (err) {
|
|
1861
|
+
output += `\n❌ Error: ${err.message}\n`;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
return this.success(output);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// ============================================================================
|
|
1868
|
+
// AUTOPILOT APPLY - Apply fixes (Pro/Compliance)
|
|
1869
|
+
// ============================================================================
|
|
1870
|
+
async handleAutopilotApply(projectPath, args) {
|
|
1871
|
+
let output = "# 🔧 vibecheck Autopilot Apply\n\n";
|
|
1872
|
+
output += `**Path:** ${projectPath}\n`;
|
|
1873
|
+
output += `**Profile:** ${args?.profile || "ship"}\n`;
|
|
1874
|
+
output += `**Dry Run:** ${args?.dryRun ? "Yes" : "No"}\n\n`;
|
|
1875
|
+
|
|
1876
|
+
try {
|
|
1877
|
+
// Fallback to CLI
|
|
1878
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" autopilot apply`;
|
|
1879
|
+
cmd += ` --profile ${args?.profile || "ship"}`;
|
|
1880
|
+
cmd += ` --max-fixes ${args?.maxFixes || 10}`;
|
|
1881
|
+
if (args?.verify === false) cmd += ` --no-verify`;
|
|
1882
|
+
if (args?.dryRun) cmd += ` --dry-run`;
|
|
1883
|
+
cmd += ` --json`;
|
|
1884
|
+
|
|
1885
|
+
const result = execSync(cmd, {
|
|
1886
|
+
cwd: projectPath,
|
|
1887
|
+
encoding: "utf8",
|
|
1888
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1889
|
+
timeout: 300000, // 5 min timeout
|
|
1890
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1893
|
+
const jsonResult = JSON.parse(result);
|
|
1894
|
+
|
|
1895
|
+
output += `## Results\n\n`;
|
|
1896
|
+
output += `- **Packs attempted:** ${jsonResult.packsAttempted}\n`;
|
|
1897
|
+
output += `- **Packs succeeded:** ${jsonResult.packsSucceeded}\n`;
|
|
1898
|
+
output += `- **Packs failed:** ${jsonResult.packsFailed}\n`;
|
|
1899
|
+
output += `- **Fixes applied:** ${jsonResult.appliedFixes?.filter(f => f.success).length || 0}\n`;
|
|
1900
|
+
output += `- **Duration:** ${jsonResult.duration}ms\n\n`;
|
|
1901
|
+
|
|
1902
|
+
if (jsonResult.verification) {
|
|
1903
|
+
output += `## Verification\n\n`;
|
|
1904
|
+
output += `- TypeScript: ${jsonResult.verification.typecheck?.passed ? "✅" : "❌"}\n`;
|
|
1905
|
+
output += `- Build: ${jsonResult.verification.build?.passed ? "✅" : "⏭️"}\n`;
|
|
1906
|
+
output += `- Overall: ${jsonResult.verification.passed ? "✅ PASSED" : "❌ FAILED"}\n\n`;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
output += `**Remaining findings:** ${jsonResult.remainingFindings}\n`;
|
|
1910
|
+
output += `**New scan verdict:** ${jsonResult.newScanVerdict}\n`;
|
|
1911
|
+
} catch (err) {
|
|
1912
|
+
output += `\n❌ Error: ${err.message}\n`;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
return this.success(output);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// ============================================================================
|
|
1919
|
+
// BADGE - Generate ship badge
|
|
1920
|
+
// ============================================================================
|
|
1921
|
+
async handleBadge(projectPath, args) {
|
|
1922
|
+
const format = args?.format || "svg";
|
|
1923
|
+
|
|
1924
|
+
let output = "# 🏅 vibecheck Badge\n\n";
|
|
1925
|
+
|
|
1926
|
+
try {
|
|
1927
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" badge --format ${format}`;
|
|
1928
|
+
if (args?.style) cmd += ` --style ${args.style}`;
|
|
1929
|
+
|
|
1930
|
+
const result = execSync(cmd, {
|
|
1931
|
+
cwd: projectPath,
|
|
1932
|
+
encoding: "utf8",
|
|
1933
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1934
|
+
env: { ...process.env, VIBECHECK_SKIP_AUTH: "1" },
|
|
1935
|
+
});
|
|
1936
|
+
|
|
1937
|
+
output += result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1938
|
+
|
|
1939
|
+
// Read the badge file
|
|
1940
|
+
const badgePath = path.join(
|
|
1941
|
+
projectPath,
|
|
1942
|
+
".vibecheck",
|
|
1943
|
+
"badges",
|
|
1944
|
+
`badge.${format}`,
|
|
1945
|
+
);
|
|
1946
|
+
try {
|
|
1947
|
+
const badge = await fs.readFile(badgePath, "utf-8");
|
|
1948
|
+
if (format === "md") {
|
|
1949
|
+
output += "\n**Markdown:**\n```\n" + badge + "\n```\n";
|
|
1950
|
+
} else if (format === "html") {
|
|
1951
|
+
output += "\n**HTML:**\n```html\n" + badge + "\n```\n";
|
|
1952
|
+
} else {
|
|
1953
|
+
output += `\n**Badge saved to:** ${badgePath}\n`;
|
|
1954
|
+
}
|
|
1955
|
+
} catch {}
|
|
1956
|
+
} catch (err) {
|
|
1957
|
+
output += `\n⚠️ Badge generation failed: ${err.message}\n`;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
return this.success(output);
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// ============================================================================
|
|
1964
|
+
// CONTEXT - AI Rules Generator
|
|
1965
|
+
// ============================================================================
|
|
1966
|
+
async handleContext(projectPath, args) {
|
|
1967
|
+
const platform = args?.platform || "all";
|
|
1968
|
+
|
|
1969
|
+
let output = "# 🧠 vibecheck Context Generator\n\n";
|
|
1970
|
+
output += `**Project:** ${path.basename(projectPath)}\n`;
|
|
1971
|
+
output += `**Platform:** ${platform}\n\n`;
|
|
1972
|
+
|
|
1973
|
+
try {
|
|
1974
|
+
let cmd = `node "${path.join(__dirname, "..", "bin", "vibecheck.js")}" context`;
|
|
1975
|
+
if (platform !== "all") cmd += ` --platform=${platform}`;
|
|
1976
|
+
|
|
1977
|
+
execSync(cmd, {
|
|
1978
|
+
cwd: projectPath,
|
|
1979
|
+
encoding: "utf8",
|
|
1980
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
output += "## ✅ Context Generated\n\n";
|
|
1984
|
+
output +=
|
|
1985
|
+
"Your AI coding assistants now have full project awareness.\n\n";
|
|
1986
|
+
|
|
1987
|
+
output += "### Generated Files\n\n";
|
|
1988
|
+
|
|
1989
|
+
if (platform === "all" || platform === "cursor") {
|
|
1990
|
+
output += "**Cursor:**\n";
|
|
1991
|
+
output += "- `.cursorrules` - Main rules file\n";
|
|
1992
|
+
output += "- `.cursor/rules/*.mdc` - Modular rules\n\n";
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
if (platform === "all" || platform === "windsurf") {
|
|
1996
|
+
output += "**Windsurf:**\n";
|
|
1997
|
+
output += "- `.windsurf/rules/*.md` - Cascade rules\n\n";
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
if (platform === "all" || platform === "copilot") {
|
|
2001
|
+
output += "**GitHub Copilot:**\n";
|
|
2002
|
+
output += "- `.github/copilot-instructions.md`\n\n";
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
output += "**Universal (MCP):**\n";
|
|
2006
|
+
output += "- `.vibecheck/context.json` - Full context\n";
|
|
2007
|
+
output += "- `.vibecheck/project-map.json` - Project analysis\n\n";
|
|
2008
|
+
|
|
2009
|
+
output += "### What Your AI Now Knows\n\n";
|
|
2010
|
+
output += "- Project architecture and structure\n";
|
|
2011
|
+
output += "- API routes and endpoints\n";
|
|
2012
|
+
output += "- Components and data models\n";
|
|
2013
|
+
output += "- Coding conventions and patterns\n";
|
|
2014
|
+
output += "- Dependencies and tech stack\n\n";
|
|
2015
|
+
|
|
2016
|
+
output +=
|
|
2017
|
+
"> **Tip:** Regenerate after major codebase changes with `vibecheck context`\n";
|
|
2018
|
+
} catch (err) {
|
|
2019
|
+
output += `\n⚠️ Context generation failed: ${err.message}\n`;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
output += "\n---\n_Context Enhanced by vibecheck AI_\n";
|
|
2023
|
+
return this.success(output);
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
// ============================================================================
|
|
2027
|
+
// STATUS
|
|
2028
|
+
// ============================================================================
|
|
2029
|
+
async handleStatus(projectPath, args) {
|
|
2030
|
+
let output = "# 📊 vibecheck Status\n\n";
|
|
2031
|
+
|
|
2032
|
+
output += "## Server\n\n";
|
|
2033
|
+
output += `- **Version:** ${VERSION}\n`;
|
|
2034
|
+
output += `- **Node:** ${process.version}\n`;
|
|
2035
|
+
output += `- **Platform:** ${process.platform}\n\n`;
|
|
2036
|
+
|
|
2037
|
+
output += "## Project\n\n";
|
|
2038
|
+
output += `- **Path:** ${projectPath}\n`;
|
|
2039
|
+
|
|
2040
|
+
// Config
|
|
2041
|
+
const configPaths = [
|
|
2042
|
+
path.join(projectPath, "vibecheck.config.json"),
|
|
2043
|
+
path.join(projectPath, ".vibecheckrc"),
|
|
2044
|
+
];
|
|
2045
|
+
let hasConfig = false;
|
|
2046
|
+
for (const p of configPaths) {
|
|
2047
|
+
try {
|
|
2048
|
+
await fs.access(p);
|
|
2049
|
+
hasConfig = true;
|
|
2050
|
+
output += `- **Config:** ✅ Found (${path.basename(p)})\n`;
|
|
2051
|
+
break;
|
|
2052
|
+
} catch {}
|
|
2053
|
+
}
|
|
2054
|
+
if (!hasConfig) {
|
|
2055
|
+
output += "- **Config:** ⚠️ Not found (run `vibecheck init`)\n";
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// Last scan
|
|
2059
|
+
const summaryPath = path.join(projectPath, ".vibecheck", "summary.json");
|
|
2060
|
+
try {
|
|
2061
|
+
const summary = JSON.parse(await fs.readFile(summaryPath, "utf-8"));
|
|
2062
|
+
output += `- **Last Scan:** ${summary.timestamp}\n`;
|
|
2063
|
+
output += `- **Last Score:** ${summary.score}/100 (${summary.grade})\n`;
|
|
2064
|
+
output += `- **Last Verdict:** ${summary.canShip ? "✅ SHIP" : "🚫 NO-SHIP"}\n`;
|
|
2065
|
+
} catch {
|
|
2066
|
+
output += "- **Last Scan:** None\n";
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
output += "\n## Available Tools\n\n";
|
|
2070
|
+
output += "| Tool | Description |\n|------|-------------|\n";
|
|
2071
|
+
for (const tool of TOOLS) {
|
|
2072
|
+
output += `| \`${tool.name}\` | ${tool.description.split("—")[0].trim()} |\n`;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
output += "\n---\n_Vibecheck v" + VERSION + " — https://vibecheckai.dev_\n";
|
|
2076
|
+
|
|
2077
|
+
return this.success(output);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// ============================================================================
|
|
2081
|
+
// RUN
|
|
2082
|
+
// ============================================================================
|
|
2083
|
+
async run() {
|
|
2084
|
+
const transport = new StdioServerTransport();
|
|
2085
|
+
await this.server.connect(transport);
|
|
2086
|
+
console.error("vibecheck MCP Server v2.0 running on stdio");
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
// Main
|
|
2091
|
+
const server = new VibecheckMCP();
|
|
2092
|
+
server.run().catch(console.error);
|