thuban 0.3.3 → 0.3.4
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/LICENSE +21 -0
- package/dist/README.md +185 -0
- package/dist/cli.js +2 -0
- package/dist/packages/scanner/ai-confidence-scorer.js +1 -0
- package/dist/packages/scanner/alert-manager.js +1 -0
- package/dist/packages/scanner/baseline-manager.js +1 -0
- package/dist/packages/scanner/code-scanner.js +1 -0
- package/dist/packages/scanner/codebase-passport.js +1 -0
- package/dist/packages/scanner/copy-paste-detector.js +1 -0
- package/dist/packages/scanner/dependency-graph.js +1 -0
- package/dist/packages/scanner/drift-detector.js +1 -0
- package/dist/packages/scanner/executive-report.js +1 -0
- package/dist/packages/scanner/file-collector.js +1 -0
- package/dist/packages/scanner/file-watcher.js +1 -0
- package/dist/packages/scanner/ghost-code-detector.js +1 -0
- package/dist/packages/scanner/hallucination-detector.js +1 -0
- package/dist/packages/scanner/health-checker.js +1 -0
- package/dist/packages/scanner/html-report.js +1 -0
- package/dist/packages/scanner/index.js +1 -0
- package/dist/packages/scanner/license-manager.js +1 -0
- package/dist/packages/scanner/master-health-checker.js +1 -0
- package/dist/packages/scanner/monitor-notifier.js +1 -0
- package/dist/packages/scanner/monitor-service.js +1 -0
- package/dist/packages/scanner/monitor-store.js +1 -0
- package/dist/packages/scanner/pre-commit-gate.js +1 -0
- package/dist/packages/scanner/scan-diff.js +1 -0
- package/dist/packages/scanner/scan-runner.js +1 -0
- package/dist/packages/scanner/secret-scanner.js +1 -0
- package/dist/packages/scanner/sentinel-core.js +1 -0
- package/dist/packages/scanner/sentinel-knowledge.js +1 -0
- package/dist/packages/scanner/tech-debt-analyzer.js +1 -0
- package/dist/packages/scanner/tech-debt-cost.js +1 -0
- package/dist/packages/scanner/widget-generator.js +1 -0
- package/package.json +12 -7
- package/cli.js +0 -2627
- package/packages/scanner/ai-confidence-scorer.js +0 -260
- package/packages/scanner/alert-manager.js +0 -398
- package/packages/scanner/baseline-manager.js +0 -109
- package/packages/scanner/code-scanner.js +0 -758
- package/packages/scanner/codebase-passport.js +0 -299
- package/packages/scanner/copy-paste-detector.js +0 -276
- package/packages/scanner/dependency-graph.js +0 -541
- package/packages/scanner/drift-detector.js +0 -423
- package/packages/scanner/executive-report.js +0 -774
- package/packages/scanner/file-collector.js +0 -64
- package/packages/scanner/file-watcher.js +0 -323
- package/packages/scanner/ghost-code-detector.js +0 -301
- package/packages/scanner/hallucination-detector.js +0 -822
- package/packages/scanner/health-checker.js +0 -586
- package/packages/scanner/html-report.js +0 -634
- package/packages/scanner/index.js +0 -100
- package/packages/scanner/license-manager.js +0 -331
- package/packages/scanner/master-health-checker.js +0 -513
- package/packages/scanner/monitor-notifier.js +0 -64
- package/packages/scanner/monitor-service.js +0 -103
- package/packages/scanner/monitor-store.js +0 -117
- package/packages/scanner/pre-commit-gate.js +0 -216
- package/packages/scanner/scan-diff.js +0 -50
- package/packages/scanner/scan-runner.js +0 -172
- package/packages/scanner/secret-scanner.js +0 -612
- package/packages/scanner/secret-scanner.test.js +0 -103
- package/packages/scanner/sentinel-core.js +0 -616
- package/packages/scanner/sentinel-knowledge.js +0 -322
- package/packages/scanner/tech-debt-analyzer.js +0 -583
- package/packages/scanner/tech-debt-cost.js +0 -194
- package/packages/scanner/widget-generator.js +0 -415
|
@@ -1,774 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thuban Executive Report Generator
|
|
3
|
-
*
|
|
4
|
-
* Generates a professional, print-ready HTML report designed for CTOs.
|
|
5
|
-
* Open in browser → Ctrl+P → perfect PDF.
|
|
6
|
-
*
|
|
7
|
-
* Shows: health score, tech debt cost, hallucinations, ghost code,
|
|
8
|
-
* AI confidence, copy-paste drift, and a clear ROI case.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const crypto = require('crypto');
|
|
14
|
-
|
|
15
|
-
class ExecutiveReport {
|
|
16
|
-
constructor(options = {}) {
|
|
17
|
-
this.rootPath = options.rootPath || process.cwd();
|
|
18
|
-
this.companyName = options.companyName || this._detectProjectName();
|
|
19
|
-
this.generatedAt = new Date();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
_detectProjectName() {
|
|
23
|
-
try {
|
|
24
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(this.rootPath, 'package.json'), 'utf-8'));
|
|
25
|
-
return pkg.name || path.basename(this.rootPath);
|
|
26
|
-
} catch {
|
|
27
|
-
return path.basename(this.rootPath);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
generate(data) {
|
|
32
|
-
const {
|
|
33
|
-
scanResults = {},
|
|
34
|
-
hallResults = {},
|
|
35
|
-
debtCost = {},
|
|
36
|
-
ghostResults = {},
|
|
37
|
-
aiScoreResults = {},
|
|
38
|
-
cloneResults = {},
|
|
39
|
-
passportData = {},
|
|
40
|
-
fileCount = 0,
|
|
41
|
-
lineCount = 0,
|
|
42
|
-
} = data;
|
|
43
|
-
|
|
44
|
-
const reportId = `THB-${Date.now().toString(36).toUpperCase()}`;
|
|
45
|
-
const score = this._calculateOverallScore(data);
|
|
46
|
-
const grade = this._scoreToGrade(score);
|
|
47
|
-
const gradeColor = this._gradeColor(grade);
|
|
48
|
-
|
|
49
|
-
return this._renderHTML({
|
|
50
|
-
reportId,
|
|
51
|
-
score,
|
|
52
|
-
grade,
|
|
53
|
-
gradeColor,
|
|
54
|
-
scanResults,
|
|
55
|
-
hallResults,
|
|
56
|
-
debtCost,
|
|
57
|
-
ghostResults,
|
|
58
|
-
aiScoreResults,
|
|
59
|
-
cloneResults,
|
|
60
|
-
passportData,
|
|
61
|
-
fileCount,
|
|
62
|
-
lineCount,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
_calculateOverallScore(data) {
|
|
67
|
-
let score = 100;
|
|
68
|
-
const hall = data.hallResults || {};
|
|
69
|
-
const ghost = data.ghostResults || {};
|
|
70
|
-
const clones = data.cloneResults || {};
|
|
71
|
-
const ai = data.aiScoreResults || {};
|
|
72
|
-
const scan = data.scanResults || {};
|
|
73
|
-
|
|
74
|
-
// Hallucinations: -15 each (critical)
|
|
75
|
-
score -= (hall.phantomAPIs?.length || 0) * 15;
|
|
76
|
-
score -= (hall.deprecatedAPIs?.length || 0) * 5;
|
|
77
|
-
|
|
78
|
-
// Ghost code: -2 per ghost
|
|
79
|
-
score -= (ghost.totalGhosts || 0) * 2;
|
|
80
|
-
|
|
81
|
-
// Clone clusters: -3 per cluster
|
|
82
|
-
score -= (clones.totalClusters || 0) * 3;
|
|
83
|
-
|
|
84
|
-
// AI-generated high confidence: -1 per function
|
|
85
|
-
score -= (ai.aiLikelyCount || 0) * 1;
|
|
86
|
-
|
|
87
|
-
// Scan issues
|
|
88
|
-
let issueCount = 0;
|
|
89
|
-
if (scan && typeof scan === 'object') {
|
|
90
|
-
for (const [, fileResults] of Object.entries(scan)) {
|
|
91
|
-
if (fileResults?.issues) issueCount += fileResults.issues.length;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
score -= Math.min(issueCount * 0.5, 30);
|
|
95
|
-
|
|
96
|
-
return Math.max(0, Math.min(100, Math.round(score)));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
_scoreToGrade(score) {
|
|
100
|
-
if (score >= 90) return 'A';
|
|
101
|
-
if (score >= 80) return 'B';
|
|
102
|
-
if (score >= 65) return 'C';
|
|
103
|
-
if (score >= 50) return 'D';
|
|
104
|
-
return 'F';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
_gradeColor(grade) {
|
|
108
|
-
const colors = { A: '#00ff88', B: '#88ff00', C: '#ffd93d', D: '#ff8844', F: '#ff4444' };
|
|
109
|
-
return colors[grade] || '#ff4444';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
_renderHTML(d) {
|
|
113
|
-
const now = this.generatedAt;
|
|
114
|
-
const dateStr = now.toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
115
|
-
const timeStr = now.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
|
|
116
|
-
|
|
117
|
-
const phantomCount = d.hallResults.phantomAPIs?.length || 0;
|
|
118
|
-
const deprecatedCount = d.hallResults.deprecatedAPIs?.length || 0;
|
|
119
|
-
const ghostCount = d.ghostResults.totalGhosts || 0;
|
|
120
|
-
const wastedLines = d.ghostResults.totalWastedLines || 0;
|
|
121
|
-
const cloneCount = d.cloneResults.totalClusters || 0;
|
|
122
|
-
const dupLines = d.cloneResults.totalDuplicateLines || 0;
|
|
123
|
-
const aiHighCount = d.aiScoreResults.aiLikelyCount || 0;
|
|
124
|
-
const aiPossibleCount = d.aiScoreResults.aiPossibleCount || 0;
|
|
125
|
-
const aiPercent = d.aiScoreResults.aiPercentage || 0;
|
|
126
|
-
|
|
127
|
-
const debtHours = d.debtCost.totalHoursManual || 0;
|
|
128
|
-
const debtCostGBP = d.debtCost.totalCostManual || 0;
|
|
129
|
-
const thubanHours = d.debtCost.totalHoursThuban || 0;
|
|
130
|
-
const savingGBP = d.debtCost.totalSaving || 0;
|
|
131
|
-
const savingPercent = d.debtCost.savingPercent || 0;
|
|
132
|
-
const weeklyDebtHours = d.debtCost.weeklyDebtAccumulation || 0.7;
|
|
133
|
-
const monthlyDebtCost = Math.round(weeklyDebtHours * 4 * 80);
|
|
134
|
-
|
|
135
|
-
// Build issue rows for hallucinations
|
|
136
|
-
let hallRows = '';
|
|
137
|
-
if (d.hallResults.phantomAPIs) {
|
|
138
|
-
for (const api of d.hallResults.phantomAPIs.slice(0, 8)) {
|
|
139
|
-
hallRows += `<tr>
|
|
140
|
-
<td class="sev-critical">CRITICAL</td>
|
|
141
|
-
<td>${this._esc(api.file || '')}:${api.line || '?'}</td>
|
|
142
|
-
<td>${this._esc(api.name || api.code || 'Phantom API')}</td>
|
|
143
|
-
<td>Will crash at runtime — method does not exist</td>
|
|
144
|
-
</tr>`;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (d.hallResults.deprecatedAPIs) {
|
|
148
|
-
for (const api of d.hallResults.deprecatedAPIs.slice(0, 5)) {
|
|
149
|
-
hallRows += `<tr>
|
|
150
|
-
<td class="sev-high">HIGH</td>
|
|
151
|
-
<td>${this._esc(api.file || '')}:${api.line || '?'}</td>
|
|
152
|
-
<td>${this._esc(api.name || api.code || 'Deprecated API')}</td>
|
|
153
|
-
<td>Deprecated — will break in future versions</td>
|
|
154
|
-
</tr>`;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Ghost code rows
|
|
159
|
-
let ghostRows = '';
|
|
160
|
-
if (d.ghostResults.ghosts) {
|
|
161
|
-
for (const ghost of d.ghostResults.ghosts.slice(0, 8)) {
|
|
162
|
-
ghostRows += `<tr>
|
|
163
|
-
<td>${this._esc(ghost.file)}:${ghost.line}</td>
|
|
164
|
-
<td>${this._esc(ghost.name)}()</td>
|
|
165
|
-
<td>${ghost.wastedLines} lines</td>
|
|
166
|
-
<td>${ghost.references} references</td>
|
|
167
|
-
</tr>`;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// AI confidence rows
|
|
172
|
-
let aiRows = '';
|
|
173
|
-
if (d.aiScoreResults.highConfidence) {
|
|
174
|
-
for (const fn of d.aiScoreResults.highConfidence.slice(0, 8)) {
|
|
175
|
-
aiRows += `<tr>
|
|
176
|
-
<td class="sev-critical">${fn.confidence}%</td>
|
|
177
|
-
<td>${this._esc(fn.file)}:${fn.line}</td>
|
|
178
|
-
<td>${this._esc(fn.name)}()</td>
|
|
179
|
-
<td>${fn.lineCount} lines</td>
|
|
180
|
-
</tr>`;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Clone rows
|
|
185
|
-
let cloneRows = '';
|
|
186
|
-
if (d.cloneResults.clusters) {
|
|
187
|
-
for (const cluster of d.cloneResults.clusters.slice(0, 6)) {
|
|
188
|
-
const locations = cluster.members.map(m => `${this._esc(m.file)}:${m.line}`).join('<br>');
|
|
189
|
-
cloneRows += `<tr>
|
|
190
|
-
<td>${this._esc(cluster.pattern)}</td>
|
|
191
|
-
<td>${cluster.totalInstances}</td>
|
|
192
|
-
<td style="font-size:0.75rem">${locations}</td>
|
|
193
|
-
<td>${this._esc(cluster.recommendation)}</td>
|
|
194
|
-
</tr>`;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return `<!DOCTYPE html>
|
|
199
|
-
<html lang="en">
|
|
200
|
-
<head>
|
|
201
|
-
<meta charset="UTF-8">
|
|
202
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
203
|
-
<title>Thuban Code Health Report — ${this._esc(this.companyName)}</title>
|
|
204
|
-
<style>
|
|
205
|
-
@page { size: A4; margin: 15mm; }
|
|
206
|
-
@media print {
|
|
207
|
-
body { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
|
|
208
|
-
.no-print { display: none !important; }
|
|
209
|
-
.page-break { page-break-before: always; }
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
:root {
|
|
213
|
-
--bg: #0a0a1a;
|
|
214
|
-
--surface: #12122a;
|
|
215
|
-
--border: #2a2a5a;
|
|
216
|
-
--text: #e0e0f0;
|
|
217
|
-
--dim: #8888aa;
|
|
218
|
-
--cyan: #00f6ff;
|
|
219
|
-
--green: #00ff88;
|
|
220
|
-
--yellow: #ffd93d;
|
|
221
|
-
--red: #ff4444;
|
|
222
|
-
--mono: 'JetBrains Mono', 'Cascadia Code', 'Consolas', monospace;
|
|
223
|
-
--sans: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
227
|
-
|
|
228
|
-
body {
|
|
229
|
-
background: var(--bg);
|
|
230
|
-
color: var(--text);
|
|
231
|
-
font-family: var(--sans);
|
|
232
|
-
font-size: 11pt;
|
|
233
|
-
line-height: 1.5;
|
|
234
|
-
max-width: 210mm;
|
|
235
|
-
margin: 0 auto;
|
|
236
|
-
padding: 20mm 15mm;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/* Header */
|
|
240
|
-
.report-header {
|
|
241
|
-
display: flex;
|
|
242
|
-
justify-content: space-between;
|
|
243
|
-
align-items: flex-start;
|
|
244
|
-
border-bottom: 2px solid var(--cyan);
|
|
245
|
-
padding-bottom: 1.5rem;
|
|
246
|
-
margin-bottom: 2rem;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.report-header .logo {
|
|
250
|
-
display: flex;
|
|
251
|
-
align-items: center;
|
|
252
|
-
gap: 0.75rem;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.report-header .logo svg { width: 36px; height: 36px; }
|
|
256
|
-
|
|
257
|
-
.report-header .logo-text {
|
|
258
|
-
font-size: 1.5rem;
|
|
259
|
-
font-weight: 300;
|
|
260
|
-
letter-spacing: 0.3em;
|
|
261
|
-
color: var(--cyan);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.report-header .meta {
|
|
265
|
-
text-align: right;
|
|
266
|
-
font-size: 0.8rem;
|
|
267
|
-
color: var(--dim);
|
|
268
|
-
line-height: 1.8;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
.report-title {
|
|
272
|
-
text-align: center;
|
|
273
|
-
margin-bottom: 2.5rem;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.report-title h1 {
|
|
277
|
-
font-size: 1.8rem;
|
|
278
|
-
font-weight: 700;
|
|
279
|
-
margin-bottom: 0.25rem;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
.report-title .project-name {
|
|
283
|
-
color: var(--cyan);
|
|
284
|
-
font-size: 1.1rem;
|
|
285
|
-
font-family: var(--mono);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/* Score Ring */
|
|
289
|
-
.score-section {
|
|
290
|
-
display: flex;
|
|
291
|
-
justify-content: center;
|
|
292
|
-
align-items: center;
|
|
293
|
-
gap: 3rem;
|
|
294
|
-
margin-bottom: 2.5rem;
|
|
295
|
-
padding: 2rem;
|
|
296
|
-
background: var(--surface);
|
|
297
|
-
border: 1px solid var(--border);
|
|
298
|
-
border-radius: 12px;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
.score-ring {
|
|
302
|
-
position: relative;
|
|
303
|
-
width: 140px;
|
|
304
|
-
height: 140px;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
.score-ring svg { transform: rotate(-90deg); }
|
|
308
|
-
|
|
309
|
-
.score-ring .score-value {
|
|
310
|
-
position: absolute;
|
|
311
|
-
top: 50%;
|
|
312
|
-
left: 50%;
|
|
313
|
-
transform: translate(-50%, -50%);
|
|
314
|
-
text-align: center;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
.score-ring .grade {
|
|
318
|
-
font-size: 2.5rem;
|
|
319
|
-
font-weight: 700;
|
|
320
|
-
font-family: var(--mono);
|
|
321
|
-
line-height: 1;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
.score-ring .number {
|
|
325
|
-
font-size: 0.85rem;
|
|
326
|
-
color: var(--dim);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
.score-summary {
|
|
330
|
-
flex: 1;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.score-summary h2 {
|
|
334
|
-
font-size: 1.3rem;
|
|
335
|
-
margin-bottom: 0.75rem;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
.stat-row {
|
|
339
|
-
display: flex;
|
|
340
|
-
gap: 2rem;
|
|
341
|
-
flex-wrap: wrap;
|
|
342
|
-
margin-top: 1rem;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
.stat {
|
|
346
|
-
text-align: center;
|
|
347
|
-
min-width: 80px;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
.stat .stat-value {
|
|
351
|
-
font-size: 1.5rem;
|
|
352
|
-
font-weight: 700;
|
|
353
|
-
font-family: var(--mono);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
.stat .stat-label {
|
|
357
|
-
font-size: 0.7rem;
|
|
358
|
-
color: var(--dim);
|
|
359
|
-
text-transform: uppercase;
|
|
360
|
-
letter-spacing: 0.05em;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
.stat-critical .stat-value { color: var(--red); }
|
|
364
|
-
.stat-warning .stat-value { color: var(--yellow); }
|
|
365
|
-
.stat-good .stat-value { color: var(--green); }
|
|
366
|
-
.stat-info .stat-value { color: var(--cyan); }
|
|
367
|
-
|
|
368
|
-
/* Sections */
|
|
369
|
-
.section {
|
|
370
|
-
margin-bottom: 2rem;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
.section-header {
|
|
374
|
-
display: flex;
|
|
375
|
-
align-items: center;
|
|
376
|
-
gap: 0.5rem;
|
|
377
|
-
margin-bottom: 1rem;
|
|
378
|
-
padding-bottom: 0.5rem;
|
|
379
|
-
border-bottom: 1px solid var(--border);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
.section-header h3 {
|
|
383
|
-
font-size: 1rem;
|
|
384
|
-
font-weight: 600;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
.section-header .icon { font-size: 1.2rem; }
|
|
388
|
-
|
|
389
|
-
/* Tables */
|
|
390
|
-
table {
|
|
391
|
-
width: 100%;
|
|
392
|
-
border-collapse: collapse;
|
|
393
|
-
font-size: 0.8rem;
|
|
394
|
-
margin-bottom: 1rem;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
th {
|
|
398
|
-
text-align: left;
|
|
399
|
-
padding: 0.5rem;
|
|
400
|
-
background: var(--surface);
|
|
401
|
-
border-bottom: 1px solid var(--border);
|
|
402
|
-
font-weight: 600;
|
|
403
|
-
font-size: 0.7rem;
|
|
404
|
-
text-transform: uppercase;
|
|
405
|
-
letter-spacing: 0.05em;
|
|
406
|
-
color: var(--dim);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
td {
|
|
410
|
-
padding: 0.4rem 0.5rem;
|
|
411
|
-
border-bottom: 1px solid rgba(42,42,90,0.3);
|
|
412
|
-
font-family: var(--mono);
|
|
413
|
-
font-size: 0.75rem;
|
|
414
|
-
word-break: break-all;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
.sev-critical { color: var(--red); font-weight: 700; }
|
|
418
|
-
.sev-high { color: var(--yellow); font-weight: 600; }
|
|
419
|
-
|
|
420
|
-
/* Cost Box */
|
|
421
|
-
.cost-box {
|
|
422
|
-
background: var(--surface);
|
|
423
|
-
border: 1px solid var(--border);
|
|
424
|
-
border-radius: 12px;
|
|
425
|
-
padding: 1.5rem 2rem;
|
|
426
|
-
margin-bottom: 1rem;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.cost-grid {
|
|
430
|
-
display: grid;
|
|
431
|
-
grid-template-columns: 1fr 1fr 1fr;
|
|
432
|
-
gap: 1.5rem;
|
|
433
|
-
text-align: center;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
.cost-item .cost-value {
|
|
437
|
-
font-size: 1.8rem;
|
|
438
|
-
font-weight: 700;
|
|
439
|
-
font-family: var(--mono);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
.cost-item .cost-label {
|
|
443
|
-
font-size: 0.75rem;
|
|
444
|
-
color: var(--dim);
|
|
445
|
-
margin-top: 0.25rem;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
.cost-manual .cost-value { color: var(--red); }
|
|
449
|
-
.cost-thuban .cost-value { color: var(--cyan); }
|
|
450
|
-
.cost-saving .cost-value { color: var(--green); }
|
|
451
|
-
|
|
452
|
-
.roi-bar {
|
|
453
|
-
margin-top: 1.5rem;
|
|
454
|
-
padding: 1rem;
|
|
455
|
-
background: rgba(0,255,136,0.08);
|
|
456
|
-
border: 1px solid rgba(0,255,136,0.2);
|
|
457
|
-
border-radius: 8px;
|
|
458
|
-
text-align: center;
|
|
459
|
-
font-size: 0.85rem;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
.roi-bar strong { color: var(--green); }
|
|
463
|
-
|
|
464
|
-
/* Warning callout */
|
|
465
|
-
.callout {
|
|
466
|
-
padding: 1rem 1.5rem;
|
|
467
|
-
border-radius: 8px;
|
|
468
|
-
margin-bottom: 1rem;
|
|
469
|
-
font-size: 0.85rem;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
.callout-danger {
|
|
473
|
-
background: rgba(255,68,68,0.08);
|
|
474
|
-
border: 1px solid rgba(255,68,68,0.2);
|
|
475
|
-
color: #ffaaaa;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
.callout-warning {
|
|
479
|
-
background: rgba(255,217,61,0.08);
|
|
480
|
-
border: 1px solid rgba(255,217,61,0.2);
|
|
481
|
-
color: #ffe088;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/* Footer */
|
|
485
|
-
.report-footer {
|
|
486
|
-
margin-top: 3rem;
|
|
487
|
-
padding-top: 1.5rem;
|
|
488
|
-
border-top: 1px solid var(--border);
|
|
489
|
-
display: flex;
|
|
490
|
-
justify-content: space-between;
|
|
491
|
-
font-size: 0.75rem;
|
|
492
|
-
color: var(--dim);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
.report-footer a { color: var(--cyan); text-decoration: none; }
|
|
496
|
-
|
|
497
|
-
/* Print button */
|
|
498
|
-
.print-btn {
|
|
499
|
-
position: fixed;
|
|
500
|
-
bottom: 2rem;
|
|
501
|
-
right: 2rem;
|
|
502
|
-
background: var(--cyan);
|
|
503
|
-
color: var(--bg);
|
|
504
|
-
border: none;
|
|
505
|
-
padding: 0.75rem 1.5rem;
|
|
506
|
-
border-radius: 8px;
|
|
507
|
-
font-weight: 600;
|
|
508
|
-
font-size: 0.9rem;
|
|
509
|
-
cursor: pointer;
|
|
510
|
-
box-shadow: 0 4px 20px rgba(0,246,255,0.3);
|
|
511
|
-
z-index: 100;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
.print-btn:hover { background: #33f8ff; }
|
|
515
|
-
|
|
516
|
-
/* Responsive for screen viewing */
|
|
517
|
-
@media screen and (max-width: 700px) {
|
|
518
|
-
body { padding: 1rem; }
|
|
519
|
-
.cost-grid { grid-template-columns: 1fr; }
|
|
520
|
-
.stat-row { flex-direction: column; gap: 0.75rem; }
|
|
521
|
-
.score-section { flex-direction: column; gap: 1.5rem; }
|
|
522
|
-
}
|
|
523
|
-
</style>
|
|
524
|
-
</head>
|
|
525
|
-
<body>
|
|
526
|
-
|
|
527
|
-
<button class="print-btn no-print" onclick="window.print()">Save as PDF</button>
|
|
528
|
-
|
|
529
|
-
<!-- Header -->
|
|
530
|
-
<div class="report-header">
|
|
531
|
-
<div class="logo">
|
|
532
|
-
<svg viewBox="0 0 32 32" fill="none">
|
|
533
|
-
<polygon points="16,2 30,28 2,28" stroke="#00f6ff" stroke-width="2" fill="none"/>
|
|
534
|
-
<polygon points="16,8 25,25 7,25" stroke="#00f6ff" stroke-width="1" fill="rgba(0,246,255,0.1)"/>
|
|
535
|
-
<circle cx="16" cy="18" r="3" fill="#00f6ff"/>
|
|
536
|
-
</svg>
|
|
537
|
-
<span class="logo-text">THUBAN</span>
|
|
538
|
-
</div>
|
|
539
|
-
<div class="meta">
|
|
540
|
-
Report ID: ${d.reportId}<br>
|
|
541
|
-
Generated: ${dateStr} at ${timeStr}<br>
|
|
542
|
-
Engine: Thuban Code Health v0.3.0
|
|
543
|
-
</div>
|
|
544
|
-
</div>
|
|
545
|
-
|
|
546
|
-
<!-- Title -->
|
|
547
|
-
<div class="report-title">
|
|
548
|
-
<h1>Code Health Report</h1>
|
|
549
|
-
<div class="project-name">${this._esc(this.companyName)}</div>
|
|
550
|
-
</div>
|
|
551
|
-
|
|
552
|
-
<!-- Score -->
|
|
553
|
-
<div class="score-section">
|
|
554
|
-
<div class="score-ring">
|
|
555
|
-
<svg width="140" height="140" viewBox="0 0 140 140">
|
|
556
|
-
<circle cx="70" cy="70" r="60" fill="none" stroke="${d.gradeColor}22" stroke-width="10"/>
|
|
557
|
-
<circle cx="70" cy="70" r="60" fill="none" stroke="${d.gradeColor}" stroke-width="10"
|
|
558
|
-
stroke-dasharray="${Math.round(d.score * 3.77)} 377"
|
|
559
|
-
stroke-linecap="round"/>
|
|
560
|
-
</svg>
|
|
561
|
-
<div class="score-value">
|
|
562
|
-
<div class="grade" style="color:${d.gradeColor}">${d.grade}</div>
|
|
563
|
-
<div class="number">${d.score}/100</div>
|
|
564
|
-
</div>
|
|
565
|
-
</div>
|
|
566
|
-
<div class="score-summary">
|
|
567
|
-
<h2>Codebase Health: ${d.grade === 'A' ? 'Excellent' : d.grade === 'B' ? 'Good' : d.grade === 'C' ? 'Needs Attention' : d.grade === 'D' ? 'At Risk' : 'Critical'}</h2>
|
|
568
|
-
<div class="stat-row">
|
|
569
|
-
<div class="stat stat-info">
|
|
570
|
-
<div class="stat-value">${d.fileCount.toLocaleString()}</div>
|
|
571
|
-
<div class="stat-label">Files Scanned</div>
|
|
572
|
-
</div>
|
|
573
|
-
<div class="stat stat-info">
|
|
574
|
-
<div class="stat-value">${d.lineCount.toLocaleString()}</div>
|
|
575
|
-
<div class="stat-label">Lines of Code</div>
|
|
576
|
-
</div>
|
|
577
|
-
<div class="stat ${phantomCount > 0 ? 'stat-critical' : 'stat-good'}">
|
|
578
|
-
<div class="stat-value">${phantomCount}</div>
|
|
579
|
-
<div class="stat-label">Hallucinated APIs</div>
|
|
580
|
-
</div>
|
|
581
|
-
<div class="stat ${ghostCount > 0 ? 'stat-warning' : 'stat-good'}">
|
|
582
|
-
<div class="stat-value">${ghostCount}</div>
|
|
583
|
-
<div class="stat-label">Ghost Functions</div>
|
|
584
|
-
</div>
|
|
585
|
-
<div class="stat ${aiHighCount > 0 ? 'stat-warning' : 'stat-good'}">
|
|
586
|
-
<div class="stat-value">${aiPercent}%</div>
|
|
587
|
-
<div class="stat-label">AI-Generated</div>
|
|
588
|
-
</div>
|
|
589
|
-
</div>
|
|
590
|
-
</div>
|
|
591
|
-
</div>
|
|
592
|
-
|
|
593
|
-
${phantomCount > 0 ? `
|
|
594
|
-
<div class="callout callout-danger">
|
|
595
|
-
<strong>PRODUCTION RISK:</strong> ${phantomCount} API call${phantomCount > 1 ? 's' : ''} in this codebase reference${phantomCount === 1 ? 's' : ''} methods that do not exist.
|
|
596
|
-
These will throw runtime errors in production. This is not tech debt — this is code that <strong>will crash</strong>.
|
|
597
|
-
</div>
|
|
598
|
-
` : ''}
|
|
599
|
-
|
|
600
|
-
<!-- Tech Debt Cost -->
|
|
601
|
-
<div class="section">
|
|
602
|
-
<div class="section-header">
|
|
603
|
-
<span class="icon">💰</span>
|
|
604
|
-
<h3>Technical Debt Cost Analysis</h3>
|
|
605
|
-
</div>
|
|
606
|
-
<div class="cost-box">
|
|
607
|
-
<div class="cost-grid">
|
|
608
|
-
<div class="cost-item cost-manual">
|
|
609
|
-
<div class="cost-value">£${debtCostGBP.toLocaleString()}</div>
|
|
610
|
-
<div class="cost-label">Manual fix cost (${debtHours} dev-hours at £80/hr)</div>
|
|
611
|
-
</div>
|
|
612
|
-
<div class="cost-item cost-thuban">
|
|
613
|
-
<div class="cost-value">${thubanHours}h</div>
|
|
614
|
-
<div class="cost-label">Fix time with Thuban auto-fix</div>
|
|
615
|
-
</div>
|
|
616
|
-
<div class="cost-item cost-saving">
|
|
617
|
-
<div class="cost-value">£${savingGBP.toLocaleString()}</div>
|
|
618
|
-
<div class="cost-label">You save (${savingPercent}%)</div>
|
|
619
|
-
</div>
|
|
620
|
-
</div>
|
|
621
|
-
<div class="roi-bar">
|
|
622
|
-
Every week this debt goes unaddressed, <strong>${weeklyDebtHours} more hours</strong> accumulate.
|
|
623
|
-
That is <strong>£${monthlyDebtCost.toLocaleString()}/month</strong> in growing technical debt.
|
|
624
|
-
Thuban Pro costs £19/month. <strong>ROI: ${debtCostGBP > 0 ? Math.round(debtCostGBP / 19) : 0}x.</strong>
|
|
625
|
-
</div>
|
|
626
|
-
</div>
|
|
627
|
-
</div>
|
|
628
|
-
|
|
629
|
-
${phantomCount + deprecatedCount > 0 ? `
|
|
630
|
-
<!-- Hallucinations -->
|
|
631
|
-
<div class="section">
|
|
632
|
-
<div class="section-header">
|
|
633
|
-
<span class="icon">🔮</span>
|
|
634
|
-
<h3>AI Hallucination Detection</h3>
|
|
635
|
-
</div>
|
|
636
|
-
<p style="font-size:0.85rem; color:var(--dim); margin-bottom:1rem;">
|
|
637
|
-
These API calls reference methods, modules, or endpoints that do not exist in the libraries being used.
|
|
638
|
-
They were likely generated by an AI coding tool and will fail at runtime.
|
|
639
|
-
</p>
|
|
640
|
-
<table>
|
|
641
|
-
<thead>
|
|
642
|
-
<tr><th>Severity</th><th>Location</th><th>API Call</th><th>Impact</th></tr>
|
|
643
|
-
</thead>
|
|
644
|
-
<tbody>${hallRows}</tbody>
|
|
645
|
-
</table>
|
|
646
|
-
</div>
|
|
647
|
-
` : ''}
|
|
648
|
-
|
|
649
|
-
${ghostCount > 0 ? `
|
|
650
|
-
<div class="page-break"></div>
|
|
651
|
-
<!-- Ghost Code -->
|
|
652
|
-
<div class="section">
|
|
653
|
-
<div class="section-header">
|
|
654
|
-
<span class="icon">👻</span>
|
|
655
|
-
<h3>Ghost Code — Dead Functions</h3>
|
|
656
|
-
</div>
|
|
657
|
-
<p style="font-size:0.85rem; color:var(--dim); margin-bottom:1rem;">
|
|
658
|
-
${ghostCount} function${ghostCount > 1 ? 's' : ''} exist in the codebase but are never called.
|
|
659
|
-
This represents ${wastedLines.toLocaleString()} wasted lines of code (${d.ghostResults.wastedPercent || '?'}% of the codebase).
|
|
660
|
-
</p>
|
|
661
|
-
<table>
|
|
662
|
-
<thead>
|
|
663
|
-
<tr><th>Location</th><th>Function</th><th>Size</th><th>References</th></tr>
|
|
664
|
-
</thead>
|
|
665
|
-
<tbody>${ghostRows}</tbody>
|
|
666
|
-
</table>
|
|
667
|
-
</div>
|
|
668
|
-
` : ''}
|
|
669
|
-
|
|
670
|
-
${aiHighCount > 0 ? `
|
|
671
|
-
<!-- AI Confidence -->
|
|
672
|
-
<div class="section">
|
|
673
|
-
<div class="section-header">
|
|
674
|
-
<span class="icon">🤖</span>
|
|
675
|
-
<h3>AI-Generated Code Confidence</h3>
|
|
676
|
-
</div>
|
|
677
|
-
<p style="font-size:0.85rem; color:var(--dim); margin-bottom:1rem;">
|
|
678
|
-
${aiPercent}% of functions show strong signals of AI generation.
|
|
679
|
-
${aiHighCount} high confidence, ${aiPossibleCount} possible.
|
|
680
|
-
These functions should be reviewed for correctness, edge cases, and test coverage.
|
|
681
|
-
</p>
|
|
682
|
-
<table>
|
|
683
|
-
<thead>
|
|
684
|
-
<tr><th>AI %</th><th>Location</th><th>Function</th><th>Size</th></tr>
|
|
685
|
-
</thead>
|
|
686
|
-
<tbody>${aiRows}</tbody>
|
|
687
|
-
</table>
|
|
688
|
-
</div>
|
|
689
|
-
` : ''}
|
|
690
|
-
|
|
691
|
-
${cloneCount > 0 ? `
|
|
692
|
-
<!-- Copy-Paste Drift -->
|
|
693
|
-
<div class="section">
|
|
694
|
-
<div class="section-header">
|
|
695
|
-
<span class="icon">📋</span>
|
|
696
|
-
<h3>Copy-Paste Drift</h3>
|
|
697
|
-
</div>
|
|
698
|
-
<p style="font-size:0.85rem; color:var(--dim); margin-bottom:1rem;">
|
|
699
|
-
${cloneCount} cluster${cloneCount > 1 ? 's' : ''} of similar code found across ${dupLines} duplicate lines.
|
|
700
|
-
These indicate copy-pasted logic that has drifted apart and should be extracted into shared utilities.
|
|
701
|
-
</p>
|
|
702
|
-
<table>
|
|
703
|
-
<thead>
|
|
704
|
-
<tr><th>Pattern</th><th>Instances</th><th>Locations</th><th>Recommendation</th></tr>
|
|
705
|
-
</thead>
|
|
706
|
-
<tbody>${cloneRows}</tbody>
|
|
707
|
-
</table>
|
|
708
|
-
</div>
|
|
709
|
-
` : ''}
|
|
710
|
-
|
|
711
|
-
<!-- Recommendations -->
|
|
712
|
-
<div class="section">
|
|
713
|
-
<div class="section-header">
|
|
714
|
-
<span class="icon">⚡</span>
|
|
715
|
-
<h3>Recommended Actions</h3>
|
|
716
|
-
</div>
|
|
717
|
-
<div style="display:grid; grid-template-columns: auto 1fr; gap: 0.5rem 1rem; font-size:0.85rem;">
|
|
718
|
-
${phantomCount > 0 ? `
|
|
719
|
-
<div style="color:var(--red); font-weight:700;">1. IMMEDIATE</div>
|
|
720
|
-
<div>Fix ${phantomCount} hallucinated API${phantomCount > 1 ? 's' : ''} — these will crash in production. Run <code style="background:var(--surface);padding:0.1rem 0.4rem;border-radius:4px;font-family:var(--mono);color:var(--cyan);">thuban fix . --fix</code></div>
|
|
721
|
-
` : ''}
|
|
722
|
-
${ghostCount > 0 ? `
|
|
723
|
-
<div style="color:var(--yellow); font-weight:700;">${phantomCount > 0 ? '2' : '1'}. HIGH</div>
|
|
724
|
-
<div>Remove ${ghostCount} ghost function${ghostCount > 1 ? 's' : ''} (${wastedLines} wasted lines). Run <code style="background:var(--surface);padding:0.1rem 0.4rem;border-radius:4px;font-family:var(--mono);color:var(--cyan);">thuban ghosts .</code> for full list</div>
|
|
725
|
-
` : ''}
|
|
726
|
-
${cloneCount > 0 ? `
|
|
727
|
-
<div style="color:var(--yellow); font-weight:700;">${phantomCount > 0 ? '3' : '2'}. MEDIUM</div>
|
|
728
|
-
<div>Consolidate ${cloneCount} copy-paste cluster${cloneCount > 1 ? 's' : ''} into shared utilities</div>
|
|
729
|
-
` : ''}
|
|
730
|
-
${aiHighCount > 0 ? `
|
|
731
|
-
<div style="color:var(--cyan); font-weight:700;">${phantomCount > 0 ? '4' : '3'}. REVIEW</div>
|
|
732
|
-
<div>Audit ${aiHighCount} high-confidence AI-generated function${aiHighCount > 1 ? 's' : ''} for correctness and test coverage</div>
|
|
733
|
-
` : ''}
|
|
734
|
-
<div style="color:var(--green); font-weight:700;">ONGOING</div>
|
|
735
|
-
<div>Install pre-commit gate: <code style="background:var(--surface);padding:0.1rem 0.4rem;border-radius:4px;font-family:var(--mono);color:var(--cyan);">thuban gate --fix</code> — blocks hallucinated code from entering your repo</div>
|
|
736
|
-
</div>
|
|
737
|
-
</div>
|
|
738
|
-
|
|
739
|
-
<!-- Footer -->
|
|
740
|
-
<div class="report-footer">
|
|
741
|
-
<div>
|
|
742
|
-
<strong>Thuban</strong> — Code Health Engine<br>
|
|
743
|
-
<a href="https://thuban.dev">thuban.dev</a> | A Silverwings product
|
|
744
|
-
</div>
|
|
745
|
-
<div style="text-align:right;">
|
|
746
|
-
Report: ${d.reportId}<br>
|
|
747
|
-
This report was generated automatically.<br>
|
|
748
|
-
For full details, run: <code style="font-family:var(--mono); color:var(--cyan);">npx thuban scan .</code>
|
|
749
|
-
</div>
|
|
750
|
-
</div>
|
|
751
|
-
|
|
752
|
-
</body>
|
|
753
|
-
</html>`;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
_esc(str) {
|
|
757
|
-
return String(str)
|
|
758
|
-
.replace(/&/g, '&')
|
|
759
|
-
.replace(/</g, '<')
|
|
760
|
-
.replace(/>/g, '>')
|
|
761
|
-
.replace(/"/g, '"');
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
save(html, outputDir) {
|
|
765
|
-
const dir = outputDir || this.rootPath;
|
|
766
|
-
const date = this.generatedAt.toISOString().split('T')[0];
|
|
767
|
-
const filename = `thuban-report-${date}.html`;
|
|
768
|
-
const outputPath = path.join(dir, filename);
|
|
769
|
-
fs.writeFileSync(outputPath, html, 'utf-8');
|
|
770
|
-
return outputPath;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
module.exports = ExecutiveReport;
|