@vibecheckai/cli 3.0.9 → 3.0.10

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.
@@ -733,6 +733,211 @@ body::before {
733
733
  color: var(--color-text);
734
734
  }
735
735
 
736
+ /* ============================================================================
737
+ COVERAGE METRICS
738
+ ============================================================================ */
739
+
740
+ .coverage-grid {
741
+ display: grid;
742
+ grid-template-columns: repeat(4, 1fr);
743
+ gap: var(--space-xl);
744
+ text-align: center;
745
+ }
746
+
747
+ @media (max-width: 768px) {
748
+ .coverage-grid {
749
+ grid-template-columns: repeat(2, 1fr);
750
+ }
751
+ }
752
+
753
+ .coverage-ring-container {
754
+ display: flex;
755
+ flex-direction: column;
756
+ align-items: center;
757
+ gap: var(--space-sm);
758
+ position: relative;
759
+ }
760
+
761
+ .coverage-ring {
762
+ display: block;
763
+ }
764
+
765
+ .coverage-ring-value {
766
+ position: absolute;
767
+ top: 35px;
768
+ left: 50%;
769
+ transform: translateX(-50%);
770
+ font-size: 1.25rem;
771
+ font-weight: 700;
772
+ }
773
+
774
+ .coverage-ring-label {
775
+ font-size: 0.75rem;
776
+ color: var(--color-text-muted);
777
+ text-transform: uppercase;
778
+ letter-spacing: 0.05em;
779
+ }
780
+
781
+ /* ============================================================================
782
+ CHARTS
783
+ ============================================================================ */
784
+
785
+ .bar-chart-container,
786
+ .sparkline-container {
787
+ padding: var(--space-md);
788
+ }
789
+
790
+ .bar-chart-header,
791
+ .sparkline-header {
792
+ display: flex;
793
+ justify-content: space-between;
794
+ align-items: center;
795
+ margin-bottom: var(--space-md);
796
+ }
797
+
798
+ .bar-chart-label,
799
+ .sparkline-label {
800
+ font-weight: 600;
801
+ font-size: 0.875rem;
802
+ }
803
+
804
+ .bar-chart-value,
805
+ .sparkline-value {
806
+ font-size: 0.75rem;
807
+ color: var(--color-text-muted);
808
+ background: var(--color-bg-elevated);
809
+ padding: var(--space-xs) var(--space-sm);
810
+ border-radius: var(--radius-sm);
811
+ }
812
+
813
+ .bar-chart {
814
+ display: flex;
815
+ align-items: flex-end;
816
+ gap: 6px;
817
+ height: 80px;
818
+ padding: var(--space-sm) 0;
819
+ }
820
+
821
+ .bar-chart .bar {
822
+ background: linear-gradient(180deg, var(--color-primary), rgba(59, 130, 246, 0.3));
823
+ border-radius: 4px 4px 0 0;
824
+ transition: height 0.3s ease;
825
+ }
826
+
827
+ .bar-chart .bar.spike {
828
+ background: linear-gradient(180deg, var(--color-warn), rgba(245, 158, 11, 0.3));
829
+ }
830
+
831
+ .bar-chart-hint,
832
+ .sparkline-hint {
833
+ font-size: 0.625rem;
834
+ color: var(--color-text-dim);
835
+ margin-top: var(--space-sm);
836
+ }
837
+
838
+ .sparkline {
839
+ display: block;
840
+ width: 100%;
841
+ }
842
+
843
+ /* ============================================================================
844
+ BROKEN FLOWS
845
+ ============================================================================ */
846
+
847
+ .broken-flows {
848
+ display: flex;
849
+ flex-direction: column;
850
+ gap: var(--space-lg);
851
+ }
852
+
853
+ .flow-card {
854
+ background: var(--color-bg-card);
855
+ border-radius: var(--radius-lg);
856
+ padding: var(--space-xl);
857
+ border: 1px solid var(--color-border);
858
+ }
859
+
860
+ .flow-header {
861
+ display: flex;
862
+ justify-content: space-between;
863
+ align-items: center;
864
+ margin-bottom: var(--space-lg);
865
+ }
866
+
867
+ .flow-title {
868
+ font-weight: 600;
869
+ font-size: 1rem;
870
+ }
871
+
872
+ .flow-severity {
873
+ padding: var(--space-xs) var(--space-md);
874
+ border-radius: var(--radius-full);
875
+ font-size: 0.625rem;
876
+ font-weight: 700;
877
+ text-transform: uppercase;
878
+ letter-spacing: 0.05em;
879
+ }
880
+
881
+ .flow-severity.blocker {
882
+ background: var(--color-block-bg);
883
+ color: var(--color-block);
884
+ }
885
+
886
+ .flow-severity.warning {
887
+ background: var(--color-warn-bg);
888
+ color: var(--color-warn);
889
+ }
890
+
891
+ .flow-steps {
892
+ display: flex;
893
+ flex-wrap: wrap;
894
+ align-items: center;
895
+ gap: var(--space-sm);
896
+ }
897
+
898
+ .flow-step {
899
+ display: inline-flex;
900
+ align-items: center;
901
+ gap: var(--space-xs);
902
+ padding: var(--space-sm) var(--space-md);
903
+ border-radius: var(--radius-md);
904
+ font-size: 0.75rem;
905
+ background: var(--color-bg-elevated);
906
+ }
907
+
908
+ .flow-step.ui {
909
+ border-left: 3px solid var(--color-primary);
910
+ }
911
+
912
+ .flow-step.api {
913
+ border-left: 3px solid #8b5cf6;
914
+ }
915
+
916
+ .flow-step.form {
917
+ border-left: 3px solid var(--color-warn);
918
+ }
919
+
920
+ .flow-step.error {
921
+ border-left: 3px solid var(--color-block);
922
+ background: var(--color-block-bg);
923
+ }
924
+
925
+ .step-type {
926
+ font-weight: 600;
927
+ font-size: 0.625rem;
928
+ color: var(--color-text-dim);
929
+ text-transform: uppercase;
930
+ }
931
+
932
+ .step-label {
933
+ color: var(--color-text);
934
+ }
935
+
936
+ .step-arrow {
937
+ color: var(--color-text-dim);
938
+ font-size: 0.875rem;
939
+ }
940
+
736
941
  /* ============================================================================
737
942
  PRINT STYLES
738
943
  ============================================================================ */
@@ -926,6 +1131,169 @@ function generateFixEstimates(fixEstimates) {
926
1131
  `;
927
1132
  }
928
1133
 
1134
+ function generateCoverageRing(percent, label, color) {
1135
+ const radius = 40;
1136
+ const circumference = 2 * Math.PI * radius;
1137
+ const progress = (percent / 100) * circumference;
1138
+ const colorVar = color || (percent >= 80 ? "var(--color-ship)" : percent >= 50 ? "var(--color-warn)" : "var(--color-block)");
1139
+
1140
+ return `
1141
+ <div class="coverage-ring-container">
1142
+ <svg class="coverage-ring" width="100" height="100" viewBox="0 0 100 100">
1143
+ <circle cx="50" cy="50" r="${radius}" fill="none" stroke="var(--color-border)" stroke-width="8"/>
1144
+ <circle cx="50" cy="50" r="${radius}" fill="none" stroke="${colorVar}" stroke-width="8"
1145
+ stroke-linecap="round" stroke-dasharray="${progress} ${circumference - progress}"
1146
+ transform="rotate(-90 50 50)" style="transition: stroke-dasharray 1s ease-out;"/>
1147
+ </svg>
1148
+ <div class="coverage-ring-value">${percent}%</div>
1149
+ <div class="coverage-ring-label">${label}</div>
1150
+ </div>
1151
+ `;
1152
+ }
1153
+
1154
+ function generateCoverageMetrics(reality) {
1155
+ if (!reality || !reality.coverage) return "";
1156
+
1157
+ const { clientCallsMapped, runtimeRequests, uiActionsVerified, authRoutes } = reality.coverage;
1158
+
1159
+ return `
1160
+ <section class="section">
1161
+ <div class="section-header">
1162
+ <div class="section-icon">📊</div>
1163
+ <div>
1164
+ <h2 class="section-title">Coverage Metrics</h2>
1165
+ <p class="section-subtitle">Runtime verification results</p>
1166
+ </div>
1167
+ </div>
1168
+ <div class="card">
1169
+ <div class="coverage-grid">
1170
+ ${generateCoverageRing(clientCallsMapped || 0, "Client Calls Mapped", "var(--color-warn)")}
1171
+ ${generateCoverageRing(runtimeRequests || 0, "Runtime Requests", "var(--color-warn)")}
1172
+ ${generateCoverageRing(uiActionsVerified || 0, "UI Actions Verified", "var(--color-ship)")}
1173
+ ${generateCoverageRing(authRoutes || 0, "Auth Routes", "var(--color-ship)")}
1174
+ </div>
1175
+ </div>
1176
+ </section>
1177
+ `;
1178
+ }
1179
+
1180
+ function generateSparklineChart(data, label, maxLabel) {
1181
+ if (!data || data.length === 0) return "";
1182
+
1183
+ const max = Math.max(...data);
1184
+ const width = 300;
1185
+ const height = 60;
1186
+ const padding = 5;
1187
+ const stepX = (width - padding * 2) / (data.length - 1);
1188
+
1189
+ const points = data.map((v, i) => {
1190
+ const x = padding + i * stepX;
1191
+ const y = height - padding - ((v / max) * (height - padding * 2));
1192
+ return `${x},${y}`;
1193
+ }).join(" ");
1194
+
1195
+ return `
1196
+ <div class="sparkline-container">
1197
+ <div class="sparkline-header">
1198
+ <span class="sparkline-label">${label}</span>
1199
+ <span class="sparkline-value">${maxLabel}</span>
1200
+ </div>
1201
+ <svg class="sparkline" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
1202
+ <polyline fill="none" stroke="var(--color-primary)" stroke-width="2" points="${points}"/>
1203
+ <circle cx="${padding + (data.length - 1) * stepX}" cy="${height - padding - ((data[data.length - 1] / max) * (height - padding * 2))}" r="4" fill="var(--color-primary)"/>
1204
+ </svg>
1205
+ <div class="sparkline-hint">smooth = stable · sharp peaks = slow endpoints</div>
1206
+ </div>
1207
+ `;
1208
+ }
1209
+
1210
+ function generateBarChart(data, label, totalLabel) {
1211
+ if (!data || data.length === 0) return "";
1212
+
1213
+ const max = Math.max(...data);
1214
+ const barWidth = 20;
1215
+ const gap = 8;
1216
+ const height = 80;
1217
+
1218
+ return `
1219
+ <div class="bar-chart-container">
1220
+ <div class="bar-chart-header">
1221
+ <span class="bar-chart-label">${label}</span>
1222
+ <span class="bar-chart-value">${data.reduce((a, b) => a + b, 0)} ${totalLabel}</span>
1223
+ </div>
1224
+ <div class="bar-chart">
1225
+ ${data.map((v, i) => {
1226
+ const barHeight = max > 0 ? (v / max) * (height - 10) : 0;
1227
+ const isSpike = v > (max * 0.7);
1228
+ return `<div class="bar ${isSpike ? 'spike' : ''}" style="height: ${barHeight}px; width: ${barWidth}px;"></div>`;
1229
+ }).join("")}
1230
+ </div>
1231
+ <div class="bar-chart-hint">last ${data.length} steps · spikes = unmapped</div>
1232
+ </div>
1233
+ `;
1234
+ }
1235
+
1236
+ function generateRuntimeCharts(reality) {
1237
+ if (!reality) return "";
1238
+
1239
+ const hasCharts = reality.requestsOverTime || reality.latencySparkline;
1240
+ if (!hasCharts) return "";
1241
+
1242
+ return `
1243
+ <section class="section">
1244
+ <div class="card-grid" style="grid-template-columns: 1fr 1fr;">
1245
+ ${reality.requestsOverTime ? `
1246
+ <div class="card">
1247
+ ${generateBarChart(reality.requestsOverTime, "Requests Over Time", "total")}
1248
+ </div>
1249
+ ` : ""}
1250
+ ${reality.latencySparkline ? `
1251
+ <div class="card">
1252
+ ${generateSparklineChart(reality.latencySparkline, "Latency Sparkline", (reality.latencyP95 || 0) + "ms p95")}
1253
+ </div>
1254
+ ` : ""}
1255
+ </div>
1256
+ </section>
1257
+ `;
1258
+ }
1259
+
1260
+ function generateBrokenFlows(reality) {
1261
+ if (!reality || !reality.brokenFlows || reality.brokenFlows.length === 0) return "";
1262
+
1263
+ const criticalCount = reality.brokenFlows.filter(f => f.severity === "BLOCK").length;
1264
+
1265
+ return `
1266
+ <section class="section">
1267
+ <div class="section-header">
1268
+ <div class="section-icon">🔗</div>
1269
+ <div>
1270
+ <h2 class="section-title">Broken User Flows</h2>
1271
+ <p class="section-subtitle">${criticalCount} critical flows detected</p>
1272
+ </div>
1273
+ </div>
1274
+ <div class="broken-flows">
1275
+ ${reality.brokenFlows.map(flow => `
1276
+ <div class="flow-card">
1277
+ <div class="flow-header">
1278
+ <span class="flow-title">${escapeHtml(flow.title)}</span>
1279
+ <span class="flow-severity ${flow.severity === 'BLOCK' ? 'blocker' : 'warning'}">${flow.severity === 'BLOCK' ? 'BLOCKER' : 'WARNING'}</span>
1280
+ </div>
1281
+ <div class="flow-steps">
1282
+ ${flow.steps.map((step, i) => `
1283
+ <div class="flow-step ${step.type}">
1284
+ <span class="step-type">${step.type.toUpperCase()}</span>
1285
+ <span class="step-label">${escapeHtml(step.label)}</span>
1286
+ </div>
1287
+ ${i < flow.steps.length - 1 ? '<span class="step-arrow">→</span>' : ''}
1288
+ `).join("")}
1289
+ </div>
1290
+ </div>
1291
+ `).join("")}
1292
+ </div>
1293
+ </section>
1294
+ `;
1295
+ }
1296
+
929
1297
  function formatDuration(mins) {
930
1298
  if (mins === 0) return "0m";
931
1299
  if (mins < 60) return `${mins}m`;
@@ -948,7 +1316,7 @@ function escapeHtml(str) {
948
1316
  // ============================================================================
949
1317
 
950
1318
  function generateWorldClassHTML(reportData, options = {}) {
951
- const { meta, summary, findings, fixEstimates, coverage, truthpack } = reportData;
1319
+ const { meta, summary, findings, fixEstimates, coverage, truthpack, reality } = reportData;
952
1320
  const verdictClass = summary.verdict.toLowerCase();
953
1321
 
954
1322
  const verdictMessages = {
@@ -1018,6 +1386,12 @@ function generateWorldClassHTML(reportData, options = {}) {
1018
1386
  </div>
1019
1387
  </section>
1020
1388
 
1389
+ <!-- Coverage Metrics (if reality data available) -->
1390
+ ${generateCoverageMetrics(reality)}
1391
+
1392
+ <!-- Runtime Charts (if reality data available) -->
1393
+ ${generateRuntimeCharts(reality)}
1394
+
1021
1395
  <!-- Category Breakdown -->
1022
1396
  <section class="section">
1023
1397
  <div class="section-header">
@@ -1032,6 +1406,9 @@ function generateWorldClassHTML(reportData, options = {}) {
1032
1406
  </div>
1033
1407
  </section>
1034
1408
 
1409
+ <!-- Broken User Flows (if reality data available) -->
1410
+ ${generateBrokenFlows(reality)}
1411
+
1035
1412
  <!-- Severity Distribution -->
1036
1413
  <section class="section">
1037
1414
  <div class="section-header">