@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.
Files changed (80) hide show
  1. package/LICENSE +119 -0
  2. package/README.md +694 -0
  3. package/dist/analyze-results.js +253 -0
  4. package/dist/analyzer.d.ts +366 -0
  5. package/dist/analyzer.d.ts.map +1 -0
  6. package/dist/analyzer.js +2592 -0
  7. package/dist/analyzer.js.map +1 -0
  8. package/dist/analyzers/async-error-analyzer.d.ts +72 -0
  9. package/dist/analyzers/async-error-analyzer.d.ts.map +1 -0
  10. package/dist/analyzers/async-error-analyzer.js +243 -0
  11. package/dist/analyzers/async-error-analyzer.js.map +1 -0
  12. package/dist/analyzers/event-listener-analyzer.d.ts +102 -0
  13. package/dist/analyzers/event-listener-analyzer.d.ts.map +1 -0
  14. package/dist/analyzers/event-listener-analyzer.js +253 -0
  15. package/dist/analyzers/event-listener-analyzer.js.map +1 -0
  16. package/dist/analyzers/react-query-analyzer.d.ts +66 -0
  17. package/dist/analyzers/react-query-analyzer.d.ts.map +1 -0
  18. package/dist/analyzers/react-query-analyzer.js +341 -0
  19. package/dist/analyzers/react-query-analyzer.js.map +1 -0
  20. package/dist/analyzers/return-value-analyzer.d.ts +61 -0
  21. package/dist/analyzers/return-value-analyzer.d.ts.map +1 -0
  22. package/dist/analyzers/return-value-analyzer.js +225 -0
  23. package/dist/analyzers/return-value-analyzer.js.map +1 -0
  24. package/dist/code-snippet.d.ts +48 -0
  25. package/dist/code-snippet.d.ts.map +1 -0
  26. package/dist/code-snippet.js +84 -0
  27. package/dist/code-snippet.js.map +1 -0
  28. package/dist/corpus-loader.d.ts +33 -0
  29. package/dist/corpus-loader.d.ts.map +1 -0
  30. package/dist/corpus-loader.js +155 -0
  31. package/dist/corpus-loader.js.map +1 -0
  32. package/dist/fixture-tester.d.ts +28 -0
  33. package/dist/fixture-tester.d.ts.map +1 -0
  34. package/dist/fixture-tester.js +176 -0
  35. package/dist/fixture-tester.js.map +1 -0
  36. package/dist/index.d.ts +6 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +375 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/package-discovery.d.ts +62 -0
  41. package/dist/package-discovery.d.ts.map +1 -0
  42. package/dist/package-discovery.js +299 -0
  43. package/dist/package-discovery.js.map +1 -0
  44. package/dist/reporter.d.ts +43 -0
  45. package/dist/reporter.d.ts.map +1 -0
  46. package/dist/reporter.js +347 -0
  47. package/dist/reporter.js.map +1 -0
  48. package/dist/reporters/benchmarking.d.ts +70 -0
  49. package/dist/reporters/benchmarking.d.ts.map +1 -0
  50. package/dist/reporters/benchmarking.js +191 -0
  51. package/dist/reporters/benchmarking.js.map +1 -0
  52. package/dist/reporters/d3-visualizer.d.ts +40 -0
  53. package/dist/reporters/d3-visualizer.d.ts.map +1 -0
  54. package/dist/reporters/d3-visualizer.js +803 -0
  55. package/dist/reporters/d3-visualizer.js.map +1 -0
  56. package/dist/reporters/health-score.d.ts +33 -0
  57. package/dist/reporters/health-score.d.ts.map +1 -0
  58. package/dist/reporters/health-score.js +149 -0
  59. package/dist/reporters/health-score.js.map +1 -0
  60. package/dist/reporters/index.d.ts +11 -0
  61. package/dist/reporters/index.d.ts.map +1 -0
  62. package/dist/reporters/index.js +11 -0
  63. package/dist/reporters/index.js.map +1 -0
  64. package/dist/reporters/package-breakdown.d.ts +48 -0
  65. package/dist/reporters/package-breakdown.d.ts.map +1 -0
  66. package/dist/reporters/package-breakdown.js +185 -0
  67. package/dist/reporters/package-breakdown.js.map +1 -0
  68. package/dist/reporters/positive-evidence.d.ts +42 -0
  69. package/dist/reporters/positive-evidence.d.ts.map +1 -0
  70. package/dist/reporters/positive-evidence.js +436 -0
  71. package/dist/reporters/positive-evidence.js.map +1 -0
  72. package/dist/tsconfig-generator.d.ts +17 -0
  73. package/dist/tsconfig-generator.d.ts.map +1 -0
  74. package/dist/tsconfig-generator.js +107 -0
  75. package/dist/tsconfig-generator.js.map +1 -0
  76. package/dist/types.d.ts +298 -0
  77. package/dist/types.d.ts.map +1 -0
  78. package/dist/types.js +5 -0
  79. package/dist/types.js.map +1 -0
  80. 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