@vibecheckai/cli 3.1.0 → 3.1.1

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 (158) hide show
  1. package/bin/.generated +25 -25
  2. package/bin/dev/run-v2-torture.js +30 -30
  3. package/bin/registry.js +105 -105
  4. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
  5. package/bin/runners/lib/analysis-core.js +271 -271
  6. package/bin/runners/lib/analyzers.js +579 -579
  7. package/bin/runners/lib/auth-truth.js +193 -193
  8. package/bin/runners/lib/backup.js +62 -62
  9. package/bin/runners/lib/billing.js +107 -107
  10. package/bin/runners/lib/claims.js +118 -118
  11. package/bin/runners/lib/cli-output.js +368 -368
  12. package/bin/runners/lib/cli-ui.js +540 -540
  13. package/bin/runners/lib/contracts/auth-contract.js +202 -202
  14. package/bin/runners/lib/contracts/env-contract.js +181 -181
  15. package/bin/runners/lib/contracts/external-contract.js +206 -206
  16. package/bin/runners/lib/contracts/guard.js +168 -168
  17. package/bin/runners/lib/contracts/index.js +89 -89
  18. package/bin/runners/lib/contracts/plan-validator.js +311 -311
  19. package/bin/runners/lib/contracts/route-contract.js +199 -199
  20. package/bin/runners/lib/contracts.js +804 -804
  21. package/bin/runners/lib/detect.js +89 -89
  22. package/bin/runners/lib/detectors-v2.js +703 -703
  23. package/bin/runners/lib/doctor/autofix.js +254 -254
  24. package/bin/runners/lib/doctor/index.js +37 -37
  25. package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
  26. package/bin/runners/lib/doctor/modules/index.js +46 -46
  27. package/bin/runners/lib/doctor/modules/network.js +250 -250
  28. package/bin/runners/lib/doctor/modules/project.js +312 -312
  29. package/bin/runners/lib/doctor/modules/runtime.js +224 -224
  30. package/bin/runners/lib/doctor/modules/security.js +348 -348
  31. package/bin/runners/lib/doctor/modules/system.js +213 -213
  32. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
  33. package/bin/runners/lib/doctor/reporter.js +262 -262
  34. package/bin/runners/lib/doctor/service.js +262 -262
  35. package/bin/runners/lib/doctor/types.js +113 -113
  36. package/bin/runners/lib/doctor/ui.js +263 -263
  37. package/bin/runners/lib/doctor-v2.js +608 -608
  38. package/bin/runners/lib/drift.js +425 -425
  39. package/bin/runners/lib/enforcement.js +72 -72
  40. package/bin/runners/lib/enterprise-detect.js +603 -603
  41. package/bin/runners/lib/enterprise-init.js +942 -942
  42. package/bin/runners/lib/entitlements-v2.js +490 -489
  43. package/bin/runners/lib/env-resolver.js +417 -417
  44. package/bin/runners/lib/env-template.js +66 -66
  45. package/bin/runners/lib/env.js +189 -189
  46. package/bin/runners/lib/extractors/client-calls.js +990 -990
  47. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
  48. package/bin/runners/lib/extractors/fastify-routes.js +426 -426
  49. package/bin/runners/lib/extractors/index.js +363 -363
  50. package/bin/runners/lib/extractors/next-routes.js +524 -524
  51. package/bin/runners/lib/extractors/proof-graph.js +431 -431
  52. package/bin/runners/lib/extractors/route-matcher.js +451 -451
  53. package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
  54. package/bin/runners/lib/extractors/ui-bindings.js +547 -547
  55. package/bin/runners/lib/findings-schema.js +281 -281
  56. package/bin/runners/lib/firewall-prompt.js +50 -50
  57. package/bin/runners/lib/graph/graph-builder.js +265 -265
  58. package/bin/runners/lib/graph/html-renderer.js +413 -413
  59. package/bin/runners/lib/graph/index.js +32 -32
  60. package/bin/runners/lib/graph/runtime-collector.js +215 -215
  61. package/bin/runners/lib/graph/static-extractor.js +518 -518
  62. package/bin/runners/lib/html-report.js +650 -650
  63. package/bin/runners/lib/init-wizard.js +308 -308
  64. package/bin/runners/lib/llm.js +75 -75
  65. package/bin/runners/lib/meter.js +61 -61
  66. package/bin/runners/lib/missions/evidence.js +126 -126
  67. package/bin/runners/lib/missions/plan.js +69 -69
  68. package/bin/runners/lib/missions/templates.js +192 -192
  69. package/bin/runners/lib/patch.js +40 -40
  70. package/bin/runners/lib/permissions/auth-model.js +213 -213
  71. package/bin/runners/lib/permissions/idor-prover.js +205 -205
  72. package/bin/runners/lib/permissions/index.js +45 -45
  73. package/bin/runners/lib/permissions/matrix-builder.js +198 -198
  74. package/bin/runners/lib/pkgjson.js +28 -28
  75. package/bin/runners/lib/policy.js +295 -295
  76. package/bin/runners/lib/preflight.js +142 -142
  77. package/bin/runners/lib/reality/correlation-detectors.js +359 -359
  78. package/bin/runners/lib/reality/index.js +318 -318
  79. package/bin/runners/lib/reality/request-hashing.js +416 -416
  80. package/bin/runners/lib/reality/request-mapper.js +453 -453
  81. package/bin/runners/lib/reality/safety-rails.js +463 -463
  82. package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
  83. package/bin/runners/lib/reality/toast-detector.js +393 -393
  84. package/bin/runners/lib/reality-findings.js +84 -84
  85. package/bin/runners/lib/receipts.js +179 -179
  86. package/bin/runners/lib/redact.js +29 -29
  87. package/bin/runners/lib/replay/capsule-manager.js +154 -154
  88. package/bin/runners/lib/replay/index.js +263 -263
  89. package/bin/runners/lib/replay/player.js +348 -348
  90. package/bin/runners/lib/replay/recorder.js +331 -331
  91. package/bin/runners/lib/report-engine.js +447 -447
  92. package/bin/runners/lib/report-html.js +1499 -1499
  93. package/bin/runners/lib/report-templates.js +969 -969
  94. package/bin/runners/lib/report.js +135 -135
  95. package/bin/runners/lib/route-detection.js +1140 -1140
  96. package/bin/runners/lib/route-truth.js +477 -477
  97. package/bin/runners/lib/sandbox/index.js +59 -59
  98. package/bin/runners/lib/sandbox/proof-chain.js +399 -399
  99. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
  100. package/bin/runners/lib/sandbox/worktree.js +174 -174
  101. package/bin/runners/lib/schema-validator.js +350 -350
  102. package/bin/runners/lib/schemas/contracts.schema.json +160 -160
  103. package/bin/runners/lib/schemas/finding.schema.json +100 -100
  104. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
  105. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
  106. package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
  107. package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
  108. package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
  109. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
  110. package/bin/runners/lib/schemas/validator.js +438 -438
  111. package/bin/runners/lib/score-history.js +282 -282
  112. package/bin/runners/lib/share-pack.js +239 -239
  113. package/bin/runners/lib/snippets.js +67 -67
  114. package/bin/runners/lib/truth.js +667 -667
  115. package/bin/runners/lib/upsell.js +510 -510
  116. package/bin/runners/lib/usage.js +153 -153
  117. package/bin/runners/lib/validate-patch.js +156 -156
  118. package/bin/runners/lib/verdict-engine.js +628 -628
  119. package/bin/runners/reality/engine.js +917 -917
  120. package/bin/runners/reality/flows.js +122 -122
  121. package/bin/runners/reality/report.js +378 -378
  122. package/bin/runners/reality/session.js +193 -193
  123. package/bin/runners/runAuth.js +51 -0
  124. package/bin/runners/runClaimVerifier.js +483 -483
  125. package/bin/runners/runContext.js +56 -56
  126. package/bin/runners/runContextCompiler.js +385 -385
  127. package/bin/runners/runCtx.js +674 -674
  128. package/bin/runners/runCtxDiff.js +301 -301
  129. package/bin/runners/runCtxGuard.js +176 -176
  130. package/bin/runners/runCtxSync.js +116 -116
  131. package/bin/runners/runGate.js +17 -17
  132. package/bin/runners/runGraph.js +454 -454
  133. package/bin/runners/runGuard.js +168 -168
  134. package/bin/runners/runInitGha.js +164 -164
  135. package/bin/runners/runInstall.js +277 -277
  136. package/bin/runners/runInteractive.js +388 -388
  137. package/bin/runners/runLabs.js +340 -340
  138. package/bin/runners/runMissionGenerator.js +282 -282
  139. package/bin/runners/runPR.js +255 -255
  140. package/bin/runners/runPermissions.js +304 -304
  141. package/bin/runners/runPreflight.js +580 -553
  142. package/bin/runners/runProve.js +1252 -1252
  143. package/bin/runners/runReality.js +1328 -1328
  144. package/bin/runners/runReplay.js +499 -499
  145. package/bin/runners/runReport.js +584 -584
  146. package/bin/runners/runShare.js +212 -212
  147. package/bin/runners/runStatus.js +138 -138
  148. package/bin/runners/runTruthpack.js +636 -636
  149. package/bin/runners/runVerify.js +272 -272
  150. package/bin/runners/runWatch.js +407 -407
  151. package/bin/vibecheck.js +2 -1
  152. package/mcp-server/consolidated-tools.js +804 -804
  153. package/mcp-server/package.json +1 -1
  154. package/mcp-server/tools/index.js +72 -72
  155. package/mcp-server/truth-context.js +581 -581
  156. package/mcp-server/truth-firewall-tools.js +1500 -1500
  157. package/package.json +1 -1
  158. package/bin/runners/runProof.zip +0 -0
@@ -1,969 +1,969 @@
1
- /**
2
- * @deprecated Use html-report.js instead. This module is kept for backward compatibility.
3
- * Import from report.js for the unified API.
4
- */
5
-
6
- /**
7
- * Enhanced Report Templates
8
- *
9
- * Beautiful, professional report generation with:
10
- * - Modern design with gradients and shadows
11
- * - Visual score gauges (SVG)
12
- * - Category progress bars
13
- * - Severity breakdown charts
14
- * - Trend sparklines
15
- * - Professional branding
16
- */
17
-
18
- /**
19
- * Generate SVG score gauge
20
- */
21
- function generateScoreGauge(score, size = 180) {
22
- const radius = (size - 20) / 2;
23
- const circumference = 2 * Math.PI * radius;
24
- const progress = (score / 100) * circumference;
25
- const remaining = circumference - progress;
26
-
27
- const color = score >= 80 ? '#22c55e' : score >= 60 ? '#eab308' : '#ef4444';
28
- const bgColor = '#e2e8f0';
29
-
30
- return `
31
- <svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" class="score-gauge">
32
- <circle
33
- cx="${size/2}" cy="${size/2}" r="${radius}"
34
- fill="none" stroke="${bgColor}" stroke-width="12"
35
- />
36
- <circle
37
- cx="${size/2}" cy="${size/2}" r="${radius}"
38
- fill="none" stroke="${color}" stroke-width="12"
39
- stroke-dasharray="${progress} ${remaining}"
40
- stroke-linecap="round"
41
- transform="rotate(-90 ${size/2} ${size/2})"
42
- class="gauge-progress"
43
- />
44
- <text x="${size/2}" y="${size/2 + 8}" text-anchor="middle" class="gauge-score">${score}</text>
45
- <text x="${size/2}" y="${size/2 + 28}" text-anchor="middle" class="gauge-label">/ 100</text>
46
- </svg>
47
- `;
48
- }
49
-
50
- /**
51
- * Generate category bar chart
52
- */
53
- function generateCategoryBars(categories) {
54
- let html = '<div class="category-bars">';
55
-
56
- for (const [name, score] of Object.entries(categories)) {
57
- const color = score >= 80 ? '#22c55e' : score >= 60 ? '#eab308' : '#ef4444';
58
- const icon = score >= 80 ? '✓' : score >= 60 ? '!' : '✗';
59
- const displayName = formatCategoryName(name);
60
-
61
- html += `
62
- <div class="category-bar-item">
63
- <div class="category-bar-header">
64
- <span class="category-icon" style="color: ${color}">${icon}</span>
65
- <span class="category-name">${displayName}</span>
66
- <span class="category-score" style="color: ${color}">${score}%</span>
67
- </div>
68
- <div class="category-bar-track">
69
- <div class="category-bar-fill" style="width: ${score}%; background: ${color}"></div>
70
- </div>
71
- </div>
72
- `;
73
- }
74
-
75
- html += '</div>';
76
- return html;
77
- }
78
-
79
- /**
80
- * Generate severity breakdown
81
- */
82
- function generateSeverityBreakdown(findings) {
83
- const counts = {
84
- critical: findings.filter(f => f.severity === 'critical').length,
85
- high: findings.filter(f => f.severity === 'high').length,
86
- medium: findings.filter(f => f.severity === 'medium').length,
87
- low: findings.filter(f => f.severity === 'low').length,
88
- };
89
-
90
- const total = counts.critical + counts.high + counts.medium + counts.low;
91
-
92
- const colors = {
93
- critical: '#dc2626',
94
- high: '#f97316',
95
- medium: '#eab308',
96
- low: '#94a3b8',
97
- };
98
-
99
- const icons = {
100
- critical: '🔴',
101
- high: '🟠',
102
- medium: '🟡',
103
- low: '⚪',
104
- };
105
-
106
- let html = `
107
- <div class="severity-breakdown">
108
- <div class="severity-chart">
109
- `;
110
-
111
- // Stacked bar
112
- if (total > 0) {
113
- html += '<div class="severity-bar">';
114
- for (const [sev, count] of Object.entries(counts)) {
115
- if (count > 0) {
116
- const width = (count / total) * 100;
117
- html += `<div class="severity-segment" style="width: ${width}%; background: ${colors[sev]}" title="${sev}: ${count}"></div>`;
118
- }
119
- }
120
- html += '</div>';
121
- }
122
-
123
- html += '</div><div class="severity-legend">';
124
-
125
- for (const [sev, count] of Object.entries(counts)) {
126
- html += `
127
- <div class="severity-item">
128
- <span class="severity-icon">${icons[sev]}</span>
129
- <span class="severity-label">${sev.charAt(0).toUpperCase() + sev.slice(1)}</span>
130
- <span class="severity-count">${count}</span>
131
- </div>
132
- `;
133
- }
134
-
135
- html += '</div></div>';
136
- return html;
137
- }
138
-
139
- /**
140
- * Generate findings list
141
- */
142
- function generateFindingsList(findings, options = {}) {
143
- const limit = options.limit || 10;
144
- const showFile = !options.redactPaths;
145
-
146
- const severityColors = {
147
- critical: '#dc2626',
148
- high: '#f97316',
149
- medium: '#eab308',
150
- low: '#94a3b8',
151
- };
152
-
153
- let html = '<div class="findings-list">';
154
-
155
- const displayFindings = findings.slice(0, limit);
156
-
157
- for (const finding of displayFindings) {
158
- const color = severityColors[finding.severity] || '#94a3b8';
159
- const sevLabel = (finding.severity || 'medium').toUpperCase();
160
-
161
- html += `
162
- <div class="finding-card" style="border-left-color: ${color}">
163
- <div class="finding-header">
164
- <span class="finding-severity" style="background: ${color}">${sevLabel}</span>
165
- <span class="finding-title">${finding.message || finding.title || 'Finding'}</span>
166
- </div>
167
- ${finding.description ? `<p class="finding-description">${finding.description}</p>` : ''}
168
- ${showFile && finding.file ? `<code class="finding-file">${finding.file}${finding.line ? ':' + finding.line : ''}</code>` : ''}
169
- ${finding.fix ? `<div class="finding-fix"><strong>Fix:</strong> ${finding.fix}</div>` : ''}
170
- </div>
171
- `;
172
- }
173
-
174
- if (findings.length > limit) {
175
- html += `<div class="findings-more">+ ${findings.length - limit} more findings...</div>`;
176
- }
177
-
178
- html += '</div>';
179
- return html;
180
- }
181
-
182
- /**
183
- * Get enhanced CSS styles
184
- */
185
- function getEnhancedStyles() {
186
- return `
187
- :root {
188
- --primary: #3b82f6;
189
- --primary-dark: #1d4ed8;
190
- --success: #22c55e;
191
- --warning: #eab308;
192
- --danger: #ef4444;
193
- --gray-50: #f8fafc;
194
- --gray-100: #f1f5f9;
195
- --gray-200: #e2e8f0;
196
- --gray-300: #cbd5e1;
197
- --gray-600: #475569;
198
- --gray-800: #1e293b;
199
- --gray-900: #0f172a;
200
- --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
201
- --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
202
- }
203
-
204
- * { box-sizing: border-box; margin: 0; padding: 0; }
205
-
206
- body {
207
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
208
- color: var(--gray-800);
209
- line-height: 1.6;
210
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
211
- min-height: 100vh;
212
- padding: 40px 20px;
213
- }
214
-
215
- .report {
216
- max-width: 900px;
217
- margin: 0 auto;
218
- background: white;
219
- border-radius: 24px;
220
- box-shadow: var(--shadow-lg);
221
- overflow: hidden;
222
- }
223
-
224
- /* Header */
225
- .report-header {
226
- background: linear-gradient(135deg, var(--gray-900) 0%, var(--gray-800) 100%);
227
- color: white;
228
- padding: 48px;
229
- position: relative;
230
- overflow: hidden;
231
- }
232
-
233
- .report-header::before {
234
- content: '';
235
- position: absolute;
236
- top: -50%;
237
- right: -20%;
238
- width: 60%;
239
- height: 200%;
240
- background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, transparent 100%);
241
- transform: rotate(-15deg);
242
- }
243
-
244
- .report-header h1 {
245
- font-size: 32px;
246
- font-weight: 700;
247
- margin-bottom: 8px;
248
- position: relative;
249
- }
250
-
251
- .report-meta {
252
- opacity: 0.8;
253
- font-size: 14px;
254
- }
255
-
256
- .report-logo {
257
- position: absolute;
258
- top: 48px;
259
- right: 48px;
260
- font-size: 24px;
261
- font-weight: 700;
262
- opacity: 0.3;
263
- }
264
-
265
- /* Score Section */
266
- .score-section {
267
- display: flex;
268
- align-items: center;
269
- justify-content: space-between;
270
- padding: 48px;
271
- background: var(--gray-50);
272
- border-bottom: 1px solid var(--gray-200);
273
- }
274
-
275
- .score-main {
276
- display: flex;
277
- align-items: center;
278
- gap: 32px;
279
- }
280
-
281
- .score-gauge {
282
- filter: drop-shadow(0 4px 6px rgb(0 0 0 / 0.1));
283
- }
284
-
285
- .gauge-progress {
286
- transition: stroke-dasharray 1s ease-out;
287
- }
288
-
289
- .gauge-score {
290
- font-size: 42px;
291
- font-weight: 700;
292
- fill: var(--gray-900);
293
- }
294
-
295
- .gauge-label {
296
- font-size: 14px;
297
- fill: var(--gray-600);
298
- }
299
-
300
- .verdict-badge {
301
- padding: 12px 28px;
302
- border-radius: 50px;
303
- font-weight: 600;
304
- font-size: 18px;
305
- text-transform: uppercase;
306
- letter-spacing: 0.5px;
307
- box-shadow: var(--shadow);
308
- }
309
-
310
- .verdict-badge.ship {
311
- background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
312
- color: white;
313
- }
314
-
315
- .verdict-badge.warn {
316
- background: linear-gradient(135deg, #eab308 0%, #ca8a04 100%);
317
- color: white;
318
- }
319
-
320
- .verdict-badge.block {
321
- background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
322
- color: white;
323
- }
324
-
325
- .score-info h2 {
326
- font-size: 24px;
327
- margin-bottom: 4px;
328
- }
329
-
330
- .score-info p {
331
- color: var(--gray-600);
332
- font-size: 14px;
333
- }
334
-
335
- /* Content Sections */
336
- .report-content {
337
- padding: 48px;
338
- }
339
-
340
- .section {
341
- margin-bottom: 48px;
342
- }
343
-
344
- .section-header {
345
- display: flex;
346
- align-items: center;
347
- gap: 12px;
348
- margin-bottom: 24px;
349
- }
350
-
351
- .section-icon {
352
- width: 40px;
353
- height: 40px;
354
- border-radius: 10px;
355
- display: flex;
356
- align-items: center;
357
- justify-content: center;
358
- font-size: 20px;
359
- background: var(--primary);
360
- color: white;
361
- }
362
-
363
- .section-title {
364
- font-size: 20px;
365
- font-weight: 600;
366
- }
367
-
368
- /* Category Bars */
369
- .category-bars {
370
- display: flex;
371
- flex-direction: column;
372
- gap: 16px;
373
- }
374
-
375
- .category-bar-item {
376
- background: var(--gray-50);
377
- padding: 16px;
378
- border-radius: 12px;
379
- }
380
-
381
- .category-bar-header {
382
- display: flex;
383
- align-items: center;
384
- gap: 8px;
385
- margin-bottom: 8px;
386
- }
387
-
388
- .category-icon {
389
- font-weight: 600;
390
- }
391
-
392
- .category-name {
393
- flex: 1;
394
- font-weight: 500;
395
- }
396
-
397
- .category-score {
398
- font-weight: 700;
399
- }
400
-
401
- .category-bar-track {
402
- height: 8px;
403
- background: var(--gray-200);
404
- border-radius: 4px;
405
- overflow: hidden;
406
- }
407
-
408
- .category-bar-fill {
409
- height: 100%;
410
- border-radius: 4px;
411
- transition: width 0.5s ease-out;
412
- }
413
-
414
- /* Severity Breakdown */
415
- .severity-breakdown {
416
- background: var(--gray-50);
417
- padding: 24px;
418
- border-radius: 12px;
419
- }
420
-
421
- .severity-bar {
422
- display: flex;
423
- height: 24px;
424
- border-radius: 12px;
425
- overflow: hidden;
426
- margin-bottom: 20px;
427
- }
428
-
429
- .severity-segment {
430
- transition: width 0.5s ease-out;
431
- }
432
-
433
- .severity-legend {
434
- display: grid;
435
- grid-template-columns: repeat(4, 1fr);
436
- gap: 16px;
437
- }
438
-
439
- .severity-item {
440
- display: flex;
441
- align-items: center;
442
- gap: 8px;
443
- }
444
-
445
- .severity-label {
446
- flex: 1;
447
- font-size: 14px;
448
- color: var(--gray-600);
449
- }
450
-
451
- .severity-count {
452
- font-weight: 700;
453
- font-size: 18px;
454
- }
455
-
456
- /* Findings List */
457
- .findings-list {
458
- display: flex;
459
- flex-direction: column;
460
- gap: 16px;
461
- }
462
-
463
- .finding-card {
464
- background: var(--gray-50);
465
- border-left: 4px solid;
466
- padding: 16px 20px;
467
- border-radius: 0 12px 12px 0;
468
- }
469
-
470
- .finding-header {
471
- display: flex;
472
- align-items: center;
473
- gap: 12px;
474
- margin-bottom: 8px;
475
- }
476
-
477
- .finding-severity {
478
- padding: 2px 8px;
479
- border-radius: 4px;
480
- font-size: 10px;
481
- font-weight: 700;
482
- color: white;
483
- }
484
-
485
- .finding-title {
486
- font-weight: 600;
487
- }
488
-
489
- .finding-description {
490
- color: var(--gray-600);
491
- font-size: 14px;
492
- margin-bottom: 8px;
493
- }
494
-
495
- .finding-file {
496
- display: inline-block;
497
- background: var(--gray-200);
498
- padding: 4px 8px;
499
- border-radius: 4px;
500
- font-size: 12px;
501
- color: var(--gray-600);
502
- }
503
-
504
- .finding-fix {
505
- margin-top: 8px;
506
- padding: 8px 12px;
507
- background: rgba(34, 197, 94, 0.1);
508
- border-radius: 6px;
509
- font-size: 13px;
510
- color: #166534;
511
- }
512
-
513
- .findings-more {
514
- text-align: center;
515
- padding: 16px;
516
- color: var(--gray-600);
517
- font-style: italic;
518
- }
519
-
520
- /* Footer */
521
- .report-footer {
522
- background: var(--gray-900);
523
- color: white;
524
- padding: 32px 48px;
525
- display: flex;
526
- justify-content: space-between;
527
- align-items: center;
528
- }
529
-
530
- .footer-brand {
531
- display: flex;
532
- align-items: center;
533
- gap: 8px;
534
- font-weight: 600;
535
- }
536
-
537
- .footer-meta {
538
- font-size: 12px;
539
- opacity: 0.6;
540
- }
541
-
542
- /* Recommendation */
543
- .recommendation {
544
- background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%);
545
- padding: 24px;
546
- border-radius: 12px;
547
- border: 1px solid var(--gray-200);
548
- }
549
-
550
- .recommendation h3 {
551
- margin-bottom: 8px;
552
- }
553
-
554
- .recommendation p {
555
- color: var(--gray-600);
556
- }
557
-
558
- @media print {
559
- body {
560
- background: white;
561
- padding: 0;
562
- }
563
- .report {
564
- box-shadow: none;
565
- border-radius: 0;
566
- }
567
- }
568
- `;
569
- }
570
-
571
- /**
572
- * Format category name
573
- */
574
- function formatCategoryName(name) {
575
- const names = {
576
- functionality: 'Core Functionality',
577
- auth: 'Authentication',
578
- billing: 'Payment Integration',
579
- reality: 'Runtime Verification',
580
- code_quality: 'Code Quality',
581
- };
582
- return names[name] || name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, ' ');
583
- }
584
-
585
- /**
586
- * Generate enhanced executive report
587
- */
588
- function generateEnhancedExecutiveReport(data, options = {}) {
589
- const verdictClass = data.verdict === 'SHIP' ? 'ship' : data.verdict === 'WARN' ? 'warn' : 'block';
590
- const verdictText = data.verdict === 'SHIP' ? '✅ Ready to Ship' :
591
- data.verdict === 'WARN' ? '⚠️ Ship with Caution' : '🚫 Not Ready';
592
- const verdictMessage = data.verdict === 'SHIP'
593
- ? 'All systems go. This application is ready for production.'
594
- : data.verdict === 'WARN'
595
- ? 'Minor issues detected. Review recommended before deployment.'
596
- : 'Critical issues found. Remediation required before shipping.';
597
-
598
- return `<!DOCTYPE html>
599
- <html lang="en">
600
- <head>
601
- <meta charset="UTF-8">
602
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
603
- <title>Ship Readiness Report - ${data.projectName}</title>
604
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
605
- <style>${getEnhancedStyles()}</style>
606
- </head>
607
- <body>
608
- <div class="report">
609
- <header class="report-header">
610
- <h1>Ship Readiness Report</h1>
611
- <div class="report-meta">
612
- <p><strong>Project:</strong> ${data.projectName}</p>
613
- <p><strong>Generated:</strong> ${new Date(data.generatedAt).toLocaleString()}</p>
614
- ${options.company ? `<p><strong>Prepared for:</strong> ${options.company}</p>` : ''}
615
- </div>
616
- <div class="report-logo">VIBECHECK</div>
617
- </header>
618
-
619
- <section class="score-section">
620
- <div class="score-main">
621
- ${generateScoreGauge(data.score)}
622
- <div class="score-info">
623
- <h2>Vibe Score</h2>
624
- <p>${verdictMessage}</p>
625
- </div>
626
- </div>
627
- <div class="verdict-badge ${verdictClass}">${verdictText}</div>
628
- </section>
629
-
630
- <div class="report-content">
631
- <section class="section">
632
- <div class="section-header">
633
- <div class="section-icon">📊</div>
634
- <h3 class="section-title">Category Breakdown</h3>
635
- </div>
636
- ${generateCategoryBars(data.categoryScores)}
637
- </section>
638
-
639
- <section class="section">
640
- <div class="section-header">
641
- <div class="section-icon">🎯</div>
642
- <h3 class="section-title">Issues by Severity</h3>
643
- </div>
644
- ${generateSeverityBreakdown(data.findings)}
645
- </section>
646
-
647
- ${data.findings.length > 0 ? `
648
- <section class="section">
649
- <div class="section-header">
650
- <div class="section-icon">🔍</div>
651
- <h3 class="section-title">Key Findings</h3>
652
- </div>
653
- ${generateFindingsList(data.findings, { limit: 5, redactPaths: options.redactPaths })}
654
- </section>
655
- ` : ''}
656
-
657
- <section class="section">
658
- <div class="recommendation">
659
- <h3>📋 Recommendation</h3>
660
- <p>${data.verdict === 'SHIP'
661
- ? 'This application has passed all quality checks and is ready for production deployment. Continue monitoring after launch.'
662
- : data.verdict === 'WARN'
663
- ? 'This application can be deployed, but the identified issues should be addressed in the next sprint to ensure long-term stability.'
664
- : 'This application requires immediate attention. Critical issues must be resolved before deployment to prevent production incidents.'
665
- }</p>
666
- </div>
667
- </section>
668
- </div>
669
-
670
- <footer class="report-footer">
671
- <div class="footer-brand">
672
- <span>⚡</span>
673
- <span>Vibecheck</span>
674
- </div>
675
- <div class="footer-meta">
676
- Report ID: VC-${Date.now().toString(36).toUpperCase()} · vibecheck.dev
677
- </div>
678
- </footer>
679
- </div>
680
- </body>
681
- </html>`;
682
- }
683
-
684
- /**
685
- * Generate enhanced technical report
686
- */
687
- function generateEnhancedTechnicalReport(data, options = {}) {
688
- const verdictClass = data.verdict === 'SHIP' ? 'ship' : data.verdict === 'WARN' ? 'warn' : 'block';
689
-
690
- return `<!DOCTYPE html>
691
- <html lang="en">
692
- <head>
693
- <meta charset="UTF-8">
694
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
695
- <title>Technical Quality Report - ${data.projectName}</title>
696
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono&display=swap" rel="stylesheet">
697
- <style>
698
- ${getEnhancedStyles()}
699
- .code-block { font-family: 'JetBrains Mono', monospace; background: #1e293b; color: #e2e8f0; padding: 16px; border-radius: 8px; overflow-x: auto; }
700
- .finding-card { margin-bottom: 16px; }
701
- .tech-table { width: 100%; border-collapse: collapse; margin: 16px 0; }
702
- .tech-table th, .tech-table td { padding: 12px; text-align: left; border-bottom: 1px solid var(--gray-200); }
703
- .tech-table th { background: var(--gray-100); font-weight: 600; }
704
- .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
705
- .badge-critical { background: #fee2e2; color: #991b1b; }
706
- .badge-high { background: #ffedd5; color: #9a3412; }
707
- .badge-medium { background: #fef9c3; color: #854d0e; }
708
- .badge-low { background: #f1f5f9; color: #475569; }
709
- </style>
710
- </head>
711
- <body>
712
- <div class="report">
713
- <header class="report-header">
714
- <h1>Technical Quality Report</h1>
715
- <div class="report-meta">
716
- <p><strong>Project:</strong> ${data.projectName}</p>
717
- <p><strong>Generated:</strong> ${new Date(data.generatedAt).toLocaleString()}</p>
718
- <p><strong>Scan ID:</strong> VC-${Date.now().toString(36).toUpperCase()}</p>
719
- </div>
720
- <div class="report-logo">VIBECHECK</div>
721
- </header>
722
-
723
- <section class="score-section">
724
- <div class="score-main">
725
- ${generateScoreGauge(data.score)}
726
- <div class="score-info">
727
- <h2>Technical Score</h2>
728
- <p>${data.findings.length} issues found across ${Object.keys(data.categoryScores).length} categories</p>
729
- </div>
730
- </div>
731
- <div class="verdict-badge ${verdictClass}">${data.verdict}</div>
732
- </section>
733
-
734
- <div class="report-content">
735
- <section class="section">
736
- <div class="section-header">
737
- <div class="section-icon">📊</div>
738
- <h3 class="section-title">Score Breakdown</h3>
739
- </div>
740
- <table class="tech-table">
741
- <thead>
742
- <tr><th>Category</th><th>Score</th><th>Status</th><th>Details</th></tr>
743
- </thead>
744
- <tbody>
745
- ${Object.entries(data.categoryScores).map(([cat, score]) => `
746
- <tr>
747
- <td>${formatCategoryName(cat)}</td>
748
- <td><strong>${score}%</strong></td>
749
- <td>${score >= 80 ? '✅ Pass' : score >= 60 ? '⚠️ Warning' : '❌ Fail'}</td>
750
- <td>${score >= 80 ? 'No issues' : 'Requires attention'}</td>
751
- </tr>
752
- `).join('')}
753
- </tbody>
754
- </table>
755
- </section>
756
-
757
- <section class="section">
758
- <div class="section-header">
759
- <div class="section-icon">🎯</div>
760
- <h3 class="section-title">Issues by Severity</h3>
761
- </div>
762
- ${generateSeverityBreakdown(data.findings)}
763
- </section>
764
-
765
- <section class="section">
766
- <div class="section-header">
767
- <div class="section-icon">🔍</div>
768
- <h3 class="section-title">All Findings (${data.findings.length})</h3>
769
- </div>
770
- ${generateFindingsList(data.findings, { limit: 50, redactPaths: options.redactPaths })}
771
- </section>
772
- </div>
773
-
774
- <footer class="report-footer">
775
- <div class="footer-brand">
776
- <span>⚡</span>
777
- <span>Vibecheck Technical Report</span>
778
- </div>
779
- <div class="footer-meta">
780
- vibecheck.dev
781
- </div>
782
- </footer>
783
- </div>
784
- </body>
785
- </html>`;
786
- }
787
-
788
- /**
789
- * Generate enhanced compliance report
790
- */
791
- function generateEnhancedComplianceReport(data, options = {}) {
792
- const assessmentId = `VC-${new Date().toISOString().split('T')[0].replace(/-/g, '')}-${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
793
- const verdictClass = data.verdict === 'SHIP' ? 'ship' : data.verdict === 'WARN' ? 'warn' : 'block';
794
-
795
- const criticalCount = data.findings.filter(f => f.severity === 'critical').length;
796
- const highCount = data.findings.filter(f => f.severity === 'high').length;
797
-
798
- return `<!DOCTYPE html>
799
- <html lang="en">
800
- <head>
801
- <meta charset="UTF-8">
802
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
803
- <title>Security Assessment Report - ${data.projectName}</title>
804
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
805
- <style>
806
- ${getEnhancedStyles()}
807
- .compliance-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin: 24px 0; }
808
- .compliance-card { background: var(--gray-50); padding: 20px; border-radius: 12px; border: 1px solid var(--gray-200); }
809
- .compliance-card h4 { margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
810
- .control-item { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--gray-200); }
811
- .control-item:last-child { border-bottom: none; }
812
- .control-status { width: 24px; text-align: center; }
813
- .attestation { background: linear-gradient(135deg, #1e293b 0%, #334155 100%); color: white; padding: 32px; border-radius: 12px; margin-top: 32px; }
814
- .attestation h3 { margin-bottom: 16px; }
815
- .signature-line { margin-top: 32px; padding-top: 16px; border-top: 1px solid rgba(255,255,255,0.2); display: flex; justify-content: space-between; }
816
- </style>
817
- </head>
818
- <body>
819
- <div class="report">
820
- <header class="report-header" style="background: linear-gradient(135deg, #1e3a5f 0%, #0f172a 100%);">
821
- <h1>Application Security Assessment</h1>
822
- <div class="report-meta">
823
- <p><strong>Application:</strong> ${data.projectName}</p>
824
- <p><strong>Assessment Date:</strong> ${new Date(data.generatedAt).toLocaleDateString()}</p>
825
- <p><strong>Assessment ID:</strong> ${assessmentId}</p>
826
- ${options.company ? `<p><strong>Prepared For:</strong> ${options.company}</p>` : ''}
827
- </div>
828
- <div class="report-logo">VIBECHECK</div>
829
- </header>
830
-
831
- <section class="score-section">
832
- <div class="score-main">
833
- ${generateScoreGauge(data.score)}
834
- <div class="score-info">
835
- <h2>Security Score</h2>
836
- <p>Based on automated code analysis and security checks</p>
837
- </div>
838
- </div>
839
- <div class="verdict-badge ${verdictClass}">${data.verdict === 'SHIP' ? '✅ COMPLIANT' : data.verdict === 'WARN' ? '⚠️ PARTIAL' : '🚫 NON-COMPLIANT'}</div>
840
- </section>
841
-
842
- <div class="report-content">
843
- <section class="section">
844
- <div class="section-header">
845
- <div class="section-icon" style="background: #1e3a5f;">📋</div>
846
- <h3 class="section-title">Executive Summary</h3>
847
- </div>
848
- <p style="margin-bottom: 16px;">
849
- This assessment evaluates the application's compliance with security best practices
850
- and industry standards. The analysis covers code quality, authentication mechanisms,
851
- data protection, and operational security controls.
852
- </p>
853
- <div class="compliance-grid">
854
- <div class="compliance-card">
855
- <h4>🔴 Critical Findings</h4>
856
- <div style="font-size: 32px; font-weight: 700; color: ${criticalCount > 0 ? '#dc2626' : '#22c55e'};">${criticalCount}</div>
857
- <p style="color: var(--gray-600); font-size: 14px;">${criticalCount > 0 ? 'Immediate remediation required' : 'No critical issues'}</p>
858
- </div>
859
- <div class="compliance-card">
860
- <h4>🟠 High Priority</h4>
861
- <div style="font-size: 32px; font-weight: 700; color: ${highCount > 0 ? '#f97316' : '#22c55e'};">${highCount}</div>
862
- <p style="color: var(--gray-600); font-size: 14px;">${highCount > 0 ? 'Should be addressed soon' : 'No high priority issues'}</p>
863
- </div>
864
- </div>
865
- </section>
866
-
867
- <section class="section">
868
- <div class="section-header">
869
- <div class="section-icon" style="background: #1e3a5f;">🔒</div>
870
- <h3 class="section-title">Control Areas Assessed</h3>
871
- </div>
872
- <div class="compliance-grid">
873
- <div class="compliance-card">
874
- <h4>🔐 Access Control</h4>
875
- <div class="control-item">
876
- <span class="control-status">${data.categoryScores.auth >= 80 ? '✅' : '❌'}</span>
877
- <span>Authentication required for protected routes</span>
878
- </div>
879
- <div class="control-item">
880
- <span class="control-status">⬜</span>
881
- <span>Session management (manual review)</span>
882
- </div>
883
- <div class="control-item">
884
- <span class="control-status">⬜</span>
885
- <span>Password hashing (manual review)</span>
886
- </div>
887
- </div>
888
- <div class="compliance-card">
889
- <h4>🛡️ Data Protection</h4>
890
- <div class="control-item">
891
- <span class="control-status">⬜</span>
892
- <span>Encryption in transit (TLS)</span>
893
- </div>
894
- <div class="control-item">
895
- <span class="control-status">${data.findings.filter(f => f.type === 'secret').length === 0 ? '✅' : '❌'}</span>
896
- <span>No secrets in codebase</span>
897
- </div>
898
- </div>
899
- <div class="compliance-card">
900
- <h4>💳 Payment Security</h4>
901
- <div class="control-item">
902
- <span class="control-status">${data.categoryScores.billing >= 80 ? '✅' : '❌'}</span>
903
- <span>Billing gates enforced server-side</span>
904
- </div>
905
- </div>
906
- <div class="compliance-card">
907
- <h4>📝 Code Quality</h4>
908
- <div class="control-item">
909
- <span class="control-status">${data.categoryScores.code_quality >= 80 ? '✅' : '❌'}</span>
910
- <span>No mock/demo code in production paths</span>
911
- </div>
912
- </div>
913
- </div>
914
- </section>
915
-
916
- ${data.findings.length > 0 ? `
917
- <section class="section">
918
- <div class="section-header">
919
- <div class="section-icon" style="background: #1e3a5f;">🔍</div>
920
- <h3 class="section-title">Findings Detail</h3>
921
- </div>
922
- ${generateFindingsList(data.findings, { limit: 10, redactPaths: options.redactPaths })}
923
- </section>
924
- ` : ''}
925
-
926
- <div class="attestation">
927
- <h3>📜 Attestation</h3>
928
- <p>
929
- This assessment was conducted using automated static code analysis and security scanning tools.
930
- ${data.verdict === 'SHIP'
931
- ? 'The application meets the baseline security requirements for production deployment.'
932
- : 'The application has identified issues that should be remediated before production deployment.'}
933
- </p>
934
- <div class="signature-line">
935
- <div>
936
- <strong>Assessment ID:</strong> ${assessmentId}
937
- </div>
938
- <div>
939
- <strong>Verification:</strong> vibecheck.dev/verify/${assessmentId}
940
- </div>
941
- </div>
942
- </div>
943
- </div>
944
-
945
- <footer class="report-footer">
946
- <div class="footer-brand">
947
- <span>🔒</span>
948
- <span>Vibecheck Security Assessment</span>
949
- </div>
950
- <div class="footer-meta">
951
- vibecheck.dev
952
- </div>
953
- </footer>
954
- </div>
955
- </body>
956
- </html>`;
957
- }
958
-
959
- module.exports = {
960
- generateScoreGauge,
961
- generateCategoryBars,
962
- generateSeverityBreakdown,
963
- generateFindingsList,
964
- getEnhancedStyles,
965
- generateEnhancedExecutiveReport,
966
- generateEnhancedTechnicalReport,
967
- generateEnhancedComplianceReport,
968
- formatCategoryName,
969
- };
1
+ /**
2
+ * @deprecated Use html-report.js instead. This module is kept for backward compatibility.
3
+ * Import from report.js for the unified API.
4
+ */
5
+
6
+ /**
7
+ * Enhanced Report Templates
8
+ *
9
+ * Beautiful, professional report generation with:
10
+ * - Modern design with gradients and shadows
11
+ * - Visual score gauges (SVG)
12
+ * - Category progress bars
13
+ * - Severity breakdown charts
14
+ * - Trend sparklines
15
+ * - Professional branding
16
+ */
17
+
18
+ /**
19
+ * Generate SVG score gauge
20
+ */
21
+ function generateScoreGauge(score, size = 180) {
22
+ const radius = (size - 20) / 2;
23
+ const circumference = 2 * Math.PI * radius;
24
+ const progress = (score / 100) * circumference;
25
+ const remaining = circumference - progress;
26
+
27
+ const color = score >= 80 ? '#22c55e' : score >= 60 ? '#eab308' : '#ef4444';
28
+ const bgColor = '#e2e8f0';
29
+
30
+ return `
31
+ <svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" class="score-gauge">
32
+ <circle
33
+ cx="${size/2}" cy="${size/2}" r="${radius}"
34
+ fill="none" stroke="${bgColor}" stroke-width="12"
35
+ />
36
+ <circle
37
+ cx="${size/2}" cy="${size/2}" r="${radius}"
38
+ fill="none" stroke="${color}" stroke-width="12"
39
+ stroke-dasharray="${progress} ${remaining}"
40
+ stroke-linecap="round"
41
+ transform="rotate(-90 ${size/2} ${size/2})"
42
+ class="gauge-progress"
43
+ />
44
+ <text x="${size/2}" y="${size/2 + 8}" text-anchor="middle" class="gauge-score">${score}</text>
45
+ <text x="${size/2}" y="${size/2 + 28}" text-anchor="middle" class="gauge-label">/ 100</text>
46
+ </svg>
47
+ `;
48
+ }
49
+
50
+ /**
51
+ * Generate category bar chart
52
+ */
53
+ function generateCategoryBars(categories) {
54
+ let html = '<div class="category-bars">';
55
+
56
+ for (const [name, score] of Object.entries(categories)) {
57
+ const color = score >= 80 ? '#22c55e' : score >= 60 ? '#eab308' : '#ef4444';
58
+ const icon = score >= 80 ? '✓' : score >= 60 ? '!' : '✗';
59
+ const displayName = formatCategoryName(name);
60
+
61
+ html += `
62
+ <div class="category-bar-item">
63
+ <div class="category-bar-header">
64
+ <span class="category-icon" style="color: ${color}">${icon}</span>
65
+ <span class="category-name">${displayName}</span>
66
+ <span class="category-score" style="color: ${color}">${score}%</span>
67
+ </div>
68
+ <div class="category-bar-track">
69
+ <div class="category-bar-fill" style="width: ${score}%; background: ${color}"></div>
70
+ </div>
71
+ </div>
72
+ `;
73
+ }
74
+
75
+ html += '</div>';
76
+ return html;
77
+ }
78
+
79
+ /**
80
+ * Generate severity breakdown
81
+ */
82
+ function generateSeverityBreakdown(findings) {
83
+ const counts = {
84
+ critical: findings.filter(f => f.severity === 'critical').length,
85
+ high: findings.filter(f => f.severity === 'high').length,
86
+ medium: findings.filter(f => f.severity === 'medium').length,
87
+ low: findings.filter(f => f.severity === 'low').length,
88
+ };
89
+
90
+ const total = counts.critical + counts.high + counts.medium + counts.low;
91
+
92
+ const colors = {
93
+ critical: '#dc2626',
94
+ high: '#f97316',
95
+ medium: '#eab308',
96
+ low: '#94a3b8',
97
+ };
98
+
99
+ const icons = {
100
+ critical: '🔴',
101
+ high: '🟠',
102
+ medium: '🟡',
103
+ low: '⚪',
104
+ };
105
+
106
+ let html = `
107
+ <div class="severity-breakdown">
108
+ <div class="severity-chart">
109
+ `;
110
+
111
+ // Stacked bar
112
+ if (total > 0) {
113
+ html += '<div class="severity-bar">';
114
+ for (const [sev, count] of Object.entries(counts)) {
115
+ if (count > 0) {
116
+ const width = (count / total) * 100;
117
+ html += `<div class="severity-segment" style="width: ${width}%; background: ${colors[sev]}" title="${sev}: ${count}"></div>`;
118
+ }
119
+ }
120
+ html += '</div>';
121
+ }
122
+
123
+ html += '</div><div class="severity-legend">';
124
+
125
+ for (const [sev, count] of Object.entries(counts)) {
126
+ html += `
127
+ <div class="severity-item">
128
+ <span class="severity-icon">${icons[sev]}</span>
129
+ <span class="severity-label">${sev.charAt(0).toUpperCase() + sev.slice(1)}</span>
130
+ <span class="severity-count">${count}</span>
131
+ </div>
132
+ `;
133
+ }
134
+
135
+ html += '</div></div>';
136
+ return html;
137
+ }
138
+
139
+ /**
140
+ * Generate findings list
141
+ */
142
+ function generateFindingsList(findings, options = {}) {
143
+ const limit = options.limit || 10;
144
+ const showFile = !options.redactPaths;
145
+
146
+ const severityColors = {
147
+ critical: '#dc2626',
148
+ high: '#f97316',
149
+ medium: '#eab308',
150
+ low: '#94a3b8',
151
+ };
152
+
153
+ let html = '<div class="findings-list">';
154
+
155
+ const displayFindings = findings.slice(0, limit);
156
+
157
+ for (const finding of displayFindings) {
158
+ const color = severityColors[finding.severity] || '#94a3b8';
159
+ const sevLabel = (finding.severity || 'medium').toUpperCase();
160
+
161
+ html += `
162
+ <div class="finding-card" style="border-left-color: ${color}">
163
+ <div class="finding-header">
164
+ <span class="finding-severity" style="background: ${color}">${sevLabel}</span>
165
+ <span class="finding-title">${finding.message || finding.title || 'Finding'}</span>
166
+ </div>
167
+ ${finding.description ? `<p class="finding-description">${finding.description}</p>` : ''}
168
+ ${showFile && finding.file ? `<code class="finding-file">${finding.file}${finding.line ? ':' + finding.line : ''}</code>` : ''}
169
+ ${finding.fix ? `<div class="finding-fix"><strong>Fix:</strong> ${finding.fix}</div>` : ''}
170
+ </div>
171
+ `;
172
+ }
173
+
174
+ if (findings.length > limit) {
175
+ html += `<div class="findings-more">+ ${findings.length - limit} more findings...</div>`;
176
+ }
177
+
178
+ html += '</div>';
179
+ return html;
180
+ }
181
+
182
+ /**
183
+ * Get enhanced CSS styles
184
+ */
185
+ function getEnhancedStyles() {
186
+ return `
187
+ :root {
188
+ --primary: #3b82f6;
189
+ --primary-dark: #1d4ed8;
190
+ --success: #22c55e;
191
+ --warning: #eab308;
192
+ --danger: #ef4444;
193
+ --gray-50: #f8fafc;
194
+ --gray-100: #f1f5f9;
195
+ --gray-200: #e2e8f0;
196
+ --gray-300: #cbd5e1;
197
+ --gray-600: #475569;
198
+ --gray-800: #1e293b;
199
+ --gray-900: #0f172a;
200
+ --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
201
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
202
+ }
203
+
204
+ * { box-sizing: border-box; margin: 0; padding: 0; }
205
+
206
+ body {
207
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
208
+ color: var(--gray-800);
209
+ line-height: 1.6;
210
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
211
+ min-height: 100vh;
212
+ padding: 40px 20px;
213
+ }
214
+
215
+ .report {
216
+ max-width: 900px;
217
+ margin: 0 auto;
218
+ background: white;
219
+ border-radius: 24px;
220
+ box-shadow: var(--shadow-lg);
221
+ overflow: hidden;
222
+ }
223
+
224
+ /* Header */
225
+ .report-header {
226
+ background: linear-gradient(135deg, var(--gray-900) 0%, var(--gray-800) 100%);
227
+ color: white;
228
+ padding: 48px;
229
+ position: relative;
230
+ overflow: hidden;
231
+ }
232
+
233
+ .report-header::before {
234
+ content: '';
235
+ position: absolute;
236
+ top: -50%;
237
+ right: -20%;
238
+ width: 60%;
239
+ height: 200%;
240
+ background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, transparent 100%);
241
+ transform: rotate(-15deg);
242
+ }
243
+
244
+ .report-header h1 {
245
+ font-size: 32px;
246
+ font-weight: 700;
247
+ margin-bottom: 8px;
248
+ position: relative;
249
+ }
250
+
251
+ .report-meta {
252
+ opacity: 0.8;
253
+ font-size: 14px;
254
+ }
255
+
256
+ .report-logo {
257
+ position: absolute;
258
+ top: 48px;
259
+ right: 48px;
260
+ font-size: 24px;
261
+ font-weight: 700;
262
+ opacity: 0.3;
263
+ }
264
+
265
+ /* Score Section */
266
+ .score-section {
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: space-between;
270
+ padding: 48px;
271
+ background: var(--gray-50);
272
+ border-bottom: 1px solid var(--gray-200);
273
+ }
274
+
275
+ .score-main {
276
+ display: flex;
277
+ align-items: center;
278
+ gap: 32px;
279
+ }
280
+
281
+ .score-gauge {
282
+ filter: drop-shadow(0 4px 6px rgb(0 0 0 / 0.1));
283
+ }
284
+
285
+ .gauge-progress {
286
+ transition: stroke-dasharray 1s ease-out;
287
+ }
288
+
289
+ .gauge-score {
290
+ font-size: 42px;
291
+ font-weight: 700;
292
+ fill: var(--gray-900);
293
+ }
294
+
295
+ .gauge-label {
296
+ font-size: 14px;
297
+ fill: var(--gray-600);
298
+ }
299
+
300
+ .verdict-badge {
301
+ padding: 12px 28px;
302
+ border-radius: 50px;
303
+ font-weight: 600;
304
+ font-size: 18px;
305
+ text-transform: uppercase;
306
+ letter-spacing: 0.5px;
307
+ box-shadow: var(--shadow);
308
+ }
309
+
310
+ .verdict-badge.ship {
311
+ background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
312
+ color: white;
313
+ }
314
+
315
+ .verdict-badge.warn {
316
+ background: linear-gradient(135deg, #eab308 0%, #ca8a04 100%);
317
+ color: white;
318
+ }
319
+
320
+ .verdict-badge.block {
321
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
322
+ color: white;
323
+ }
324
+
325
+ .score-info h2 {
326
+ font-size: 24px;
327
+ margin-bottom: 4px;
328
+ }
329
+
330
+ .score-info p {
331
+ color: var(--gray-600);
332
+ font-size: 14px;
333
+ }
334
+
335
+ /* Content Sections */
336
+ .report-content {
337
+ padding: 48px;
338
+ }
339
+
340
+ .section {
341
+ margin-bottom: 48px;
342
+ }
343
+
344
+ .section-header {
345
+ display: flex;
346
+ align-items: center;
347
+ gap: 12px;
348
+ margin-bottom: 24px;
349
+ }
350
+
351
+ .section-icon {
352
+ width: 40px;
353
+ height: 40px;
354
+ border-radius: 10px;
355
+ display: flex;
356
+ align-items: center;
357
+ justify-content: center;
358
+ font-size: 20px;
359
+ background: var(--primary);
360
+ color: white;
361
+ }
362
+
363
+ .section-title {
364
+ font-size: 20px;
365
+ font-weight: 600;
366
+ }
367
+
368
+ /* Category Bars */
369
+ .category-bars {
370
+ display: flex;
371
+ flex-direction: column;
372
+ gap: 16px;
373
+ }
374
+
375
+ .category-bar-item {
376
+ background: var(--gray-50);
377
+ padding: 16px;
378
+ border-radius: 12px;
379
+ }
380
+
381
+ .category-bar-header {
382
+ display: flex;
383
+ align-items: center;
384
+ gap: 8px;
385
+ margin-bottom: 8px;
386
+ }
387
+
388
+ .category-icon {
389
+ font-weight: 600;
390
+ }
391
+
392
+ .category-name {
393
+ flex: 1;
394
+ font-weight: 500;
395
+ }
396
+
397
+ .category-score {
398
+ font-weight: 700;
399
+ }
400
+
401
+ .category-bar-track {
402
+ height: 8px;
403
+ background: var(--gray-200);
404
+ border-radius: 4px;
405
+ overflow: hidden;
406
+ }
407
+
408
+ .category-bar-fill {
409
+ height: 100%;
410
+ border-radius: 4px;
411
+ transition: width 0.5s ease-out;
412
+ }
413
+
414
+ /* Severity Breakdown */
415
+ .severity-breakdown {
416
+ background: var(--gray-50);
417
+ padding: 24px;
418
+ border-radius: 12px;
419
+ }
420
+
421
+ .severity-bar {
422
+ display: flex;
423
+ height: 24px;
424
+ border-radius: 12px;
425
+ overflow: hidden;
426
+ margin-bottom: 20px;
427
+ }
428
+
429
+ .severity-segment {
430
+ transition: width 0.5s ease-out;
431
+ }
432
+
433
+ .severity-legend {
434
+ display: grid;
435
+ grid-template-columns: repeat(4, 1fr);
436
+ gap: 16px;
437
+ }
438
+
439
+ .severity-item {
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 8px;
443
+ }
444
+
445
+ .severity-label {
446
+ flex: 1;
447
+ font-size: 14px;
448
+ color: var(--gray-600);
449
+ }
450
+
451
+ .severity-count {
452
+ font-weight: 700;
453
+ font-size: 18px;
454
+ }
455
+
456
+ /* Findings List */
457
+ .findings-list {
458
+ display: flex;
459
+ flex-direction: column;
460
+ gap: 16px;
461
+ }
462
+
463
+ .finding-card {
464
+ background: var(--gray-50);
465
+ border-left: 4px solid;
466
+ padding: 16px 20px;
467
+ border-radius: 0 12px 12px 0;
468
+ }
469
+
470
+ .finding-header {
471
+ display: flex;
472
+ align-items: center;
473
+ gap: 12px;
474
+ margin-bottom: 8px;
475
+ }
476
+
477
+ .finding-severity {
478
+ padding: 2px 8px;
479
+ border-radius: 4px;
480
+ font-size: 10px;
481
+ font-weight: 700;
482
+ color: white;
483
+ }
484
+
485
+ .finding-title {
486
+ font-weight: 600;
487
+ }
488
+
489
+ .finding-description {
490
+ color: var(--gray-600);
491
+ font-size: 14px;
492
+ margin-bottom: 8px;
493
+ }
494
+
495
+ .finding-file {
496
+ display: inline-block;
497
+ background: var(--gray-200);
498
+ padding: 4px 8px;
499
+ border-radius: 4px;
500
+ font-size: 12px;
501
+ color: var(--gray-600);
502
+ }
503
+
504
+ .finding-fix {
505
+ margin-top: 8px;
506
+ padding: 8px 12px;
507
+ background: rgba(34, 197, 94, 0.1);
508
+ border-radius: 6px;
509
+ font-size: 13px;
510
+ color: #166534;
511
+ }
512
+
513
+ .findings-more {
514
+ text-align: center;
515
+ padding: 16px;
516
+ color: var(--gray-600);
517
+ font-style: italic;
518
+ }
519
+
520
+ /* Footer */
521
+ .report-footer {
522
+ background: var(--gray-900);
523
+ color: white;
524
+ padding: 32px 48px;
525
+ display: flex;
526
+ justify-content: space-between;
527
+ align-items: center;
528
+ }
529
+
530
+ .footer-brand {
531
+ display: flex;
532
+ align-items: center;
533
+ gap: 8px;
534
+ font-weight: 600;
535
+ }
536
+
537
+ .footer-meta {
538
+ font-size: 12px;
539
+ opacity: 0.6;
540
+ }
541
+
542
+ /* Recommendation */
543
+ .recommendation {
544
+ background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%);
545
+ padding: 24px;
546
+ border-radius: 12px;
547
+ border: 1px solid var(--gray-200);
548
+ }
549
+
550
+ .recommendation h3 {
551
+ margin-bottom: 8px;
552
+ }
553
+
554
+ .recommendation p {
555
+ color: var(--gray-600);
556
+ }
557
+
558
+ @media print {
559
+ body {
560
+ background: white;
561
+ padding: 0;
562
+ }
563
+ .report {
564
+ box-shadow: none;
565
+ border-radius: 0;
566
+ }
567
+ }
568
+ `;
569
+ }
570
+
571
+ /**
572
+ * Format category name
573
+ */
574
+ function formatCategoryName(name) {
575
+ const names = {
576
+ functionality: 'Core Functionality',
577
+ auth: 'Authentication',
578
+ billing: 'Payment Integration',
579
+ reality: 'Runtime Verification',
580
+ code_quality: 'Code Quality',
581
+ };
582
+ return names[name] || name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, ' ');
583
+ }
584
+
585
+ /**
586
+ * Generate enhanced executive report
587
+ */
588
+ function generateEnhancedExecutiveReport(data, options = {}) {
589
+ const verdictClass = data.verdict === 'SHIP' ? 'ship' : data.verdict === 'WARN' ? 'warn' : 'block';
590
+ const verdictText = data.verdict === 'SHIP' ? '✅ Ready to Ship' :
591
+ data.verdict === 'WARN' ? '⚠️ Ship with Caution' : '🚫 Not Ready';
592
+ const verdictMessage = data.verdict === 'SHIP'
593
+ ? 'All systems go. This application is ready for production.'
594
+ : data.verdict === 'WARN'
595
+ ? 'Minor issues detected. Review recommended before deployment.'
596
+ : 'Critical issues found. Remediation required before shipping.';
597
+
598
+ return `<!DOCTYPE html>
599
+ <html lang="en">
600
+ <head>
601
+ <meta charset="UTF-8">
602
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
603
+ <title>Ship Readiness Report - ${data.projectName}</title>
604
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
605
+ <style>${getEnhancedStyles()}</style>
606
+ </head>
607
+ <body>
608
+ <div class="report">
609
+ <header class="report-header">
610
+ <h1>Ship Readiness Report</h1>
611
+ <div class="report-meta">
612
+ <p><strong>Project:</strong> ${data.projectName}</p>
613
+ <p><strong>Generated:</strong> ${new Date(data.generatedAt).toLocaleString()}</p>
614
+ ${options.company ? `<p><strong>Prepared for:</strong> ${options.company}</p>` : ''}
615
+ </div>
616
+ <div class="report-logo">VIBECHECK</div>
617
+ </header>
618
+
619
+ <section class="score-section">
620
+ <div class="score-main">
621
+ ${generateScoreGauge(data.score)}
622
+ <div class="score-info">
623
+ <h2>Vibe Score</h2>
624
+ <p>${verdictMessage}</p>
625
+ </div>
626
+ </div>
627
+ <div class="verdict-badge ${verdictClass}">${verdictText}</div>
628
+ </section>
629
+
630
+ <div class="report-content">
631
+ <section class="section">
632
+ <div class="section-header">
633
+ <div class="section-icon">📊</div>
634
+ <h3 class="section-title">Category Breakdown</h3>
635
+ </div>
636
+ ${generateCategoryBars(data.categoryScores)}
637
+ </section>
638
+
639
+ <section class="section">
640
+ <div class="section-header">
641
+ <div class="section-icon">🎯</div>
642
+ <h3 class="section-title">Issues by Severity</h3>
643
+ </div>
644
+ ${generateSeverityBreakdown(data.findings)}
645
+ </section>
646
+
647
+ ${data.findings.length > 0 ? `
648
+ <section class="section">
649
+ <div class="section-header">
650
+ <div class="section-icon">🔍</div>
651
+ <h3 class="section-title">Key Findings</h3>
652
+ </div>
653
+ ${generateFindingsList(data.findings, { limit: 5, redactPaths: options.redactPaths })}
654
+ </section>
655
+ ` : ''}
656
+
657
+ <section class="section">
658
+ <div class="recommendation">
659
+ <h3>📋 Recommendation</h3>
660
+ <p>${data.verdict === 'SHIP'
661
+ ? 'This application has passed all quality checks and is ready for production deployment. Continue monitoring after launch.'
662
+ : data.verdict === 'WARN'
663
+ ? 'This application can be deployed, but the identified issues should be addressed in the next sprint to ensure long-term stability.'
664
+ : 'This application requires immediate attention. Critical issues must be resolved before deployment to prevent production incidents.'
665
+ }</p>
666
+ </div>
667
+ </section>
668
+ </div>
669
+
670
+ <footer class="report-footer">
671
+ <div class="footer-brand">
672
+ <span>⚡</span>
673
+ <span>Vibecheck</span>
674
+ </div>
675
+ <div class="footer-meta">
676
+ Report ID: VC-${Date.now().toString(36).toUpperCase()} · vibecheck.dev
677
+ </div>
678
+ </footer>
679
+ </div>
680
+ </body>
681
+ </html>`;
682
+ }
683
+
684
+ /**
685
+ * Generate enhanced technical report
686
+ */
687
+ function generateEnhancedTechnicalReport(data, options = {}) {
688
+ const verdictClass = data.verdict === 'SHIP' ? 'ship' : data.verdict === 'WARN' ? 'warn' : 'block';
689
+
690
+ return `<!DOCTYPE html>
691
+ <html lang="en">
692
+ <head>
693
+ <meta charset="UTF-8">
694
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
695
+ <title>Technical Quality Report - ${data.projectName}</title>
696
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono&display=swap" rel="stylesheet">
697
+ <style>
698
+ ${getEnhancedStyles()}
699
+ .code-block { font-family: 'JetBrains Mono', monospace; background: #1e293b; color: #e2e8f0; padding: 16px; border-radius: 8px; overflow-x: auto; }
700
+ .finding-card { margin-bottom: 16px; }
701
+ .tech-table { width: 100%; border-collapse: collapse; margin: 16px 0; }
702
+ .tech-table th, .tech-table td { padding: 12px; text-align: left; border-bottom: 1px solid var(--gray-200); }
703
+ .tech-table th { background: var(--gray-100); font-weight: 600; }
704
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
705
+ .badge-critical { background: #fee2e2; color: #991b1b; }
706
+ .badge-high { background: #ffedd5; color: #9a3412; }
707
+ .badge-medium { background: #fef9c3; color: #854d0e; }
708
+ .badge-low { background: #f1f5f9; color: #475569; }
709
+ </style>
710
+ </head>
711
+ <body>
712
+ <div class="report">
713
+ <header class="report-header">
714
+ <h1>Technical Quality Report</h1>
715
+ <div class="report-meta">
716
+ <p><strong>Project:</strong> ${data.projectName}</p>
717
+ <p><strong>Generated:</strong> ${new Date(data.generatedAt).toLocaleString()}</p>
718
+ <p><strong>Scan ID:</strong> VC-${Date.now().toString(36).toUpperCase()}</p>
719
+ </div>
720
+ <div class="report-logo">VIBECHECK</div>
721
+ </header>
722
+
723
+ <section class="score-section">
724
+ <div class="score-main">
725
+ ${generateScoreGauge(data.score)}
726
+ <div class="score-info">
727
+ <h2>Technical Score</h2>
728
+ <p>${data.findings.length} issues found across ${Object.keys(data.categoryScores).length} categories</p>
729
+ </div>
730
+ </div>
731
+ <div class="verdict-badge ${verdictClass}">${data.verdict}</div>
732
+ </section>
733
+
734
+ <div class="report-content">
735
+ <section class="section">
736
+ <div class="section-header">
737
+ <div class="section-icon">📊</div>
738
+ <h3 class="section-title">Score Breakdown</h3>
739
+ </div>
740
+ <table class="tech-table">
741
+ <thead>
742
+ <tr><th>Category</th><th>Score</th><th>Status</th><th>Details</th></tr>
743
+ </thead>
744
+ <tbody>
745
+ ${Object.entries(data.categoryScores).map(([cat, score]) => `
746
+ <tr>
747
+ <td>${formatCategoryName(cat)}</td>
748
+ <td><strong>${score}%</strong></td>
749
+ <td>${score >= 80 ? '✅ Pass' : score >= 60 ? '⚠️ Warning' : '❌ Fail'}</td>
750
+ <td>${score >= 80 ? 'No issues' : 'Requires attention'}</td>
751
+ </tr>
752
+ `).join('')}
753
+ </tbody>
754
+ </table>
755
+ </section>
756
+
757
+ <section class="section">
758
+ <div class="section-header">
759
+ <div class="section-icon">🎯</div>
760
+ <h3 class="section-title">Issues by Severity</h3>
761
+ </div>
762
+ ${generateSeverityBreakdown(data.findings)}
763
+ </section>
764
+
765
+ <section class="section">
766
+ <div class="section-header">
767
+ <div class="section-icon">🔍</div>
768
+ <h3 class="section-title">All Findings (${data.findings.length})</h3>
769
+ </div>
770
+ ${generateFindingsList(data.findings, { limit: 50, redactPaths: options.redactPaths })}
771
+ </section>
772
+ </div>
773
+
774
+ <footer class="report-footer">
775
+ <div class="footer-brand">
776
+ <span>⚡</span>
777
+ <span>Vibecheck Technical Report</span>
778
+ </div>
779
+ <div class="footer-meta">
780
+ vibecheck.dev
781
+ </div>
782
+ </footer>
783
+ </div>
784
+ </body>
785
+ </html>`;
786
+ }
787
+
788
+ /**
789
+ * Generate enhanced compliance report
790
+ */
791
+ function generateEnhancedComplianceReport(data, options = {}) {
792
+ const assessmentId = `VC-${new Date().toISOString().split('T')[0].replace(/-/g, '')}-${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
793
+ const verdictClass = data.verdict === 'SHIP' ? 'ship' : data.verdict === 'WARN' ? 'warn' : 'block';
794
+
795
+ const criticalCount = data.findings.filter(f => f.severity === 'critical').length;
796
+ const highCount = data.findings.filter(f => f.severity === 'high').length;
797
+
798
+ return `<!DOCTYPE html>
799
+ <html lang="en">
800
+ <head>
801
+ <meta charset="UTF-8">
802
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
803
+ <title>Security Assessment Report - ${data.projectName}</title>
804
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
805
+ <style>
806
+ ${getEnhancedStyles()}
807
+ .compliance-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin: 24px 0; }
808
+ .compliance-card { background: var(--gray-50); padding: 20px; border-radius: 12px; border: 1px solid var(--gray-200); }
809
+ .compliance-card h4 { margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
810
+ .control-item { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--gray-200); }
811
+ .control-item:last-child { border-bottom: none; }
812
+ .control-status { width: 24px; text-align: center; }
813
+ .attestation { background: linear-gradient(135deg, #1e293b 0%, #334155 100%); color: white; padding: 32px; border-radius: 12px; margin-top: 32px; }
814
+ .attestation h3 { margin-bottom: 16px; }
815
+ .signature-line { margin-top: 32px; padding-top: 16px; border-top: 1px solid rgba(255,255,255,0.2); display: flex; justify-content: space-between; }
816
+ </style>
817
+ </head>
818
+ <body>
819
+ <div class="report">
820
+ <header class="report-header" style="background: linear-gradient(135deg, #1e3a5f 0%, #0f172a 100%);">
821
+ <h1>Application Security Assessment</h1>
822
+ <div class="report-meta">
823
+ <p><strong>Application:</strong> ${data.projectName}</p>
824
+ <p><strong>Assessment Date:</strong> ${new Date(data.generatedAt).toLocaleDateString()}</p>
825
+ <p><strong>Assessment ID:</strong> ${assessmentId}</p>
826
+ ${options.company ? `<p><strong>Prepared For:</strong> ${options.company}</p>` : ''}
827
+ </div>
828
+ <div class="report-logo">VIBECHECK</div>
829
+ </header>
830
+
831
+ <section class="score-section">
832
+ <div class="score-main">
833
+ ${generateScoreGauge(data.score)}
834
+ <div class="score-info">
835
+ <h2>Security Score</h2>
836
+ <p>Based on automated code analysis and security checks</p>
837
+ </div>
838
+ </div>
839
+ <div class="verdict-badge ${verdictClass}">${data.verdict === 'SHIP' ? '✅ COMPLIANT' : data.verdict === 'WARN' ? '⚠️ PARTIAL' : '🚫 NON-COMPLIANT'}</div>
840
+ </section>
841
+
842
+ <div class="report-content">
843
+ <section class="section">
844
+ <div class="section-header">
845
+ <div class="section-icon" style="background: #1e3a5f;">📋</div>
846
+ <h3 class="section-title">Executive Summary</h3>
847
+ </div>
848
+ <p style="margin-bottom: 16px;">
849
+ This assessment evaluates the application's compliance with security best practices
850
+ and industry standards. The analysis covers code quality, authentication mechanisms,
851
+ data protection, and operational security controls.
852
+ </p>
853
+ <div class="compliance-grid">
854
+ <div class="compliance-card">
855
+ <h4>🔴 Critical Findings</h4>
856
+ <div style="font-size: 32px; font-weight: 700; color: ${criticalCount > 0 ? '#dc2626' : '#22c55e'};">${criticalCount}</div>
857
+ <p style="color: var(--gray-600); font-size: 14px;">${criticalCount > 0 ? 'Immediate remediation required' : 'No critical issues'}</p>
858
+ </div>
859
+ <div class="compliance-card">
860
+ <h4>🟠 High Priority</h4>
861
+ <div style="font-size: 32px; font-weight: 700; color: ${highCount > 0 ? '#f97316' : '#22c55e'};">${highCount}</div>
862
+ <p style="color: var(--gray-600); font-size: 14px;">${highCount > 0 ? 'Should be addressed soon' : 'No high priority issues'}</p>
863
+ </div>
864
+ </div>
865
+ </section>
866
+
867
+ <section class="section">
868
+ <div class="section-header">
869
+ <div class="section-icon" style="background: #1e3a5f;">🔒</div>
870
+ <h3 class="section-title">Control Areas Assessed</h3>
871
+ </div>
872
+ <div class="compliance-grid">
873
+ <div class="compliance-card">
874
+ <h4>🔐 Access Control</h4>
875
+ <div class="control-item">
876
+ <span class="control-status">${data.categoryScores.auth >= 80 ? '✅' : '❌'}</span>
877
+ <span>Authentication required for protected routes</span>
878
+ </div>
879
+ <div class="control-item">
880
+ <span class="control-status">⬜</span>
881
+ <span>Session management (manual review)</span>
882
+ </div>
883
+ <div class="control-item">
884
+ <span class="control-status">⬜</span>
885
+ <span>Password hashing (manual review)</span>
886
+ </div>
887
+ </div>
888
+ <div class="compliance-card">
889
+ <h4>🛡️ Data Protection</h4>
890
+ <div class="control-item">
891
+ <span class="control-status">⬜</span>
892
+ <span>Encryption in transit (TLS)</span>
893
+ </div>
894
+ <div class="control-item">
895
+ <span class="control-status">${data.findings.filter(f => f.type === 'secret').length === 0 ? '✅' : '❌'}</span>
896
+ <span>No secrets in codebase</span>
897
+ </div>
898
+ </div>
899
+ <div class="compliance-card">
900
+ <h4>💳 Payment Security</h4>
901
+ <div class="control-item">
902
+ <span class="control-status">${data.categoryScores.billing >= 80 ? '✅' : '❌'}</span>
903
+ <span>Billing gates enforced server-side</span>
904
+ </div>
905
+ </div>
906
+ <div class="compliance-card">
907
+ <h4>📝 Code Quality</h4>
908
+ <div class="control-item">
909
+ <span class="control-status">${data.categoryScores.code_quality >= 80 ? '✅' : '❌'}</span>
910
+ <span>No mock/demo code in production paths</span>
911
+ </div>
912
+ </div>
913
+ </div>
914
+ </section>
915
+
916
+ ${data.findings.length > 0 ? `
917
+ <section class="section">
918
+ <div class="section-header">
919
+ <div class="section-icon" style="background: #1e3a5f;">🔍</div>
920
+ <h3 class="section-title">Findings Detail</h3>
921
+ </div>
922
+ ${generateFindingsList(data.findings, { limit: 10, redactPaths: options.redactPaths })}
923
+ </section>
924
+ ` : ''}
925
+
926
+ <div class="attestation">
927
+ <h3>📜 Attestation</h3>
928
+ <p>
929
+ This assessment was conducted using automated static code analysis and security scanning tools.
930
+ ${data.verdict === 'SHIP'
931
+ ? 'The application meets the baseline security requirements for production deployment.'
932
+ : 'The application has identified issues that should be remediated before production deployment.'}
933
+ </p>
934
+ <div class="signature-line">
935
+ <div>
936
+ <strong>Assessment ID:</strong> ${assessmentId}
937
+ </div>
938
+ <div>
939
+ <strong>Verification:</strong> vibecheck.dev/verify/${assessmentId}
940
+ </div>
941
+ </div>
942
+ </div>
943
+ </div>
944
+
945
+ <footer class="report-footer">
946
+ <div class="footer-brand">
947
+ <span>🔒</span>
948
+ <span>Vibecheck Security Assessment</span>
949
+ </div>
950
+ <div class="footer-meta">
951
+ vibecheck.dev
952
+ </div>
953
+ </footer>
954
+ </div>
955
+ </body>
956
+ </html>`;
957
+ }
958
+
959
+ module.exports = {
960
+ generateScoreGauge,
961
+ generateCategoryBars,
962
+ generateSeverityBreakdown,
963
+ generateFindingsList,
964
+ getEnhancedStyles,
965
+ generateEnhancedExecutiveReport,
966
+ generateEnhancedTechnicalReport,
967
+ generateEnhancedComplianceReport,
968
+ formatCategoryName,
969
+ };