@vibecheckai/cli 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +135 -63
  2. package/bin/_deprecations.js +447 -19
  3. package/bin/_router.js +1 -1
  4. package/bin/registry.js +347 -280
  5. package/bin/runners/context/generators/cursor-enhanced.js +2439 -0
  6. package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
  7. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
  8. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
  9. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
  10. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
  11. package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
  12. package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
  13. package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
  14. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
  15. package/bin/runners/lib/agent-firewall/index.js +200 -0
  16. package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
  17. package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
  18. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +622 -0
  19. package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
  20. package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
  21. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
  22. package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
  23. package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
  24. package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
  25. package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
  26. package/bin/runners/lib/agent-firewall/session/index.js +26 -0
  27. package/bin/runners/lib/artifact-envelope.js +540 -0
  28. package/bin/runners/lib/auth-shared.js +977 -0
  29. package/bin/runners/lib/checkpoint.js +941 -0
  30. package/bin/runners/lib/cleanup/engine.js +571 -0
  31. package/bin/runners/lib/cleanup/index.js +53 -0
  32. package/bin/runners/lib/cleanup/output.js +375 -0
  33. package/bin/runners/lib/cleanup/rules.js +1060 -0
  34. package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
  35. package/bin/runners/lib/doctor/failure-signatures.js +526 -0
  36. package/bin/runners/lib/doctor/fix-script.js +336 -0
  37. package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
  38. package/bin/runners/lib/doctor/modules/index.js +62 -3
  39. package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
  40. package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
  41. package/bin/runners/lib/doctor/safe-repair.js +384 -0
  42. package/bin/runners/lib/engines/attack-detector.js +1192 -0
  43. package/bin/runners/lib/entitlements-v2.js +2 -2
  44. package/bin/runners/lib/missions/briefing.js +427 -0
  45. package/bin/runners/lib/missions/checkpoint.js +753 -0
  46. package/bin/runners/lib/missions/hardening.js +851 -0
  47. package/bin/runners/lib/missions/plan.js +421 -32
  48. package/bin/runners/lib/missions/safety-gates.js +645 -0
  49. package/bin/runners/lib/missions/schema.js +478 -0
  50. package/bin/runners/lib/packs/bundle.js +675 -0
  51. package/bin/runners/lib/packs/evidence-pack.js +671 -0
  52. package/bin/runners/lib/packs/pack-factory.js +837 -0
  53. package/bin/runners/lib/packs/permissions-pack.js +686 -0
  54. package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
  55. package/bin/runners/lib/safelist/index.js +96 -0
  56. package/bin/runners/lib/safelist/integration.js +334 -0
  57. package/bin/runners/lib/safelist/matcher.js +696 -0
  58. package/bin/runners/lib/safelist/schema.js +948 -0
  59. package/bin/runners/lib/safelist/store.js +438 -0
  60. package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
  61. package/bin/runners/lib/ship-gate.js +832 -0
  62. package/bin/runners/lib/ship-manifest.js +1153 -0
  63. package/bin/runners/lib/ship-output.js +1 -1
  64. package/bin/runners/lib/unified-cli-output.js +710 -383
  65. package/bin/runners/lib/upsell.js +3 -3
  66. package/bin/runners/lib/why-tree.js +650 -0
  67. package/bin/runners/runAllowlist.js +33 -4
  68. package/bin/runners/runApprove.js +240 -1122
  69. package/bin/runners/runAudit.js +692 -0
  70. package/bin/runners/runAuth.js +325 -29
  71. package/bin/runners/runCheckpoint.js +442 -494
  72. package/bin/runners/runCleanup.js +343 -0
  73. package/bin/runners/runDoctor.js +269 -19
  74. package/bin/runners/runFix.js +411 -32
  75. package/bin/runners/runForge.js +411 -0
  76. package/bin/runners/runIntent.js +906 -0
  77. package/bin/runners/runKickoff.js +878 -0
  78. package/bin/runners/runLaunch.js +2000 -0
  79. package/bin/runners/runLink.js +785 -0
  80. package/bin/runners/runMcp.js +1741 -837
  81. package/bin/runners/runPacks.js +2089 -0
  82. package/bin/runners/runPolish.js +41 -0
  83. package/bin/runners/runSafelist.js +1190 -0
  84. package/bin/runners/runScan.js +21 -9
  85. package/bin/runners/runShield.js +1282 -0
  86. package/bin/runners/runShip.js +395 -16
  87. package/bin/vibecheck.js +34 -6
  88. package/mcp-server/README.md +117 -158
  89. package/mcp-server/handlers/tool-handler.ts +3 -3
  90. package/mcp-server/index.js +16 -0
  91. package/mcp-server/intent-firewall-interceptor.js +529 -0
  92. package/mcp-server/manifest.json +473 -0
  93. package/mcp-server/package.json +1 -1
  94. package/mcp-server/registry/tool-registry.js +315 -523
  95. package/mcp-server/registry/tools.json +442 -428
  96. package/mcp-server/tier-auth.js +68 -11
  97. package/mcp-server/tools-v3.js +70 -16
  98. package/package.json +1 -1
  99. package/bin/runners/runProof.zip +0 -0
@@ -0,0 +1,779 @@
1
+ // bin/runners/lib/packs/proof-graph-pack.js
2
+ // ═══════════════════════════════════════════════════════════════════════════════
3
+ // PROOF GRAPH PACK - Proof graph with cross-links to receipts and visualization
4
+ // Interactive graph showing verification coverage and proof chains
5
+ // ═══════════════════════════════════════════════════════════════════════════════
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const {
10
+ createPackBuilder,
11
+ PACK_TYPE,
12
+ ARTIFACT_TYPE,
13
+ collectReceiptReferences,
14
+ loadReceipt,
15
+ } = require('./pack-factory');
16
+
17
+ // ═══════════════════════════════════════════════════════════════════════════════
18
+ // GRAPH DATA LOADING
19
+ // ═══════════════════════════════════════════════════════════════════════════════
20
+
21
+ /**
22
+ * Load proof graph from various sources
23
+ * @param {string} repoRoot - Repository root
24
+ * @returns {object|null} Proof graph data
25
+ */
26
+ function loadProofGraph(repoRoot) {
27
+ // Try primary location
28
+ const primaryPath = path.join(repoRoot, '.vibecheck/proof-graph.json');
29
+ if (fs.existsSync(primaryPath)) {
30
+ try {
31
+ return JSON.parse(fs.readFileSync(primaryPath, 'utf8'));
32
+ } catch (e) {
33
+ // Continue to fallback
34
+ }
35
+ }
36
+
37
+ // Try reality graph
38
+ const realityPath = path.join(repoRoot, '.vibecheck/reality-graph.json');
39
+ if (fs.existsSync(realityPath)) {
40
+ try {
41
+ return JSON.parse(fs.readFileSync(realityPath, 'utf8'));
42
+ } catch (e) {
43
+ // Continue to fallback
44
+ }
45
+ }
46
+
47
+ // Try to build from scan results
48
+ const scanPath = path.join(repoRoot, '.vibecheck/results/latest.json');
49
+ if (fs.existsSync(scanPath)) {
50
+ try {
51
+ const scan = JSON.parse(fs.readFileSync(scanPath, 'utf8'));
52
+ return buildGraphFromScan(scan);
53
+ } catch (e) {
54
+ // Return empty graph
55
+ }
56
+ }
57
+
58
+ return null;
59
+ }
60
+
61
+ /**
62
+ * Build basic graph from scan results
63
+ * @param {object} scan - Scan results
64
+ * @returns {object} Graph data
65
+ */
66
+ function buildGraphFromScan(scan) {
67
+ const nodes = [];
68
+ const edges = [];
69
+
70
+ // Create nodes from findings
71
+ const findings = scan.findings || scan.report?.findings || [];
72
+
73
+ for (const finding of findings) {
74
+ nodes.push({
75
+ id: finding.id,
76
+ type: 'finding',
77
+ label: finding.title || finding.message,
78
+ category: finding.category,
79
+ severity: finding.severity,
80
+ file: finding.file || finding.evidence?.[0]?.file,
81
+ status: 'unverified',
82
+ });
83
+ }
84
+
85
+ // Create file nodes
86
+ const files = new Set(findings.map(f => f.file || f.evidence?.[0]?.file).filter(Boolean));
87
+ for (const file of files) {
88
+ nodes.push({
89
+ id: `file:${file}`,
90
+ type: 'file',
91
+ label: path.basename(file),
92
+ path: file,
93
+ });
94
+ }
95
+
96
+ // Create edges from findings to files
97
+ for (const finding of findings) {
98
+ const file = finding.file || finding.evidence?.[0]?.file;
99
+ if (file) {
100
+ edges.push({
101
+ source: finding.id,
102
+ target: `file:${file}`,
103
+ type: 'located_in',
104
+ });
105
+ }
106
+ }
107
+
108
+ return {
109
+ nodes,
110
+ edges,
111
+ metadata: {
112
+ generatedFrom: 'scan',
113
+ findingCount: findings.length,
114
+ fileCount: files.size,
115
+ },
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Load proof runs from receipts
121
+ * @param {string} repoRoot - Repository root
122
+ * @returns {Array} Proof runs
123
+ */
124
+ function loadProofRuns(repoRoot) {
125
+ const runs = [];
126
+ const receiptsDir = path.join(repoRoot, '.vibecheck/receipts');
127
+
128
+ if (!fs.existsSync(receiptsDir)) return runs;
129
+
130
+ const dirs = fs.readdirSync(receiptsDir, { withFileTypes: true })
131
+ .filter(d => d.isDirectory())
132
+ .map(d => d.name);
133
+
134
+ for (const dir of dirs) {
135
+ const manifestPath = path.join(receiptsDir, dir, 'manifest.json');
136
+ if (fs.existsSync(manifestPath)) {
137
+ try {
138
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
139
+ runs.push({
140
+ runId: dir,
141
+ ...manifest,
142
+ });
143
+ } catch (e) {
144
+ // Skip invalid
145
+ }
146
+ }
147
+ }
148
+
149
+ return runs.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
150
+ }
151
+
152
+ // ═══════════════════════════════════════════════════════════════════════════════
153
+ // GRAPH ENHANCEMENT
154
+ // ═══════════════════════════════════════════════════════════════════════════════
155
+
156
+ /**
157
+ * Enhance graph with receipt cross-links
158
+ * @param {object} graph - Base graph
159
+ * @param {object} receipts - Receipt references
160
+ * @param {Array} proofRuns - Proof runs
161
+ * @returns {object} Enhanced graph
162
+ */
163
+ function enhanceGraphWithReceipts(graph, receipts, proofRuns) {
164
+ const enhanced = {
165
+ ...graph,
166
+ nodes: [...(graph.nodes || [])],
167
+ edges: [...(graph.edges || [])],
168
+ receipts: receipts || {},
169
+ proofRuns: proofRuns || [],
170
+ };
171
+
172
+ // Add receipt nodes
173
+ if (receipts.ship) {
174
+ enhanced.nodes.push({
175
+ id: 'receipt:ship',
176
+ type: 'receipt',
177
+ label: `Ship: ${receipts.ship.verdict || 'N/A'}`,
178
+ receiptType: 'ship',
179
+ verdict: receipts.ship.verdict,
180
+ timestamp: receipts.ship.timestamp,
181
+ });
182
+ }
183
+
184
+ if (receipts.reality) {
185
+ enhanced.nodes.push({
186
+ id: 'receipt:reality',
187
+ type: 'receipt',
188
+ label: `Reality: ${receipts.reality.verdict || 'N/A'}`,
189
+ receiptType: 'reality',
190
+ verdict: receipts.reality.verdict,
191
+ runId: receipts.reality.runId,
192
+ timestamp: receipts.reality.timestamp,
193
+ });
194
+ }
195
+
196
+ if (receipts.scan) {
197
+ enhanced.nodes.push({
198
+ id: 'receipt:scan',
199
+ type: 'receipt',
200
+ label: `Scan: ${receipts.scan.findingCount} findings`,
201
+ receiptType: 'scan',
202
+ findingCount: receipts.scan.findingCount,
203
+ timestamp: receipts.scan.timestamp,
204
+ });
205
+
206
+ // Link findings to scan receipt
207
+ for (const node of enhanced.nodes) {
208
+ if (node.type === 'finding') {
209
+ enhanced.edges.push({
210
+ source: node.id,
211
+ target: 'receipt:scan',
212
+ type: 'detected_by',
213
+ });
214
+ }
215
+ }
216
+ }
217
+
218
+ // Compute statistics
219
+ enhanced.statistics = computeGraphStats(enhanced);
220
+
221
+ return enhanced;
222
+ }
223
+
224
+ /**
225
+ * Compute graph statistics
226
+ * @param {object} graph - Graph data
227
+ * @returns {object} Statistics
228
+ */
229
+ function computeGraphStats(graph) {
230
+ const nodes = graph.nodes || [];
231
+ const edges = graph.edges || [];
232
+
233
+ const nodesByType = {};
234
+ for (const node of nodes) {
235
+ nodesByType[node.type] = (nodesByType[node.type] || 0) + 1;
236
+ }
237
+
238
+ const edgesByType = {};
239
+ for (const edge of edges) {
240
+ edgesByType[edge.type] = (edgesByType[edge.type] || 0) + 1;
241
+ }
242
+
243
+ const findingsBySeverity = {};
244
+ for (const node of nodes.filter(n => n.type === 'finding')) {
245
+ findingsBySeverity[node.severity] = (findingsBySeverity[node.severity] || 0) + 1;
246
+ }
247
+
248
+ return {
249
+ totalNodes: nodes.length,
250
+ totalEdges: edges.length,
251
+ nodesByType,
252
+ edgesByType,
253
+ findingsBySeverity,
254
+ verifiedNodes: nodes.filter(n => n.status === 'verified').length,
255
+ unverifiedNodes: nodes.filter(n => n.status === 'unverified').length,
256
+ };
257
+ }
258
+
259
+ // ═══════════════════════════════════════════════════════════════════════════════
260
+ // GRAPH FORMAT CONVERTERS
261
+ // ═══════════════════════════════════════════════════════════════════════════════
262
+
263
+ /**
264
+ * Convert graph to DOT format
265
+ * @param {object} graph - Graph data
266
+ * @returns {string} DOT content
267
+ */
268
+ function graphToDot(graph) {
269
+ const lines = ['digraph ProofGraph {'];
270
+ lines.push(' rankdir=LR;');
271
+ lines.push(' node [shape=box, style=filled];');
272
+ lines.push('');
273
+
274
+ // Node styles by type
275
+ const styles = {
276
+ finding: 'fillcolor="#ff7b7233", color="#ff7b72"',
277
+ file: 'fillcolor="#58a6ff33", color="#58a6ff"',
278
+ receipt: 'fillcolor="#3fb95033", color="#3fb950", shape=diamond',
279
+ route: 'fillcolor="#a371f733", color="#a371f7"',
280
+ component: 'fillcolor="#f778ba33", color="#f778ba"',
281
+ };
282
+
283
+ // Add nodes
284
+ for (const node of graph.nodes || []) {
285
+ const style = styles[node.type] || 'fillcolor="#8b949e33", color="#8b949e"';
286
+ const label = (node.label || node.id).replace(/"/g, '\\"').slice(0, 50);
287
+ lines.push(` "${node.id}" [label="${label}", ${style}];`);
288
+ }
289
+
290
+ lines.push('');
291
+
292
+ // Add edges
293
+ for (const edge of graph.edges || []) {
294
+ const label = edge.type || '';
295
+ lines.push(` "${edge.source}" -> "${edge.target}" [label="${label}"];`);
296
+ }
297
+
298
+ lines.push('}');
299
+
300
+ return lines.join('\n');
301
+ }
302
+
303
+ /**
304
+ * Convert graph to Mermaid format
305
+ * @param {object} graph - Graph data
306
+ * @returns {string} Mermaid content
307
+ */
308
+ function graphToMermaid(graph) {
309
+ const lines = ['flowchart LR'];
310
+
311
+ // Node shape by type
312
+ const shapes = {
313
+ finding: ['{{', '}}'],
314
+ file: ['[/', '/]'],
315
+ receipt: ['([', '])'],
316
+ route: ['[', ']'],
317
+ component: ['(', ')'],
318
+ };
319
+
320
+ // Add nodes
321
+ for (const node of graph.nodes || []) {
322
+ const [open, close] = shapes[node.type] || ['[', ']'];
323
+ const label = (node.label || node.id).replace(/"/g, '').slice(0, 40);
324
+ const id = node.id.replace(/[^a-zA-Z0-9]/g, '_');
325
+ lines.push(` ${id}${open}"${label}"${close}`);
326
+ }
327
+
328
+ lines.push('');
329
+
330
+ // Add edges
331
+ for (const edge of graph.edges || []) {
332
+ const sourceId = edge.source.replace(/[^a-zA-Z0-9]/g, '_');
333
+ const targetId = edge.target.replace(/[^a-zA-Z0-9]/g, '_');
334
+ const label = edge.type ? `|${edge.type}|` : '';
335
+ lines.push(` ${sourceId} -->${label} ${targetId}`);
336
+ }
337
+
338
+ return lines.join('\n');
339
+ }
340
+
341
+ // ═══════════════════════════════════════════════════════════════════════════════
342
+ // HTML VISUALIZATION
343
+ // ═══════════════════════════════════════════════════════════════════════════════
344
+
345
+ /**
346
+ * Generate interactive HTML visualization
347
+ * @param {object} graph - Enhanced graph data
348
+ * @returns {string} HTML content
349
+ */
350
+ function generateHtmlVisualization(graph) {
351
+ const stats = graph.statistics || {};
352
+ const mermaidCode = graphToMermaid(graph).replace(/`/g, '\\`');
353
+
354
+ const receiptCards = [];
355
+ if (graph.receipts?.ship) {
356
+ receiptCards.push(`
357
+ <div class="receipt-card ship">
358
+ <div class="receipt-icon">🚀</div>
359
+ <div class="receipt-info">
360
+ <div class="receipt-type">Ship Receipt</div>
361
+ <div class="receipt-verdict">${graph.receipts.ship.verdict || 'N/A'}</div>
362
+ <div class="receipt-time">${graph.receipts.ship.timestamp ? new Date(graph.receipts.ship.timestamp).toLocaleString() : 'N/A'}</div>
363
+ </div>
364
+ </div>
365
+ `);
366
+ }
367
+ if (graph.receipts?.reality) {
368
+ receiptCards.push(`
369
+ <div class="receipt-card reality">
370
+ <div class="receipt-icon">🔍</div>
371
+ <div class="receipt-info">
372
+ <div class="receipt-type">Reality Receipt</div>
373
+ <div class="receipt-verdict">${graph.receipts.reality.verdict || 'N/A'}</div>
374
+ <div class="receipt-time">Run: ${graph.receipts.reality.runId || 'N/A'}</div>
375
+ </div>
376
+ </div>
377
+ `);
378
+ }
379
+ if (graph.receipts?.scan) {
380
+ receiptCards.push(`
381
+ <div class="receipt-card scan">
382
+ <div class="receipt-icon">📊</div>
383
+ <div class="receipt-info">
384
+ <div class="receipt-type">Scan Receipt</div>
385
+ <div class="receipt-verdict">${graph.receipts.scan.findingCount} findings</div>
386
+ <div class="receipt-time">${graph.receipts.scan.timestamp ? new Date(graph.receipts.scan.timestamp).toLocaleString() : 'N/A'}</div>
387
+ </div>
388
+ </div>
389
+ `);
390
+ }
391
+
392
+ return `<!DOCTYPE html>
393
+ <html lang="en">
394
+ <head>
395
+ <meta charset="UTF-8">
396
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
397
+ <title>Proof Graph - Vibecheck</title>
398
+ <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
399
+ <style>
400
+ :root {
401
+ --bg: #0d1117;
402
+ --surface: #161b22;
403
+ --border: #30363d;
404
+ --text: #c9d1d9;
405
+ --text-muted: #8b949e;
406
+ --accent: #ff9650;
407
+ --success: #3fb950;
408
+ --warning: #d29922;
409
+ --danger: #f85149;
410
+ --info: #58a6ff;
411
+ }
412
+
413
+ * { box-sizing: border-box; margin: 0; padding: 0; }
414
+
415
+ body {
416
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
417
+ background: var(--bg);
418
+ color: var(--text);
419
+ line-height: 1.6;
420
+ }
421
+
422
+ .header {
423
+ background: var(--surface);
424
+ border-bottom: 1px solid var(--border);
425
+ padding: 1.5rem 2rem;
426
+ display: flex;
427
+ justify-content: space-between;
428
+ align-items: center;
429
+ }
430
+
431
+ h1 {
432
+ font-size: 1.5rem;
433
+ color: var(--accent);
434
+ }
435
+
436
+ .stats {
437
+ display: flex;
438
+ gap: 2rem;
439
+ }
440
+
441
+ .stat {
442
+ text-align: center;
443
+ }
444
+
445
+ .stat-value {
446
+ font-size: 1.5rem;
447
+ font-weight: bold;
448
+ }
449
+
450
+ .stat-label {
451
+ font-size: 0.75rem;
452
+ color: var(--text-muted);
453
+ text-transform: uppercase;
454
+ }
455
+
456
+ .container {
457
+ display: flex;
458
+ height: calc(100vh - 80px);
459
+ }
460
+
461
+ .sidebar {
462
+ width: 320px;
463
+ background: var(--surface);
464
+ border-right: 1px solid var(--border);
465
+ overflow-y: auto;
466
+ padding: 1rem;
467
+ }
468
+
469
+ .sidebar h2 {
470
+ font-size: 0.875rem;
471
+ color: var(--text-muted);
472
+ text-transform: uppercase;
473
+ letter-spacing: 0.05em;
474
+ margin-bottom: 1rem;
475
+ padding-bottom: 0.5rem;
476
+ border-bottom: 1px solid var(--border);
477
+ }
478
+
479
+ .receipt-card {
480
+ background: var(--bg);
481
+ border-radius: 8px;
482
+ padding: 1rem;
483
+ margin-bottom: 0.75rem;
484
+ display: flex;
485
+ gap: 1rem;
486
+ align-items: center;
487
+ border-left: 3px solid;
488
+ }
489
+
490
+ .receipt-card.ship { border-color: var(--success); }
491
+ .receipt-card.reality { border-color: var(--info); }
492
+ .receipt-card.scan { border-color: var(--warning); }
493
+
494
+ .receipt-icon {
495
+ font-size: 1.5rem;
496
+ }
497
+
498
+ .receipt-type {
499
+ font-size: 0.75rem;
500
+ color: var(--text-muted);
501
+ text-transform: uppercase;
502
+ }
503
+
504
+ .receipt-verdict {
505
+ font-weight: 600;
506
+ }
507
+
508
+ .receipt-time {
509
+ font-size: 0.75rem;
510
+ color: var(--text-muted);
511
+ }
512
+
513
+ .legend {
514
+ margin-top: 1.5rem;
515
+ }
516
+
517
+ .legend-item {
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 0.5rem;
521
+ margin-bottom: 0.5rem;
522
+ font-size: 0.875rem;
523
+ }
524
+
525
+ .legend-color {
526
+ width: 12px;
527
+ height: 12px;
528
+ border-radius: 3px;
529
+ }
530
+
531
+ .legend-color.finding { background: #ff7b72; }
532
+ .legend-color.file { background: #58a6ff; }
533
+ .legend-color.receipt { background: #3fb950; }
534
+ .legend-color.route { background: #a371f7; }
535
+
536
+ .main {
537
+ flex: 1;
538
+ overflow: auto;
539
+ padding: 2rem;
540
+ display: flex;
541
+ flex-direction: column;
542
+ align-items: center;
543
+ }
544
+
545
+ .mermaid {
546
+ background: var(--surface);
547
+ border-radius: 12px;
548
+ padding: 2rem;
549
+ min-width: 600px;
550
+ }
551
+
552
+ .controls {
553
+ margin-bottom: 1rem;
554
+ display: flex;
555
+ gap: 0.5rem;
556
+ }
557
+
558
+ button {
559
+ background: var(--surface);
560
+ color: var(--text);
561
+ border: 1px solid var(--border);
562
+ padding: 0.5rem 1rem;
563
+ border-radius: 6px;
564
+ cursor: pointer;
565
+ font-size: 0.875rem;
566
+ }
567
+
568
+ button:hover {
569
+ border-color: var(--accent);
570
+ }
571
+
572
+ .node-list {
573
+ max-height: 300px;
574
+ overflow-y: auto;
575
+ }
576
+
577
+ .node-item {
578
+ padding: 0.5rem;
579
+ border-radius: 4px;
580
+ margin-bottom: 0.25rem;
581
+ font-size: 0.8rem;
582
+ background: var(--bg);
583
+ cursor: pointer;
584
+ }
585
+
586
+ .node-item:hover {
587
+ border: 1px solid var(--accent);
588
+ }
589
+
590
+ .node-type {
591
+ font-size: 0.65rem;
592
+ color: var(--text-muted);
593
+ text-transform: uppercase;
594
+ }
595
+ </style>
596
+ </head>
597
+ <body>
598
+ <div class="header">
599
+ <h1>Proof Graph</h1>
600
+ <div class="stats">
601
+ <div class="stat">
602
+ <div class="stat-value">${stats.totalNodes || 0}</div>
603
+ <div class="stat-label">Nodes</div>
604
+ </div>
605
+ <div class="stat">
606
+ <div class="stat-value">${stats.totalEdges || 0}</div>
607
+ <div class="stat-label">Edges</div>
608
+ </div>
609
+ <div class="stat">
610
+ <div class="stat-value">${stats.findingsBySeverity?.BLOCK || 0}</div>
611
+ <div class="stat-label">Blocks</div>
612
+ </div>
613
+ <div class="stat">
614
+ <div class="stat-value">${stats.findingsBySeverity?.WARN || 0}</div>
615
+ <div class="stat-label">Warns</div>
616
+ </div>
617
+ </div>
618
+ </div>
619
+
620
+ <div class="container">
621
+ <div class="sidebar">
622
+ <h2>Cross-References</h2>
623
+ ${receiptCards.length > 0 ? receiptCards.join('') : '<p style="color: var(--text-muted); font-size: 0.875rem;">No receipts found</p>'}
624
+
625
+ <div class="legend">
626
+ <h2>Legend</h2>
627
+ <div class="legend-item">
628
+ <div class="legend-color finding"></div>
629
+ <span>Finding</span>
630
+ </div>
631
+ <div class="legend-item">
632
+ <div class="legend-color file"></div>
633
+ <span>File</span>
634
+ </div>
635
+ <div class="legend-item">
636
+ <div class="legend-color receipt"></div>
637
+ <span>Receipt</span>
638
+ </div>
639
+ <div class="legend-item">
640
+ <div class="legend-color route"></div>
641
+ <span>Route</span>
642
+ </div>
643
+ </div>
644
+
645
+ <h2 style="margin-top: 1.5rem;">Nodes (${(graph.nodes || []).length})</h2>
646
+ <div class="node-list">
647
+ ${(graph.nodes || []).slice(0, 50).map(n => `
648
+ <div class="node-item">
649
+ <div class="node-type">${n.type}</div>
650
+ <div>${(n.label || n.id).slice(0, 40)}</div>
651
+ </div>
652
+ `).join('')}
653
+ ${(graph.nodes || []).length > 50 ? `<p style="color: var(--text-muted); padding: 0.5rem;">... and ${(graph.nodes || []).length - 50} more</p>` : ''}
654
+ </div>
655
+ </div>
656
+
657
+ <div class="main">
658
+ <div class="controls">
659
+ <button onclick="location.reload()">Refresh</button>
660
+ <button onclick="downloadSVG()">Download SVG</button>
661
+ </div>
662
+
663
+ <pre class="mermaid">
664
+ ${mermaidCode}
665
+ </pre>
666
+ </div>
667
+ </div>
668
+
669
+ <script>
670
+ mermaid.initialize({
671
+ startOnLoad: true,
672
+ theme: 'dark',
673
+ securityLevel: 'loose',
674
+ });
675
+
676
+ function downloadSVG() {
677
+ const svg = document.querySelector('.mermaid svg');
678
+ if (svg) {
679
+ const svgData = new XMLSerializer().serializeToString(svg);
680
+ const blob = new Blob([svgData], { type: 'image/svg+xml' });
681
+ const url = URL.createObjectURL(blob);
682
+ const a = document.createElement('a');
683
+ a.href = url;
684
+ a.download = 'proof-graph.svg';
685
+ a.click();
686
+ URL.revokeObjectURL(url);
687
+ }
688
+ }
689
+
690
+ // Embedded graph data
691
+ window.PROOF_GRAPH = ${JSON.stringify(graph, null, 2)};
692
+ </script>
693
+ </body>
694
+ </html>`;
695
+ }
696
+
697
+ // ═══════════════════════════════════════════════════════════════════════════════
698
+ // PACK BUILDER
699
+ // ═══════════════════════════════════════════════════════════════════════════════
700
+
701
+ /**
702
+ * Build proof graph pack
703
+ * @param {string} repoRoot - Repository root
704
+ * @param {object} options - Build options
705
+ * @returns {object} Build result
706
+ */
707
+ async function buildProofGraphPack(repoRoot, options = {}) {
708
+ const { outputDir, createZip = false } = options;
709
+
710
+ // Load graph data
711
+ let graph = loadProofGraph(repoRoot);
712
+
713
+ if (!graph) {
714
+ graph = { nodes: [], edges: [], metadata: { empty: true } };
715
+ }
716
+
717
+ // Load receipts and proof runs
718
+ const receipts = collectReceiptReferences(repoRoot);
719
+ const proofRuns = loadProofRuns(repoRoot);
720
+
721
+ // Enhance graph
722
+ const enhancedGraph = enhanceGraphWithReceipts(graph, receipts, proofRuns);
723
+
724
+ // Generate formats
725
+ const dotFormat = graphToDot(enhancedGraph);
726
+ const mermaidFormat = graphToMermaid(enhancedGraph);
727
+ const htmlVisualization = generateHtmlVisualization(enhancedGraph);
728
+
729
+ // Build pack
730
+ const builder = createPackBuilder(PACK_TYPE.PROOF_GRAPH, repoRoot);
731
+
732
+ if (outputDir) {
733
+ builder.outputTo(outputDir);
734
+ }
735
+
736
+ builder
737
+ .addArtifact('proof-graph.json', JSON.stringify(enhancedGraph, null, 2), ARTIFACT_TYPE.JSON, 'Enhanced proof graph data')
738
+ .addArtifact('proof-graph.dot', dotFormat, ARTIFACT_TYPE.DOT, 'Graphviz DOT format')
739
+ .addArtifact('proof-graph.mmd', mermaidFormat, ARTIFACT_TYPE.MERMAID, 'Mermaid diagram format')
740
+ .addArtifact('visualization.html', htmlVisualization, ARTIFACT_TYPE.HTML, 'Interactive graph visualization')
741
+ .withMetadata({
742
+ nodeCount: enhancedGraph.nodes?.length || 0,
743
+ edgeCount: enhancedGraph.edges?.length || 0,
744
+ hasShipReceipt: !!receipts.ship,
745
+ hasRealityReceipt: !!receipts.reality,
746
+ hasScanReceipt: !!receipts.scan,
747
+ proofRunCount: proofRuns.length,
748
+ statistics: enhancedGraph.statistics,
749
+ });
750
+
751
+ if (createZip) {
752
+ return builder.buildZip();
753
+ }
754
+
755
+ return builder.build();
756
+ }
757
+
758
+ // ═══════════════════════════════════════════════════════════════════════════════
759
+ // EXPORTS
760
+ // ═══════════════════════════════════════════════════════════════════════════════
761
+
762
+ module.exports = {
763
+ // Loading
764
+ loadProofGraph,
765
+ loadProofRuns,
766
+ buildGraphFromScan,
767
+
768
+ // Enhancement
769
+ enhanceGraphWithReceipts,
770
+ computeGraphStats,
771
+
772
+ // Formatters
773
+ graphToDot,
774
+ graphToMermaid,
775
+ generateHtmlVisualization,
776
+
777
+ // Builder
778
+ buildProofGraphPack,
779
+ };