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.
Files changed (66) hide show
  1. package/dist/LICENSE +21 -0
  2. package/dist/README.md +185 -0
  3. package/dist/cli.js +2 -0
  4. package/dist/packages/scanner/ai-confidence-scorer.js +1 -0
  5. package/dist/packages/scanner/alert-manager.js +1 -0
  6. package/dist/packages/scanner/baseline-manager.js +1 -0
  7. package/dist/packages/scanner/code-scanner.js +1 -0
  8. package/dist/packages/scanner/codebase-passport.js +1 -0
  9. package/dist/packages/scanner/copy-paste-detector.js +1 -0
  10. package/dist/packages/scanner/dependency-graph.js +1 -0
  11. package/dist/packages/scanner/drift-detector.js +1 -0
  12. package/dist/packages/scanner/executive-report.js +1 -0
  13. package/dist/packages/scanner/file-collector.js +1 -0
  14. package/dist/packages/scanner/file-watcher.js +1 -0
  15. package/dist/packages/scanner/ghost-code-detector.js +1 -0
  16. package/dist/packages/scanner/hallucination-detector.js +1 -0
  17. package/dist/packages/scanner/health-checker.js +1 -0
  18. package/dist/packages/scanner/html-report.js +1 -0
  19. package/dist/packages/scanner/index.js +1 -0
  20. package/dist/packages/scanner/license-manager.js +1 -0
  21. package/dist/packages/scanner/master-health-checker.js +1 -0
  22. package/dist/packages/scanner/monitor-notifier.js +1 -0
  23. package/dist/packages/scanner/monitor-service.js +1 -0
  24. package/dist/packages/scanner/monitor-store.js +1 -0
  25. package/dist/packages/scanner/pre-commit-gate.js +1 -0
  26. package/dist/packages/scanner/scan-diff.js +1 -0
  27. package/dist/packages/scanner/scan-runner.js +1 -0
  28. package/dist/packages/scanner/secret-scanner.js +1 -0
  29. package/dist/packages/scanner/sentinel-core.js +1 -0
  30. package/dist/packages/scanner/sentinel-knowledge.js +1 -0
  31. package/dist/packages/scanner/tech-debt-analyzer.js +1 -0
  32. package/dist/packages/scanner/tech-debt-cost.js +1 -0
  33. package/dist/packages/scanner/widget-generator.js +1 -0
  34. package/package.json +12 -7
  35. package/cli.js +0 -2627
  36. package/packages/scanner/ai-confidence-scorer.js +0 -260
  37. package/packages/scanner/alert-manager.js +0 -398
  38. package/packages/scanner/baseline-manager.js +0 -109
  39. package/packages/scanner/code-scanner.js +0 -758
  40. package/packages/scanner/codebase-passport.js +0 -299
  41. package/packages/scanner/copy-paste-detector.js +0 -276
  42. package/packages/scanner/dependency-graph.js +0 -541
  43. package/packages/scanner/drift-detector.js +0 -423
  44. package/packages/scanner/executive-report.js +0 -774
  45. package/packages/scanner/file-collector.js +0 -64
  46. package/packages/scanner/file-watcher.js +0 -323
  47. package/packages/scanner/ghost-code-detector.js +0 -301
  48. package/packages/scanner/hallucination-detector.js +0 -822
  49. package/packages/scanner/health-checker.js +0 -586
  50. package/packages/scanner/html-report.js +0 -634
  51. package/packages/scanner/index.js +0 -100
  52. package/packages/scanner/license-manager.js +0 -331
  53. package/packages/scanner/master-health-checker.js +0 -513
  54. package/packages/scanner/monitor-notifier.js +0 -64
  55. package/packages/scanner/monitor-service.js +0 -103
  56. package/packages/scanner/monitor-store.js +0 -117
  57. package/packages/scanner/pre-commit-gate.js +0 -216
  58. package/packages/scanner/scan-diff.js +0 -50
  59. package/packages/scanner/scan-runner.js +0 -172
  60. package/packages/scanner/secret-scanner.js +0 -612
  61. package/packages/scanner/secret-scanner.test.js +0 -103
  62. package/packages/scanner/sentinel-core.js +0 -616
  63. package/packages/scanner/sentinel-knowledge.js +0 -322
  64. package/packages/scanner/tech-debt-analyzer.js +0 -583
  65. package/packages/scanner/tech-debt-cost.js +0 -194
  66. 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">&#128176;</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">&pound;${debtCostGBP.toLocaleString()}</div>
610
- <div class="cost-label">Manual fix cost (${debtHours} dev-hours at &pound;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">&pound;${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>&pound;${monthlyDebtCost.toLocaleString()}/month</strong> in growing technical debt.
624
- Thuban Pro costs &pound;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">&#128302;</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">&#128123;</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">&#129302;</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">&#128203;</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">&#9889;</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> &nbsp;|&nbsp; 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, '&amp;')
759
- .replace(/</g, '&lt;')
760
- .replace(/>/g, '&gt;')
761
- .replace(/"/g, '&quot;');
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;