@behavioral-contracts/verify-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +119 -0
- package/README.md +694 -0
- package/dist/analyze-results.js +253 -0
- package/dist/analyzer.d.ts +366 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +2592 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/analyzers/async-error-analyzer.d.ts +72 -0
- package/dist/analyzers/async-error-analyzer.d.ts.map +1 -0
- package/dist/analyzers/async-error-analyzer.js +243 -0
- package/dist/analyzers/async-error-analyzer.js.map +1 -0
- package/dist/analyzers/event-listener-analyzer.d.ts +102 -0
- package/dist/analyzers/event-listener-analyzer.d.ts.map +1 -0
- package/dist/analyzers/event-listener-analyzer.js +253 -0
- package/dist/analyzers/event-listener-analyzer.js.map +1 -0
- package/dist/analyzers/react-query-analyzer.d.ts +66 -0
- package/dist/analyzers/react-query-analyzer.d.ts.map +1 -0
- package/dist/analyzers/react-query-analyzer.js +341 -0
- package/dist/analyzers/react-query-analyzer.js.map +1 -0
- package/dist/analyzers/return-value-analyzer.d.ts +61 -0
- package/dist/analyzers/return-value-analyzer.d.ts.map +1 -0
- package/dist/analyzers/return-value-analyzer.js +225 -0
- package/dist/analyzers/return-value-analyzer.js.map +1 -0
- package/dist/code-snippet.d.ts +48 -0
- package/dist/code-snippet.d.ts.map +1 -0
- package/dist/code-snippet.js +84 -0
- package/dist/code-snippet.js.map +1 -0
- package/dist/corpus-loader.d.ts +33 -0
- package/dist/corpus-loader.d.ts.map +1 -0
- package/dist/corpus-loader.js +155 -0
- package/dist/corpus-loader.js.map +1 -0
- package/dist/fixture-tester.d.ts +28 -0
- package/dist/fixture-tester.d.ts.map +1 -0
- package/dist/fixture-tester.js +176 -0
- package/dist/fixture-tester.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +375 -0
- package/dist/index.js.map +1 -0
- package/dist/package-discovery.d.ts +62 -0
- package/dist/package-discovery.d.ts.map +1 -0
- package/dist/package-discovery.js +299 -0
- package/dist/package-discovery.js.map +1 -0
- package/dist/reporter.d.ts +43 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +347 -0
- package/dist/reporter.js.map +1 -0
- package/dist/reporters/benchmarking.d.ts +70 -0
- package/dist/reporters/benchmarking.d.ts.map +1 -0
- package/dist/reporters/benchmarking.js +191 -0
- package/dist/reporters/benchmarking.js.map +1 -0
- package/dist/reporters/d3-visualizer.d.ts +40 -0
- package/dist/reporters/d3-visualizer.d.ts.map +1 -0
- package/dist/reporters/d3-visualizer.js +803 -0
- package/dist/reporters/d3-visualizer.js.map +1 -0
- package/dist/reporters/health-score.d.ts +33 -0
- package/dist/reporters/health-score.d.ts.map +1 -0
- package/dist/reporters/health-score.js +149 -0
- package/dist/reporters/health-score.js.map +1 -0
- package/dist/reporters/index.d.ts +11 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +11 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/package-breakdown.d.ts +48 -0
- package/dist/reporters/package-breakdown.d.ts.map +1 -0
- package/dist/reporters/package-breakdown.js +185 -0
- package/dist/reporters/package-breakdown.js.map +1 -0
- package/dist/reporters/positive-evidence.d.ts +42 -0
- package/dist/reporters/positive-evidence.d.ts.map +1 -0
- package/dist/reporters/positive-evidence.js +436 -0
- package/dist/reporters/positive-evidence.js.map +1 -0
- package/dist/tsconfig-generator.d.ts +17 -0
- package/dist/tsconfig-generator.d.ts.map +1 -0
- package/dist/tsconfig-generator.js +107 -0
- package/dist/tsconfig-generator.js.map +1 -0
- package/dist/types.d.ts +298 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D3.js Visualization Generator - Professional Sentry-Inspired Design
|
|
3
|
+
*
|
|
4
|
+
* Design principles (following .claude/rules/design-system.md):
|
|
5
|
+
* - Clean, minimal, professional aesthetic
|
|
6
|
+
* - Subtle color palette (dark grays, muted accents)
|
|
7
|
+
* - Small fonts (12-14px), generous whitespace
|
|
8
|
+
* - Sentry-like professionalism
|
|
9
|
+
*
|
|
10
|
+
* Color palette:
|
|
11
|
+
* - Background: #0E1116 (dark)
|
|
12
|
+
* - Cards: #1C1F26 (slightly lighter)
|
|
13
|
+
* - Borders: #2D3139 (subtle)
|
|
14
|
+
* - Text: #E6EDF3 (light gray)
|
|
15
|
+
* - Muted: #7D8590 (gray)
|
|
16
|
+
* - Accent: #8B5CF6 (purple)
|
|
17
|
+
* - Success: #3FB950 (green)
|
|
18
|
+
* - Warning: #D29922 (amber)
|
|
19
|
+
* - Error: #F85149 (red)
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Generate interactive D3.js HTML dashboard
|
|
23
|
+
*/
|
|
24
|
+
export function generateD3Dashboard(data) {
|
|
25
|
+
const { audit, health, packageBreakdown, benchmarking, benchmark } = data;
|
|
26
|
+
// Extract repo name
|
|
27
|
+
const repoName = extractRepoName(audit.tsconfig);
|
|
28
|
+
const timestamp = new Date(audit.timestamp).toLocaleString();
|
|
29
|
+
return `<!DOCTYPE html>
|
|
30
|
+
<html lang="en">
|
|
31
|
+
<head>
|
|
32
|
+
<meta charset="UTF-8">
|
|
33
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
34
|
+
<title>Behavioral Contracts Analysis - ${repoName}</title>
|
|
35
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
36
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
37
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
38
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
39
|
+
<style>
|
|
40
|
+
* {
|
|
41
|
+
margin: 0;
|
|
42
|
+
padding: 0;
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:root {
|
|
47
|
+
/* Sentry-inspired color palette */
|
|
48
|
+
--bg-primary: #0E1116;
|
|
49
|
+
--bg-secondary: #1C1F26;
|
|
50
|
+
--bg-tertiary: #22252D;
|
|
51
|
+
--border-primary: #2D3139;
|
|
52
|
+
--border-secondary: #3D4149;
|
|
53
|
+
--text-primary: #E6EDF3;
|
|
54
|
+
--text-secondary: #B1BAC4;
|
|
55
|
+
--text-muted: #7D8590;
|
|
56
|
+
--accent-purple: #8B5CF6;
|
|
57
|
+
--success-green: #3FB950;
|
|
58
|
+
--warning-amber: #D29922;
|
|
59
|
+
--error-red: #F85149;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
body {
|
|
63
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
64
|
+
background: var(--bg-primary);
|
|
65
|
+
color: var(--text-primary);
|
|
66
|
+
font-size: 13px;
|
|
67
|
+
line-height: 1.5;
|
|
68
|
+
padding: 0;
|
|
69
|
+
min-height: 100vh;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.container {
|
|
73
|
+
max-width: 1400px;
|
|
74
|
+
margin: 0 auto;
|
|
75
|
+
padding: 24px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Header */
|
|
79
|
+
.header {
|
|
80
|
+
background: var(--bg-secondary);
|
|
81
|
+
border: 1px solid var(--border-primary);
|
|
82
|
+
border-radius: 6px;
|
|
83
|
+
padding: 20px 24px;
|
|
84
|
+
margin-bottom: 24px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.header h1 {
|
|
88
|
+
color: var(--text-primary);
|
|
89
|
+
font-size: 18px;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
margin-bottom: 8px;
|
|
92
|
+
letter-spacing: -0.01em;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.header .meta {
|
|
96
|
+
color: var(--text-muted);
|
|
97
|
+
font-size: 12px;
|
|
98
|
+
display: flex;
|
|
99
|
+
gap: 16px;
|
|
100
|
+
flex-wrap: wrap;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.header .meta span {
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 6px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.header .meta .separator {
|
|
110
|
+
color: var(--border-secondary);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Grid Layout */
|
|
114
|
+
.grid {
|
|
115
|
+
display: grid;
|
|
116
|
+
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
117
|
+
gap: 16px;
|
|
118
|
+
margin-bottom: 16px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.grid-full {
|
|
122
|
+
grid-column: 1 / -1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Card */
|
|
126
|
+
.card {
|
|
127
|
+
background: var(--bg-secondary);
|
|
128
|
+
border: 1px solid var(--border-primary);
|
|
129
|
+
border-radius: 6px;
|
|
130
|
+
padding: 20px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.card-header {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: space-between;
|
|
137
|
+
margin-bottom: 16px;
|
|
138
|
+
padding-bottom: 12px;
|
|
139
|
+
border-bottom: 1px solid var(--border-primary);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.card-title {
|
|
143
|
+
font-size: 14px;
|
|
144
|
+
font-weight: 600;
|
|
145
|
+
color: var(--text-primary);
|
|
146
|
+
letter-spacing: -0.01em;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Stats Grid */
|
|
150
|
+
.stats-grid {
|
|
151
|
+
display: grid;
|
|
152
|
+
grid-template-columns: repeat(2, 1fr);
|
|
153
|
+
gap: 12px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.stat {
|
|
157
|
+
background: var(--bg-tertiary);
|
|
158
|
+
border: 1px solid var(--border-primary);
|
|
159
|
+
border-radius: 4px;
|
|
160
|
+
padding: 12px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.stat-value {
|
|
164
|
+
font-size: 24px;
|
|
165
|
+
font-weight: 700;
|
|
166
|
+
color: var(--text-primary);
|
|
167
|
+
line-height: 1;
|
|
168
|
+
margin-bottom: 4px;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.stat-label {
|
|
172
|
+
font-size: 11px;
|
|
173
|
+
color: var(--text-muted);
|
|
174
|
+
text-transform: uppercase;
|
|
175
|
+
letter-spacing: 0.05em;
|
|
176
|
+
font-weight: 500;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.stat-value.success { color: var(--success-green); }
|
|
180
|
+
.stat-value.error { color: var(--error-red); }
|
|
181
|
+
.stat-value.warning { color: var(--warning-amber); }
|
|
182
|
+
.stat-value.accent { color: var(--accent-purple); }
|
|
183
|
+
|
|
184
|
+
/* Gauge */
|
|
185
|
+
.gauge-container {
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-direction: column;
|
|
188
|
+
align-items: center;
|
|
189
|
+
padding: 12px 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.gauge-score {
|
|
193
|
+
font-size: 48px;
|
|
194
|
+
font-weight: 700;
|
|
195
|
+
margin-top: 8px;
|
|
196
|
+
line-height: 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.gauge-label {
|
|
200
|
+
font-size: 11px;
|
|
201
|
+
color: var(--text-muted);
|
|
202
|
+
text-transform: uppercase;
|
|
203
|
+
letter-spacing: 0.05em;
|
|
204
|
+
margin-top: 8px;
|
|
205
|
+
font-weight: 500;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Table */
|
|
209
|
+
.table-container {
|
|
210
|
+
overflow-x: auto;
|
|
211
|
+
margin-top: 12px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
table {
|
|
215
|
+
width: 100%;
|
|
216
|
+
border-collapse: collapse;
|
|
217
|
+
font-size: 12px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
thead {
|
|
221
|
+
border-bottom: 1px solid var(--border-primary);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
th {
|
|
225
|
+
text-align: left;
|
|
226
|
+
padding: 8px 12px;
|
|
227
|
+
font-size: 11px;
|
|
228
|
+
font-weight: 600;
|
|
229
|
+
color: var(--text-muted);
|
|
230
|
+
text-transform: uppercase;
|
|
231
|
+
letter-spacing: 0.05em;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
td {
|
|
235
|
+
padding: 10px 12px;
|
|
236
|
+
border-bottom: 1px solid var(--border-primary);
|
|
237
|
+
color: var(--text-secondary);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
tbody tr:last-child td {
|
|
241
|
+
border-bottom: none;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
tbody tr:hover {
|
|
245
|
+
background: var(--bg-tertiary);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* Badge */
|
|
249
|
+
.badge {
|
|
250
|
+
display: inline-flex;
|
|
251
|
+
align-items: center;
|
|
252
|
+
padding: 2px 8px;
|
|
253
|
+
border-radius: 12px;
|
|
254
|
+
font-size: 10px;
|
|
255
|
+
font-weight: 600;
|
|
256
|
+
text-transform: uppercase;
|
|
257
|
+
letter-spacing: 0.05em;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.badge-success {
|
|
261
|
+
background: rgba(63, 185, 80, 0.15);
|
|
262
|
+
color: var(--success-green);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.badge-error {
|
|
266
|
+
background: rgba(248, 81, 73, 0.15);
|
|
267
|
+
color: var(--error-red);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Progress Bar */
|
|
271
|
+
.progress-bar {
|
|
272
|
+
height: 4px;
|
|
273
|
+
background: var(--bg-tertiary);
|
|
274
|
+
border-radius: 2px;
|
|
275
|
+
overflow: hidden;
|
|
276
|
+
margin-top: 6px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.progress-fill {
|
|
280
|
+
height: 100%;
|
|
281
|
+
background: linear-gradient(90deg, var(--accent-purple), var(--success-green));
|
|
282
|
+
transition: width 0.3s ease;
|
|
283
|
+
border-radius: 2px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Insights */
|
|
287
|
+
.insights {
|
|
288
|
+
background: rgba(139, 92, 246, 0.08);
|
|
289
|
+
border: 1px solid rgba(139, 92, 246, 0.2);
|
|
290
|
+
border-radius: 4px;
|
|
291
|
+
padding: 12px 16px;
|
|
292
|
+
margin-top: 16px;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.insights-title {
|
|
296
|
+
font-size: 11px;
|
|
297
|
+
font-weight: 600;
|
|
298
|
+
color: var(--accent-purple);
|
|
299
|
+
text-transform: uppercase;
|
|
300
|
+
letter-spacing: 0.05em;
|
|
301
|
+
margin-bottom: 8px;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.insights ul {
|
|
305
|
+
list-style: none;
|
|
306
|
+
margin: 0;
|
|
307
|
+
padding: 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.insights li {
|
|
311
|
+
padding: 4px 0;
|
|
312
|
+
font-size: 12px;
|
|
313
|
+
color: var(--text-secondary);
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: flex-start;
|
|
316
|
+
gap: 8px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.insights li::before {
|
|
320
|
+
content: "→";
|
|
321
|
+
color: var(--accent-purple);
|
|
322
|
+
flex-shrink: 0;
|
|
323
|
+
margin-top: 2px;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* Benchmark Card */
|
|
327
|
+
.benchmark-layout {
|
|
328
|
+
display: grid;
|
|
329
|
+
grid-template-columns: 320px 1fr;
|
|
330
|
+
gap: 24px;
|
|
331
|
+
align-items: center;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.benchmark-stats {
|
|
335
|
+
display: grid;
|
|
336
|
+
grid-template-columns: repeat(2, 1fr);
|
|
337
|
+
gap: 12px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.benchmark-highlight {
|
|
341
|
+
margin-top: 12px;
|
|
342
|
+
padding: 12px;
|
|
343
|
+
background: rgba(63, 185, 80, 0.08);
|
|
344
|
+
border: 1px solid rgba(63, 185, 80, 0.2);
|
|
345
|
+
border-radius: 4px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.benchmark-highlight strong {
|
|
349
|
+
color: var(--success-green);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
@media (max-width: 768px) {
|
|
353
|
+
.grid {
|
|
354
|
+
grid-template-columns: 1fr;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.benchmark-layout {
|
|
358
|
+
grid-template-columns: 1fr;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.stats-grid {
|
|
362
|
+
grid-template-columns: 1fr;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
</style>
|
|
366
|
+
</head>
|
|
367
|
+
<body>
|
|
368
|
+
<div class="container">
|
|
369
|
+
<!-- Header -->
|
|
370
|
+
<div class="header">
|
|
371
|
+
<h1>Behavioral Contracts Analysis</h1>
|
|
372
|
+
<div class="meta">
|
|
373
|
+
<span><strong>${repoName}</strong></span>
|
|
374
|
+
<span class="separator">•</span>
|
|
375
|
+
<span>${timestamp}</span>
|
|
376
|
+
<span class="separator">•</span>
|
|
377
|
+
<span>${audit.files_analyzed} files</span>
|
|
378
|
+
<span class="separator">•</span>
|
|
379
|
+
<span>${audit.contracts_applied} checks</span>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<!-- Main Grid -->
|
|
384
|
+
<div class="grid">
|
|
385
|
+
<!-- Health Score -->
|
|
386
|
+
<div class="card">
|
|
387
|
+
<div class="card-header">
|
|
388
|
+
<div class="card-title">Health Score</div>
|
|
389
|
+
</div>
|
|
390
|
+
<div class="gauge-container">
|
|
391
|
+
<svg id="health-gauge" width="280" height="160"></svg>
|
|
392
|
+
<div class="gauge-score" id="gauge-score">--</div>
|
|
393
|
+
<div class="gauge-label">Overall Code Health</div>
|
|
394
|
+
</div>
|
|
395
|
+
<div class="stats-grid" style="margin-top: 16px;">
|
|
396
|
+
<div class="stat">
|
|
397
|
+
<div class="stat-value">${health.errorHandlingCompliance}%</div>
|
|
398
|
+
<div class="stat-label">Error Handling</div>
|
|
399
|
+
</div>
|
|
400
|
+
<div class="stat">
|
|
401
|
+
<div class="stat-value">${health.packageCoverage}%</div>
|
|
402
|
+
<div class="stat-label">Coverage</div>
|
|
403
|
+
</div>
|
|
404
|
+
<div class="stat">
|
|
405
|
+
<div class="stat-value">${health.codeMaturity}</div>
|
|
406
|
+
<div class="stat-label">Maturity</div>
|
|
407
|
+
</div>
|
|
408
|
+
<div class="stat">
|
|
409
|
+
<div class="stat-value">${health.riskLevel}</div>
|
|
410
|
+
<div class="stat-label">Risk Level</div>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
<!-- Summary Stats -->
|
|
416
|
+
<div class="card">
|
|
417
|
+
<div class="card-header">
|
|
418
|
+
<div class="card-title">Summary</div>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="stats-grid">
|
|
421
|
+
<div class="stat">
|
|
422
|
+
<div class="stat-value accent">${health.checksPerformed}</div>
|
|
423
|
+
<div class="stat-label">Checks Performed</div>
|
|
424
|
+
</div>
|
|
425
|
+
<div class="stat">
|
|
426
|
+
<div class="stat-value success">${health.checksPassed}</div>
|
|
427
|
+
<div class="stat-label">Checks Passed</div>
|
|
428
|
+
</div>
|
|
429
|
+
<div class="stat">
|
|
430
|
+
<div class="stat-value ${audit.violations.length === 0 ? 'success' : 'error'}">${audit.violations.length}</div>
|
|
431
|
+
<div class="stat-label">Violations Found</div>
|
|
432
|
+
</div>
|
|
433
|
+
<div class="stat">
|
|
434
|
+
<div class="stat-value">${packageBreakdown.packagesWithContracts}</div>
|
|
435
|
+
<div class="stat-label">Packages</div>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
${generateInsightsHTML(health, audit, benchmarking)}
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
${benchmarking && benchmark ? generateBenchmarkingHTML(benchmarking, benchmark) : ''}
|
|
443
|
+
|
|
444
|
+
<!-- Package Breakdown -->
|
|
445
|
+
<div class="card grid-full">
|
|
446
|
+
<div class="card-header">
|
|
447
|
+
<div class="card-title">Package Breakdown</div>
|
|
448
|
+
<div style="font-size: 11px; color: var(--text-muted);">
|
|
449
|
+
${packageBreakdown.packagesFullyCompliant} passing, ${packageBreakdown.packagesWithViolations} failing
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
<div class="table-container">
|
|
453
|
+
<table>
|
|
454
|
+
<thead>
|
|
455
|
+
<tr>
|
|
456
|
+
<th>Package</th>
|
|
457
|
+
<th style="text-align: center;">Checks</th>
|
|
458
|
+
<th style="text-align: center;">Passed</th>
|
|
459
|
+
<th style="text-align: center;">Failed</th>
|
|
460
|
+
<th>Compliance</th>
|
|
461
|
+
<th>Status</th>
|
|
462
|
+
</tr>
|
|
463
|
+
</thead>
|
|
464
|
+
<tbody>
|
|
465
|
+
${packageBreakdown.packages.map(pkg => `
|
|
466
|
+
<tr>
|
|
467
|
+
<td><strong>${pkg.packageName}</strong></td>
|
|
468
|
+
<td style="text-align: center;">${pkg.contractsApplied}</td>
|
|
469
|
+
<td style="text-align: center; color: var(--success-green);">${pkg.checksPassedCount}</td>
|
|
470
|
+
<td style="text-align: center; color: ${pkg.violationsFound > 0 ? 'var(--error-red)' : 'var(--text-muted)'};">${pkg.violationsFound}</td>
|
|
471
|
+
<td>
|
|
472
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
473
|
+
<span style="min-width: 32px;">${pkg.compliancePercent}%</span>
|
|
474
|
+
<div class="progress-bar" style="flex: 1;">
|
|
475
|
+
<div class="progress-fill" style="width: ${pkg.compliancePercent}%"></div>
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
</td>
|
|
479
|
+
<td>
|
|
480
|
+
<span class="badge badge-${pkg.status === 'PASS' ? 'success' : 'error'}">
|
|
481
|
+
${pkg.status === 'PASS' ? '✓ Pass' : '✗ Fail'}
|
|
482
|
+
</span>
|
|
483
|
+
</td>
|
|
484
|
+
</tr>
|
|
485
|
+
`).join('')}
|
|
486
|
+
</tbody>
|
|
487
|
+
</table>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
<script>
|
|
493
|
+
// Data
|
|
494
|
+
const healthScore = ${health.overallScore};
|
|
495
|
+
${benchmarking && benchmark ? `
|
|
496
|
+
const benchmarkData = ${JSON.stringify(benchmark)};
|
|
497
|
+
const comparisonData = ${JSON.stringify(benchmarking)};
|
|
498
|
+
` : ''}
|
|
499
|
+
|
|
500
|
+
// Health Score Gauge
|
|
501
|
+
function drawHealthGauge() {
|
|
502
|
+
const width = 280;
|
|
503
|
+
const height = 160;
|
|
504
|
+
const radius = 70;
|
|
505
|
+
|
|
506
|
+
const svg = d3.select('#health-gauge');
|
|
507
|
+
|
|
508
|
+
// Color scale - subtle gradient
|
|
509
|
+
const colorScale = d3.scaleLinear()
|
|
510
|
+
.domain([0, 50, 70, 90, 100])
|
|
511
|
+
.range(['#F85149', '#D29922', '#8B5CF6', '#3FB950', '#3FB950']);
|
|
512
|
+
|
|
513
|
+
// Background arc
|
|
514
|
+
const bgArc = d3.arc()
|
|
515
|
+
.innerRadius(radius - 12)
|
|
516
|
+
.outerRadius(radius)
|
|
517
|
+
.startAngle(-Math.PI / 2)
|
|
518
|
+
.endAngle(Math.PI / 2);
|
|
519
|
+
|
|
520
|
+
svg.append('path')
|
|
521
|
+
.attr('d', bgArc)
|
|
522
|
+
.attr('transform', \`translate(\${width/2}, \${height-20})\`)
|
|
523
|
+
.attr('fill', '#22252D')
|
|
524
|
+
.attr('opacity', 0.5);
|
|
525
|
+
|
|
526
|
+
// Score arc (animated)
|
|
527
|
+
const scoreAngle = -Math.PI / 2 + (healthScore / 100) * Math.PI;
|
|
528
|
+
|
|
529
|
+
const scoreArc = d3.arc()
|
|
530
|
+
.innerRadius(radius - 12)
|
|
531
|
+
.outerRadius(radius)
|
|
532
|
+
.startAngle(-Math.PI / 2)
|
|
533
|
+
.endAngle(scoreAngle);
|
|
534
|
+
|
|
535
|
+
const path = svg.append('path')
|
|
536
|
+
.attr('transform', \`translate(\${width/2}, \${height-20})\`)
|
|
537
|
+
.attr('fill', colorScale(healthScore));
|
|
538
|
+
|
|
539
|
+
// Animate the arc
|
|
540
|
+
path.transition()
|
|
541
|
+
.duration(1200)
|
|
542
|
+
.ease(d3.easeCubicOut)
|
|
543
|
+
.attrTween('d', function() {
|
|
544
|
+
const interpolate = d3.interpolate(-Math.PI / 2, scoreAngle);
|
|
545
|
+
return function(t) {
|
|
546
|
+
const currentArc = d3.arc()
|
|
547
|
+
.innerRadius(radius - 12)
|
|
548
|
+
.outerRadius(radius)
|
|
549
|
+
.startAngle(-Math.PI / 2)
|
|
550
|
+
.endAngle(interpolate(t));
|
|
551
|
+
return currentArc();
|
|
552
|
+
};
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Animate the score number
|
|
556
|
+
d3.select('#gauge-score')
|
|
557
|
+
.transition()
|
|
558
|
+
.duration(1200)
|
|
559
|
+
.tween('text', function() {
|
|
560
|
+
const interpolate = d3.interpolate(0, healthScore);
|
|
561
|
+
return function(t) {
|
|
562
|
+
this.textContent = Math.round(interpolate(t));
|
|
563
|
+
};
|
|
564
|
+
})
|
|
565
|
+
.style('color', colorScale(healthScore));
|
|
566
|
+
|
|
567
|
+
// Add subtle tick marks
|
|
568
|
+
const ticks = [0, 25, 50, 75, 100];
|
|
569
|
+
ticks.forEach(tick => {
|
|
570
|
+
const angle = -Math.PI / 2 + (tick / 100) * Math.PI;
|
|
571
|
+
const x1 = Math.cos(angle) * (radius - 15);
|
|
572
|
+
const y1 = Math.sin(angle) * (radius - 15);
|
|
573
|
+
const x2 = Math.cos(angle) * (radius + 2);
|
|
574
|
+
const y2 = Math.sin(angle) * (radius + 2);
|
|
575
|
+
|
|
576
|
+
svg.append('line')
|
|
577
|
+
.attr('transform', \`translate(\${width/2}, \${height-20})\`)
|
|
578
|
+
.attr('x1', x1)
|
|
579
|
+
.attr('y1', y1)
|
|
580
|
+
.attr('x2', x2)
|
|
581
|
+
.attr('y2', y2)
|
|
582
|
+
.attr('stroke', '#3D4149')
|
|
583
|
+
.attr('stroke-width', 1.5);
|
|
584
|
+
|
|
585
|
+
svg.append('text')
|
|
586
|
+
.attr('transform', \`translate(\${width/2}, \${height-20})\`)
|
|
587
|
+
.attr('x', Math.cos(angle) * (radius + 15))
|
|
588
|
+
.attr('y', Math.sin(angle) * (radius + 15))
|
|
589
|
+
.attr('text-anchor', 'middle')
|
|
590
|
+
.attr('dominant-baseline', 'middle')
|
|
591
|
+
.attr('fill', '#7D8590')
|
|
592
|
+
.attr('font-size', '10px')
|
|
593
|
+
.text(tick);
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
${benchmarking && benchmark ? `
|
|
598
|
+
// Benchmarking Distribution Chart
|
|
599
|
+
function drawBenchmarkChart() {
|
|
600
|
+
const margin = {top: 10, right: 20, bottom: 30, left: 50};
|
|
601
|
+
const width = 450 - margin.left - margin.right;
|
|
602
|
+
const height = 200 - margin.top - margin.bottom;
|
|
603
|
+
|
|
604
|
+
const svg = d3.select('#benchmark-chart')
|
|
605
|
+
.append('svg')
|
|
606
|
+
.attr('width', width + margin.left + margin.right)
|
|
607
|
+
.attr('height', height + margin.top + margin.bottom)
|
|
608
|
+
.append('g')
|
|
609
|
+
.attr('transform', \`translate(\${margin.left},\${margin.top})\`);
|
|
610
|
+
|
|
611
|
+
const percentiles = [
|
|
612
|
+
{ label: '25th', value: benchmarkData.percentiles.p25, y: 0.75 },
|
|
613
|
+
{ label: 'Median', value: benchmarkData.percentiles.p50, y: 0.5 },
|
|
614
|
+
{ label: '75th', value: benchmarkData.percentiles.p75, y: 0.25 },
|
|
615
|
+
{ label: '90th', value: benchmarkData.percentiles.p90, y: 0.1 }
|
|
616
|
+
];
|
|
617
|
+
|
|
618
|
+
const maxViolations = Math.max(benchmarkData.percentiles.p90, comparisonData.your_violations) + 10;
|
|
619
|
+
|
|
620
|
+
const x = d3.scaleLinear()
|
|
621
|
+
.domain([0, maxViolations])
|
|
622
|
+
.range([0, width]);
|
|
623
|
+
|
|
624
|
+
const y = d3.scaleLinear()
|
|
625
|
+
.domain([0, 1])
|
|
626
|
+
.range([height, 0]);
|
|
627
|
+
|
|
628
|
+
// Draw distribution area
|
|
629
|
+
const area = d3.area()
|
|
630
|
+
.x(d => x(d.value))
|
|
631
|
+
.y0(d => y(d.y - 0.12))
|
|
632
|
+
.y1(d => y(d.y + 0.12))
|
|
633
|
+
.curve(d3.curveBasis);
|
|
634
|
+
|
|
635
|
+
svg.append('path')
|
|
636
|
+
.datum(percentiles)
|
|
637
|
+
.attr('fill', '#8B5CF6')
|
|
638
|
+
.attr('opacity', 0.2)
|
|
639
|
+
.attr('d', area);
|
|
640
|
+
|
|
641
|
+
// Draw percentile markers
|
|
642
|
+
percentiles.forEach(p => {
|
|
643
|
+
svg.append('line')
|
|
644
|
+
.attr('x1', x(p.value))
|
|
645
|
+
.attr('x2', x(p.value))
|
|
646
|
+
.attr('y1', y(p.y - 0.1))
|
|
647
|
+
.attr('y2', y(p.y + 0.1))
|
|
648
|
+
.attr('stroke', '#8B5CF6')
|
|
649
|
+
.attr('stroke-width', 1.5);
|
|
650
|
+
|
|
651
|
+
svg.append('text')
|
|
652
|
+
.attr('x', x(p.value))
|
|
653
|
+
.attr('y', y(p.y - 0.18))
|
|
654
|
+
.attr('text-anchor', 'middle')
|
|
655
|
+
.attr('font-size', '10px')
|
|
656
|
+
.attr('fill', '#7D8590')
|
|
657
|
+
.text(p.label);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// Draw your position
|
|
661
|
+
const yourX = x(comparisonData.your_violations);
|
|
662
|
+
svg.append('line')
|
|
663
|
+
.attr('x1', yourX)
|
|
664
|
+
.attr('x2', yourX)
|
|
665
|
+
.attr('y1', 0)
|
|
666
|
+
.attr('y2', height)
|
|
667
|
+
.attr('stroke', '#3FB950')
|
|
668
|
+
.attr('stroke-width', 2)
|
|
669
|
+
.attr('stroke-dasharray', '3,3');
|
|
670
|
+
|
|
671
|
+
svg.append('circle')
|
|
672
|
+
.attr('cx', yourX)
|
|
673
|
+
.attr('cy', height / 2)
|
|
674
|
+
.attr('r', 5)
|
|
675
|
+
.attr('fill', '#3FB950')
|
|
676
|
+
.attr('stroke', '#1C1F26')
|
|
677
|
+
.attr('stroke-width', 2);
|
|
678
|
+
|
|
679
|
+
svg.append('text')
|
|
680
|
+
.attr('x', yourX)
|
|
681
|
+
.attr('y', height / 2 - 15)
|
|
682
|
+
.attr('text-anchor', 'middle')
|
|
683
|
+
.attr('font-weight', '600')
|
|
684
|
+
.attr('font-size', '10px')
|
|
685
|
+
.attr('fill', '#3FB950')
|
|
686
|
+
.text('YOU');
|
|
687
|
+
|
|
688
|
+
// X-axis
|
|
689
|
+
svg.append('g')
|
|
690
|
+
.attr('transform', \`translate(0,\${height})\`)
|
|
691
|
+
.call(d3.axisBottom(x).ticks(5))
|
|
692
|
+
.attr('color', '#3D4149')
|
|
693
|
+
.selectAll('text')
|
|
694
|
+
.attr('font-size', '10px')
|
|
695
|
+
.attr('fill', '#7D8590');
|
|
696
|
+
|
|
697
|
+
svg.append('text')
|
|
698
|
+
.attr('x', width / 2)
|
|
699
|
+
.attr('y', height + 28)
|
|
700
|
+
.attr('text-anchor', 'middle')
|
|
701
|
+
.attr('font-size', '10px')
|
|
702
|
+
.attr('fill', '#7D8590')
|
|
703
|
+
.text('Violations');
|
|
704
|
+
}
|
|
705
|
+
` : ''}
|
|
706
|
+
|
|
707
|
+
// Initialize
|
|
708
|
+
drawHealthGauge();
|
|
709
|
+
${benchmarking && benchmark ? 'drawBenchmarkChart();' : ''}
|
|
710
|
+
</script>
|
|
711
|
+
</body>
|
|
712
|
+
</html>`;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Generate insights HTML
|
|
716
|
+
*/
|
|
717
|
+
function generateInsightsHTML(health, audit, benchmarking) {
|
|
718
|
+
const insights = [];
|
|
719
|
+
if (health.errorHandlingCompliance === 100) {
|
|
720
|
+
insights.push('Perfect compliance across all package usage points');
|
|
721
|
+
}
|
|
722
|
+
else if (health.errorHandlingCompliance >= 95) {
|
|
723
|
+
insights.push(`High compliance rate with only ${audit.violations.length} issues`);
|
|
724
|
+
}
|
|
725
|
+
if (benchmarking && benchmarking.violations_avoided > 0) {
|
|
726
|
+
insights.push(`Avoided ${benchmarking.violations_avoided} violations vs average repo`);
|
|
727
|
+
}
|
|
728
|
+
if (health.checksPerformed > 100) {
|
|
729
|
+
insights.push(`Comprehensive analysis with ${health.checksPerformed} checks performed`);
|
|
730
|
+
}
|
|
731
|
+
if (insights.length === 0)
|
|
732
|
+
return '';
|
|
733
|
+
return `
|
|
734
|
+
<div class="insights">
|
|
735
|
+
<div class="insights-title">Key Insights</div>
|
|
736
|
+
<ul>
|
|
737
|
+
${insights.map(insight => `<li>${insight}</li>`).join('')}
|
|
738
|
+
</ul>
|
|
739
|
+
</div>
|
|
740
|
+
`;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Generate benchmarking section HTML
|
|
744
|
+
*/
|
|
745
|
+
function generateBenchmarkingHTML(benchmarking, benchmark) {
|
|
746
|
+
return `
|
|
747
|
+
<div class="card grid-full">
|
|
748
|
+
<div class="card-header">
|
|
749
|
+
<div class="card-title">Benchmarking</div>
|
|
750
|
+
<div style="font-size: 11px; color: var(--text-muted);">
|
|
751
|
+
Compared against ${benchmark.sample_size} repositories
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
<div class="benchmark-layout">
|
|
755
|
+
<div>
|
|
756
|
+
<div class="benchmark-stats">
|
|
757
|
+
<div class="stat">
|
|
758
|
+
<div class="stat-value">${benchmarking.your_violations}</div>
|
|
759
|
+
<div class="stat-label">Your Violations</div>
|
|
760
|
+
</div>
|
|
761
|
+
<div class="stat">
|
|
762
|
+
<div class="stat-value">${benchmarking.avg_violations}</div>
|
|
763
|
+
<div class="stat-label">Average</div>
|
|
764
|
+
</div>
|
|
765
|
+
<div class="stat">
|
|
766
|
+
<div class="stat-value success">Top ${100 - benchmarking.percentile_rank}%</div>
|
|
767
|
+
<div class="stat-label">Ranking</div>
|
|
768
|
+
</div>
|
|
769
|
+
<div class="stat">
|
|
770
|
+
<div class="stat-value">${benchmark.sample_size}</div>
|
|
771
|
+
<div class="stat-label">Repos Analyzed</div>
|
|
772
|
+
</div>
|
|
773
|
+
</div>
|
|
774
|
+
<div class="benchmark-highlight" style="font-size: 12px;">
|
|
775
|
+
Your repo is <strong>${benchmarking.comparison.toLowerCase()}</strong> than ${benchmarking.percentile_rank}% of repos scanned.
|
|
776
|
+
${benchmarking.violations_avoided > 0 ? `<br>You avoided <strong>${benchmarking.violations_avoided} violations</strong> compared to average.` : ''}
|
|
777
|
+
</div>
|
|
778
|
+
</div>
|
|
779
|
+
<div id="benchmark-chart"></div>
|
|
780
|
+
</div>
|
|
781
|
+
</div>
|
|
782
|
+
`;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Extract repository name from tsconfig path
|
|
786
|
+
*/
|
|
787
|
+
function extractRepoName(tsconfigPath) {
|
|
788
|
+
const parts = tsconfigPath.split('/');
|
|
789
|
+
const repoIndex = parts.findIndex(p => p === 'test-repos') + 1;
|
|
790
|
+
if (repoIndex > 0 && repoIndex < parts.length) {
|
|
791
|
+
return parts[repoIndex];
|
|
792
|
+
}
|
|
793
|
+
return parts[parts.length - 2] || 'Unknown Repository';
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Write D3 visualization to file
|
|
797
|
+
*/
|
|
798
|
+
export async function writeD3Visualization(data, outputPath) {
|
|
799
|
+
const { writeFile } = await import('fs/promises');
|
|
800
|
+
const html = generateD3Dashboard(data);
|
|
801
|
+
await writeFile(outputPath, html, 'utf-8');
|
|
802
|
+
}
|
|
803
|
+
//# sourceMappingURL=d3-visualizer.js.map
|