@vibecheckai/cli 3.5.0 → 3.5.2
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/registry.js +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/scan-output.js +91 -76
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -1,100 +1,114 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTML Proof Report Generator
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Creates a beautiful, self-contained HTML report that proves your app works.
|
|
5
|
+
* Includes embedded video, network evidence, and clear verdicts.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* - Embedded video/screenshot evidence
|
|
9
|
-
* - Interactive findings browser
|
|
10
|
-
* - Vibe score visualization
|
|
11
|
-
* - Export to PDF capability
|
|
12
|
-
* - Dark/light mode support
|
|
7
|
+
* Designed to be shareable: drop into a PR, CI artifact, or send to stakeholders.
|
|
13
8
|
*/
|
|
14
9
|
|
|
15
10
|
"use strict";
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
16
|
+
// HTML TEMPLATE
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
|
|
19
|
+
function escapeHtml(str) {
|
|
20
|
+
if (!str) return '';
|
|
21
|
+
return String(str)
|
|
22
|
+
.replace(/&/g, '&')
|
|
23
|
+
.replace(/</g, '<')
|
|
24
|
+
.replace(/>/g, '>')
|
|
25
|
+
.replace(/"/g, '"')
|
|
26
|
+
.replace(/'/g, ''');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatDuration(ms) {
|
|
30
|
+
if (!ms) return '0s';
|
|
31
|
+
if (ms < 1000) return `${ms}ms`;
|
|
32
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
33
|
+
const mins = Math.floor(ms / 60000);
|
|
34
|
+
const secs = Math.floor((ms % 60000) / 1000);
|
|
35
|
+
return `${mins}m ${secs}s`;
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
function generateHtmlProofReport(data) {
|
|
23
39
|
const {
|
|
24
|
-
verdict
|
|
25
|
-
projectName
|
|
26
|
-
url
|
|
27
|
-
startedAt
|
|
28
|
-
finishedAt
|
|
29
|
-
durationMs
|
|
40
|
+
verdict,
|
|
41
|
+
projectName,
|
|
42
|
+
url,
|
|
43
|
+
startedAt,
|
|
44
|
+
finishedAt,
|
|
45
|
+
durationMs,
|
|
30
46
|
findings = [],
|
|
31
47
|
coverage = null,
|
|
32
48
|
passes = {},
|
|
33
|
-
artifacts =
|
|
49
|
+
artifacts = {},
|
|
34
50
|
meta = {},
|
|
35
|
-
stabilityRuns = 1
|
|
51
|
+
stabilityRuns = 1
|
|
36
52
|
} = data;
|
|
37
53
|
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
// Calculate duration string
|
|
42
|
-
const durationSec = Math.round(durationMs / 1000);
|
|
43
|
-
const durationStr = durationSec < 60 ? `${durationSec}s` : `${Math.floor(durationSec / 60)}m ${durationSec % 60}s`;
|
|
54
|
+
const blocks = findings.filter(f => f.severity === 'BLOCK').length;
|
|
55
|
+
const warns = findings.filter(f => f.severity === 'WARN').length;
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
WARN: {
|
|
49
|
-
BLOCK: {
|
|
57
|
+
const verdictConfig = {
|
|
58
|
+
SHIP: { emoji: '✅', color: '#10b981', bg: '#ecfdf5', label: 'PROVED REAL' },
|
|
59
|
+
CLEAN: { emoji: '✅', color: '#10b981', bg: '#ecfdf5', label: 'PROVED REAL' },
|
|
60
|
+
WARN: { emoji: '⚠️', color: '#f59e0b', bg: '#fffbeb', label: 'WARNINGS FOUND' },
|
|
61
|
+
BLOCK: { emoji: '🛑', color: '#ef4444', bg: '#fef2f2', label: 'NOT PROVED' }
|
|
50
62
|
};
|
|
51
|
-
const vc = verdictColors[verdict] || verdictColors.BLOCK;
|
|
52
63
|
|
|
64
|
+
const v = verdictConfig[verdict] || verdictConfig.BLOCK;
|
|
65
|
+
|
|
66
|
+
// Group findings by category
|
|
67
|
+
const findingsByCategory = {};
|
|
68
|
+
for (const f of findings) {
|
|
69
|
+
const cat = f.category || 'Other';
|
|
70
|
+
if (!findingsByCategory[cat]) findingsByCategory[cat] = [];
|
|
71
|
+
findingsByCategory[cat].push(f);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Build HTML
|
|
53
75
|
return `<!DOCTYPE html>
|
|
54
76
|
<html lang="en">
|
|
55
77
|
<head>
|
|
56
78
|
<meta charset="UTF-8">
|
|
57
79
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
58
|
-
<title>
|
|
80
|
+
<title>Reality Proof: ${escapeHtml(projectName)}</title>
|
|
59
81
|
<style>
|
|
60
82
|
:root {
|
|
61
|
-
--
|
|
62
|
-
--bg
|
|
63
|
-
--
|
|
64
|
-
--text-primary: #f1f5f9;
|
|
65
|
-
--text-secondary: #94a3b8;
|
|
66
|
-
--text-muted: #64748b;
|
|
67
|
-
--accent: #8b5cf6;
|
|
83
|
+
--verdict-color: ${v.color};
|
|
84
|
+
--verdict-bg: ${v.bg};
|
|
85
|
+
--primary: #6366f1;
|
|
68
86
|
--success: #10b981;
|
|
69
87
|
--warning: #f59e0b;
|
|
70
|
-
--
|
|
71
|
-
--
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
--text-muted: #94a3b8;
|
|
82
|
-
--border: #e2e8f0;
|
|
83
|
-
}
|
|
88
|
+
--danger: #ef4444;
|
|
89
|
+
--gray-50: #f9fafb;
|
|
90
|
+
--gray-100: #f3f4f6;
|
|
91
|
+
--gray-200: #e5e7eb;
|
|
92
|
+
--gray-300: #d1d5db;
|
|
93
|
+
--gray-400: #9ca3af;
|
|
94
|
+
--gray-500: #6b7280;
|
|
95
|
+
--gray-600: #4b5563;
|
|
96
|
+
--gray-700: #374151;
|
|
97
|
+
--gray-800: #1f2937;
|
|
98
|
+
--gray-900: #111827;
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
* {
|
|
87
|
-
box-sizing: border-box;
|
|
88
102
|
margin: 0;
|
|
89
103
|
padding: 0;
|
|
104
|
+
box-sizing: border-box;
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
body {
|
|
93
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
94
|
-
background: var(--
|
|
95
|
-
color: var(--
|
|
108
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
109
|
+
background: var(--gray-50);
|
|
110
|
+
color: var(--gray-800);
|
|
96
111
|
line-height: 1.6;
|
|
97
|
-
min-height: 100vh;
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
.container {
|
|
@@ -107,252 +121,394 @@ function generateHtmlProofReport(data) {
|
|
|
107
121
|
.header {
|
|
108
122
|
text-align: center;
|
|
109
123
|
margin-bottom: 3rem;
|
|
110
|
-
padding-bottom: 2rem;
|
|
111
|
-
border-bottom: 1px solid var(--border);
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
.logo {
|
|
115
127
|
font-size: 2rem;
|
|
116
|
-
font-weight:
|
|
117
|
-
|
|
118
|
-
-webkit-background-clip: text;
|
|
119
|
-
-webkit-text-fill-color: transparent;
|
|
120
|
-
background-clip: text;
|
|
128
|
+
font-weight: 700;
|
|
129
|
+
color: var(--primary);
|
|
121
130
|
margin-bottom: 0.5rem;
|
|
122
131
|
}
|
|
123
132
|
|
|
124
|
-
.
|
|
125
|
-
color: var(--
|
|
126
|
-
font-
|
|
133
|
+
.logo span {
|
|
134
|
+
color: var(--gray-400);
|
|
135
|
+
font-weight: 400;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
.
|
|
130
|
-
|
|
131
|
-
font-
|
|
132
|
-
margin-top: 1rem;
|
|
133
|
-
color: var(--text-primary);
|
|
138
|
+
.subtitle {
|
|
139
|
+
color: var(--gray-500);
|
|
140
|
+
font-size: 1rem;
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
/* Verdict Card */
|
|
137
144
|
.verdict-card {
|
|
138
|
-
background:
|
|
139
|
-
color: ${vc.text};
|
|
140
|
-
border: 2px solid ${vc.border};
|
|
145
|
+
background: white;
|
|
141
146
|
border-radius: 16px;
|
|
142
|
-
|
|
147
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
148
|
+
padding: 2.5rem;
|
|
143
149
|
text-align: center;
|
|
144
150
|
margin-bottom: 2rem;
|
|
145
|
-
|
|
151
|
+
border: 2px solid var(--verdict-color);
|
|
146
152
|
}
|
|
147
153
|
|
|
148
|
-
.verdict-
|
|
154
|
+
.verdict-emoji {
|
|
149
155
|
font-size: 4rem;
|
|
150
156
|
margin-bottom: 1rem;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
|
-
.verdict-
|
|
154
|
-
font-size:
|
|
155
|
-
font-weight:
|
|
156
|
-
|
|
159
|
+
.verdict-label {
|
|
160
|
+
font-size: 1.75rem;
|
|
161
|
+
font-weight: 700;
|
|
162
|
+
color: var(--verdict-color);
|
|
157
163
|
margin-bottom: 0.5rem;
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
.verdict-
|
|
161
|
-
|
|
162
|
-
|
|
166
|
+
.verdict-desc {
|
|
167
|
+
color: var(--gray-500);
|
|
168
|
+
margin-bottom: 1.5rem;
|
|
163
169
|
}
|
|
164
170
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
margin-bottom: 3rem;
|
|
171
|
+
.verdict-stats {
|
|
172
|
+
display: flex;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
gap: 3rem;
|
|
175
|
+
flex-wrap: wrap;
|
|
171
176
|
}
|
|
172
177
|
|
|
173
|
-
.stat
|
|
174
|
-
background: var(--bg-secondary);
|
|
175
|
-
border: 1px solid var(--border);
|
|
176
|
-
border-radius: 12px;
|
|
177
|
-
padding: 1.5rem;
|
|
178
|
+
.stat {
|
|
178
179
|
text-align: center;
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
.stat-value {
|
|
182
|
-
font-size:
|
|
183
|
+
font-size: 2rem;
|
|
183
184
|
font-weight: 700;
|
|
184
|
-
color: var(--
|
|
185
|
+
color: var(--gray-800);
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
.stat-value.
|
|
188
|
+
.stat-value.danger { color: var(--danger); }
|
|
188
189
|
.stat-value.warning { color: var(--warning); }
|
|
189
|
-
.stat-value.
|
|
190
|
+
.stat-value.success { color: var(--success); }
|
|
190
191
|
|
|
191
192
|
.stat-label {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
font-size: 0.875rem;
|
|
194
|
+
color: var(--gray-500);
|
|
195
|
+
text-transform: uppercase;
|
|
196
|
+
letter-spacing: 0.05em;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Meta Info */
|
|
200
|
+
.meta-grid {
|
|
201
|
+
display: grid;
|
|
202
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
203
|
+
gap: 1rem;
|
|
204
|
+
margin-bottom: 2rem;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.meta-item {
|
|
208
|
+
background: white;
|
|
209
|
+
border-radius: 8px;
|
|
210
|
+
padding: 1rem;
|
|
211
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.meta-label {
|
|
215
|
+
font-size: 0.75rem;
|
|
216
|
+
color: var(--gray-500);
|
|
217
|
+
text-transform: uppercase;
|
|
218
|
+
letter-spacing: 0.05em;
|
|
219
|
+
margin-bottom: 0.25rem;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.meta-value {
|
|
223
|
+
font-weight: 600;
|
|
224
|
+
color: var(--gray-800);
|
|
225
|
+
word-break: break-all;
|
|
195
226
|
}
|
|
196
227
|
|
|
197
228
|
/* Sections */
|
|
198
229
|
.section {
|
|
199
|
-
background:
|
|
200
|
-
border: 1px solid var(--border);
|
|
230
|
+
background: white;
|
|
201
231
|
border-radius: 12px;
|
|
202
|
-
|
|
232
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
203
233
|
margin-bottom: 2rem;
|
|
234
|
+
overflow: hidden;
|
|
204
235
|
}
|
|
205
236
|
|
|
206
237
|
.section-header {
|
|
238
|
+
background: var(--gray-50);
|
|
239
|
+
padding: 1rem 1.5rem;
|
|
240
|
+
border-bottom: 1px solid var(--gray-200);
|
|
207
241
|
display: flex;
|
|
208
242
|
align-items: center;
|
|
209
243
|
gap: 0.75rem;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.section-icon {
|
|
210
247
|
font-size: 1.25rem;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.section-title {
|
|
211
251
|
font-weight: 600;
|
|
212
|
-
|
|
213
|
-
padding-bottom: 1rem;
|
|
214
|
-
border-bottom: 1px solid var(--border);
|
|
252
|
+
color: var(--gray-800);
|
|
215
253
|
}
|
|
216
254
|
|
|
217
|
-
.section-
|
|
218
|
-
|
|
255
|
+
.section-badge {
|
|
256
|
+
margin-left: auto;
|
|
257
|
+
background: var(--gray-200);
|
|
258
|
+
color: var(--gray-600);
|
|
259
|
+
padding: 0.25rem 0.75rem;
|
|
260
|
+
border-radius: 999px;
|
|
261
|
+
font-size: 0.75rem;
|
|
262
|
+
font-weight: 500;
|
|
219
263
|
}
|
|
220
264
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
265
|
+
.section-body {
|
|
266
|
+
padding: 1.5rem;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Evidence Section */
|
|
270
|
+
.evidence-grid {
|
|
271
|
+
display: grid;
|
|
225
272
|
gap: 1rem;
|
|
226
273
|
}
|
|
227
274
|
|
|
228
|
-
.
|
|
229
|
-
|
|
275
|
+
.evidence-row {
|
|
276
|
+
display: grid;
|
|
277
|
+
grid-template-columns: 1fr 1fr;
|
|
278
|
+
gap: 1rem;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@media (max-width: 768px) {
|
|
282
|
+
.evidence-row {
|
|
283
|
+
grid-template-columns: 1fr;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* Video Player */
|
|
288
|
+
.video-container {
|
|
289
|
+
background: var(--gray-900);
|
|
230
290
|
border-radius: 8px;
|
|
231
|
-
|
|
232
|
-
|
|
291
|
+
overflow: hidden;
|
|
292
|
+
aspect-ratio: 16/9;
|
|
233
293
|
}
|
|
234
294
|
|
|
235
|
-
.
|
|
236
|
-
|
|
295
|
+
.video-container video {
|
|
296
|
+
width: 100%;
|
|
297
|
+
height: 100%;
|
|
298
|
+
object-fit: contain;
|
|
299
|
+
}
|
|
237
300
|
|
|
238
|
-
.
|
|
301
|
+
.video-label {
|
|
302
|
+
text-align: center;
|
|
303
|
+
padding: 0.75rem;
|
|
304
|
+
background: var(--gray-100);
|
|
305
|
+
font-size: 0.875rem;
|
|
306
|
+
color: var(--gray-600);
|
|
307
|
+
font-weight: 500;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.video-placeholder {
|
|
239
311
|
display: flex;
|
|
240
|
-
justify-content: space-between;
|
|
241
312
|
align-items: center;
|
|
242
|
-
|
|
313
|
+
justify-content: center;
|
|
314
|
+
height: 200px;
|
|
315
|
+
background: var(--gray-100);
|
|
316
|
+
color: var(--gray-400);
|
|
317
|
+
font-size: 0.875rem;
|
|
243
318
|
}
|
|
244
319
|
|
|
245
|
-
|
|
246
|
-
|
|
320
|
+
/* Network Evidence */
|
|
321
|
+
.network-table {
|
|
322
|
+
width: 100%;
|
|
323
|
+
border-collapse: collapse;
|
|
247
324
|
}
|
|
248
325
|
|
|
249
|
-
.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
326
|
+
.network-table th,
|
|
327
|
+
.network-table td {
|
|
328
|
+
padding: 0.75rem;
|
|
329
|
+
text-align: left;
|
|
330
|
+
border-bottom: 1px solid var(--gray-200);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.network-table th {
|
|
334
|
+
background: var(--gray-50);
|
|
253
335
|
font-weight: 600;
|
|
336
|
+
font-size: 0.75rem;
|
|
254
337
|
text-transform: uppercase;
|
|
338
|
+
letter-spacing: 0.05em;
|
|
339
|
+
color: var(--gray-500);
|
|
255
340
|
}
|
|
256
341
|
|
|
257
|
-
.
|
|
258
|
-
|
|
259
|
-
color: white;
|
|
342
|
+
.network-table td {
|
|
343
|
+
font-size: 0.875rem;
|
|
260
344
|
}
|
|
261
345
|
|
|
262
|
-
.
|
|
263
|
-
|
|
264
|
-
|
|
346
|
+
.status-badge {
|
|
347
|
+
display: inline-flex;
|
|
348
|
+
align-items: center;
|
|
349
|
+
gap: 0.25rem;
|
|
350
|
+
padding: 0.125rem 0.5rem;
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
font-size: 0.75rem;
|
|
353
|
+
font-weight: 500;
|
|
265
354
|
}
|
|
266
355
|
|
|
267
|
-
.
|
|
268
|
-
|
|
269
|
-
|
|
356
|
+
.status-badge.success { background: #dcfce7; color: #166534; }
|
|
357
|
+
.status-badge.warning { background: #fef3c7; color: #92400e; }
|
|
358
|
+
.status-badge.error { background: #fee2e2; color: #991b1b; }
|
|
359
|
+
|
|
360
|
+
/* Findings */
|
|
361
|
+
.finding-card {
|
|
362
|
+
border: 1px solid var(--gray-200);
|
|
363
|
+
border-radius: 8px;
|
|
364
|
+
margin-bottom: 1rem;
|
|
365
|
+
overflow: hidden;
|
|
270
366
|
}
|
|
271
367
|
|
|
272
|
-
.finding-
|
|
273
|
-
|
|
274
|
-
font-size: 0.85rem;
|
|
275
|
-
font-family: monospace;
|
|
276
|
-
margin-top: 0.5rem;
|
|
368
|
+
.finding-card.block {
|
|
369
|
+
border-left: 4px solid var(--danger);
|
|
277
370
|
}
|
|
278
371
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
height: 24px;
|
|
282
|
-
background: var(--bg-tertiary);
|
|
283
|
-
border-radius: 12px;
|
|
284
|
-
overflow: hidden;
|
|
285
|
-
margin: 1rem 0;
|
|
372
|
+
.finding-card.warn {
|
|
373
|
+
border-left: 4px solid var(--warning);
|
|
286
374
|
}
|
|
287
375
|
|
|
288
|
-
.
|
|
289
|
-
|
|
290
|
-
background:
|
|
291
|
-
|
|
292
|
-
|
|
376
|
+
.finding-header {
|
|
377
|
+
padding: 1rem;
|
|
378
|
+
background: var(--gray-50);
|
|
379
|
+
display: flex;
|
|
380
|
+
align-items: flex-start;
|
|
381
|
+
gap: 0.75rem;
|
|
293
382
|
}
|
|
294
383
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
384
|
+
.finding-severity {
|
|
385
|
+
flex-shrink: 0;
|
|
386
|
+
padding: 0.25rem 0.5rem;
|
|
387
|
+
border-radius: 4px;
|
|
388
|
+
font-size: 0.75rem;
|
|
389
|
+
font-weight: 700;
|
|
390
|
+
text-transform: uppercase;
|
|
300
391
|
}
|
|
301
392
|
|
|
302
|
-
.
|
|
303
|
-
|
|
304
|
-
|
|
393
|
+
.finding-severity.block { background: #fee2e2; color: #991b1b; }
|
|
394
|
+
.finding-severity.warn { background: #fef3c7; color: #92400e; }
|
|
395
|
+
|
|
396
|
+
.finding-title {
|
|
397
|
+
font-weight: 600;
|
|
398
|
+
color: var(--gray-800);
|
|
399
|
+
flex: 1;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.finding-body {
|
|
305
403
|
padding: 1rem;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.finding-meta {
|
|
306
407
|
display: flex;
|
|
307
|
-
|
|
408
|
+
flex-wrap: wrap;
|
|
308
409
|
gap: 1rem;
|
|
410
|
+
font-size: 0.875rem;
|
|
411
|
+
color: var(--gray-500);
|
|
412
|
+
margin-bottom: 0.75rem;
|
|
309
413
|
}
|
|
310
414
|
|
|
311
|
-
.
|
|
312
|
-
|
|
415
|
+
.finding-reason {
|
|
416
|
+
color: var(--gray-600);
|
|
313
417
|
}
|
|
314
418
|
|
|
315
|
-
.
|
|
316
|
-
|
|
419
|
+
.finding-screenshot {
|
|
420
|
+
margin-top: 1rem;
|
|
421
|
+
border-radius: 8px;
|
|
422
|
+
overflow: hidden;
|
|
423
|
+
border: 1px solid var(--gray-200);
|
|
317
424
|
}
|
|
318
425
|
|
|
319
|
-
.
|
|
320
|
-
|
|
426
|
+
.finding-screenshot img {
|
|
427
|
+
width: 100%;
|
|
428
|
+
height: auto;
|
|
429
|
+
display: block;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/* Coverage */
|
|
433
|
+
.coverage-bar {
|
|
434
|
+
height: 12px;
|
|
435
|
+
background: var(--gray-200);
|
|
436
|
+
border-radius: 6px;
|
|
437
|
+
overflow: hidden;
|
|
438
|
+
margin-bottom: 0.5rem;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.coverage-fill {
|
|
442
|
+
height: 100%;
|
|
443
|
+
background: linear-gradient(90deg, var(--primary), #818cf8);
|
|
444
|
+
border-radius: 6px;
|
|
445
|
+
transition: width 0.3s ease;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.coverage-label {
|
|
449
|
+
display: flex;
|
|
450
|
+
justify-content: space-between;
|
|
451
|
+
font-size: 0.875rem;
|
|
452
|
+
color: var(--gray-600);
|
|
321
453
|
}
|
|
322
454
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
455
|
+
/* Trace Link */
|
|
456
|
+
.trace-link {
|
|
457
|
+
display: inline-flex;
|
|
458
|
+
align-items: center;
|
|
459
|
+
gap: 0.5rem;
|
|
460
|
+
padding: 0.75rem 1rem;
|
|
461
|
+
background: var(--gray-100);
|
|
462
|
+
border-radius: 8px;
|
|
463
|
+
color: var(--primary);
|
|
464
|
+
text-decoration: none;
|
|
465
|
+
font-weight: 500;
|
|
466
|
+
transition: background 0.2s;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.trace-link:hover {
|
|
470
|
+
background: var(--gray-200);
|
|
327
471
|
}
|
|
328
472
|
|
|
329
473
|
/* Footer */
|
|
330
474
|
.footer {
|
|
331
475
|
text-align: center;
|
|
332
|
-
padding
|
|
333
|
-
|
|
334
|
-
|
|
476
|
+
padding: 2rem;
|
|
477
|
+
color: var(--gray-400);
|
|
478
|
+
font-size: 0.875rem;
|
|
335
479
|
}
|
|
336
480
|
|
|
337
481
|
.footer a {
|
|
338
|
-
color: var(--
|
|
482
|
+
color: var(--primary);
|
|
339
483
|
text-decoration: none;
|
|
340
484
|
}
|
|
341
485
|
|
|
342
|
-
.footer a:hover {
|
|
343
|
-
text-decoration: underline;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
486
|
/* Print styles */
|
|
347
487
|
@media print {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
488
|
+
.video-container { display: none; }
|
|
489
|
+
.container { max-width: 100%; padding: 1rem; }
|
|
490
|
+
.section { break-inside: avoid; }
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/* Collapsible */
|
|
494
|
+
.collapsible-header {
|
|
495
|
+
cursor: pointer;
|
|
496
|
+
user-select: none;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.collapsible-header::after {
|
|
500
|
+
content: '▼';
|
|
501
|
+
font-size: 0.75rem;
|
|
502
|
+
margin-left: 0.5rem;
|
|
503
|
+
transition: transform 0.2s;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.collapsible.collapsed .collapsible-header::after {
|
|
507
|
+
transform: rotate(-90deg);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.collapsible.collapsed .collapsible-body {
|
|
511
|
+
display: none;
|
|
356
512
|
}
|
|
357
513
|
</style>
|
|
358
514
|
</head>
|
|
@@ -360,204 +516,398 @@ function generateHtmlProofReport(data) {
|
|
|
360
516
|
<div class="container">
|
|
361
517
|
<!-- Header -->
|
|
362
518
|
<header class="header">
|
|
363
|
-
<div class="logo">vibecheck</div>
|
|
364
|
-
<
|
|
365
|
-
<div class="project-name">${escapeHtml(projectName)}</div>
|
|
366
|
-
${url ? `<div style="color: var(--text-muted); margin-top: 0.5rem;">${escapeHtml(url)}</div>` : ''}
|
|
519
|
+
<div class="logo">vibecheck <span>prove</span></div>
|
|
520
|
+
<p class="subtitle">Reality Proof Report</p>
|
|
367
521
|
</header>
|
|
368
522
|
|
|
369
523
|
<!-- Verdict Card -->
|
|
370
524
|
<div class="verdict-card">
|
|
371
|
-
<div class="verdict-
|
|
372
|
-
<div class="verdict-
|
|
373
|
-
<
|
|
525
|
+
<div class="verdict-emoji">${v.emoji}</div>
|
|
526
|
+
<div class="verdict-label">${v.label}</div>
|
|
527
|
+
<p class="verdict-desc">
|
|
528
|
+
${verdict === 'SHIP' || verdict === 'CLEAN'
|
|
529
|
+
? 'Your app has been verified with real user flows hitting real APIs.'
|
|
530
|
+
: verdict === 'WARN'
|
|
531
|
+
? 'Verification complete with warnings. Review before shipping.'
|
|
532
|
+
: 'Verification failed. Critical issues must be fixed.'}
|
|
533
|
+
</p>
|
|
534
|
+
<div class="verdict-stats">
|
|
535
|
+
<div class="stat">
|
|
536
|
+
<div class="stat-value ${blocks > 0 ? 'danger' : 'success'}">${blocks}</div>
|
|
537
|
+
<div class="stat-label">Blockers</div>
|
|
538
|
+
</div>
|
|
539
|
+
<div class="stat">
|
|
540
|
+
<div class="stat-value ${warns > 0 ? 'warning' : 'success'}">${warns}</div>
|
|
541
|
+
<div class="stat-label">Warnings</div>
|
|
542
|
+
</div>
|
|
543
|
+
<div class="stat">
|
|
544
|
+
<div class="stat-value">${coverage?.percent || 0}%</div>
|
|
545
|
+
<div class="stat-label">Coverage</div>
|
|
546
|
+
</div>
|
|
547
|
+
<div class="stat">
|
|
548
|
+
<div class="stat-value">${formatDuration(durationMs)}</div>
|
|
549
|
+
<div class="stat-label">Duration</div>
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
374
552
|
</div>
|
|
375
553
|
|
|
376
|
-
<!--
|
|
377
|
-
<div class="
|
|
378
|
-
<div class="
|
|
379
|
-
<div class="
|
|
380
|
-
<div class="
|
|
381
|
-
</div>
|
|
382
|
-
<div class="stat-card">
|
|
383
|
-
<div class="stat-value ${warnings.length > 0 ? 'warning' : 'success'}">${warnings.length}</div>
|
|
384
|
-
<div class="stat-label">Warnings</div>
|
|
554
|
+
<!-- Meta Info -->
|
|
555
|
+
<div class="meta-grid">
|
|
556
|
+
<div class="meta-item">
|
|
557
|
+
<div class="meta-label">Project</div>
|
|
558
|
+
<div class="meta-value">${escapeHtml(projectName)}</div>
|
|
385
559
|
</div>
|
|
386
|
-
<div class="
|
|
387
|
-
<div class="
|
|
388
|
-
<div class="
|
|
560
|
+
<div class="meta-item">
|
|
561
|
+
<div class="meta-label">URL Tested</div>
|
|
562
|
+
<div class="meta-value">${escapeHtml(url || 'N/A')}</div>
|
|
389
563
|
</div>
|
|
390
|
-
<div class="
|
|
391
|
-
<div class="
|
|
392
|
-
<div class="
|
|
564
|
+
<div class="meta-item">
|
|
565
|
+
<div class="meta-label">Tested At</div>
|
|
566
|
+
<div class="meta-value">${new Date(startedAt).toLocaleString()}</div>
|
|
393
567
|
</div>
|
|
394
|
-
<div class="
|
|
395
|
-
<div class="
|
|
396
|
-
<div class="
|
|
568
|
+
<div class="meta-item">
|
|
569
|
+
<div class="meta-label">Stability Runs</div>
|
|
570
|
+
<div class="meta-value">${stabilityRuns}</div>
|
|
397
571
|
</div>
|
|
398
572
|
</div>
|
|
399
573
|
|
|
400
|
-
${
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
<span>UI Path Coverage</span>
|
|
406
|
-
</div>
|
|
407
|
-
<div class="coverage-bar">
|
|
408
|
-
<div class="coverage-fill" style="width: ${coverage.percent}%"></div>
|
|
409
|
-
</div>
|
|
410
|
-
<p style="text-align: center; color: var(--text-secondary);">
|
|
411
|
-
${coverage.hit || 0} of ${coverage.total || 0} paths visited (${coverage.percent}%)
|
|
412
|
-
</p>
|
|
413
|
-
${coverage.missed?.length > 0 ? `
|
|
414
|
-
<div style="margin-top: 1rem;">
|
|
415
|
-
<p style="font-weight: 600; margin-bottom: 0.5rem;">Missed paths:</p>
|
|
416
|
-
<ul style="color: var(--text-secondary); padding-left: 1.5rem;">
|
|
417
|
-
${coverage.missed.slice(0, 5).map(p => `<li style="font-family: monospace;">${escapeHtml(p)}</li>`).join('')}
|
|
418
|
-
${coverage.missed.length > 5 ? `<li style="color: var(--text-muted);">... and ${coverage.missed.length - 5} more</li>` : ''}
|
|
419
|
-
</ul>
|
|
420
|
-
</div>
|
|
421
|
-
` : ''}
|
|
422
|
-
</div>
|
|
423
|
-
` : ''}
|
|
574
|
+
${generateVideoSection(artifacts, passes)}
|
|
575
|
+
|
|
576
|
+
${generateNetworkSection(passes)}
|
|
577
|
+
|
|
578
|
+
${generateCoverageSection(coverage)}
|
|
424
579
|
|
|
425
|
-
${findings
|
|
426
|
-
|
|
580
|
+
${generateFindingsSection(findings, findingsByCategory)}
|
|
581
|
+
|
|
582
|
+
${generateArtifactsSection(artifacts)}
|
|
583
|
+
|
|
584
|
+
<!-- Footer -->
|
|
585
|
+
<footer class="footer">
|
|
586
|
+
Generated by <a href="https://vibecheck.ai">vibecheck</a> ·
|
|
587
|
+
${new Date().toISOString()}
|
|
588
|
+
</footer>
|
|
589
|
+
</div>
|
|
590
|
+
|
|
591
|
+
<script>
|
|
592
|
+
// Collapsible sections
|
|
593
|
+
document.querySelectorAll('.collapsible').forEach(el => {
|
|
594
|
+
const header = el.querySelector('.collapsible-header');
|
|
595
|
+
if (header) {
|
|
596
|
+
header.addEventListener('click', () => {
|
|
597
|
+
el.classList.toggle('collapsed');
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
</script>
|
|
602
|
+
</body>
|
|
603
|
+
</html>`;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function generateVideoSection(artifacts, passes) {
|
|
607
|
+
const hasVideos = artifacts?.videos?.anon || artifacts?.videos?.auth;
|
|
608
|
+
const hasTraces = artifacts?.traces?.anon || artifacts?.traces?.auth;
|
|
609
|
+
|
|
610
|
+
if (!hasVideos && !hasTraces) {
|
|
611
|
+
return '';
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return `
|
|
615
|
+
<!-- Video Evidence -->
|
|
427
616
|
<div class="section">
|
|
428
617
|
<div class="section-header">
|
|
429
|
-
<span class="section-icon"
|
|
430
|
-
<span>
|
|
618
|
+
<span class="section-icon">🎬</span>
|
|
619
|
+
<span class="section-title">Video Evidence</span>
|
|
620
|
+
<span class="section-badge">Runtime Proof</span>
|
|
431
621
|
</div>
|
|
432
|
-
<div class="
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
622
|
+
<div class="section-body">
|
|
623
|
+
<p style="color: var(--gray-500); margin-bottom: 1rem;">
|
|
624
|
+
These recordings show real browser sessions testing your application.
|
|
625
|
+
</p>
|
|
626
|
+
<div class="evidence-row">
|
|
627
|
+
${artifacts?.videos?.anon ? `
|
|
628
|
+
<div>
|
|
629
|
+
<div class="video-container">
|
|
630
|
+
<video controls preload="metadata">
|
|
631
|
+
<source src="${escapeHtml(artifacts.videos.anon)}" type="video/webm">
|
|
632
|
+
Your browser does not support video playback.
|
|
633
|
+
</video>
|
|
634
|
+
</div>
|
|
635
|
+
<div class="video-label">👤 Anonymous Session</div>
|
|
636
|
+
</div>
|
|
637
|
+
` : '<div class="video-placeholder">No anonymous session video</div>'}
|
|
638
|
+
|
|
639
|
+
${artifacts?.videos?.auth ? `
|
|
640
|
+
<div>
|
|
641
|
+
<div class="video-container">
|
|
642
|
+
<video controls preload="metadata">
|
|
643
|
+
<source src="${escapeHtml(artifacts.videos.auth)}" type="video/webm">
|
|
644
|
+
Your browser does not support video playback.
|
|
645
|
+
</video>
|
|
646
|
+
</div>
|
|
647
|
+
<div class="video-label">🔑 Authenticated Session</div>
|
|
438
648
|
</div>
|
|
439
|
-
|
|
440
|
-
${f.page || f.file ? `<div class="finding-location">${escapeHtml(f.page || f.file)}${f.line ? `:${f.line}` : ''}</div>` : ''}
|
|
649
|
+
` : ''}
|
|
441
650
|
</div>
|
|
442
|
-
|
|
443
|
-
${
|
|
651
|
+
|
|
652
|
+
${hasTraces ? `
|
|
653
|
+
<div style="margin-top: 1.5rem;">
|
|
654
|
+
<p style="color: var(--gray-600); font-weight: 500; margin-bottom: 0.75rem;">
|
|
655
|
+
📊 Full Traces (for detailed debugging)
|
|
656
|
+
</p>
|
|
657
|
+
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
658
|
+
${artifacts?.traces?.anon ? `
|
|
659
|
+
<a href="https://trace.playwright.dev" class="trace-link" target="_blank">
|
|
660
|
+
View Anonymous Trace ↗
|
|
661
|
+
</a>
|
|
662
|
+
` : ''}
|
|
663
|
+
${artifacts?.traces?.auth ? `
|
|
664
|
+
<a href="https://trace.playwright.dev" class="trace-link" target="_blank">
|
|
665
|
+
View Authenticated Trace ↗
|
|
666
|
+
</a>
|
|
667
|
+
` : ''}
|
|
668
|
+
</div>
|
|
669
|
+
<p style="color: var(--gray-400); font-size: 0.75rem; margin-top: 0.5rem;">
|
|
670
|
+
Download trace files and upload to trace.playwright.dev
|
|
671
|
+
</p>
|
|
672
|
+
</div>
|
|
673
|
+
` : ''}
|
|
444
674
|
</div>
|
|
445
675
|
</div>
|
|
446
|
-
|
|
676
|
+
`;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function generateNetworkSection(passes) {
|
|
680
|
+
const anonErrors = passes?.anon?.networkErrors || [];
|
|
681
|
+
const authErrors = passes?.auth?.networkErrors || [];
|
|
682
|
+
const allErrors = [...anonErrors, ...authErrors].slice(0, 10);
|
|
683
|
+
|
|
684
|
+
const anonResponses = passes?.anon?.fakeDataDetections || [];
|
|
685
|
+
const fakeDetections = anonResponses.filter(d => d.confidence >= 0.7).slice(0, 5);
|
|
686
|
+
|
|
687
|
+
return `
|
|
688
|
+
<!-- Network Evidence -->
|
|
447
689
|
<div class="section">
|
|
448
690
|
<div class="section-header">
|
|
449
|
-
<span class="section-icon"
|
|
450
|
-
<span>
|
|
691
|
+
<span class="section-icon">📡</span>
|
|
692
|
+
<span class="section-title">Network Evidence</span>
|
|
693
|
+
<span class="section-badge">${allErrors.length} errors, ${fakeDetections.length} fakes detected</span>
|
|
694
|
+
</div>
|
|
695
|
+
<div class="section-body">
|
|
696
|
+
${allErrors.length > 0 ? `
|
|
697
|
+
<h4 style="margin-bottom: 0.75rem; color: var(--gray-700);">Network Errors</h4>
|
|
698
|
+
<table class="network-table">
|
|
699
|
+
<thead>
|
|
700
|
+
<tr>
|
|
701
|
+
<th>URL</th>
|
|
702
|
+
<th>Error</th>
|
|
703
|
+
</tr>
|
|
704
|
+
</thead>
|
|
705
|
+
<tbody>
|
|
706
|
+
${allErrors.map(e => `
|
|
707
|
+
<tr>
|
|
708
|
+
<td style="word-break: break-all; max-width: 400px;">${escapeHtml(e.url)}</td>
|
|
709
|
+
<td><span class="status-badge error">${escapeHtml(e.failure || 'Failed')}</span></td>
|
|
710
|
+
</tr>
|
|
711
|
+
`).join('')}
|
|
712
|
+
</tbody>
|
|
713
|
+
</table>
|
|
714
|
+
` : `
|
|
715
|
+
<div style="text-align: center; padding: 2rem; color: var(--gray-400);">
|
|
716
|
+
✅ No network errors detected
|
|
717
|
+
</div>
|
|
718
|
+
`}
|
|
719
|
+
|
|
720
|
+
${fakeDetections.length > 0 ? `
|
|
721
|
+
<h4 style="margin: 1.5rem 0 0.75rem; color: var(--gray-700);">Fake Data Detections</h4>
|
|
722
|
+
<table class="network-table">
|
|
723
|
+
<thead>
|
|
724
|
+
<tr>
|
|
725
|
+
<th>Type</th>
|
|
726
|
+
<th>Evidence</th>
|
|
727
|
+
<th>Confidence</th>
|
|
728
|
+
</tr>
|
|
729
|
+
</thead>
|
|
730
|
+
<tbody>
|
|
731
|
+
${fakeDetections.map(d => `
|
|
732
|
+
<tr>
|
|
733
|
+
<td>${escapeHtml(d.type)}</td>
|
|
734
|
+
<td>${escapeHtml(d.evidence)}</td>
|
|
735
|
+
<td><span class="status-badge ${d.confidence >= 0.9 ? 'error' : 'warning'}">${Math.round(d.confidence * 100)}%</span></td>
|
|
736
|
+
</tr>
|
|
737
|
+
`).join('')}
|
|
738
|
+
</tbody>
|
|
739
|
+
</table>
|
|
740
|
+
` : ''}
|
|
451
741
|
</div>
|
|
452
|
-
<p style="text-align: center; color: var(--success); font-size: 1.2rem;">
|
|
453
|
-
All UI elements are responsive and functional!
|
|
454
|
-
</p>
|
|
455
742
|
</div>
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
743
|
+
`;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function generateCoverageSection(coverage) {
|
|
747
|
+
if (!coverage) return '';
|
|
748
|
+
|
|
749
|
+
return `
|
|
750
|
+
<!-- Coverage -->
|
|
460
751
|
<div class="section">
|
|
461
752
|
<div class="section-header">
|
|
462
|
-
<span class="section-icon"
|
|
463
|
-
<span>
|
|
753
|
+
<span class="section-icon">📊</span>
|
|
754
|
+
<span class="section-title">UI Path Coverage</span>
|
|
755
|
+
<span class="section-badge">${coverage.hit || 0}/${coverage.total || 0} paths</span>
|
|
464
756
|
</div>
|
|
465
|
-
<div class="
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
<div class="artifact-icon">📸</div>
|
|
469
|
-
<div class="artifact-info">
|
|
470
|
-
<div class="artifact-name">Screenshots</div>
|
|
471
|
-
<div class="artifact-path">${artifacts.screenshots}</div>
|
|
472
|
-
</div>
|
|
757
|
+
<div class="section-body">
|
|
758
|
+
<div class="coverage-bar">
|
|
759
|
+
<div class="coverage-fill" style="width: ${coverage.percent || 0}%"></div>
|
|
473
760
|
</div>
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
<div class="artifact-icon">🎬</div>
|
|
478
|
-
<div class="artifact-info">
|
|
479
|
-
<div class="artifact-name">Video Recordings</div>
|
|
480
|
-
<div class="artifact-path">${artifacts.videos.directory}</div>
|
|
481
|
-
</div>
|
|
761
|
+
<div class="coverage-label">
|
|
762
|
+
<span>${coverage.percent || 0}% covered</span>
|
|
763
|
+
<span>${coverage.hit || 0} of ${coverage.total || 0} paths visited</span>
|
|
482
764
|
</div>
|
|
483
|
-
|
|
484
|
-
${
|
|
485
|
-
<div
|
|
486
|
-
<
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
${artifacts.har?.directory ? `
|
|
494
|
-
<div class="artifact-card">
|
|
495
|
-
<div class="artifact-icon">📡</div>
|
|
496
|
-
<div class="artifact-info">
|
|
497
|
-
<div class="artifact-name">HAR Network Logs</div>
|
|
498
|
-
<div class="artifact-path">${artifacts.har.directory}</div>
|
|
499
|
-
</div>
|
|
765
|
+
|
|
766
|
+
${coverage.missed && coverage.missed.length > 0 ? `
|
|
767
|
+
<div style="margin-top: 1rem;">
|
|
768
|
+
<p style="color: var(--gray-600); font-weight: 500; margin-bottom: 0.5rem;">
|
|
769
|
+
Missed Paths (${coverage.missed.length})
|
|
770
|
+
</p>
|
|
771
|
+
<ul style="color: var(--gray-500); font-size: 0.875rem; padding-left: 1.5rem;">
|
|
772
|
+
${coverage.missed.slice(0, 5).map(p => `<li>${escapeHtml(p)}</li>`).join('')}
|
|
773
|
+
${coverage.missed.length > 5 ? `<li>...and ${coverage.missed.length - 5} more</li>` : ''}
|
|
774
|
+
</ul>
|
|
500
775
|
</div>
|
|
501
776
|
` : ''}
|
|
502
777
|
</div>
|
|
503
778
|
</div>
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
779
|
+
`;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function generateFindingsSection(findings, findingsByCategory) {
|
|
783
|
+
if (findings.length === 0) {
|
|
784
|
+
return `
|
|
785
|
+
<!-- Findings -->
|
|
507
786
|
<div class="section">
|
|
508
787
|
<div class="section-header">
|
|
509
|
-
<span class="section-icon"
|
|
510
|
-
<span>
|
|
788
|
+
<span class="section-icon">✅</span>
|
|
789
|
+
<span class="section-title">Findings</span>
|
|
790
|
+
<span class="section-badge">0 issues</span>
|
|
511
791
|
</div>
|
|
512
|
-
<div
|
|
513
|
-
<div>
|
|
514
|
-
|
|
515
|
-
<div>${new Date(startedAt).toLocaleString()}</div>
|
|
516
|
-
</div>
|
|
517
|
-
<div>
|
|
518
|
-
<div style="color: var(--text-muted); font-size: 0.85rem;">Completed</div>
|
|
519
|
-
<div>${new Date(finishedAt).toLocaleString()}</div>
|
|
520
|
-
</div>
|
|
521
|
-
<div>
|
|
522
|
-
<div style="color: var(--text-muted); font-size: 0.85rem;">Duration</div>
|
|
523
|
-
<div>${durationStr}</div>
|
|
792
|
+
<div class="section-body">
|
|
793
|
+
<div style="text-align: center; padding: 2rem; color: var(--gray-400);">
|
|
794
|
+
✨ No issues found! Your UI is responsive and real.
|
|
524
795
|
</div>
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
`;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const blocks = findings.filter(f => f.severity === 'BLOCK');
|
|
802
|
+
const warns = findings.filter(f => f.severity === 'WARN');
|
|
803
|
+
|
|
804
|
+
return `
|
|
805
|
+
<!-- Findings -->
|
|
806
|
+
<div class="section">
|
|
807
|
+
<div class="section-header">
|
|
808
|
+
<span class="section-icon">🔍</span>
|
|
809
|
+
<span class="section-title">Findings</span>
|
|
810
|
+
<span class="section-badge">${blocks.length} blockers, ${warns.length} warnings</span>
|
|
811
|
+
</div>
|
|
812
|
+
<div class="section-body">
|
|
813
|
+
${Object.entries(findingsByCategory).map(([category, catFindings]) => `
|
|
814
|
+
<div class="collapsible" style="margin-bottom: 1.5rem;">
|
|
815
|
+
<h4 class="collapsible-header" style="color: var(--gray-700); margin-bottom: 0.75rem; cursor: pointer;">
|
|
816
|
+
${getCategoryEmoji(category)} ${escapeHtml(category)}
|
|
817
|
+
<span style="color: var(--gray-400); font-weight: normal;">(${catFindings.length})</span>
|
|
818
|
+
</h4>
|
|
819
|
+
<div class="collapsible-body">
|
|
820
|
+
${catFindings.slice(0, 10).map(f => `
|
|
821
|
+
<div class="finding-card ${f.severity.toLowerCase()}">
|
|
822
|
+
<div class="finding-header">
|
|
823
|
+
<span class="finding-severity ${f.severity.toLowerCase()}">${f.severity}</span>
|
|
824
|
+
<span class="finding-title">${escapeHtml(f.title)}</span>
|
|
825
|
+
</div>
|
|
826
|
+
<div class="finding-body">
|
|
827
|
+
<div class="finding-meta">
|
|
828
|
+
${f.page ? `<span>📍 ${escapeHtml(truncateUrl(f.page))}</span>` : ''}
|
|
829
|
+
${f.confidence ? `<span>🎯 ${Math.round(f.confidence * 100)}% confidence</span>` : ''}
|
|
830
|
+
</div>
|
|
831
|
+
${f.reason ? `<p class="finding-reason">${escapeHtml(f.reason)}</p>` : ''}
|
|
832
|
+
${f.screenshot ? `
|
|
833
|
+
<div class="finding-screenshot">
|
|
834
|
+
<img src="${escapeHtml(f.screenshot)}" alt="Screenshot" loading="lazy">
|
|
835
|
+
</div>
|
|
836
|
+
` : ''}
|
|
837
|
+
</div>
|
|
838
|
+
</div>
|
|
839
|
+
`).join('')}
|
|
840
|
+
${catFindings.length > 10 ? `
|
|
841
|
+
<p style="color: var(--gray-400); font-size: 0.875rem; text-align: center;">
|
|
842
|
+
...and ${catFindings.length - 10} more in this category
|
|
843
|
+
</p>
|
|
844
|
+
` : ''}
|
|
532
845
|
</div>
|
|
533
846
|
</div>
|
|
534
|
-
`
|
|
847
|
+
`).join('')}
|
|
535
848
|
</div>
|
|
536
849
|
</div>
|
|
537
|
-
|
|
538
|
-
<!-- Footer -->
|
|
539
|
-
<footer class="footer">
|
|
540
|
-
<p>Generated by <a href="https://vibecheckai.dev">vibecheck</a></p>
|
|
541
|
-
<p style="margin-top: 0.5rem; font-size: 0.85rem;">
|
|
542
|
-
The vibe coder's reality check • Prove your code actually works
|
|
543
|
-
</p>
|
|
544
|
-
</footer>
|
|
545
|
-
</div>
|
|
546
|
-
</body>
|
|
547
|
-
</html>`;
|
|
850
|
+
`;
|
|
548
851
|
}
|
|
549
852
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
if (!
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
853
|
+
function generateArtifactsSection(artifacts) {
|
|
854
|
+
const hasArtifacts = artifacts?.screenshots || artifacts?.videos?.directory ||
|
|
855
|
+
artifacts?.traces?.directory || artifacts?.har?.directory;
|
|
856
|
+
|
|
857
|
+
if (!hasArtifacts) return '';
|
|
858
|
+
|
|
859
|
+
return `
|
|
860
|
+
<!-- Artifacts -->
|
|
861
|
+
<div class="section">
|
|
862
|
+
<div class="section-header">
|
|
863
|
+
<span class="section-icon">📁</span>
|
|
864
|
+
<span class="section-title">Artifacts</span>
|
|
865
|
+
</div>
|
|
866
|
+
<div class="section-body">
|
|
867
|
+
<p style="color: var(--gray-500); margin-bottom: 1rem;">
|
|
868
|
+
Full evidence files for CI/CD integration and audit trails.
|
|
869
|
+
</p>
|
|
870
|
+
<ul style="list-style: none; color: var(--gray-600);">
|
|
871
|
+
${artifacts?.screenshots ? `<li style="margin-bottom: 0.5rem;">📸 Screenshots: <code style="background: var(--gray-100); padding: 0.25rem 0.5rem; border-radius: 4px;">${escapeHtml(artifacts.screenshots)}</code></li>` : ''}
|
|
872
|
+
${artifacts?.videos?.directory ? `<li style="margin-bottom: 0.5rem;">🎬 Videos: <code style="background: var(--gray-100); padding: 0.25rem 0.5rem; border-radius: 4px;">${escapeHtml(artifacts.videos.directory)}</code></li>` : ''}
|
|
873
|
+
${artifacts?.traces?.directory ? `<li style="margin-bottom: 0.5rem;">📊 Traces: <code style="background: var(--gray-100); padding: 0.25rem 0.5rem; border-radius: 4px;">${escapeHtml(artifacts.traces.directory)}</code></li>` : ''}
|
|
874
|
+
${artifacts?.har?.directory ? `<li style="margin-bottom: 0.5rem;">📡 HAR Files: <code style="background: var(--gray-100); padding: 0.25rem 0.5rem; border-radius: 4px;">${escapeHtml(artifacts.har.directory)}</code></li>` : ''}
|
|
875
|
+
</ul>
|
|
876
|
+
</div>
|
|
877
|
+
</div>
|
|
878
|
+
`;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function getCategoryEmoji(category) {
|
|
882
|
+
const emojis = {
|
|
883
|
+
'DeadUI': '💀',
|
|
884
|
+
'AuthCoverage': '🔒',
|
|
885
|
+
'HTTPError': '📡',
|
|
886
|
+
'FakeDomain': '🔗',
|
|
887
|
+
'FakeResponse': '🎭',
|
|
888
|
+
'MockStatus': '📡',
|
|
889
|
+
'StubCode': '📝',
|
|
890
|
+
'TODOCode': '📋',
|
|
891
|
+
'PlaceholderSecret': '🔐',
|
|
892
|
+
'Other': '📌'
|
|
893
|
+
};
|
|
894
|
+
return emojis[category] || '📌';
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function truncateUrl(url) {
|
|
898
|
+
if (!url) return '';
|
|
899
|
+
try {
|
|
900
|
+
const parsed = new URL(url);
|
|
901
|
+
return parsed.pathname + (parsed.search ? '?' + parsed.search.slice(1, 20) + '...' : '');
|
|
902
|
+
} catch {
|
|
903
|
+
return url.length > 60 ? url.slice(0, 60) + '...' : url;
|
|
904
|
+
}
|
|
561
905
|
}
|
|
562
906
|
|
|
563
|
-
|
|
907
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
908
|
+
// EXPORT
|
|
909
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
910
|
+
|
|
911
|
+
module.exports = {
|
|
912
|
+
generateHtmlProofReport
|
|
913
|
+
};
|