git-archaeologist 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -46
- package/dist/output/htmlReport.d.ts.map +1 -1
- package/dist/output/htmlReport.js +193 -140
- package/dist/output/htmlReport.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
# git-archaeologist
|
|
2
2
|
|
|
3
|
-
Run one command. Find the time bombs in any codebase.
|
|
4
|
-
|
|
5
3
|

|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## What it does
|
|
5
|
+
[](https://www.npmjs.com/package/git-archaeologist)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
> Dig through your git history and find what's actually dangerous.
|
|
16
10
|
|
|
17
|
-
|
|
11
|
+
[Install](#install) • [Usage](#usage) • [What it finds](#what-it-finds) • [How scoring works](#how-scoring-works)
|
|
18
12
|
|
|
19
|
-
|
|
13
|
+
---
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
You inherit a codebase. You touch a file. Three things break that you had no idea were connected.
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
This tool reads your entire git history and surfaces four things: which files are ticking time bombs, who will take a whole module down when they quit, which files are secretly coupled even though nothing in the code shows it, and who truly owns what right now — not who created it.
|
|
24
18
|
|
|
25
19
|
## Install
|
|
26
20
|
|
|
@@ -28,57 +22,47 @@ I wrote this after two days tracking a bug that started because I touched a file
|
|
|
28
22
|
npm install -g git-archaeologist
|
|
29
23
|
```
|
|
30
24
|
|
|
31
|
-
---
|
|
32
|
-
|
|
33
25
|
## Usage
|
|
34
26
|
|
|
35
27
|
```bash
|
|
36
|
-
# full report
|
|
37
28
|
git-arch analyze /path/to/repo
|
|
29
|
+
git-arch analyze /path/to/repo --html # dark-themed shareable report
|
|
30
|
+
git-arch cursed --top 10 # just the danger ranking
|
|
31
|
+
git-arch analyze /path/to/repo --json # pipe into other tools
|
|
32
|
+
```
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
git-arch cursed --top 10
|
|
34
|
+
## What it finds
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
git-arch analyze /path/to/repo --html
|
|
36
|
+
**Cursed files** — ranked by instability score, not just change count. A file touched 100 times in 6 months by 15 different developers scores way higher than one changed 200 times over a decade by the same person. The score weights recency, author chaos, and churn rate together.
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
git-arch analyze /path/to/repo --json
|
|
47
|
-
```
|
|
38
|
+
**Bus factor per folder** — not per repo. "The whole repo has bus factor 2" is useless. "The lib/ folder will be orphaned the day this one person leaves" is something you can act on.
|
|
48
39
|
|
|
49
|
-
|
|
40
|
+
**Implicit coupling** — pairs of files that always appear in the same commit, even though nothing in the code connects them. These are your hidden dependencies and your future bugs.
|
|
50
41
|
|
|
51
|
-
|
|
42
|
+
**Ownership** — who owns the lines that are actually alive in HEAD right now. Not who created the file. Not who committed most recently.
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
## Tested on Express.js
|
|
54
45
|
|
|
55
|
-
|
|
46
|
+
Express is one of the most downloaded npm packages in history. 230 contributors. 16 years old.
|
|
56
47
|
|
|
57
|
-
|
|
48
|
+
Running git-archaeologist on it takes 3 seconds and finds:
|
|
58
49
|
|
|
59
|
-
`
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
- `lib/response.js` — 128 changes, 53 different authors, curse score 2261. The core HTTP response logic of the framework has been touched by 53 people and nobody fully owns it.
|
|
51
|
+
- Bus factor across every module (lib/, test/, examples/, benchmarks/) is 1. One person. The entire project depends on Douglas Christopher Wilson continuing to show up.
|
|
52
|
+
- `benchmarks/Makefile` and `benchmarks/run` have been committed together 100% of the time. They have never changed separately. They are one file.
|
|
62
53
|
|
|
63
|
-
##
|
|
54
|
+
## How scoring works
|
|
64
55
|
|
|
65
56
|
```
|
|
66
|
-
curse_score = changes
|
|
57
|
+
curse_score = changes × log₂(authors+1) × exp(-0.5 × age_years) × log₂(churn_rate+2)
|
|
67
58
|
```
|
|
68
59
|
|
|
69
|
-
The exponential decay on age
|
|
60
|
+
The exponential decay on age is the important part. A file that was chaotic 5 years ago and has been stable since will not show up. Only files that are actively dangerous right now.
|
|
70
61
|
|
|
71
|
-
|
|
62
|
+
## Requirements
|
|
72
63
|
|
|
73
|
-
|
|
64
|
+
Node.js >= 18 and git >= 2.30. Works on Linux, macOS, and Windows (WSL).
|
|
74
65
|
|
|
75
|
-
|
|
76
|
-
git clone https://github.com/SushantVerma7969/git-archaeologist.git
|
|
77
|
-
cd git-archaeologist
|
|
78
|
-
npm install && npm run build
|
|
79
|
-
node dist/index.js analyze /any/repo
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
---
|
|
66
|
+
## License
|
|
83
67
|
|
|
84
|
-
MIT
|
|
68
|
+
MIT
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlReport.d.ts","sourceRoot":"","sources":["../../src/output/htmlReport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"htmlReport.d.ts","sourceRoot":"","sources":["../../src/output/htmlReport.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AA8B1C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAsLnF"}
|
|
@@ -35,158 +35,211 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.generateHtmlReport = generateHtmlReport;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
-
function
|
|
39
|
-
return
|
|
40
|
-
.replace(/&/g, '&')
|
|
41
|
-
.replace(/</g, '<')
|
|
42
|
-
.replace(/>/g, '>')
|
|
43
|
-
.replace(/"/g, '"');
|
|
38
|
+
function esc(s) {
|
|
39
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
44
40
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
function col(score, max) {
|
|
42
|
+
const r = Math.min(score / Math.max(max, 1), 1);
|
|
43
|
+
if (r > 0.7)
|
|
44
|
+
return '#ef4444';
|
|
45
|
+
if (r > 0.4)
|
|
46
|
+
return '#f97316';
|
|
47
|
+
if (r > 0.2)
|
|
48
|
+
return '#eab308';
|
|
49
|
+
if (r > 0.05)
|
|
50
|
+
return '#22c55e';
|
|
51
|
+
return '#3b82f6';
|
|
53
52
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
53
|
+
function buildTree(result) {
|
|
54
|
+
const sm = new Map();
|
|
55
|
+
for (const f of result.cursedFiles)
|
|
56
|
+
sm.set(f.filepath, f.curseScore);
|
|
57
|
+
const fm = new Map();
|
|
58
|
+
for (const [fp, stats] of result.fileStats) {
|
|
59
|
+
const parts = fp.split('/');
|
|
60
|
+
const folder = parts.length > 1 ? parts[0] : '(root)';
|
|
61
|
+
const score = sm.get(fp) ?? 0;
|
|
62
|
+
const last = new Date(stats.lastChanged * 1000).toISOString().split('T')[0];
|
|
63
|
+
if (!fm.has(folder))
|
|
64
|
+
fm.set(folder, []);
|
|
65
|
+
fm.get(folder).push({ name: parts[parts.length - 1], filepath: fp, value: Math.max(stats.totalChanges, 1), score, changes: stats.totalChanges, authors: stats.uniqueAuthors.size, lastTouched: last });
|
|
66
|
+
}
|
|
67
|
+
return { name: result.repoName, children: Array.from(fm.entries()).map(([f, files]) => ({ name: f, children: files })) };
|
|
67
68
|
}
|
|
68
69
|
function generateHtmlReport(result, outputPath) {
|
|
69
70
|
const from = result.dateRange.from.toISOString().split('T')[0];
|
|
70
71
|
const to = result.dateRange.to.toISOString().split('T')[0];
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<td
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<td>${
|
|
83
|
-
<td
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
<td
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<td
|
|
101
|
-
<td
|
|
102
|
-
<td>${
|
|
103
|
-
|
|
104
|
-
|
|
72
|
+
const maxS = result.cursedFiles[0]?.curseScore ?? 1;
|
|
73
|
+
const treeJSON = JSON.stringify(buildTree(result));
|
|
74
|
+
const cursedRows = result.cursedFiles.slice(0, 20).map((f, i) => {
|
|
75
|
+
const c = col(f.curseScore, maxS);
|
|
76
|
+
const stats = result.fileStats.get(f.filepath);
|
|
77
|
+
const last = stats ? new Date(stats.lastChanged * 1000).toISOString().split('T')[0] : '—';
|
|
78
|
+
return `<tr onclick="hl('${esc(f.filepath)}')" style="cursor:pointer">
|
|
79
|
+
<td style="color:#64748b;width:32px">${i + 1}</td>
|
|
80
|
+
<td style="font-family:monospace;font-size:12px">${esc(f.filepath)}</td>
|
|
81
|
+
<td><span style="background:${c};color:#000;padding:2px 10px;border-radius:20px;font-size:12px;font-weight:700">${f.curseScore.toFixed(0)}</span></td>
|
|
82
|
+
<td style="color:#94a3b8">${f.totalChanges}</td>
|
|
83
|
+
<td style="color:#94a3b8">${f.uniqueAuthors}</td>
|
|
84
|
+
<td style="color:#64748b;font-size:12px">${last}</td>
|
|
85
|
+
</tr>`;
|
|
86
|
+
}).join('');
|
|
87
|
+
const busRows = result.busFactor.map(b => {
|
|
88
|
+
const bc = b.busFactor === 1 ? '#ef4444' : b.busFactor === 2 ? '#f97316' : '#22c55e';
|
|
89
|
+
const icon = b.busFactor === 1 ? '⚠' : b.busFactor === 2 ? '⚡' : '✓';
|
|
90
|
+
return `<tr>
|
|
91
|
+
<td style="font-family:monospace;font-size:12px">${esc(b.scope)}</td>
|
|
92
|
+
<td><span style="background:${bc};color:#000;padding:2px 10px;border-radius:20px;font-weight:700">${icon} ${b.busFactor}</span></td>
|
|
93
|
+
<td style="color:#94a3b8">${b.filesAtRisk}</td>
|
|
94
|
+
<td style="color:#94a3b8;font-size:12px">${esc(b.atRiskAuthors.slice(0, 2).join(', '))}</td>
|
|
95
|
+
</tr>`;
|
|
96
|
+
}).join('');
|
|
97
|
+
const couplingRows = result.coupling.slice(0, 15).map(c2 => {
|
|
98
|
+
const cc = c2.couplingScore >= 80 ? '#ef4444' : c2.couplingScore >= 50 ? '#f97316' : '#eab308';
|
|
99
|
+
return `<tr>
|
|
100
|
+
<td style="font-family:monospace;font-size:11px">${esc(c2.fileA)}</td>
|
|
101
|
+
<td style="color:#475569;text-align:center;padding:0 8px">↔</td>
|
|
102
|
+
<td style="font-family:monospace;font-size:11px">${esc(c2.fileB)}</td>
|
|
103
|
+
<td><span style="background:${cc};color:#000;padding:2px 10px;border-radius:20px;font-weight:600">${c2.couplingScore}%</span></td>
|
|
104
|
+
</tr>`;
|
|
105
|
+
}).join('');
|
|
105
106
|
const html = `<!DOCTYPE html>
|
|
106
107
|
<html lang="en">
|
|
107
108
|
<head>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
109
|
+
<meta charset="UTF-8"/>
|
|
110
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
111
|
+
<title>⛏ ${esc(result.repoName)} — Git Archaeologist</title>
|
|
112
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"><\/script>
|
|
113
|
+
<style>
|
|
114
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
115
|
+
:root{--bg:#0a0d14;--s1:#111520;--s2:#161b28;--bd:#1e2535;--tx:#e2e8f0;--mu:#64748b;--ac:#a78bfa}
|
|
116
|
+
body{background:var(--bg);color:var(--tx);font-family:Inter,system-ui,sans-serif;font-size:14px;line-height:1.6}
|
|
117
|
+
header{background:var(--s1);border-bottom:1px solid var(--bd);padding:18px 32px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:100}
|
|
118
|
+
header h1{font-size:18px;font-weight:700;color:var(--ac)}
|
|
119
|
+
header p{font-size:12px;color:var(--mu);margin-top:2px}
|
|
120
|
+
.legend{font-size:12px;color:var(--mu);display:flex;gap:12px;align-items:center}
|
|
121
|
+
.legend span{display:flex;align-items:center;gap:4px}
|
|
122
|
+
.dot{width:10px;height:10px;border-radius:50%;display:inline-block}
|
|
123
|
+
.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;padding:24px 32px 0}
|
|
124
|
+
.stat{background:var(--s1);border:1px solid var(--bd);border-radius:12px;padding:16px;text-align:center}
|
|
125
|
+
.stat .n{font-size:30px;font-weight:800;color:var(--ac);letter-spacing:-0.03em}
|
|
126
|
+
.stat .l{font-size:11px;color:var(--mu);text-transform:uppercase;letter-spacing:.06em;margin-top:4px}
|
|
127
|
+
.stat.d .n{color:#ef4444}
|
|
128
|
+
.tm-wrap{padding:20px 32px}
|
|
129
|
+
.tm-wrap h2{font-size:12px;font-weight:600;color:var(--mu);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
|
|
130
|
+
#tm{width:100%;height:500px;background:var(--s1);border:1px solid var(--bd);border-radius:12px;overflow:hidden}
|
|
131
|
+
#tip{position:fixed;background:rgba(10,13,20,.97);border:1px solid var(--bd);border-radius:10px;padding:12px 16px;pointer-events:none;font-size:12px;z-index:999;max-width:300px;display:none;box-shadow:0 8px 32px rgba(0,0,0,.5)}
|
|
132
|
+
.tn{font-family:monospace;font-weight:600;color:var(--ac);margin-bottom:8px;word-break:break-all;font-size:11px}
|
|
133
|
+
.tr{display:flex;justify-content:space-between;gap:20px;color:var(--mu);margin-top:3px}
|
|
134
|
+
.tr span:last-child{color:var(--tx);font-weight:500}
|
|
135
|
+
.tables{display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:20px 32px 32px}
|
|
136
|
+
.full{grid-column:1/-1}
|
|
137
|
+
.card{background:var(--s1);border:1px solid var(--bd);border-radius:12px;overflow:hidden}
|
|
138
|
+
.ch{padding:14px 20px;border-bottom:1px solid var(--bd);font-size:13px;font-weight:600;color:var(--ac)}
|
|
139
|
+
table{width:100%;border-collapse:collapse}
|
|
140
|
+
th{padding:9px 20px;text-align:left;font-size:11px;font-weight:500;color:var(--mu);text-transform:uppercase;letter-spacing:.06em;background:var(--s2)}
|
|
141
|
+
td{padding:10px 20px;border-top:1px solid var(--bd);vertical-align:middle}
|
|
142
|
+
tr:hover td{background:rgba(167,139,250,.04)}
|
|
143
|
+
footer{text-align:center;padding:24px;color:var(--mu);font-size:12px;border-top:1px solid var(--bd)}
|
|
144
|
+
footer a{color:var(--ac);text-decoration:none}
|
|
145
|
+
@media(max-width:900px){.stats{grid-template-columns:repeat(3,1fr)}.tables{grid-template-columns:1fr}}
|
|
146
|
+
</style>
|
|
138
147
|
</head>
|
|
139
148
|
<body>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
<
|
|
150
|
-
<
|
|
151
|
-
<div class="stat-card"><div class="label">Cursed Files</div><div class="value" style="color:#f87171">${result.cursedFiles.length}</div><div class="sub">top instability</div></div>
|
|
152
|
-
<div class="stat-card"><div class="label">Bus Factor 1</div><div class="value" style="color:#f87171">${result.busFactor.filter(b => b.busFactor === 1).length}</div><div class="sub">single-owner modules</div></div>
|
|
153
|
-
<div class="stat-card"><div class="label">Coupled Pairs</div><div class="value" style="color:#fbbf24">${result.coupling.length}</div><div class="sub">implicit links</div></div>
|
|
149
|
+
<header>
|
|
150
|
+
<div>
|
|
151
|
+
<h1>⛏ Git Archaeologist</h1>
|
|
152
|
+
<p>${esc(result.repoName)} · ${result.totalCommits.toLocaleString()} commits · ${from} → ${to} · analyzed ${result.analyzedAt.toLocaleString()}</p>
|
|
153
|
+
</div>
|
|
154
|
+
<div class="legend">
|
|
155
|
+
<span><i class="dot" style="background:#ef4444"></i>Critical</span>
|
|
156
|
+
<span><i class="dot" style="background:#f97316"></i>High</span>
|
|
157
|
+
<span><i class="dot" style="background:#eab308"></i>Medium</span>
|
|
158
|
+
<span><i class="dot" style="background:#22c55e"></i>Low</span>
|
|
159
|
+
<span><i class="dot" style="background:#3b82f6"></i>Safe</span>
|
|
154
160
|
</div>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
161
|
+
</header>
|
|
162
|
+
<div class="stats">
|
|
163
|
+
<div class="stat"><div class="n">${result.totalCommits.toLocaleString()}</div><div class="l">Commits</div></div>
|
|
164
|
+
<div class="stat"><div class="n">${result.totalFiles.toLocaleString()}</div><div class="l">Files</div></div>
|
|
165
|
+
<div class="stat"><div class="n">${result.totalAuthors.toLocaleString()}</div><div class="l">Authors</div></div>
|
|
166
|
+
<div class="stat d"><div class="n">${result.cursedFiles.length}</div><div class="l">Cursed Files</div></div>
|
|
167
|
+
<div class="stat d"><div class="n">${result.busFactor.filter((b) => b.busFactor === 1).length}</div><div class="l">Bus Factor 1</div></div>
|
|
168
|
+
<div class="stat d"><div class="n">${result.coupling.length}</div><div class="l">Coupled Pairs</div></div>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="tm-wrap">
|
|
171
|
+
<h2>🗺 Codebase Risk Map — size = commit frequency · color = curse score</h2>
|
|
172
|
+
<div id="tm"></div>
|
|
173
|
+
</div>
|
|
174
|
+
<div id="tip">
|
|
175
|
+
<div class="tn" id="tt-fp"></div>
|
|
176
|
+
<div class="tr"><span>Curse score</span><span id="tt-sc"></span></div>
|
|
177
|
+
<div class="tr"><span>Changes</span><span id="tt-ch"></span></div>
|
|
178
|
+
<div class="tr"><span>Authors</span><span id="tt-au"></span></div>
|
|
179
|
+
<div class="tr"><span>Last touched</span><span id="tt-lt"></span></div>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="tables">
|
|
182
|
+
<div class="card full"><div class="ch">💀 Cursed Files — highest instability score</div>
|
|
183
|
+
<table><thead><tr><th>#</th><th>File</th><th>Score</th><th>Changes</th><th>Authors</th><th>Last touched</th></tr></thead>
|
|
184
|
+
<tbody>${cursedRows}</tbody></table></div>
|
|
185
|
+
<div class="card"><div class="ch">🚌 Bus Factor — single points of failure</div>
|
|
186
|
+
<table><thead><tr><th>Module</th><th>Factor</th><th>Files</th><th>Key people</th></tr></thead>
|
|
187
|
+
<tbody>${busRows}</tbody></table></div>
|
|
188
|
+
<div class="card"><div class="ch">🔗 Implicit Coupling</div>
|
|
189
|
+
<table><thead><tr><th>File A</th><th></th><th>File B</th><th>Score</th></tr></thead>
|
|
190
|
+
<tbody>${couplingRows}</tbody></table></div>
|
|
191
|
+
</div>
|
|
192
|
+
<footer>Generated by <a href="https://github.com/SushantVerma7969/git-archaeologist">⛏ git-archaeologist</a> · <a href="https://www.npmjs.com/package/git-archaeologist">npm</a></footer>
|
|
193
|
+
<script>
|
|
194
|
+
const DATA=${treeJSON};
|
|
195
|
+
const MAX=${maxS};
|
|
196
|
+
function sc(s){const r=Math.min(s/Math.max(MAX,1),1);if(r>.7)return"#ef4444";if(r>.4)return"#f97316";if(r>.2)return"#eab308";if(r>.05)return"#22c55e";return"#3b82f6";}
|
|
197
|
+
function dk(h,a){const n=parseInt(h.slice(1),16);return"#"+[Math.max(0,(n>>16)-a),Math.max(0,((n>>8)&255)-a),Math.max(0,(n&255)-a)].map(v=>v.toString(16).padStart(2,"0")).join("");}
|
|
198
|
+
const el=document.getElementById("tm");
|
|
199
|
+
const W=el.clientWidth,H=el.clientHeight;
|
|
200
|
+
const svg=d3.select("#tm").append("svg").attr("width",W).attr("height",H);
|
|
201
|
+
const root=d3.hierarchy(DATA).sum(d=>d.value||0).sort((a,b)=>(b.value||0)-(a.value||0));
|
|
202
|
+
d3.treemap().size([W,H]).padding(2).paddingTop(22).round(true)(root);
|
|
203
|
+
const tip=document.getElementById("tip");
|
|
204
|
+
function show(e,d){
|
|
205
|
+
if(!d.data.filepath)return;
|
|
206
|
+
document.getElementById("tt-fp").textContent=d.data.filepath;
|
|
207
|
+
document.getElementById("tt-sc").textContent=d.data.score?d.data.score.toFixed(1):"—";
|
|
208
|
+
document.getElementById("tt-ch").textContent=d.data.changes||"—";
|
|
209
|
+
document.getElementById("tt-au").textContent=d.data.authors||"—";
|
|
210
|
+
document.getElementById("tt-lt").textContent=d.data.lastTouched||"—";
|
|
211
|
+
tip.style.display="block";mv(e);
|
|
212
|
+
}
|
|
213
|
+
function mv(e){
|
|
214
|
+
const x=e.clientX+16,y=e.clientY-10;
|
|
215
|
+
const tw=tip.offsetWidth,th=tip.offsetHeight;
|
|
216
|
+
tip.style.left=(x+tw>window.innerWidth?x-tw-32:x)+"px";
|
|
217
|
+
tip.style.top=(y+th>window.innerHeight?y-th:y)+"px";
|
|
218
|
+
}
|
|
219
|
+
svg.selectAll("g.fd").data(root.children||[]).join("g").attr("class","fd").call(g=>{
|
|
220
|
+
g.append("rect").attr("x",d=>d.x0).attr("y",d=>d.y0).attr("width",d=>d.x1-d.x0).attr("height",d=>d.y1-d.y0).attr("fill","none").attr("stroke","#1e2535").attr("stroke-width",1);
|
|
221
|
+
g.append("text").attr("x",d=>d.x0+6).attr("y",d=>d.y0+15).attr("fill","#64748b").attr("font-size","11px").attr("font-weight","600").attr("font-family","monospace").text(d=>d.data.name);
|
|
222
|
+
});
|
|
223
|
+
const lv=svg.selectAll("g.lf").data(root.leaves()).join("g").attr("class","lf");
|
|
224
|
+
lv.append("rect")
|
|
225
|
+
.attr("x",d=>d.x0+1).attr("y",d=>d.y0+1)
|
|
226
|
+
.attr("width",d=>Math.max(0,d.x1-d.x0-2)).attr("height",d=>Math.max(0,d.y1-d.y0-2))
|
|
227
|
+
.attr("rx",3).attr("fill",d=>sc(d.data.score||0)).attr("fill-opacity",.82)
|
|
228
|
+
.attr("stroke",d=>dk(sc(d.data.score||0),40)).attr("stroke-width",.5)
|
|
229
|
+
.style("cursor","pointer")
|
|
230
|
+
.on("mouseenter",function(e,d){d3.select(this).attr("fill-opacity",1).attr("stroke-width",2);show(e,d);})
|
|
231
|
+
.on("mousemove",mv)
|
|
232
|
+
.on("mouseleave",function(){d3.select(this).attr("fill-opacity",.82).attr("stroke-width",.5);tip.style.display="none";});
|
|
233
|
+
lv.filter(d=>(d.x1-d.x0)>55&&(d.y1-d.y0)>22).append("text")
|
|
234
|
+
.attr("x",d=>d.x0+5).attr("y",d=>d.y0+14)
|
|
235
|
+
.attr("fill","rgba(0,0,0,.85)").attr("font-size","10px").attr("font-family","monospace").attr("pointer-events","none")
|
|
236
|
+
.text(d=>{const nm=d.data.name;const mx=Math.floor((d.x1-d.x0-10)/6);return nm.length>mx?nm.slice(0,mx-1)+"…":nm;});
|
|
237
|
+
window.hl=function(fp){
|
|
238
|
+
lv.selectAll("rect").attr("stroke-width",d=>d.data.filepath===fp?3:.5).attr("stroke",d=>d.data.filepath===fp?"#fff":dk(sc(d.data.score||0),40)).attr("fill-opacity",d=>d.data.filepath===fp?1:.82);
|
|
239
|
+
const node=root.leaves().find(d=>d.data.filepath===fp);
|
|
240
|
+
if(node){const cx=(node.x0+node.x1)/2,cy=(node.y0+node.y1)/2;svg.transition().duration(400);}
|
|
241
|
+
};
|
|
242
|
+
<\/script>
|
|
190
243
|
</body>
|
|
191
244
|
</html>`;
|
|
192
245
|
fs.writeFileSync(outputPath, html, 'utf8');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlReport.js","sourceRoot":"","sources":["../../src/output/htmlReport.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,
|
|
1
|
+
{"version":3,"file":"htmlReport.js","sourceRoot":"","sources":["../../src/output/htmlReport.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-archaeologist",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Uncover the hidden history, ownership, and tech debt in any git repository",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -43,4 +43,4 @@
|
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=18"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|