@vibecheckai/cli 3.0.9 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * vibecheck graph - Reality Proof Graph
3
3
  *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * ENTERPRISE EDITION - World-Class Terminal Experience
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
4
8
  * Builds and visualizes the complete causal chain:
5
9
  * UI action → client function → network call → server route → handler → DB/external
6
10
  *
@@ -27,63 +31,209 @@ const {
27
31
  mergeRuntimeResults
28
32
  } = require("./lib/graph");
29
33
 
34
+ // Entitlements enforcement
35
+ const entitlements = require("./lib/entitlements-v2");
36
+
37
+ // ═══════════════════════════════════════════════════════════════════════════════
38
+ // ADVANCED TERMINAL - ANSI CODES & UTILITIES
39
+ // ═══════════════════════════════════════════════════════════════════════════════
40
+
30
41
  const c = {
31
42
  reset: '\x1b[0m',
32
43
  bold: '\x1b[1m',
33
44
  dim: '\x1b[2m',
45
+ italic: '\x1b[3m',
46
+ red: '\x1b[31m',
34
47
  green: '\x1b[32m',
35
48
  yellow: '\x1b[33m',
36
- cyan: '\x1b[36m',
37
- red: '\x1b[31m',
38
49
  blue: '\x1b[34m',
50
+ magenta: '\x1b[35m',
51
+ cyan: '\x1b[36m',
52
+ white: '\x1b[37m',
53
+ gray: '\x1b[90m',
54
+ clearLine: '\x1b[2K',
55
+ hideCursor: '\x1b[?25l',
56
+ showCursor: '\x1b[?25h',
39
57
  };
40
58
 
59
+ const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
60
+ const bgRgb = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
61
+
62
+ const colors = {
63
+ gradient1: rgb(255, 100, 150),
64
+ gradient2: rgb(255, 80, 170),
65
+ gradient3: rgb(255, 60, 190),
66
+ shipGreen: rgb(0, 255, 150),
67
+ warnAmber: rgb(255, 200, 0),
68
+ blockRed: rgb(255, 80, 80),
69
+ accent: rgb(255, 100, 150),
70
+ muted: rgb(120, 120, 140),
71
+ node: rgb(100, 200, 255),
72
+ edge: rgb(150, 255, 150),
73
+ broken: rgb(255, 100, 100),
74
+ };
75
+
76
+ // ═══════════════════════════════════════════════════════════════════════════════
77
+ // PREMIUM BANNER
78
+ // ═══════════════════════════════════════════════════════════════════════════════
79
+
80
+ const GRAPH_BANNER = `
81
+ ${rgb(255, 100, 150)} ██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗${c.reset}
82
+ ${rgb(255, 80, 170)} ██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██║ ██║${c.reset}
83
+ ${rgb(255, 60, 190)} ██║ ███╗██████╔╝███████║██████╔╝███████║${c.reset}
84
+ ${rgb(255, 40, 210)} ██║ ██║██╔══██╗██╔══██║██╔═══╝ ██╔══██║${c.reset}
85
+ ${rgb(255, 20, 230)} ╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║${c.reset}
86
+ ${rgb(255, 0, 255)} ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${c.reset}
87
+ `;
88
+
89
+ const BANNER_FULL = `
90
+ ${rgb(255, 100, 150)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${c.reset}
91
+ ${rgb(255, 90, 160)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${c.reset}
92
+ ${rgb(255, 80, 170)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${c.reset}
93
+ ${rgb(255, 60, 190)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${c.reset}
94
+ ${rgb(255, 40, 210)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${c.reset}
95
+ ${rgb(255, 20, 230)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${c.reset}
96
+
97
+ ${c.dim} ┌─────────────────────────────────────────────────────────────────────┐${c.reset}
98
+ ${c.dim} │${c.reset} ${rgb(255, 100, 150)}📊${c.reset} ${c.bold}GRAPH${c.reset} ${c.dim}•${c.reset} ${rgb(200, 200, 200)}Proof Visualization${c.reset} ${c.dim}•${c.reset} ${rgb(150, 150, 150)}Causal Chain${c.reset} ${c.dim}•${c.reset} ${rgb(100, 100, 100)}Edge Analysis${c.reset} ${c.dim}│${c.reset}
99
+ ${c.dim} └─────────────────────────────────────────────────────────────────────┘${c.reset}
100
+ `;
101
+
102
+ const ICONS = {
103
+ graph: '📊',
104
+ check: '✓',
105
+ cross: '✗',
106
+ warning: '⚠',
107
+ arrow: '→',
108
+ bullet: '•',
109
+ node: '◉',
110
+ edge: '─',
111
+ broken: '╳',
112
+ sparkle: '✨',
113
+ file: '📄',
114
+ folder: '📁',
115
+ view: '👁️',
116
+ };
117
+
118
+ const SPINNER_DOTS = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
119
+ let spinnerIndex = 0;
120
+ let spinnerInterval = null;
121
+ let spinnerStartTime = null;
122
+
123
+ function formatDuration(ms) {
124
+ if (ms < 1000) return `${ms}ms`;
125
+ return `${(ms / 1000).toFixed(1)}s`;
126
+ }
127
+
128
+ function startSpinner(message) {
129
+ spinnerStartTime = Date.now();
130
+ process.stdout.write(c.hideCursor);
131
+ spinnerInterval = setInterval(() => {
132
+ const elapsed = formatDuration(Date.now() - spinnerStartTime);
133
+ process.stdout.write(`\r${c.clearLine} ${colors.accent}${SPINNER_DOTS[spinnerIndex]}${c.reset} ${message} ${c.dim}${elapsed}${c.reset}`);
134
+ spinnerIndex = (spinnerIndex + 1) % SPINNER_DOTS.length;
135
+ }, 80);
136
+ }
137
+
138
+ function stopSpinner(message, success = true) {
139
+ if (spinnerInterval) {
140
+ clearInterval(spinnerInterval);
141
+ spinnerInterval = null;
142
+ }
143
+ const elapsed = spinnerStartTime ? formatDuration(Date.now() - spinnerStartTime) : '';
144
+ const icon = success ? `${colors.shipGreen}${ICONS.check}${c.reset}` : `${colors.blockRed}${ICONS.cross}${c.reset}`;
145
+ process.stdout.write(`\r${c.clearLine} ${icon} ${message} ${c.dim}${elapsed}${c.reset}\n`);
146
+ process.stdout.write(c.showCursor);
147
+ spinnerStartTime = null;
148
+ }
149
+
150
+ function printDivider(char = '─', width = 69, color = c.dim) {
151
+ console.log(`${color} ${char.repeat(width)}${c.reset}`);
152
+ }
153
+
154
+ function printSection(title, icon = '◆') {
155
+ console.log();
156
+ console.log(` ${colors.accent}${icon}${c.reset} ${c.bold}${title}${c.reset}`);
157
+ printDivider();
158
+ }
159
+
160
+ function progressBar(percent, width = 25) {
161
+ const filled = Math.round((percent / 100) * width);
162
+ const empty = width - filled;
163
+ const color = percent >= 80 ? colors.shipGreen : percent >= 50 ? colors.warnAmber : colors.blockRed;
164
+ return `${color}${'█'.repeat(filled)}${c.dim}${'░'.repeat(empty)}${c.reset}`;
165
+ }
166
+
41
167
  function ensureDir(p) {
42
168
  fs.mkdirSync(p, { recursive: true });
43
169
  }
44
170
 
45
171
  async function runGraph(args) {
46
172
  const opts = parseArgs(args);
173
+ const startTime = Date.now();
47
174
 
48
175
  if (opts.help) {
49
176
  printHelp();
50
177
  return 0;
51
178
  }
52
179
 
180
+ // TIER ENFORCEMENT: COMPLETE only
181
+ const access = await entitlements.enforce("graph", {
182
+ projectPath: opts.path || process.cwd(),
183
+ silent: false,
184
+ });
185
+
186
+ if (!access.allowed) {
187
+ console.log(`\n${c.yellow}Tip:${c.reset} The graph command requires COMPLETE tier for advanced proof graph visualization.`);
188
+ return entitlements.EXIT_FEATURE_NOT_ALLOWED;
189
+ }
190
+
53
191
  const root = path.resolve(opts.path || process.cwd());
192
+ const projectName = path.basename(root);
54
193
  const outDir = path.join(root, ".vibecheck", "graph");
55
194
  ensureDir(outDir);
56
195
 
57
- console.log(`\n${c.cyan}${c.bold}🔍 vibecheck graph${c.reset}`);
58
- console.log(`${c.dim}Building Reality Proof Graph...${c.reset}\n`);
196
+ // Print banner
197
+ console.log(BANNER_FULL);
198
+ console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
199
+ console.log(` ${c.dim}Path:${c.reset} ${root}`);
200
+ console.log();
59
201
 
60
202
  // Load or build truthpack
203
+ startSpinner('Loading truthpack...');
61
204
  let truthpack = loadTruthpack(root);
62
205
  if (!truthpack) {
63
- console.log(`${c.dim}Building truthpack...${c.reset}`);
206
+ stopSpinner('Truthpack not found, building...', true);
207
+ startSpinner('Building truthpack...');
64
208
  truthpack = await buildTruthpack({ repoRoot: root, fastifyEntry: opts.fastifyEntry });
65
209
  }
210
+ stopSpinner('Truthpack loaded', true);
66
211
 
67
212
  // Extract static graph
68
- console.log(`${c.dim}Extracting static edges...${c.reset}`);
213
+ startSpinner('Extracting static edges...');
69
214
  const staticData = await extractStaticGraph(root, truthpack);
215
+ stopSpinner(`Extracted ${staticData.edges?.length || 0} edges`, true);
70
216
 
71
217
  // Build proof graph
218
+ startSpinner('Building proof graph...');
72
219
  let graph = buildProofGraph({
73
220
  nodes: staticData.nodes,
74
221
  edges: staticData.edges,
75
222
  meta: { repoRoot: root }
76
223
  });
224
+ stopSpinner(`Built graph with ${graph.nodes?.length || 0} nodes`, true);
77
225
 
78
226
  // Runtime verification (optional)
79
227
  if (opts.runtime && opts.url) {
80
- console.log(`${c.dim}Collecting runtime edges from ${opts.url}...${c.reset}`);
228
+ startSpinner(`Collecting runtime edges from ${opts.url}...`);
81
229
 
82
230
  try {
83
231
  const runtimeEdges = await collectRuntimeEdges(opts.url, graph, opts);
84
232
  graph = mergeRuntimeResults(graph, runtimeEdges);
233
+ stopSpinner('Runtime edges collected', true);
85
234
  } catch (e) {
86
- console.log(`${c.yellow}⚠️ Runtime collection failed: ${e.message}${c.reset}`);
235
+ stopSpinner('Runtime collection failed', false);
236
+ console.log(` ${c.dim}${e.message}${c.reset}`);
87
237
  }
88
238
  }
89
239
 
@@ -91,6 +241,7 @@ async function runGraph(args) {
91
241
  const findings = getFindingsFromGraph(graph);
92
242
 
93
243
  // Write outputs
244
+ startSpinner('Writing outputs...');
94
245
  fs.writeFileSync(path.join(outDir, "graph.json"), JSON.stringify(graph, null, 2), "utf8");
95
246
 
96
247
  if (!opts.jsonOnly) {
@@ -104,23 +255,31 @@ async function runGraph(args) {
104
255
  if (findings.length > 0) {
105
256
  fs.writeFileSync(path.join(outDir, "broken-edges.json"), JSON.stringify(findings, null, 2), "utf8");
106
257
  }
258
+ stopSpinner('Outputs written', true);
107
259
 
108
260
  // Print summary
109
- console.log(`\n${c.bold}Graph Summary${c.reset}`);
110
- console.log(` Nodes: ${graph.nodes.length}`);
111
- console.log(` Valid edges: ${c.green}${graph.edges.length}${c.reset}`);
112
- console.log(` Broken edges: ${graph.brokenEdges.length > 0 ? c.red : c.green}${graph.brokenEdges.length}${c.reset}`);
113
- console.log(` Coverage: ${graph.coverage.percent}%`);
261
+ const duration = Date.now() - startTime;
262
+ const coverage = graph.coverage?.percent || 0;
263
+
264
+ printSection('GRAPH SUMMARY', ICONS.graph);
265
+ console.log();
266
+ console.log(` ${colors.node}${ICONS.node}${c.reset} ${c.bold}Nodes:${c.reset} ${graph.nodes?.length || 0}`);
267
+ console.log(` ${colors.edge}${ICONS.edge}${c.reset} ${c.bold}Valid edges:${c.reset} ${colors.shipGreen}${graph.edges?.length || 0}${c.reset}`);
268
+ console.log(` ${colors.broken}${ICONS.broken}${c.reset} ${c.bold}Broken edges:${c.reset} ${graph.brokenEdges?.length > 0 ? colors.blockRed : colors.shipGreen}${graph.brokenEdges?.length || 0}${c.reset}`);
269
+ console.log();
270
+ console.log(` ${c.bold}Coverage:${c.reset} ${progressBar(coverage)} ${coverage}%`);
271
+ console.log(` ${c.bold}Duration:${c.reset} ${formatDuration(duration)}`);
114
272
 
115
273
  if (opts.broken || findings.length > 0) {
116
- console.log(`\n${c.bold}Broken Edges${c.reset}`);
274
+ printSection('BROKEN EDGES', ICONS.broken);
275
+ console.log();
117
276
 
118
277
  if (findings.length === 0) {
119
- console.log(` ${c.green} No broken edges found${c.reset}`);
278
+ console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} No broken edges found`);
120
279
  } else {
121
280
  for (const f of findings.slice(0, 10)) {
122
- const icon = f.severity === "BLOCK" ? `${c.red}✗` : `${c.yellow}⚠`;
123
- console.log(` ${icon} ${f.title}${c.reset}`);
281
+ const icon = f.severity === "BLOCK" ? `${colors.blockRed}●` : `${colors.warnAmber}●`;
282
+ console.log(` ${icon}${c.reset} ${f.title}`);
124
283
  }
125
284
  if (findings.length > 10) {
126
285
  console.log(` ${c.dim}... and ${findings.length - 10} more${c.reset}`);
@@ -128,21 +287,22 @@ async function runGraph(args) {
128
287
  }
129
288
  }
130
289
 
131
- console.log(`\n${c.bold}Output${c.reset}`);
132
- console.log(` ${c.dim}Graph:${c.reset} .vibecheck/graph/graph.json`);
290
+ printSection('OUTPUT', ICONS.folder);
291
+ console.log();
292
+ console.log(` ${ICONS.file} ${colors.accent}graph.json${c.reset} ${c.dim}Machine-readable graph${c.reset}`);
133
293
  if (!opts.jsonOnly) {
134
- console.log(` ${c.dim}Visualization:${c.reset} .vibecheck/graph/graph.html`);
135
- console.log(` ${c.dim}Mermaid:${c.reset} .vibecheck/graph/graph.mmd`);
294
+ console.log(` ${ICONS.view} ${colors.accent}graph.html${c.reset} ${c.dim}Interactive visualization${c.reset}`);
295
+ console.log(` ${ICONS.file} ${colors.accent}graph.mmd${c.reset} ${c.dim}Mermaid diagram${c.reset}`);
136
296
  }
137
297
  if (findings.length > 0) {
138
- console.log(` ${c.dim}Broken edges:${c.reset} .vibecheck/graph/broken-edges.json`);
298
+ console.log(` ${ICONS.warning} ${colors.accent}broken-edges.json${c.reset} ${c.dim}Broken edges as findings${c.reset}`);
139
299
  }
140
300
 
141
301
  if (opts.view && !opts.jsonOnly) {
142
302
  const htmlPath = path.join(outDir, "graph.html");
143
- console.log(`\n${c.dim}Opening graph visualization...${c.reset}`);
303
+ console.log();
304
+ console.log(` ${c.dim}Opening visualization...${c.reset}`);
144
305
 
145
- // Cross-platform open
146
306
  const { exec } = require("child_process");
147
307
  const cmd = process.platform === "win32" ? `start "" "${htmlPath}"` :
148
308
  process.platform === "darwin" ? `open "${htmlPath}"` :
@@ -153,7 +313,13 @@ async function runGraph(args) {
153
313
  const blocks = findings.filter(f => f.severity === "BLOCK").length;
154
314
  const warns = findings.filter(f => f.severity === "WARN").length;
155
315
 
156
- console.log(`\n${c.bold}Verdict:${c.reset} ${blocks ? `${c.red}🛑 BLOCK${c.reset}` : warns ? `${c.yellow}⚠️ WARN${c.reset}` : `${c.green}✅ CLEAN${c.reset}`}`);
316
+ // Verdict card
317
+ console.log();
318
+ const verdictColor = blocks ? colors.blockRed : warns ? colors.warnAmber : colors.shipGreen;
319
+ const verdictIcon = blocks ? '🛑' : warns ? '⚠️' : '✅';
320
+ const verdictText = blocks ? 'BLOCK' : warns ? 'WARN' : 'CLEAN';
321
+ console.log(` ${verdictColor}${c.bold}${verdictIcon} ${verdictText}${c.reset}`);
322
+ console.log();
157
323
 
158
324
  return blocks ? 2 : warns ? 1 : 0;
159
325
  }
@@ -228,56 +394,61 @@ function parseArgs(args) {
228
394
  }
229
395
 
230
396
  function printHelp() {
397
+ console.log(BANNER_FULL);
231
398
  console.log(`
232
- ${c.cyan}${c.bold}🔍 vibecheck graph${c.reset} - Reality Proof Graph
233
-
234
- Build and visualize the complete causal chain from UI to database.
235
- Identify broken edges that indicate missing routes, runtime failures, or causal contradictions.
236
-
237
- ${c.bold}USAGE${c.reset}
238
- vibecheck graph ${c.dim}# Build static graph${c.reset}
239
- vibecheck graph --runtime --url http://... ${c.dim}# Add runtime edges${c.reset}
240
- vibecheck graph --view ${c.dim}# Open visualization${c.reset}
241
- vibecheck graph --broken ${c.dim}# List broken edges${c.reset}
242
-
243
- ${c.bold}OPTIONS${c.reset}
244
- --build Build static graph (default)
245
- --runtime Collect runtime edges via Playwright
246
- --url <url> Target URL for runtime collection
247
- --view Open interactive HTML visualization
248
- --broken Show only broken edges
249
- --json Output JSON only (no HTML/Mermaid)
250
- --headed Run browser in headed mode
251
- --fastify-entry Fastify entry file (e.g. src/server.ts)
252
- --path, -p Project path (default: current directory)
253
- --help, -h Show this help
254
-
255
- ${c.bold}GRAPH NODES${c.reset}
256
- UI Action onClick, onSubmit, etc.
257
- • Client Function Functions that make network calls
258
- Network Call fetch(), axios.*()
259
- Server Route Next API routes, Fastify routes
260
- Handler Route handler functions
261
- DB Call Prisma, raw SQL
262
- • External Call Stripe, GitHub, etc.
263
-
264
- ${c.bold}BROKEN EDGE TYPES${c.reset}
265
- ${c.red}MissingRoute${c.reset} Route referenced but doesn't exist
266
- ${c.red}RuntimeFailure${c.reset} Route returns 4xx/5xx at runtime
267
- • ${c.red}CausalContradiction${c.reset} UI shows success but server returned error
268
-
269
- ${c.bold}OUTPUT${c.reset}
270
- .vibecheck/graph/
271
- graph.json Machine-readable graph
272
- graph.html Interactive D3 visualization
273
- graph.mmd Mermaid diagram
274
- broken-edges.json Broken edges as findings
275
-
276
- ${c.bold}EXAMPLES${c.reset}
277
- vibecheck graph --view ${c.dim}# Build + open visualization${c.reset}
278
- vibecheck graph --runtime --url http://localhost:3000 --view
279
- vibecheck graph --broken --json ${c.dim}# CI mode: just broken edges${c.reset}
280
- `);
399
+ ${c.bold}Usage:${c.reset} vibecheck graph [options]
400
+
401
+ ${c.bold}Reality Proof Graph${c.reset} — Visualize the complete causal chain from UI to database.
402
+
403
+ ${c.bold}Modes:${c.reset}
404
+ ${colors.accent}vibecheck graph${c.reset} ${c.dim}Build static graph${c.reset}
405
+ ${colors.accent}vibecheck graph --runtime --url http://...${c.reset} ${c.dim}Add runtime edges${c.reset}
406
+ ${colors.accent}vibecheck graph --view${c.reset} ${c.dim}Open visualization${c.reset}
407
+ ${colors.accent}vibecheck graph --broken${c.reset} ${c.dim}List broken edges${c.reset}
408
+
409
+ ${c.bold}Options:${c.reset}
410
+ ${colors.accent}--build${c.reset} Build static graph ${c.dim}(default)${c.reset}
411
+ ${colors.accent}--runtime${c.reset} Collect runtime edges via Playwright
412
+ ${colors.accent}--url <url>${c.reset} Target URL for runtime collection
413
+ ${colors.accent}--view${c.reset} Open interactive HTML visualization
414
+ ${colors.accent}--broken${c.reset} Show only broken edges
415
+ ${colors.accent}--json${c.reset} Output JSON only ${c.dim}(no HTML/Mermaid)${c.reset}
416
+ ${colors.accent}--headed${c.reset} Run browser in headed mode
417
+ ${colors.accent}--fastify-entry${c.reset} Fastify entry file ${c.dim}(e.g. src/server.ts)${c.reset}
418
+ ${colors.accent}--path, -p${c.reset} Project path ${c.dim}(default: current directory)${c.reset}
419
+ ${colors.accent}--help, -h${c.reset} Show this help
420
+
421
+ ${c.bold}Graph Nodes:${c.reset}
422
+ ${colors.node}◉${c.reset} UI Action ${c.dim}onClick, onSubmit, etc.${c.reset}
423
+ ${colors.node}◉${c.reset} Client Function ${c.dim}Functions that make network calls${c.reset}
424
+ ${colors.node}◉${c.reset} Network Call ${c.dim}fetch(), axios.*()${c.reset}
425
+ ${colors.node}◉${c.reset} Server Route ${c.dim}Next API routes, Fastify routes${c.reset}
426
+ ${colors.node}◉${c.reset} Handler ${c.dim}Route handler functions${c.reset}
427
+ ${colors.node}◉${c.reset} DB Call ${c.dim}Prisma, raw SQL${c.reset}
428
+ ${colors.node}◉${c.reset} External Call ${c.dim}Stripe, GitHub, etc.${c.reset}
429
+
430
+ ${c.bold}Broken Edge Types:${c.reset}
431
+ ${colors.blockRed}╳${c.reset} MissingRoute ${c.dim}Route referenced but doesn't exist${c.reset}
432
+ ${colors.blockRed}╳${c.reset} RuntimeFailure ${c.dim}Route returns 4xx/5xx at runtime${c.reset}
433
+ ${colors.blockRed}╳${c.reset} CausalContradiction ${c.dim}UI shows success but server returned error${c.reset}
434
+
435
+ ${c.bold}Output:${c.reset}
436
+ ${c.dim}.vibecheck/graph/${c.reset}
437
+ ${colors.accent}graph.json${c.reset} ${c.dim}Machine-readable graph${c.reset}
438
+ ${colors.accent}graph.html${c.reset} ${c.dim}Interactive D3 visualization${c.reset}
439
+ ${colors.accent}graph.mmd${c.reset} ${c.dim}Mermaid diagram${c.reset}
440
+ ${colors.accent}broken-edges.json${c.reset} ${c.dim}Broken edges as findings${c.reset}
441
+
442
+ ${c.bold}Examples:${c.reset}
443
+ ${c.dim}# Build + open visualization${c.reset}
444
+ vibecheck graph --view
445
+
446
+ ${c.dim}# Runtime verification with Playwright${c.reset}
447
+ vibecheck graph --runtime --url http://localhost:3000 --view
448
+
449
+ ${c.dim}# CI mode: just broken edges as JSON${c.reset}
450
+ vibecheck graph --broken --json
451
+ `);
281
452
  }
282
453
 
283
454
  module.exports = { runGraph };