tech-debt-visualizer 0.2.2 → 0.2.3
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/dist/reports/assets/report.css +69 -111
- package/dist/reports/assets/report.js +85 -39
- package/dist/reports/html.js +17 -55
- package/package.json +1 -1
|
@@ -46,11 +46,11 @@ body.dashboard-page {
|
|
|
46
46
|
/* ——— Dashboard header (Grafana-style top bar) ——— */
|
|
47
47
|
.dashboard-header {
|
|
48
48
|
display: flex;
|
|
49
|
-
align-items:
|
|
49
|
+
align-items: flex-start;
|
|
50
50
|
justify-content: space-between;
|
|
51
51
|
flex-wrap: wrap;
|
|
52
|
-
gap:
|
|
53
|
-
padding:
|
|
52
|
+
gap: 1rem;
|
|
53
|
+
padding: 1rem 1.5rem;
|
|
54
54
|
background: var(--bg-elevated);
|
|
55
55
|
border-bottom: 1px solid var(--border);
|
|
56
56
|
position: sticky;
|
|
@@ -60,75 +60,69 @@ body.dashboard-page {
|
|
|
60
60
|
|
|
61
61
|
.dashboard-header-left {
|
|
62
62
|
display: flex;
|
|
63
|
-
align-items:
|
|
64
|
-
gap:
|
|
63
|
+
align-items: flex-start;
|
|
64
|
+
gap: 1rem;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
flex: 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.dashboard-score-badge {
|
|
70
|
+
flex-shrink: 0;
|
|
71
|
+
line-height: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.dashboard-score-badge .score-badge-svg {
|
|
75
|
+
width: 56px;
|
|
76
|
+
height: auto;
|
|
77
|
+
display: block;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.dashboard-score-badge .score-badge-num { font-size: 22px; }
|
|
81
|
+
.dashboard-score-badge .score-badge-of { font-size: 9px; }
|
|
82
|
+
|
|
83
|
+
.dashboard-hero {
|
|
65
84
|
min-width: 0;
|
|
66
85
|
}
|
|
67
86
|
|
|
68
87
|
.dashboard-title {
|
|
69
|
-
margin: 0;
|
|
88
|
+
margin: 0 0 0.25rem;
|
|
70
89
|
font-size: 1.125rem;
|
|
71
90
|
font-weight: 600;
|
|
72
91
|
letter-spacing: -0.01em;
|
|
73
92
|
color: var(--text);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.dashboard-blurb {
|
|
96
|
+
margin: 0 0 0.35rem;
|
|
97
|
+
font-size: 14px;
|
|
98
|
+
line-height: 1.45;
|
|
99
|
+
color: var(--text);
|
|
100
|
+
max-width: 42em;
|
|
77
101
|
}
|
|
78
102
|
|
|
79
103
|
.dashboard-meta {
|
|
80
104
|
font-size: 12px;
|
|
81
105
|
color: var(--text-muted);
|
|
82
|
-
white-space: nowrap;
|
|
83
|
-
overflow: hidden;
|
|
84
|
-
text-overflow: ellipsis;
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
.dashboard-header-right {
|
|
88
109
|
display: flex;
|
|
89
|
-
|
|
90
|
-
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
align-items: flex-end;
|
|
112
|
+
gap: 0.25rem;
|
|
91
113
|
flex-shrink: 0;
|
|
92
114
|
}
|
|
93
115
|
|
|
94
|
-
.dashboard-
|
|
95
|
-
|
|
96
|
-
align-items: baseline;
|
|
97
|
-
gap: 0.2rem;
|
|
98
|
-
padding: 0.35rem 0.65rem;
|
|
99
|
-
border-radius: var(--radius);
|
|
100
|
-
font-weight: 600;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.dashboard-score-value {
|
|
104
|
-
font-size: 1.25rem;
|
|
105
|
-
line-height: 1;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.dashboard-score-of {
|
|
109
|
-
font-size: 0.85rem;
|
|
110
|
-
font-weight: 500;
|
|
116
|
+
.dashboard-stats {
|
|
117
|
+
font-size: 12px;
|
|
111
118
|
color: var(--text-muted);
|
|
119
|
+
white-space: nowrap;
|
|
112
120
|
}
|
|
113
121
|
|
|
114
|
-
.dashboard-score-label {
|
|
115
|
-
font-size: 11px;
|
|
116
|
-
font-weight: 500;
|
|
117
|
-
text-transform: uppercase;
|
|
118
|
-
letter-spacing: 0.04em;
|
|
119
|
-
margin-left: 0.35rem;
|
|
120
|
-
opacity: 0.95;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.dashboard-score.tier-1 { background: rgba(204, 0, 0, 0.2); color: #f66; }
|
|
124
|
-
.dashboard-score.tier-2 { background: rgba(232, 93, 0, 0.2); color: #f90; }
|
|
125
|
-
.dashboard-score.tier-3 { background: rgba(184, 134, 11, 0.2); color: #db9; }
|
|
126
|
-
.dashboard-score.tier-4 { background: rgba(0, 102, 153, 0.2); color: #6cf; }
|
|
127
|
-
.dashboard-score.tier-5 { background: rgba(10, 107, 10, 0.2); color: #6c6; }
|
|
128
|
-
|
|
129
122
|
.dashboard-date {
|
|
130
|
-
font-size:
|
|
123
|
+
font-size: 11px;
|
|
131
124
|
color: var(--text-muted);
|
|
125
|
+
opacity: 0.9;
|
|
132
126
|
}
|
|
133
127
|
|
|
134
128
|
/* ——— Main content & grid ——— */
|
|
@@ -144,23 +138,17 @@ body.dashboard-page {
|
|
|
144
138
|
margin-bottom: 1rem;
|
|
145
139
|
}
|
|
146
140
|
|
|
147
|
-
.dashboard-grid-stats {
|
|
148
|
-
grid-template-columns: repeat(4, 1fr);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
141
|
.dashboard-grid-half {
|
|
152
142
|
grid-template-columns: repeat(2, 1fr);
|
|
153
143
|
}
|
|
154
144
|
|
|
155
145
|
@media (max-width: 900px) {
|
|
156
|
-
.dashboard-grid-stats { grid-template-columns: repeat(2, 1fr); }
|
|
157
146
|
.dashboard-grid-half { grid-template-columns: 1fr; }
|
|
158
147
|
}
|
|
159
148
|
|
|
160
149
|
@media (max-width: 560px) {
|
|
161
|
-
.dashboard-header { padding: 0.
|
|
150
|
+
.dashboard-header { padding: 0.75rem 1rem; }
|
|
162
151
|
.dashboard-main { padding: 1rem; }
|
|
163
|
-
.dashboard-grid-stats { grid-template-columns: 1fr; }
|
|
164
152
|
}
|
|
165
153
|
|
|
166
154
|
/* ——— Panels (Grafana-style) ——— */
|
|
@@ -205,69 +193,29 @@ body.dashboard-page {
|
|
|
205
193
|
text-align: center;
|
|
206
194
|
}
|
|
207
195
|
|
|
208
|
-
/*
|
|
209
|
-
.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
.
|
|
214
|
-
font-size: 1.75rem;
|
|
215
|
-
font-weight: 700;
|
|
216
|
-
line-height: 1.2;
|
|
217
|
-
color: var(--text);
|
|
218
|
-
letter-spacing: -0.02em;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.stat-label {
|
|
222
|
-
font-size: 11px;
|
|
223
|
-
font-weight: 500;
|
|
224
|
-
text-transform: uppercase;
|
|
225
|
-
letter-spacing: 0.04em;
|
|
226
|
-
color: var(--text-muted);
|
|
227
|
-
margin-top: 0.25rem;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
.stat-panel-warn .stat-value { color: #f44; }
|
|
231
|
-
|
|
232
|
-
/* Score panel (cleanliness gauge) */
|
|
233
|
-
.panel-score .panel-body-center {
|
|
234
|
-
padding: 1.25rem 1rem;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.panel-score .score-badge {
|
|
238
|
-
display: inline-block;
|
|
239
|
-
margin-bottom: 0.75rem;
|
|
240
|
-
line-height: 0;
|
|
196
|
+
/* ——— Heatmap panel ——— */
|
|
197
|
+
.panel-heatmap .panel-header-heatmap {
|
|
198
|
+
display: flex;
|
|
199
|
+
flex-wrap: wrap;
|
|
200
|
+
align-items: baseline;
|
|
201
|
+
gap: 0.75rem 1.25rem;
|
|
241
202
|
}
|
|
242
203
|
|
|
243
|
-
.panel-
|
|
244
|
-
|
|
245
|
-
width: 120px;
|
|
246
|
-
height: auto;
|
|
247
|
-
}
|
|
204
|
+
.panel-heatmap .panel-title { margin-right: 0.5rem; }
|
|
205
|
+
.panel-heatmap .panel-desc { margin: 0.15rem 0 0; flex-basis: 100%; }
|
|
248
206
|
|
|
249
|
-
.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
letter-spacing: -0.02em;
|
|
207
|
+
.legend-inline {
|
|
208
|
+
margin: 0;
|
|
209
|
+
margin-left: auto;
|
|
253
210
|
}
|
|
254
211
|
|
|
255
|
-
.panel-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
letter-spacing: 0.08em;
|
|
259
|
-
text-transform: uppercase;
|
|
260
|
-
opacity: 0.95;
|
|
212
|
+
.panel-body-heatmap {
|
|
213
|
+
padding: 1rem;
|
|
214
|
+
padding-top: 0.5rem;
|
|
261
215
|
}
|
|
262
216
|
|
|
263
|
-
.panel-
|
|
264
|
-
|
|
265
|
-
color: var(--text-muted);
|
|
266
|
-
margin: 0;
|
|
267
|
-
max-width: 320px;
|
|
268
|
-
margin-left: auto;
|
|
269
|
-
margin-right: auto;
|
|
270
|
-
line-height: 1.45;
|
|
217
|
+
.panel-body-heatmap #treemap {
|
|
218
|
+
border-radius: var(--radius);
|
|
271
219
|
}
|
|
272
220
|
|
|
273
221
|
/* LLM panel accent */
|
|
@@ -379,8 +327,18 @@ body.dashboard-page {
|
|
|
379
327
|
|
|
380
328
|
.debt-list .title { font-weight: 600; margin-bottom: 0.2rem; }
|
|
381
329
|
.debt-list .meta { font-size: 12px; color: var(--text-muted); display: block; margin-top: 0.25rem; font-family: ui-monospace, monospace; }
|
|
330
|
+
.debt-list-explanation {
|
|
331
|
+
font-size: 12px;
|
|
332
|
+
color: var(--text-muted);
|
|
333
|
+
margin: 0.35rem 0 0;
|
|
334
|
+
line-height: 1.4;
|
|
335
|
+
display: -webkit-box;
|
|
336
|
+
-webkit-line-clamp: 2;
|
|
337
|
+
-webkit-box-orient: vertical;
|
|
338
|
+
overflow: hidden;
|
|
339
|
+
}
|
|
382
340
|
.debt-list .insight { font-size: 13px; color: var(--text-muted); margin-top: 0.25rem; line-height: 1.4; white-space: pre-wrap; word-break: break-word; }
|
|
383
|
-
.debt-list-ratings { display: flex; align-items: center; gap: 1rem; margin: 0.25rem 0; flex-wrap: wrap; }
|
|
341
|
+
.debt-list-ratings { display: flex; align-items: center; gap: 1rem; margin: 0.25rem 0 0; flex-wrap: wrap; }
|
|
384
342
|
.debt-list-rating { display: inline-flex; align-items: center; gap: 0.35rem; }
|
|
385
343
|
.debt-list-rating-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-muted); }
|
|
386
344
|
.debt-list-llm-none { color: var(--text-muted); font-size: 12px; }
|
|
@@ -166,44 +166,81 @@ document.getElementById("q2").innerHTML = highImpact
|
|
|
166
166
|
.map(function (d) { return '<li style="font-size:0.8rem">' + escapeHtml(d.file) + "</li>"; })
|
|
167
167
|
.join("");
|
|
168
168
|
|
|
169
|
-
// Debt list:
|
|
170
|
-
var sev = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
169
|
+
// Debt list: every file rated above none by static OR LLM; show both ratings and short explanation
|
|
170
|
+
var sev = { critical: 4, high: 3, medium: 2, low: 1, none: 0 };
|
|
171
171
|
var list = document.getElementById("debtList");
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
|
|
173
|
+
function severityNum(s) { return sev[s] || 0; }
|
|
174
|
+
function fileWorstStatic(items) {
|
|
175
|
+
if (!items || !items.length) return 0;
|
|
176
|
+
return Math.max.apply(null, items.map(function (d) { return severityNum(d.severity); }));
|
|
177
|
+
}
|
|
178
|
+
function fileWorstLlm(metric) {
|
|
179
|
+
if (!metric || !metric.llmSeverity || metric.llmSeverity === "none") return 0;
|
|
180
|
+
return severityNum(metric.llmSeverity);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
var filesFromStatic = new Set(debtByFile.keys());
|
|
184
|
+
var filesFromLlm = new Set();
|
|
185
|
+
DATA.fileMetrics.forEach(function (m) {
|
|
186
|
+
if (m.llmSeverity && m.llmSeverity !== "none") filesFromLlm.add(m.file);
|
|
187
|
+
});
|
|
188
|
+
var fileSet = new Set([].concat(Array.from(filesFromStatic), Array.from(filesFromLlm)));
|
|
189
|
+
|
|
190
|
+
var filesWithDebt = Array.from(fileSet).sort(function (fa, fb) {
|
|
191
|
+
var itemsA = debtByFile.get(fa) || [];
|
|
192
|
+
var itemsB = debtByFile.get(fb) || [];
|
|
193
|
+
var metricA = DATA.fileMetrics.find(function (m) { return m.file === fa; });
|
|
194
|
+
var metricB = DATA.fileMetrics.find(function (m) { return m.file === fb; });
|
|
195
|
+
var worstA = Math.max(fileWorstStatic(itemsA), fileWorstLlm(metricA));
|
|
196
|
+
var worstB = Math.max(fileWorstStatic(itemsB), fileWorstLlm(metricB));
|
|
177
197
|
if (worstB !== worstA) return worstB - worstA;
|
|
178
198
|
return fa.localeCompare(fb);
|
|
179
199
|
});
|
|
200
|
+
|
|
201
|
+
function firstLine(text) {
|
|
202
|
+
if (!text || !String(text).trim()) return "";
|
|
203
|
+
return String(text).trim().split(/\n/)[0].trim().slice(0, 120);
|
|
204
|
+
}
|
|
205
|
+
|
|
180
206
|
filesWithDebt.forEach(function (file) {
|
|
181
|
-
var items = debtByFile.get(file);
|
|
207
|
+
var items = debtByFile.get(file) || [];
|
|
208
|
+
var fileM = DATA.fileMetrics.find(function (m) { return m.file === file; });
|
|
182
209
|
var worstSeverityVal = items.length ? items.reduce(function (best, d) {
|
|
183
|
-
return (
|
|
210
|
+
return severityNum(d.severity) > severityNum(best.severity) ? d : best;
|
|
184
211
|
}, items[0]) : null;
|
|
185
|
-
var staticSeverity = worstSeverityVal ? worstSeverityVal.severity :
|
|
186
|
-
var
|
|
187
|
-
|
|
188
|
-
var staticBadge =
|
|
189
|
-
|
|
190
|
-
? '<span class="badge badge-' + fileLlmSeverity + '" title="LLM rating for this file">' + fileLlmSeverity + "</span>"
|
|
212
|
+
var staticSeverity = worstSeverityVal ? worstSeverityVal.severity : null;
|
|
213
|
+
var fileLlmSeverity = fileM && fileM.llmSeverity && fileM.llmSeverity !== "none" ? fileM.llmSeverity : null;
|
|
214
|
+
|
|
215
|
+
var staticBadge = staticSeverity
|
|
216
|
+
? '<span class="badge badge-' + staticSeverity + '" title="Static (worst of ' + items.length + ')">' + staticSeverity + "</span>"
|
|
191
217
|
: '<span class="debt-list-llm-none">—</span>';
|
|
192
|
-
var
|
|
193
|
-
?
|
|
194
|
-
:
|
|
218
|
+
var llmBadge = fileLlmSeverity
|
|
219
|
+
? '<span class="badge badge-' + fileLlmSeverity + '" title="LLM">' + fileLlmSeverity + "</span>"
|
|
220
|
+
: '<span class="debt-list-llm-none">—</span>';
|
|
221
|
+
|
|
222
|
+
var explanation = "";
|
|
223
|
+
if (items.length && worstSeverityVal) {
|
|
224
|
+
explanation = escapeHtml(firstLine(worstSeverityVal.title || worstSeverityVal.description || ""));
|
|
225
|
+
if (items.length > 1) explanation += " (+" + (items.length - 1) + " more)";
|
|
226
|
+
}
|
|
227
|
+
if (fileM && (fileM.llmAssessment || fileM.llmRawAssessment)) {
|
|
228
|
+
var llmBlurb = firstLine(stripTrailingSeverityAndScore(fileM.llmRawAssessment || fileM.llmAssessment || ""));
|
|
229
|
+
if (llmBlurb) explanation = explanation ? explanation + " · " + escapeHtml(llmBlurb) : escapeHtml(llmBlurb);
|
|
230
|
+
}
|
|
231
|
+
if (!explanation) explanation = "Rated by static or LLM.";
|
|
232
|
+
|
|
195
233
|
var ratingsRow =
|
|
196
234
|
'<div class="debt-list-ratings">' +
|
|
197
235
|
'<span class="debt-list-rating"><span class="debt-list-rating-label">Static</span> ' + staticBadge + "</span>" +
|
|
198
|
-
'<span class="debt-list-rating"><span class="debt-list-rating-label">LLM</span> ' +
|
|
236
|
+
'<span class="debt-list-rating"><span class="debt-list-rating-label">LLM</span> ' + llmBadge + "</span>" +
|
|
199
237
|
"</div>";
|
|
200
238
|
var li = document.createElement("li");
|
|
201
239
|
li.innerHTML =
|
|
202
|
-
'<span class="title">' +
|
|
203
|
-
escapeHtml(titleText) +
|
|
204
|
-
"</span> " +
|
|
240
|
+
'<span class="title">' + escapeHtml(file.split("/").pop() || file) + "</span> " +
|
|
205
241
|
ratingsRow +
|
|
206
|
-
'<span class="meta">' + escapeHtml(file) + "</span>"
|
|
242
|
+
'<span class="meta">' + escapeHtml(file) + "</span>" +
|
|
243
|
+
'<p class="debt-list-explanation">' + explanation + "</p>";
|
|
207
244
|
li.addEventListener("click", function () { showDetail(file, items); });
|
|
208
245
|
list.appendChild(li);
|
|
209
246
|
});
|
|
@@ -212,35 +249,44 @@ function showDetail(file, items) {
|
|
|
212
249
|
var panel = document.getElementById("detail");
|
|
213
250
|
var fileMetric = DATA.fileMetrics.find(function (m) { return m.file === file; });
|
|
214
251
|
var worstItem = items.length ? items.reduce(function (best, d) {
|
|
215
|
-
return (
|
|
252
|
+
return severityNum(d.severity) > severityNum(best.severity) ? d : best;
|
|
216
253
|
}, items[0]) : null;
|
|
217
|
-
var staticSeverity = worstItem ? worstItem.severity :
|
|
254
|
+
var staticSeverity = worstItem ? worstItem.severity : null;
|
|
218
255
|
|
|
219
|
-
|
|
256
|
+
var titleText = items.length === 1
|
|
220
257
|
? (items[0].title || "Debt item")
|
|
221
|
-
: items.length
|
|
258
|
+
: items.length > 1
|
|
259
|
+
? items.length + " static issues"
|
|
260
|
+
: (fileMetric && fileMetric.llmSeverity ? "LLM assessment" : "File details");
|
|
261
|
+
document.getElementById("detailTitle").textContent = titleText;
|
|
222
262
|
document.getElementById("detailFile").textContent = file;
|
|
223
263
|
|
|
224
264
|
var explanationEl = document.getElementById("detailExplanation");
|
|
225
265
|
var parts = [];
|
|
226
266
|
parts.push(
|
|
227
267
|
'<div class="detail-severities">' +
|
|
228
|
-
'<span class="detail-sev"><strong>Static</strong>
|
|
268
|
+
'<span class="detail-sev"><strong>Static</strong> ' +
|
|
269
|
+
(staticSeverity ? '<span class="badge badge-' + staticSeverity + '">' + staticSeverity + "</span> (worst of " + items.length + ")" : "<span class=\"debt-list-llm-none\">\u2014</span>") +
|
|
270
|
+
"</span> " +
|
|
229
271
|
'<span class="detail-sev"><strong>LLM</strong> ' +
|
|
230
|
-
(fileMetric && fileMetric.llmSeverity ? '<span class="badge badge-' + fileMetric.llmSeverity + '">' + fileMetric.llmSeverity + "</span>" : "<span class=\"debt-list-llm-none\"
|
|
272
|
+
(fileMetric && fileMetric.llmSeverity && fileMetric.llmSeverity !== "none" ? '<span class="badge badge-' + fileMetric.llmSeverity + '">' + fileMetric.llmSeverity + "</span>" : "<span class=\"debt-list-llm-none\">\u2014</span>") +
|
|
231
273
|
"</span></div>"
|
|
232
274
|
);
|
|
233
275
|
parts.push('<div class="detail-static-desc"><strong>Static issues</strong><ul class="detail-issues-list">');
|
|
234
|
-
items.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
276
|
+
if (items.length) {
|
|
277
|
+
items.forEach(function (item) {
|
|
278
|
+
parts.push(
|
|
279
|
+
'<li><span class="badge badge-' + item.severity + '">' + item.severity + "</span> " +
|
|
280
|
+
escapeHtml(item.title || "Issue") +
|
|
281
|
+
(item.line ? " <span class=\"detail-line\">line " + item.line + "</span>" : "")
|
|
282
|
+
);
|
|
283
|
+
if (item.description)
|
|
284
|
+
parts.push('<div class="detail-issue-desc">' + escapeHtml(item.description).replace(/\n/g, "<br>") + "</div>");
|
|
285
|
+
parts.push("</li>");
|
|
286
|
+
});
|
|
287
|
+
} else {
|
|
288
|
+
parts.push("<li class=\"detail-no-llm\">No static issues for this file.</li>");
|
|
289
|
+
}
|
|
244
290
|
parts.push("</ul></div>");
|
|
245
291
|
parts.push('<div class="detail-llm-label"><strong>LLM assessment</strong></div>');
|
|
246
292
|
if (fileMetric && (fileMetric.llmRawAssessment || fileMetric.llmAssessment)) {
|
package/dist/reports/html.js
CHANGED
|
@@ -93,6 +93,7 @@ function buildHtml(run, title, darkMode, css, script) {
|
|
|
93
93
|
</div>
|
|
94
94
|
</div>`
|
|
95
95
|
: "";
|
|
96
|
+
const statsLine = `${run.fileMetrics.length} files · ${run.debtItems.length} items · ${highCriticalCount} high/crit · ${hotspotCount} hotspots`;
|
|
96
97
|
return `<!DOCTYPE html>
|
|
97
98
|
<html lang="en" data-theme="${theme}">
|
|
98
99
|
<head>
|
|
@@ -111,74 +112,35 @@ function buildHtml(run, title, darkMode, css, script) {
|
|
|
111
112
|
${!hasLlm ? `<div class="no-llm-banner"><p class="no-llm-cta">Analysis run without LLM — for full results, run with LLM</p></div>` : ""}
|
|
112
113
|
<header class="dashboard-header">
|
|
113
114
|
<div class="dashboard-header-left">
|
|
114
|
-
<
|
|
115
|
-
<
|
|
115
|
+
<div class="dashboard-score-badge tier-${cleanliness.tier}" aria-label="Score ${cleanliness.tier} of 5">${scoreBadgeSvg}</div>
|
|
116
|
+
<div class="dashboard-hero">
|
|
117
|
+
<h1 class="dashboard-title">${escapeHtml(title)}</h1>
|
|
118
|
+
<p class="dashboard-blurb">${escapeHtml(cleanliness.description)}</p>
|
|
119
|
+
<span class="dashboard-meta">${escapeHtml(run.repoPath)}</span>
|
|
120
|
+
</div>
|
|
116
121
|
</div>
|
|
117
122
|
<div class="dashboard-header-right">
|
|
118
|
-
<
|
|
119
|
-
<span class="dashboard-score-value">${cleanliness.tier}</span>
|
|
120
|
-
<span class="dashboard-score-of">/ 5</span>
|
|
121
|
-
<span class="dashboard-score-label">${escapeHtml(cleanliness.label)}</span>
|
|
122
|
-
</div>
|
|
123
|
+
<span class="dashboard-stats">${statsLine}</span>
|
|
123
124
|
<span class="dashboard-date">${run.completedAt ?? run.startedAt}</span>
|
|
124
125
|
</div>
|
|
125
126
|
</header>
|
|
126
127
|
|
|
127
128
|
<main class="dashboard-main">
|
|
128
|
-
<div class="dashboard-grid dashboard-grid-stats">
|
|
129
|
-
<div class="panel stat-panel">
|
|
130
|
-
<div class="panel-body">
|
|
131
|
-
<div class="stat-value">${run.fileMetrics.length}</div>
|
|
132
|
-
<div class="stat-label">Files analyzed</div>
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
<div class="panel stat-panel">
|
|
136
|
-
<div class="panel-body">
|
|
137
|
-
<div class="stat-value">${run.debtItems.length}</div>
|
|
138
|
-
<div class="stat-label">Debt items</div>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
<div class="panel stat-panel stat-panel-warn">
|
|
142
|
-
<div class="panel-body">
|
|
143
|
-
<div class="stat-value">${highCriticalCount}</div>
|
|
144
|
-
<div class="stat-label">High / Critical</div>
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
<div class="panel stat-panel">
|
|
148
|
-
<div class="panel-body">
|
|
149
|
-
<div class="stat-value">${hotspotCount}</div>
|
|
150
|
-
<div class="stat-label">Hotspots</div>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<div class="dashboard-grid">
|
|
156
|
-
<div class="panel panel-score tier-${cleanliness.tier}">
|
|
157
|
-
<div class="panel-header">
|
|
158
|
-
<h2 class="panel-title">Cleanliness score</h2>
|
|
159
|
-
</div>
|
|
160
|
-
<div class="panel-body panel-body-center">
|
|
161
|
-
<div class="score-badge" aria-hidden="true">${scoreBadgeSvg}</div>
|
|
162
|
-
<p class="score-desc">${escapeHtml(cleanliness.description)}</p>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
129
|
${llmPanelHtml}
|
|
168
130
|
|
|
169
|
-
<div class="panel">
|
|
170
|
-
<div class="panel-header">
|
|
131
|
+
<div class="panel panel-heatmap">
|
|
132
|
+
<div class="panel-header panel-header-heatmap">
|
|
171
133
|
<h2 class="panel-title">Files by debt</h2>
|
|
172
|
-
<p class="panel-desc">Size = complexity + churn. Color = LLM
|
|
173
|
-
|
|
174
|
-
<div class="panel-body">
|
|
175
|
-
<div class="legend">
|
|
134
|
+
<p class="panel-desc">Size = complexity + churn. Color = severity (static or LLM). Click for details.</p>
|
|
135
|
+
<div class="legend legend-inline">
|
|
176
136
|
<span><span class="swatch swatch-crit"></span> Critical</span>
|
|
177
137
|
<span><span class="swatch swatch-high"></span> High</span>
|
|
178
138
|
<span><span class="swatch swatch-med"></span> Medium</span>
|
|
179
139
|
<span><span class="swatch swatch-low"></span> Low</span>
|
|
180
|
-
<span><span class="swatch swatch-none"></span>
|
|
140
|
+
<span><span class="swatch swatch-none"></span> None</span>
|
|
181
141
|
</div>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="panel-body panel-body-heatmap">
|
|
182
144
|
<div id="treemap"></div>
|
|
183
145
|
</div>
|
|
184
146
|
</div>
|
|
@@ -206,8 +168,8 @@ function buildHtml(run, title, darkMode, css, script) {
|
|
|
206
168
|
|
|
207
169
|
<div class="panel">
|
|
208
170
|
<div class="panel-header">
|
|
209
|
-
<h2 class="panel-title">
|
|
210
|
-
<p class="panel-desc">
|
|
171
|
+
<h2 class="panel-title">Files with debt (static or LLM)</h2>
|
|
172
|
+
<p class="panel-desc">Every file rated above none by static analysis or LLM. Click a row for full ratings and explanations.</p>
|
|
211
173
|
</div>
|
|
212
174
|
<div class="panel-body">
|
|
213
175
|
<ul class="debt-list" id="debtList"></ul>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tech-debt-visualizer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Language-agnostic CLI that analyzes repos and generates interactive technical debt visualizations with AI-powered insights",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|