@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.
- package/bin/runners/lib/report-html.js +378 -1
- package/bin/runners/runBadge.js +823 -116
- package/bin/runners/runCtx.js +602 -119
- package/bin/runners/runDoctor.js +329 -42
- package/bin/runners/runFix.js +544 -85
- package/bin/runners/runGraph.js +231 -74
- package/bin/runners/runInit.js +647 -88
- package/bin/runners/runInstall.js +207 -46
- package/bin/runners/runPR.js +123 -32
- package/bin/runners/runProve.js +818 -97
- package/bin/runners/runReality.js +812 -92
- package/bin/runners/runReport.js +68 -2
- package/bin/runners/runShare.js +156 -38
- package/bin/runners/runShip.js +920 -889
- package/bin/runners/runWatch.js +175 -55
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
|
@@ -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">
|