@sun-asterisk/sunlint 1.3.34 โ†’ 1.3.35

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 (90) hide show
  1. package/core/architecture-integration.js +16 -7
  2. package/core/auto-performance-manager.js +1 -1
  3. package/core/cli-action-handler.js +92 -2
  4. package/core/cli-program.js +96 -138
  5. package/core/file-targeting-service.js +62 -4
  6. package/core/git-utils.js +19 -12
  7. package/core/github-annotate-service.js +326 -11
  8. package/core/html-report-generator.js +326 -731
  9. package/core/impact-integration.js +433 -0
  10. package/core/output-service.js +293 -21
  11. package/core/scoring-service.js +3 -2
  12. package/engines/arch-detect/core/analyzer.js +413 -0
  13. package/engines/arch-detect/core/index.js +22 -0
  14. package/engines/arch-detect/engine/hybrid-detector.js +176 -0
  15. package/engines/arch-detect/engine/index.js +24 -0
  16. package/engines/arch-detect/engine/rule-executor.js +228 -0
  17. package/engines/arch-detect/engine/score-calculator.js +214 -0
  18. package/engines/arch-detect/engine/violation-detector.js +616 -0
  19. package/engines/arch-detect/index.js +50 -0
  20. package/engines/arch-detect/rules/base-rule.js +187 -0
  21. package/engines/arch-detect/rules/index.js +35 -0
  22. package/engines/arch-detect/rules/layered/index.js +28 -0
  23. package/engines/arch-detect/rules/layered/l001-presentation-layer.js +237 -0
  24. package/engines/arch-detect/rules/layered/l002-business-layer.js +215 -0
  25. package/engines/arch-detect/rules/layered/l003-data-layer.js +229 -0
  26. package/engines/arch-detect/rules/layered/l004-model-layer.js +204 -0
  27. package/engines/arch-detect/rules/layered/l005-layer-separation.js +215 -0
  28. package/engines/arch-detect/rules/layered/l006-dependency-direction.js +221 -0
  29. package/engines/arch-detect/rules/layered/layered-rules-collection.js +445 -0
  30. package/engines/arch-detect/rules/modular/index.js +27 -0
  31. package/engines/arch-detect/rules/modular/m001-feature-modules.js +238 -0
  32. package/engines/arch-detect/rules/modular/m002-core-module.js +169 -0
  33. package/engines/arch-detect/rules/modular/m003-module-declaration.js +186 -0
  34. package/engines/arch-detect/rules/modular/m004-public-api.js +171 -0
  35. package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +220 -0
  36. package/engines/arch-detect/rules/modular/modular-rules-collection.js +357 -0
  37. package/engines/arch-detect/rules/presentation/index.js +27 -0
  38. package/engines/arch-detect/rules/presentation/pr001-view-layer.js +221 -0
  39. package/engines/arch-detect/rules/presentation/pr002-presentation-logic.js +192 -0
  40. package/engines/arch-detect/rules/presentation/pr004-data-binding.js +187 -0
  41. package/engines/arch-detect/rules/presentation/pr006-router-layer.js +185 -0
  42. package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +181 -0
  43. package/engines/arch-detect/rules/presentation/presentation-rules-collection.js +507 -0
  44. package/engines/arch-detect/rules/project-scanner/index.js +31 -0
  45. package/engines/arch-detect/rules/project-scanner/ps001-project-root.js +213 -0
  46. package/engines/arch-detect/rules/project-scanner/ps002-language-detection.js +192 -0
  47. package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +339 -0
  48. package/engines/arch-detect/rules/project-scanner/ps004-build-system.js +171 -0
  49. package/engines/arch-detect/rules/project-scanner/ps005-source-directory.js +163 -0
  50. package/engines/arch-detect/rules/project-scanner/ps006-test-directory.js +184 -0
  51. package/engines/arch-detect/rules/project-scanner/ps007-documentation.js +149 -0
  52. package/engines/arch-detect/rules/project-scanner/ps008-cicd-detection.js +163 -0
  53. package/engines/arch-detect/rules/project-scanner/ps009-code-quality.js +152 -0
  54. package/engines/arch-detect/rules/project-scanner/ps010-statistics.js +180 -0
  55. package/engines/arch-detect/rules/rule-registry.js +111 -0
  56. package/engines/arch-detect/types/context.types.js +60 -0
  57. package/engines/arch-detect/types/enums.js +161 -0
  58. package/engines/arch-detect/types/index.js +25 -0
  59. package/engines/arch-detect/types/result.types.js +7 -0
  60. package/engines/arch-detect/types/rule.types.js +7 -0
  61. package/engines/arch-detect/utils/file-scanner.js +411 -0
  62. package/engines/arch-detect/utils/index.js +23 -0
  63. package/engines/arch-detect/utils/pattern-matcher.js +328 -0
  64. package/engines/impact/cli.js +106 -0
  65. package/engines/impact/config/default-config.js +54 -0
  66. package/engines/impact/core/change-detector.js +258 -0
  67. package/engines/impact/core/detectors/database-detector.js +1317 -0
  68. package/engines/impact/core/detectors/endpoint-detector.js +55 -0
  69. package/engines/impact/core/impact-analyzer.js +124 -0
  70. package/engines/impact/core/report-generator.js +462 -0
  71. package/engines/impact/core/utils/ast-parser.js +241 -0
  72. package/engines/impact/core/utils/dependency-graph.js +159 -0
  73. package/engines/impact/core/utils/file-utils.js +116 -0
  74. package/engines/impact/core/utils/git-utils.js +203 -0
  75. package/engines/impact/core/utils/logger.js +13 -0
  76. package/engines/impact/core/utils/method-call-graph.js +1192 -0
  77. package/engines/impact/index.js +135 -0
  78. package/engines/impact/package.json +29 -0
  79. package/package.json +18 -43
  80. package/scripts/build-release.sh +0 -0
  81. package/scripts/copy-impact-analyzer.js +135 -0
  82. package/scripts/install.sh +0 -0
  83. package/scripts/manual-release.sh +0 -0
  84. package/scripts/pre-release-test.sh +0 -0
  85. package/scripts/prepare-release.sh +0 -0
  86. package/scripts/quick-performance-test.js +0 -0
  87. package/scripts/setup-github-registry.sh +0 -0
  88. package/scripts/trigger-release.sh +0 -0
  89. package/scripts/verify-install.sh +0 -0
  90. package/templates/combined-report.html +1418 -0
@@ -0,0 +1,1418 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SunLint Code Quality Report</title>
7
+ <style>
8
+ :root {
9
+ /* GitHub Dark Theme */
10
+ --color-canvas-default: #0d1117;
11
+ --color-canvas-subtle: #161b22;
12
+ --color-canvas-inset: #010409;
13
+ --color-border-default: #30363d;
14
+ --color-border-muted: #21262d;
15
+
16
+ --color-fg-default: #e6edf3;
17
+ --color-fg-muted: #8b949e;
18
+ --color-fg-subtle: #6e7681;
19
+
20
+ --color-accent-fg: #58a6ff;
21
+ --color-accent-emphasis: #1f6feb;
22
+ --color-accent-muted: rgba(56, 139, 253, 0.4);
23
+ --color-accent-subtle: rgba(56, 139, 253, 0.15);
24
+
25
+ --color-success-fg: #3fb950;
26
+ --color-success-emphasis: #238636;
27
+ --color-success-muted: rgba(46, 160, 67, 0.4);
28
+ --color-success-subtle: rgba(46, 160, 67, 0.15);
29
+
30
+ --color-attention-fg: #d29922;
31
+ --color-attention-emphasis: #9e6a03;
32
+ --color-attention-muted: rgba(187, 128, 9, 0.4);
33
+ --color-attention-subtle: rgba(187, 128, 9, 0.15);
34
+
35
+ --color-danger-fg: #f85149;
36
+ --color-danger-emphasis: #da3633;
37
+ --color-danger-muted: rgba(248, 81, 73, 0.4);
38
+ --color-danger-subtle: rgba(248, 81, 73, 0.15);
39
+
40
+ --color-done-fg: #a371f7;
41
+ --color-done-emphasis: #8957e5;
42
+ --color-done-subtle: rgba(163, 113, 247, 0.15);
43
+
44
+ --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
45
+ --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
46
+
47
+ --radius-1: 6px;
48
+ --radius-2: 12px;
49
+ }
50
+
51
+ * {
52
+ margin: 0;
53
+ padding: 0;
54
+ box-sizing: border-box;
55
+ }
56
+
57
+ body {
58
+ font-family: var(--font-sans);
59
+ font-size: 14px;
60
+ line-height: 1.5;
61
+ color: var(--color-fg-default);
62
+ background: var(--color-canvas-default);
63
+ -webkit-font-smoothing: antialiased;
64
+ }
65
+
66
+ a {
67
+ color: var(--color-accent-fg);
68
+ text-decoration: none;
69
+ }
70
+
71
+ a:hover {
72
+ text-decoration: underline;
73
+ }
74
+
75
+ .container {
76
+ max-width: 1280px;
77
+ margin: 0 auto;
78
+ padding: 16px 24px;
79
+ }
80
+
81
+ /* Header */
82
+ .Header {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: space-between;
86
+ padding: 12px 0;
87
+ margin-bottom: 16px;
88
+ border-bottom: 1px solid var(--color-border-default);
89
+ }
90
+
91
+ .Header-brand {
92
+ display: flex;
93
+ align-items: center;
94
+ gap: 16px;
95
+ }
96
+
97
+ .Header-brand img {
98
+ height: 36px;
99
+ }
100
+
101
+ .Header-brand-text {
102
+ display: flex;
103
+ flex-direction: column;
104
+ gap: 2px;
105
+ }
106
+
107
+ .Header-brand h1 {
108
+ font-size: 22px;
109
+ font-weight: 700;
110
+ color: var(--color-fg-default);
111
+ letter-spacing: -0.5px;
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 8px;
115
+ }
116
+
117
+ .Header-brand h1::after {
118
+ content: 'Report';
119
+ font-size: 11px;
120
+ font-weight: 600;
121
+ padding: 2px 8px;
122
+ background: var(--color-accent-subtle);
123
+ color: var(--color-accent-fg);
124
+ border-radius: 4px;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.5px;
127
+ }
128
+
129
+ .Header-brand-tagline {
130
+ font-size: 13px;
131
+ color: var(--color-fg-muted);
132
+ }
133
+
134
+ .version-sup {
135
+ font-size: 10px;
136
+ font-weight: 600;
137
+ color: var(--color-done-fg);
138
+ background: var(--color-done-subtle);
139
+ padding: 1px 5px;
140
+ border-radius: 4px;
141
+ margin-left: 4px;
142
+ vertical-align: super;
143
+ }
144
+
145
+ .Header-meta {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 12px;
149
+ font-size: 12px;
150
+ color: var(--color-fg-muted);
151
+ }
152
+
153
+ .Header-date {
154
+ display: inline-flex;
155
+ align-items: center;
156
+ gap: 6px;
157
+ padding: 4px 12px;
158
+ background: var(--color-canvas-subtle);
159
+ border: 1px solid var(--color-border-default);
160
+ border-radius: 2em;
161
+ }
162
+
163
+ .Header-date svg {
164
+ opacity: 0.7;
165
+ }
166
+
167
+ .pulse-dot {
168
+ width: 8px;
169
+ height: 8px;
170
+ background: var(--color-success-fg);
171
+ border-radius: 50%;
172
+ animation: pulse 2s ease-in-out infinite;
173
+ }
174
+
175
+ @keyframes pulse {
176
+ 0%, 100% { opacity: 1; transform: scale(1); }
177
+ 50% { opacity: 0.6; transform: scale(0.9); }
178
+ }
179
+
180
+ .Label {
181
+ display: inline-flex;
182
+ align-items: center;
183
+ gap: 6px;
184
+ padding: 0 8px;
185
+ font-size: 12px;
186
+ font-weight: 500;
187
+ line-height: 22px;
188
+ border-radius: 2em;
189
+ white-space: nowrap;
190
+ }
191
+
192
+ .Label--success {
193
+ color: var(--color-success-fg);
194
+ background: var(--color-success-subtle);
195
+ border: 1px solid var(--color-success-muted);
196
+ }
197
+
198
+ .Label--attention {
199
+ color: var(--color-attention-fg);
200
+ background: var(--color-attention-subtle);
201
+ border: 1px solid var(--color-attention-muted);
202
+ }
203
+
204
+ .Label--danger {
205
+ color: var(--color-danger-fg);
206
+ background: var(--color-danger-subtle);
207
+ border: 1px solid var(--color-danger-muted);
208
+ }
209
+
210
+ .Label--accent {
211
+ color: var(--color-accent-fg);
212
+ background: var(--color-accent-subtle);
213
+ border: 1px solid var(--color-accent-muted);
214
+ }
215
+
216
+ .Label--done {
217
+ color: var(--color-done-fg);
218
+ background: var(--color-done-subtle);
219
+ border: 1px solid rgba(163, 113, 247, 0.4);
220
+ }
221
+
222
+ /* Score Overview */
223
+ .ScoreOverview {
224
+ display: grid;
225
+ grid-template-columns: 280px 1fr;
226
+ gap: 20px;
227
+ padding: 20px;
228
+ background: var(--color-canvas-subtle);
229
+ border: 1px solid var(--color-border-default);
230
+ border-radius: var(--radius-2);
231
+ margin-bottom: 16px;
232
+ }
233
+
234
+ .ScoreRing {
235
+ display: flex;
236
+ align-items: center;
237
+ justify-content: center;
238
+ }
239
+
240
+ .ScoreRing-circle {
241
+ position: relative;
242
+ width: 180px;
243
+ height: 180px;
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: center;
247
+ }
248
+
249
+ .ScoreRing-circle svg {
250
+ position: absolute;
251
+ top: 0;
252
+ left: 0;
253
+ transform: rotate(-90deg);
254
+ }
255
+
256
+ .ScoreRing-circle .track {
257
+ fill: none;
258
+ stroke: var(--color-border-default);
259
+ stroke-width: 8;
260
+ }
261
+
262
+ .ScoreRing-circle .progress {
263
+ fill: none;
264
+ stroke-width: 8;
265
+ stroke-linecap: round;
266
+ transition: stroke-dashoffset 0.6s ease;
267
+ }
268
+
269
+ .ScoreRing-circle .progress.excellent { stroke: var(--color-success-fg); }
270
+ .ScoreRing-circle .progress.good { stroke: var(--color-attention-fg); }
271
+ .ScoreRing-circle .progress.poor { stroke: var(--color-danger-fg); }
272
+
273
+ .ScoreRing-value {
274
+ position: relative;
275
+ text-align: center;
276
+ z-index: 1;
277
+ }
278
+
279
+ .ScoreRing-number {
280
+ font-size: 48px;
281
+ font-weight: 600;
282
+ line-height: 1;
283
+ color: var(--color-fg-default);
284
+ font-feature-settings: 'tnum';
285
+ }
286
+
287
+ .ScoreRing-label {
288
+ font-size: 13px;
289
+ color: var(--color-fg-muted);
290
+ margin-top: 2px;
291
+ }
292
+
293
+ .ScoreDetails {
294
+ display: flex;
295
+ flex-direction: column;
296
+ gap: 20px;
297
+ }
298
+
299
+ .ScoreDetails-grade {
300
+ display: flex;
301
+ align-items: center;
302
+ gap: 16px;
303
+ }
304
+
305
+ .GradeBadge {
306
+ display: flex;
307
+ align-items: center;
308
+ justify-content: center;
309
+ width: 64px;
310
+ height: 64px;
311
+ font-size: 28px;
312
+ font-weight: 700;
313
+ border-radius: var(--radius-1);
314
+ }
315
+
316
+ .GradeBadge.excellent {
317
+ background: var(--color-success-subtle);
318
+ color: var(--color-success-fg);
319
+ border: 2px solid var(--color-success-emphasis);
320
+ }
321
+
322
+ .GradeBadge.good {
323
+ background: var(--color-attention-subtle);
324
+ color: var(--color-attention-fg);
325
+ border: 2px solid var(--color-attention-emphasis);
326
+ }
327
+
328
+ .GradeBadge.poor {
329
+ background: var(--color-danger-subtle);
330
+ color: var(--color-danger-fg);
331
+ border: 2px solid var(--color-danger-emphasis);
332
+ }
333
+
334
+ .GradeInfo h2 {
335
+ font-size: 24px;
336
+ font-weight: 600;
337
+ color: var(--color-fg-default);
338
+ }
339
+
340
+ .GradeInfo p {
341
+ font-size: 14px;
342
+ color: var(--color-fg-muted);
343
+ margin-top: 2px;
344
+ }
345
+
346
+ .CounterGroup {
347
+ display: flex;
348
+ gap: 12px;
349
+ }
350
+
351
+ .Counter {
352
+ flex: 1;
353
+ display: flex;
354
+ align-items: center;
355
+ gap: 12px;
356
+ padding: 16px;
357
+ background: var(--color-canvas-default);
358
+ border: 1px solid var(--color-border-default);
359
+ border-radius: var(--radius-1);
360
+ }
361
+
362
+ .Counter-icon {
363
+ width: 40px;
364
+ height: 40px;
365
+ display: flex;
366
+ align-items: center;
367
+ justify-content: center;
368
+ border-radius: var(--radius-1);
369
+ font-size: 18px;
370
+ }
371
+
372
+ .Counter-icon.danger {
373
+ background: var(--color-danger-subtle);
374
+ color: var(--color-danger-fg);
375
+ }
376
+
377
+ .Counter-icon.attention {
378
+ background: var(--color-attention-subtle);
379
+ color: var(--color-attention-fg);
380
+ }
381
+
382
+ .Counter-icon.accent {
383
+ background: var(--color-accent-subtle);
384
+ color: var(--color-accent-fg);
385
+ }
386
+
387
+ .Counter-value {
388
+ font-size: 28px;
389
+ font-weight: 600;
390
+ color: var(--color-fg-default);
391
+ font-feature-settings: 'tnum';
392
+ }
393
+
394
+ .Counter-label {
395
+ font-size: 12px;
396
+ color: var(--color-fg-muted);
397
+ text-transform: uppercase;
398
+ letter-spacing: 0.5px;
399
+ }
400
+
401
+ /* Git Bar */
402
+ .GitBar {
403
+ display: flex;
404
+ flex-wrap: wrap;
405
+ gap: 8px;
406
+ margin-top: 20px;
407
+ padding-top: 20px;
408
+ border-top: 1px solid var(--color-border-default);
409
+ }
410
+
411
+ .GitBar-item {
412
+ display: inline-flex;
413
+ align-items: center;
414
+ gap: 6px;
415
+ padding: 4px 12px;
416
+ font-size: 12px;
417
+ color: var(--color-fg-muted);
418
+ background: var(--color-canvas-default);
419
+ border: 1px solid var(--color-border-default);
420
+ border-radius: 2em;
421
+ }
422
+
423
+ .GitBar-item.highlight {
424
+ font-family: var(--font-mono);
425
+ color: var(--color-done-fg);
426
+ background: var(--color-done-subtle);
427
+ border-color: rgba(163, 113, 247, 0.4);
428
+ }
429
+
430
+ a.GitBar-item {
431
+ text-decoration: none;
432
+ transition: all 0.15s ease;
433
+ }
434
+
435
+ a.GitBar-item:hover {
436
+ border-color: var(--color-accent-fg);
437
+ color: var(--color-accent-fg);
438
+ text-decoration: none;
439
+ }
440
+
441
+ /* Box */
442
+ .Box {
443
+ background: var(--color-canvas-subtle);
444
+ border: 1px solid var(--color-border-default);
445
+ border-radius: var(--radius-1);
446
+ margin-bottom: 16px;
447
+ }
448
+
449
+ .Box-header {
450
+ display: flex;
451
+ align-items: center;
452
+ justify-content: space-between;
453
+ padding: 16px;
454
+ background: var(--color-canvas-default);
455
+ border-bottom: 1px solid var(--color-border-default);
456
+ border-radius: var(--radius-1) var(--radius-1) 0 0;
457
+ }
458
+
459
+ .Box-header h3 {
460
+ display: flex;
461
+ align-items: center;
462
+ gap: 8px;
463
+ font-size: 14px;
464
+ font-weight: 600;
465
+ color: var(--color-fg-default);
466
+ }
467
+
468
+ .Box-body {
469
+ padding: 16px;
470
+ }
471
+
472
+ .Box-row {
473
+ padding: 12px 16px;
474
+ border-bottom: 1px solid var(--color-border-muted);
475
+ }
476
+
477
+ .Box-row:last-child {
478
+ border-bottom: none;
479
+ }
480
+
481
+ /* Cards Grid */
482
+ .CardsGrid {
483
+ display: grid;
484
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
485
+ gap: 12px;
486
+ margin-bottom: 16px;
487
+ }
488
+
489
+ .Card {
490
+ background: var(--color-canvas-subtle);
491
+ border: 1px solid var(--color-border-default);
492
+ border-radius: var(--radius-1);
493
+ overflow: hidden;
494
+ }
495
+
496
+ .Card-header {
497
+ display: flex;
498
+ align-items: center;
499
+ gap: 12px;
500
+ padding: 12px 16px;
501
+ background: var(--color-canvas-default);
502
+ border-bottom: 1px solid var(--color-border-default);
503
+ }
504
+
505
+ .Card-header-icon {
506
+ font-size: 16px;
507
+ }
508
+
509
+ .Card-header h4 {
510
+ flex: 1;
511
+ font-size: 14px;
512
+ font-weight: 600;
513
+ color: var(--color-fg-default);
514
+ }
515
+
516
+ .Card-body {
517
+ padding: 16px;
518
+ }
519
+
520
+ /* AI Content */
521
+ .AiContent {
522
+ font-size: 13px;
523
+ line-height: 1.6;
524
+ color: var(--color-fg-muted);
525
+ }
526
+
527
+ .ai-verdict {
528
+ display: flex;
529
+ align-items: center;
530
+ gap: 12px;
531
+ padding: 12px 16px;
532
+ border-radius: var(--radius-1);
533
+ margin-bottom: 16px;
534
+ }
535
+
536
+ .ai-verdict--danger {
537
+ background: var(--color-danger-subtle);
538
+ border: 1px solid var(--color-danger-muted);
539
+ }
540
+
541
+ .ai-verdict--warning {
542
+ background: var(--color-attention-subtle);
543
+ border: 1px solid var(--color-attention-muted);
544
+ }
545
+
546
+ .ai-verdict--success {
547
+ background: var(--color-success-subtle);
548
+ border: 1px solid var(--color-success-muted);
549
+ }
550
+
551
+ .ai-verdict-icon {
552
+ font-size: 24px;
553
+ }
554
+
555
+ .ai-verdict-text {
556
+ font-size: 16px;
557
+ font-weight: 600;
558
+ color: var(--color-fg-default);
559
+ }
560
+
561
+ .ai-verdict--danger .ai-verdict-text { color: var(--color-danger-fg); }
562
+ .ai-verdict--warning .ai-verdict-text { color: var(--color-attention-fg); }
563
+ .ai-verdict--success .ai-verdict-text { color: var(--color-success-fg); }
564
+
565
+ .ai-verdict-desc {
566
+ font-size: 12px;
567
+ color: var(--color-fg-muted);
568
+ margin-left: auto;
569
+ }
570
+
571
+ .AiContent .issue-section,
572
+ .AiContent .recommendation-section {
573
+ margin-bottom: 16px;
574
+ }
575
+
576
+ .AiContent .section-title {
577
+ display: flex;
578
+ align-items: center;
579
+ gap: 6px;
580
+ font-size: 12px;
581
+ font-weight: 600;
582
+ color: var(--color-fg-default);
583
+ margin-bottom: 8px;
584
+ text-transform: uppercase;
585
+ letter-spacing: 0.5px;
586
+ }
587
+
588
+ .AiContent ul {
589
+ list-style: none;
590
+ margin: 0;
591
+ padding: 0;
592
+ }
593
+
594
+ .AiContent li {
595
+ padding: 10px 12px;
596
+ margin-bottom: 6px;
597
+ background: var(--color-canvas-default);
598
+ border-radius: var(--radius-1);
599
+ border-left: 3px solid var(--color-done-fg);
600
+ }
601
+
602
+ .AiContent .rule-tag {
603
+ display: inline-block;
604
+ padding: 1px 6px;
605
+ font-family: var(--font-mono);
606
+ font-size: 11px;
607
+ font-weight: 600;
608
+ color: var(--color-done-fg);
609
+ background: var(--color-done-subtle);
610
+ border-radius: 4px;
611
+ }
612
+
613
+ .AiContent .highlight-error {
614
+ color: var(--color-danger-fg);
615
+ font-weight: 600;
616
+ }
617
+
618
+ .AiContent .highlight-warning {
619
+ color: var(--color-attention-fg);
620
+ }
621
+
622
+ /* Metric */
623
+ .Metric {
624
+ text-align: center;
625
+ padding: 16px 0;
626
+ }
627
+
628
+ .Metric-value {
629
+ font-size: 48px;
630
+ font-weight: 600;
631
+ line-height: 1;
632
+ color: var(--color-fg-default);
633
+ font-feature-settings: 'tnum';
634
+ }
635
+
636
+ .Metric-value.success { color: var(--color-success-fg); }
637
+ .Metric-value.attention { color: var(--color-attention-fg); }
638
+ .Metric-value.danger { color: var(--color-danger-fg); }
639
+
640
+ .Metric-label {
641
+ font-size: 12px;
642
+ color: var(--color-fg-muted);
643
+ margin-top: 4px;
644
+ }
645
+
646
+ .ProgressBar {
647
+ height: 8px;
648
+ background: var(--color-canvas-default);
649
+ border-radius: 4px;
650
+ margin: 16px 0;
651
+ overflow: hidden;
652
+ }
653
+
654
+ .ProgressBar-fill {
655
+ height: 100%;
656
+ border-radius: 4px;
657
+ transition: width 0.6s ease;
658
+ }
659
+
660
+ .ProgressBar-fill.success { background: var(--color-success-fg); }
661
+ .ProgressBar-fill.attention { background: var(--color-attention-fg); }
662
+ .ProgressBar-fill.danger { background: var(--color-danger-fg); }
663
+
664
+ .MetricGrid {
665
+ display: grid;
666
+ grid-template-columns: repeat(2, 1fr);
667
+ gap: 12px;
668
+ }
669
+
670
+ .MetricGrid-item {
671
+ text-align: center;
672
+ padding: 12px;
673
+ background: var(--color-canvas-default);
674
+ border-radius: var(--radius-1);
675
+ }
676
+
677
+ .MetricGrid-item .value {
678
+ font-size: 24px;
679
+ font-weight: 600;
680
+ color: var(--color-fg-default);
681
+ }
682
+
683
+ .MetricGrid-item .label {
684
+ font-size: 11px;
685
+ color: var(--color-fg-muted);
686
+ text-transform: uppercase;
687
+ letter-spacing: 0.5px;
688
+ }
689
+
690
+ /* Table */
691
+ .Table {
692
+ width: 100%;
693
+ border-collapse: collapse;
694
+ font-size: 13px;
695
+ }
696
+
697
+ .Table th {
698
+ padding: 12px 16px;
699
+ text-align: left;
700
+ font-size: 12px;
701
+ font-weight: 600;
702
+ color: var(--color-fg-muted);
703
+ background: var(--color-canvas-default);
704
+ border-bottom: 1px solid var(--color-border-default);
705
+ }
706
+
707
+ .Table td {
708
+ padding: 12px 16px;
709
+ color: var(--color-fg-default);
710
+ border-bottom: 1px solid var(--color-border-muted);
711
+ vertical-align: middle;
712
+ }
713
+
714
+ .Table tr:last-child td {
715
+ border-bottom: none;
716
+ }
717
+
718
+ .Table tr:hover {
719
+ background: var(--color-canvas-inset);
720
+ }
721
+
722
+ .SeverityBadge {
723
+ display: inline-flex;
724
+ align-items: center;
725
+ gap: 4px;
726
+ padding: 2px 8px;
727
+ font-size: 12px;
728
+ font-weight: 500;
729
+ border-radius: 2em;
730
+ }
731
+
732
+ .SeverityBadge.error {
733
+ color: var(--color-danger-fg);
734
+ background: var(--color-danger-subtle);
735
+ }
736
+
737
+ .SeverityBadge.warning {
738
+ color: var(--color-attention-fg);
739
+ background: var(--color-attention-subtle);
740
+ }
741
+
742
+ .RuleId {
743
+ display: inline-block;
744
+ padding: 2px 8px;
745
+ font-family: var(--font-mono);
746
+ font-size: 12px;
747
+ font-weight: 600;
748
+ color: var(--color-accent-fg);
749
+ background: var(--color-accent-subtle);
750
+ border-radius: 4px;
751
+ text-decoration: none;
752
+ transition: all 0.15s ease;
753
+ }
754
+
755
+ a.RuleId:hover {
756
+ background: var(--color-accent-muted);
757
+ text-decoration: none;
758
+ }
759
+
760
+ /* Tooltip */
761
+ .Tooltip {
762
+ position: relative;
763
+ cursor: help;
764
+ }
765
+
766
+ .Tooltip-content {
767
+ position: absolute;
768
+ bottom: calc(100% + 12px);
769
+ left: 50%;
770
+ transform: translateX(-50%);
771
+ padding: 12px 16px;
772
+ background: var(--color-canvas-default);
773
+ border: 1px solid var(--color-border-default);
774
+ border-radius: var(--radius-1);
775
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
776
+ font-size: 12px;
777
+ line-height: 1.6;
778
+ color: var(--color-fg-muted);
779
+ white-space: nowrap;
780
+ opacity: 0;
781
+ visibility: hidden;
782
+ transition: all 0.2s ease;
783
+ z-index: 100;
784
+ }
785
+
786
+ .Tooltip-content::after {
787
+ content: '';
788
+ position: absolute;
789
+ top: 100%;
790
+ left: 50%;
791
+ transform: translateX(-50%);
792
+ border: 8px solid transparent;
793
+ border-top-color: var(--color-border-default);
794
+ }
795
+
796
+ .Tooltip:hover .Tooltip-content {
797
+ opacity: 1;
798
+ visibility: visible;
799
+ }
800
+
801
+ .Tooltip-content .formula {
802
+ font-family: var(--font-mono);
803
+ font-size: 11px;
804
+ color: var(--color-accent-fg);
805
+ background: var(--color-accent-subtle);
806
+ padding: 8px 12px;
807
+ border-radius: 4px;
808
+ margin-top: 8px;
809
+ display: block;
810
+ }
811
+
812
+ .Tooltip-content .title {
813
+ font-weight: 600;
814
+ color: var(--color-fg-default);
815
+ margin-bottom: 4px;
816
+ }
817
+
818
+ .FilePath {
819
+ font-family: var(--font-mono);
820
+ font-size: 12px;
821
+ color: var(--color-accent-fg);
822
+ }
823
+
824
+ .LineNumber {
825
+ font-family: var(--font-mono);
826
+ font-size: 12px;
827
+ color: var(--color-fg-subtle);
828
+ }
829
+
830
+ .Message {
831
+ max-width: 500px;
832
+ color: var(--color-fg-muted);
833
+ line-height: 1.5;
834
+ word-break: break-word;
835
+ }
836
+
837
+ /* Empty State */
838
+ .EmptyState {
839
+ display: flex;
840
+ flex-direction: column;
841
+ align-items: center;
842
+ justify-content: center;
843
+ padding: 48px 24px;
844
+ text-align: center;
845
+ }
846
+
847
+ .EmptyState-icon {
848
+ font-size: 48px;
849
+ margin-bottom: 16px;
850
+ opacity: 0.6;
851
+ }
852
+
853
+ .EmptyState-title {
854
+ font-size: 18px;
855
+ font-weight: 600;
856
+ color: var(--color-fg-default);
857
+ margin-bottom: 8px;
858
+ }
859
+
860
+ .EmptyState-desc {
861
+ font-size: 14px;
862
+ color: var(--color-fg-muted);
863
+ }
864
+
865
+ /* Endpoints */
866
+ .EndpointList {
867
+ display: flex;
868
+ flex-direction: column;
869
+ gap: 6px;
870
+ }
871
+
872
+ .Endpoint {
873
+ display: flex;
874
+ align-items: center;
875
+ gap: 12px;
876
+ padding: 10px 12px;
877
+ background: var(--color-canvas-default);
878
+ border-radius: var(--radius-1);
879
+ }
880
+
881
+ .MethodBadge {
882
+ padding: 2px 8px;
883
+ font-family: var(--font-mono);
884
+ font-size: 11px;
885
+ font-weight: 700;
886
+ border-radius: 4px;
887
+ text-transform: uppercase;
888
+ }
889
+
890
+ .MethodBadge.get { background: var(--color-accent-subtle); color: var(--color-accent-fg); }
891
+ .MethodBadge.post { background: var(--color-success-subtle); color: var(--color-success-fg); }
892
+ .MethodBadge.put { background: var(--color-attention-subtle); color: var(--color-attention-fg); }
893
+ .MethodBadge.delete { background: var(--color-danger-subtle); color: var(--color-danger-fg); }
894
+ .MethodBadge.patch { background: var(--color-done-subtle); color: var(--color-done-fg); }
895
+
896
+ .EndpointPath {
897
+ font-family: var(--font-mono);
898
+ font-size: 13px;
899
+ color: var(--color-fg-muted);
900
+ }
901
+
902
+ /* Footer */
903
+ .Footer {
904
+ display: flex;
905
+ align-items: center;
906
+ justify-content: space-between;
907
+ padding: 16px 0;
908
+ margin-top: 16px;
909
+ border-top: 1px solid var(--color-border-default);
910
+ font-size: 12px;
911
+ color: var(--color-fg-muted);
912
+ }
913
+
914
+ .Footer-brand {
915
+ display: flex;
916
+ align-items: center;
917
+ gap: 8px;
918
+ }
919
+
920
+ .Footer-brand img {
921
+ height: 20px;
922
+ opacity: 0.6;
923
+ }
924
+
925
+ /* Section Header */
926
+ .SectionHeader {
927
+ display: flex;
928
+ align-items: center;
929
+ justify-content: space-between;
930
+ margin-bottom: 8px;
931
+ margin-top: 8px;
932
+ }
933
+
934
+ .SectionHeader h2 {
935
+ display: flex;
936
+ align-items: center;
937
+ gap: 8px;
938
+ font-size: 16px;
939
+ font-weight: 600;
940
+ color: var(--color-fg-default);
941
+ }
942
+
943
+ .SectionHeader .count {
944
+ font-size: 12px;
945
+ color: var(--color-fg-muted);
946
+ background: var(--color-canvas-subtle);
947
+ padding: 2px 10px;
948
+ border-radius: 2em;
949
+ border: 1px solid var(--color-border-default);
950
+ }
951
+
952
+ /* Tabs */
953
+ .TabNav {
954
+ display: flex;
955
+ gap: 0;
956
+ border-bottom: 1px solid var(--color-border-default);
957
+ background: var(--color-canvas-default);
958
+ padding: 0 16px;
959
+ }
960
+
961
+ .TabNav-item {
962
+ display: inline-flex;
963
+ align-items: center;
964
+ gap: 8px;
965
+ padding: 12px 16px;
966
+ font-size: 14px;
967
+ font-weight: 500;
968
+ color: var(--color-fg-muted);
969
+ background: transparent;
970
+ border: none;
971
+ border-bottom: 2px solid transparent;
972
+ cursor: pointer;
973
+ transition: all 0.15s ease;
974
+ }
975
+
976
+ .TabNav-item:hover {
977
+ color: var(--color-fg-default);
978
+ border-bottom-color: var(--color-border-muted);
979
+ }
980
+
981
+ .TabNav-item.selected {
982
+ color: var(--color-fg-default);
983
+ border-bottom-color: var(--color-accent-fg);
984
+ }
985
+
986
+ .TabNav-item .Counter {
987
+ display: inline-flex;
988
+ align-items: center;
989
+ justify-content: center;
990
+ min-width: 20px;
991
+ height: 20px;
992
+ padding: 0 6px;
993
+ font-size: 12px;
994
+ font-weight: 600;
995
+ border-radius: 10px;
996
+ background: var(--color-canvas-subtle);
997
+ color: var(--color-fg-muted);
998
+ }
999
+
1000
+ .TabNav-item.selected .Counter {
1001
+ background: var(--color-accent-subtle);
1002
+ color: var(--color-accent-fg);
1003
+ }
1004
+
1005
+ .TabNav-item.danger .Counter {
1006
+ background: var(--color-danger-subtle);
1007
+ color: var(--color-danger-fg);
1008
+ }
1009
+
1010
+ .TabNav-item.attention .Counter {
1011
+ background: var(--color-attention-subtle);
1012
+ color: var(--color-attention-fg);
1013
+ }
1014
+
1015
+ .TabPanel {
1016
+ display: none;
1017
+ }
1018
+
1019
+ .TabPanel.active {
1020
+ display: block;
1021
+ }
1022
+
1023
+ /* Responsive */
1024
+ @media (max-width: 768px) {
1025
+ .container {
1026
+ padding: 16px;
1027
+ }
1028
+
1029
+ .Header {
1030
+ flex-direction: column;
1031
+ gap: 12px;
1032
+ text-align: center;
1033
+ }
1034
+
1035
+ .ScoreOverview {
1036
+ grid-template-columns: 1fr;
1037
+ text-align: center;
1038
+ }
1039
+
1040
+ .ScoreDetails-grade {
1041
+ justify-content: center;
1042
+ }
1043
+
1044
+ .CounterGroup {
1045
+ flex-direction: column;
1046
+ }
1047
+
1048
+ .GitBar {
1049
+ justify-content: center;
1050
+ }
1051
+
1052
+ .CardsGrid {
1053
+ grid-template-columns: 1fr;
1054
+ }
1055
+
1056
+ .Table {
1057
+ font-size: 12px;
1058
+ }
1059
+
1060
+ .Table th,
1061
+ .Table td {
1062
+ padding: 8px 12px;
1063
+ }
1064
+
1065
+ .Message {
1066
+ max-width: 150px;
1067
+ }
1068
+
1069
+ .Footer {
1070
+ flex-direction: column;
1071
+ gap: 12px;
1072
+ text-align: center;
1073
+ }
1074
+ }
1075
+ </style>
1076
+ </head>
1077
+ <body>
1078
+ <div class="container">
1079
+ <!-- Header -->
1080
+ <header class="Header">
1081
+ <div class="Header-brand">
1082
+ <a href="https://coding-standards.sun-asterisk.vn/">
1083
+ <img src="https://coding-standards.sun-asterisk.vn/logo-light.svg" alt="SunLint">
1084
+ </a>
1085
+ <div class="Header-brand-text">
1086
+ <h1>Code Quality</h1>
1087
+ <span class="Header-brand-tagline">Static Analysis by SunLint<sup class="version-sup">v{{version}}</sup></span>
1088
+ </div>
1089
+ </div>
1090
+ <div class="Header-meta">
1091
+ <span class="Label Label--success">
1092
+ <span class="pulse-dot"></span> Complete
1093
+ </span>
1094
+ <span class="Header-date">
1095
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
1096
+ <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"/>
1097
+ </svg>
1098
+ {{generated_date}}
1099
+ </span>
1100
+ </div>
1101
+ </header>
1102
+
1103
+ <!-- Score Overview -->
1104
+ <div class="ScoreOverview">
1105
+ <div class="ScoreRing">
1106
+ <div class="ScoreRing-circle">
1107
+ <svg width="180" height="180" viewBox="0 0 180 180">
1108
+ <circle class="track" cx="90" cy="90" r="80"></circle>
1109
+ <circle class="progress {{quality_bar_class}}" cx="90" cy="90" r="80"
1110
+ stroke-dasharray="502.65"
1111
+ stroke-dashoffset="calc(502.65 - (502.65 * {{quality_score}}) / 100)">
1112
+ </circle>
1113
+ </svg>
1114
+ <div class="ScoreRing-value Tooltip">
1115
+ <div class="ScoreRing-number">{{quality_score}}</div>
1116
+ <div class="ScoreRing-label">out of 100</div>
1117
+ <div class="Tooltip-content">
1118
+ <div class="title">๐Ÿ“Š Score Formula</div>
1119
+ <div>Base: 100 points</div>
1120
+ <div>Error penalty: -6 pts/KLOC</div>
1121
+ <div>Warning penalty: -2 pts/KLOC</div>
1122
+ <code class="formula">Score = 100 - (Errorsร—6 + Warningsร—2) / KLOC</code>
1123
+ </div>
1124
+ </div>
1125
+ </div>
1126
+ </div>
1127
+
1128
+ <div class="ScoreDetails">
1129
+ <div class="ScoreDetails-grade">
1130
+ <div class="GradeBadge {{quality_bar_class}}">{{quality_grade}}</div>
1131
+ <div class="GradeInfo">
1132
+ <h2>{{grade_emoji}} {{quality_grade}} ยท {{grade_desc}}</h2>
1133
+ <p>Quality Grade</p>
1134
+ </div>
1135
+ </div>
1136
+
1137
+ <div class="CounterGroup">
1138
+ <div class="Counter">
1139
+ <div class="Counter-icon danger">โœ•</div>
1140
+ <div>
1141
+ <div class="Counter-value">{{error_count}}</div>
1142
+ <div class="Counter-label">Errors</div>
1143
+ </div>
1144
+ </div>
1145
+ <div class="Counter">
1146
+ <div class="Counter-icon attention">โš </div>
1147
+ <div>
1148
+ <div class="Counter-value">{{warning_count}}</div>
1149
+ <div class="Counter-label">Warnings</div>
1150
+ </div>
1151
+ </div>
1152
+ <div class="Counter">
1153
+ <div class="Counter-icon accent">๐Ÿ“</div>
1154
+ <div>
1155
+ <div class="Counter-value">{{total_files}}</div>
1156
+ <div class="Counter-label">Files</div>
1157
+ </div>
1158
+ </div>
1159
+ </div>
1160
+
1161
+ <div class="GitBar">
1162
+ <a href="https://github.com/{{repo_name}}" target="_blank" class="GitBar-item">๐Ÿ“ฆ {{repo_name}}</a>
1163
+ <a href="https://github.com/{{repo_name}}/tree/{{branch}}" target="_blank" class="GitBar-item">๐ŸŒฟ {{branch}}</a>
1164
+ <a href="https://github.com/{{repo_name}}/commit/{{commit_short}}" target="_blank" class="GitBar-item highlight">{{commit_short}}</a>
1165
+ <span class="GitBar-item">๐Ÿ‘ค {{commit_author}}</span>
1166
+ </div>
1167
+ </div>
1168
+ </div>
1169
+
1170
+ <!-- Cards Grid -->
1171
+ <div class="CardsGrid">
1172
+ <!-- AI Insights -->
1173
+ {{#if has_ai_summary}}
1174
+ <div class="Card">
1175
+ <div class="Card-header">
1176
+ <span class="Card-header-icon">๐Ÿง </span>
1177
+ <h4>AI Insights</h4>
1178
+ <span class="Label Label--done">GPT-4o</span>
1179
+ </div>
1180
+ <div class="Card-body">
1181
+ <div class="AiContent">
1182
+ {{ai_summary_html}}
1183
+ </div>
1184
+ </div>
1185
+ </div>
1186
+ {{/if}}
1187
+
1188
+ <!-- Architecture -->
1189
+ {{#if has_architecture}}
1190
+ <div class="Card">
1191
+ <div class="Card-header">
1192
+ <span class="Card-header-icon">๐Ÿ›๏ธ</span>
1193
+ <h4>Architecture</h4>
1194
+ <span class="Label Label--accent">{{arch_pattern}}</span>
1195
+ </div>
1196
+ <div class="Card-body">
1197
+ <div class="Metric">
1198
+ <div class="Metric-value {{arch_color}}">{{arch_health}}</div>
1199
+ <div class="Metric-label">Health Score</div>
1200
+ </div>
1201
+ <div class="ProgressBar">
1202
+ <div class="ProgressBar-fill {{arch_bar_class}}" style="width: {{arch_health}}%"></div>
1203
+ </div>
1204
+ <div class="MetricGrid">
1205
+ <div class="MetricGrid-item">
1206
+ <div class="value">{{arch_confidence}}%</div>
1207
+ <div class="label">Confidence</div>
1208
+ </div>
1209
+ <div class="MetricGrid-item">
1210
+ <div class="value">{{arch_violations}}</div>
1211
+ <div class="label">Violations</div>
1212
+ </div>
1213
+ </div>
1214
+ </div>
1215
+ </div>
1216
+ {{/if}}
1217
+
1218
+ <!-- Impact -->
1219
+ {{#if has_impact}}
1220
+ <div class="Card">
1221
+ <div class="Card-header">
1222
+ <span class="Card-header-icon">๐ŸŽฏ</span>
1223
+ <h4>Impact Analysis</h4>
1224
+ <span class="Label Label--{{impact_badge_class}}">{{impact_severity}}</span>
1225
+ </div>
1226
+ <div class="Card-body">
1227
+ <div class="Metric">
1228
+ <div class="Metric-value">{{impact_score}}</div>
1229
+ <div class="Metric-label">Impact Score</div>
1230
+ </div>
1231
+ <div class="ProgressBar">
1232
+ <div class="ProgressBar-fill {{impact_bar_class}}" style="width: {{impact_score}}%"></div>
1233
+ </div>
1234
+ <div class="MetricGrid">
1235
+ <div class="MetricGrid-item">
1236
+ <div class="value">{{affected_apis}}</div>
1237
+ <div class="label">APIs</div>
1238
+ </div>
1239
+ <div class="MetricGrid-item">
1240
+ <div class="value">{{affected_tables}}</div>
1241
+ <div class="label">Tables</div>
1242
+ </div>
1243
+ </div>
1244
+ </div>
1245
+ </div>
1246
+ {{/if}}
1247
+ </div>
1248
+
1249
+ <!-- Endpoints -->
1250
+ {{#if has_endpoints}}
1251
+ <div class="Box">
1252
+ <div class="Box-header">
1253
+ <h3>๐Ÿ”— Affected API Endpoints</h3>
1254
+ <span class="Label Label--accent">{{affected_apis}} endpoints</span>
1255
+ </div>
1256
+ <div class="Box-body">
1257
+ <div class="EndpointList">
1258
+ {{#each endpoints}}
1259
+ <div class="Endpoint">
1260
+ <span class="MethodBadge {{method_lower}}">{{method}}</span>
1261
+ <span class="EndpointPath">{{path}}</span>
1262
+ </div>
1263
+ {{/each}}
1264
+ </div>
1265
+ </div>
1266
+ </div>
1267
+ {{/if}}
1268
+
1269
+ <!-- Violations -->
1270
+ <div class="SectionHeader">
1271
+ <h2>๐Ÿ“‹ Code Violations</h2>
1272
+ <span class="count">{{total_violations}} issues</span>
1273
+ </div>
1274
+ <div class="Box">
1275
+ {{#if has_violations}}
1276
+ <div class="TabNav" role="tablist">
1277
+ <button class="TabNav-item danger selected" role="tab" data-tab="errors" aria-selected="true">
1278
+ <span style="color: var(--color-danger-fg)">โœ•</span> Errors <span class="Counter">{{error_count}}</span>
1279
+ </button>
1280
+ <button class="TabNav-item attention" role="tab" data-tab="warnings" aria-selected="false">
1281
+ <span style="color: var(--color-attention-fg)">โš </span> Warnings <span class="Counter">{{warning_count}}</span>
1282
+ </button>
1283
+ <button class="TabNav-item" role="tab" data-tab="all" aria-selected="false">
1284
+ All <span class="Counter">{{total_violations}}</span>
1285
+ </button>
1286
+ </div>
1287
+ <div class="TabPanel active" data-panel="all">
1288
+ <table class="Table">
1289
+ <thead>
1290
+ <tr>
1291
+ <th>Severity</th>
1292
+ <th>Rule</th>
1293
+ <th>File</th>
1294
+ <th>Line</th>
1295
+ <th>Message</th>
1296
+ </tr>
1297
+ </thead>
1298
+ <tbody>
1299
+ {{#each violations}}
1300
+ <tr data-severity="{{severity}}">
1301
+ <td>
1302
+ <span class="SeverityBadge {{severity}}">
1303
+ {{severity_icon}} {{severity_label}}
1304
+ </span>
1305
+ </td>
1306
+ <td><a href="https://coding-standards.sun-asterisk.vn/rules?rule={{rule}}" target="_blank" class="RuleId">{{rule}}</a></td>
1307
+ <td><span class="FilePath">{{file}}</span></td>
1308
+ <td><span class="LineNumber">:{{line}}</span></td>
1309
+ <td><span class="Message">{{message}}</span></td>
1310
+ </tr>
1311
+ {{/each}}
1312
+ </tbody>
1313
+ </table>
1314
+ </div>
1315
+ {{/if}}
1316
+ {{#if no_violations}}
1317
+ <div class="EmptyState">
1318
+ <div class="EmptyState-icon">โœ…</div>
1319
+ <div class="EmptyState-title">No violations found</div>
1320
+ <div class="EmptyState-desc">Your code passed all quality checks. Great job!</div>
1321
+ </div>
1322
+ {{/if}}
1323
+ </div>
1324
+
1325
+ <!-- Architecture Violations -->
1326
+ {{#if has_arch_violations}}
1327
+ <div class="SectionHeader">
1328
+ <h2>๐Ÿ›๏ธ Architecture Violations</h2>
1329
+ <span class="count">{{arch_violations}} issues</span>
1330
+ </div>
1331
+ <div class="Box">
1332
+ <table class="Table">
1333
+ <thead>
1334
+ <tr>
1335
+ <th>Type</th>
1336
+ <th>Source</th>
1337
+ <th>Target</th>
1338
+ <th>Description</th>
1339
+ </tr>
1340
+ </thead>
1341
+ <tbody>
1342
+ {{#each arch_violation_list}}
1343
+ <tr>
1344
+ <td><span class="SeverityBadge warning">{{type}}</span></td>
1345
+ <td><span class="FilePath">{{source}}</span></td>
1346
+ <td><span class="FilePath">{{target}}</span></td>
1347
+ <td><span class="Message">{{description}}</span></td>
1348
+ </tr>
1349
+ {{/each}}
1350
+ </tbody>
1351
+ </table>
1352
+ </div>
1353
+ {{/if}}
1354
+
1355
+ <!-- Footer -->
1356
+ <footer class="Footer">
1357
+ <div class="Footer-brand">
1358
+ <a href="https://coding-standards.sun-asterisk.vn/">
1359
+ <img src="https://coding-standards.sun-asterisk.vn/logo-light.svg" alt="SunLint">
1360
+ </a>
1361
+ <span>SunLint v{{version}}</span>
1362
+ </div>
1363
+ <div>
1364
+ ยฉ {{year}} Sun* Engineering Excellence ยท <a href="https://coding-standards.sun-asterisk.vn/">coding-standards.sun-asterisk.vn</a>
1365
+ </div>
1366
+ </footer>
1367
+ </div>
1368
+
1369
+ <script>
1370
+ // Tab navigation functionality
1371
+ document.addEventListener('DOMContentLoaded', function() {
1372
+ const tabNav = document.querySelector('.TabNav');
1373
+ if (!tabNav) return;
1374
+
1375
+ const tabs = tabNav.querySelectorAll('.TabNav-item');
1376
+ const table = document.querySelector('.TabPanel table tbody');
1377
+ if (!table) return;
1378
+
1379
+ const allRows = Array.from(table.querySelectorAll('tr'));
1380
+
1381
+ // Filter function
1382
+ function filterRows(filter) {
1383
+ allRows.forEach(row => {
1384
+ const severity = row.dataset.severity;
1385
+
1386
+ if (filter === 'all') {
1387
+ row.style.display = '';
1388
+ } else if (filter === 'errors' && severity === 'error') {
1389
+ row.style.display = '';
1390
+ } else if (filter === 'warnings' && severity === 'warning') {
1391
+ row.style.display = '';
1392
+ } else {
1393
+ row.style.display = 'none';
1394
+ }
1395
+ });
1396
+ }
1397
+
1398
+ // Show errors by default
1399
+ filterRows('errors');
1400
+
1401
+ tabs.forEach(tab => {
1402
+ tab.addEventListener('click', function() {
1403
+ // Update selected state
1404
+ tabs.forEach(t => {
1405
+ t.classList.remove('selected');
1406
+ t.setAttribute('aria-selected', 'false');
1407
+ });
1408
+ this.classList.add('selected');
1409
+ this.setAttribute('aria-selected', 'true');
1410
+
1411
+ // Filter rows based on tab
1412
+ filterRows(this.dataset.tab);
1413
+ });
1414
+ });
1415
+ });
1416
+ </script>
1417
+ </body>
1418
+ </html>