@mneme-ai/xray 2.183.0 → 2.184.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/package.json +1 -1
- package/public/card.js +32 -32
- package/public/index.html +9 -12
- package/public/report.html +9 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mneme-ai/xray",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.184.0",
|
|
4
4
|
"description": "Mneme Repo X-Ray — a signed, raw-free, deterministic X-Ray of any repo. Every number is reproducible from git/AST/metadata and sealed with an offline-verifiable NOTARY receipt. No source code ever leaves the machine; no LLM guesses anything.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
package/public/card.js
CHANGED
|
@@ -110,41 +110,41 @@
|
|
|
110
110
|
// of truth). Every node/edge is verbatim from the signed report; nothing invented.
|
|
111
111
|
function mix(a, b, t) { const p = (h) => [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16)); const A = p(a), B = p(b); return "#" + A.map((v, i) => Math.round(v + (B[i] - v) * t).toString(16).padStart(2, "0")).join(""); }
|
|
112
112
|
const riskColor = (t) => (t < 0.5 ? mix("#16a34a", "#d97706", t / 0.5) : mix("#d97706", "#e11d48", (t - 0.5) / 0.5));
|
|
113
|
+
// RISK MAP — a ranked KEY-PERSON-RISK bar chart (NOT a bubble cloud). Instantly
|
|
114
|
+
// readable: one row per file, bar length = single-author share (risk if they leave),
|
|
115
|
+
// colour by severity, worst on top, a "↔N" badge for how many files it's coupled to.
|
|
116
|
+
// Every value verbatim from the signed report — no AI guessed it, nothing overlaps.
|
|
113
117
|
function riskMapHTML(r) {
|
|
114
118
|
const num = (x) => (Number.isFinite(Number(x)) ? Number(x) : 0), strv = (x) => (typeof x === "string" ? x : "");
|
|
115
|
-
const bf = r.busFactor || {}, hs = r.hotspots || {},
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
(
|
|
120
|
-
(hs.hotspots || []).forEach((x) => { const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// TOP key-person risks as WORDS (the part a CEO/dev reads) — concrete + actionable
|
|
140
|
-
const topRisk = nodes.filter((n) => n.risk >= 0.5).slice(0, 6);
|
|
141
|
-
const riskList = topRisk.length ? `<div class="rmlist"><div class="rmlt">⚠️ Top key-person risks — fix these first</div>${topRisk.map((n) => `<div class="rmli"><span class="rmdot" style="background:${riskColor(n.risk)}"></span><b>${esc(base(n.file))}</b><span class="rmwhy">one author owns <b>${Math.round(n.ownerPct * 100)}%</b> of its history — add a reviewer / write docs before they leave</span></div>`).join("")}</div>` : `<div class="rmlist rmok">✓ No single-owner files — knowledge is well spread across the team.</div>`;
|
|
119
|
+
const bf = r.busFactor || {}, hs = r.hotspots || {}, cp = r.coupling || {};
|
|
120
|
+
const base = (f) => { const p = String(f).split("/"); return p[p.length - 1] || f; };
|
|
121
|
+
// coupling degree per file (how many distinct files it changes with)
|
|
122
|
+
const deg = new Map();
|
|
123
|
+
(cp.pairs || []).forEach((p) => { const a = strv(p && p.a), b = strv(p && p.b); if (!a || !b || a === b) return; if (!deg.has(a)) deg.set(a, new Set()); if (!deg.has(b)) deg.set(b, new Set()); deg.get(a).add(b); deg.get(b).add(a); });
|
|
124
|
+
const churn = new Map(); (hs.hotspots || []).forEach((x) => { const f = strv(x && x.file); if (f) churn.set(f, Math.max(churn.get(f) || 0, num(x.changes))); });
|
|
125
|
+
// the key-person list = files with a measured single-author share, worst-first
|
|
126
|
+
let files = (bf.fragileFiles || []).map((x) => ({ file: strv(x && x.file), pct: Math.min(1, Math.max(0, num(x.topAuthorShare))), commits: num(x && x.commits) })).filter((x) => x.file);
|
|
127
|
+
files.sort((a, b) => (b.pct - a.pct) || (b.commits - a.commits) || a.file.localeCompare(b.file));
|
|
128
|
+
files = files.slice(0, 12);
|
|
129
|
+
if (!files.length) return `<div class="riskmap"><div class="rmhead">🔑 Key-person risk</div><div class="rmsub">✓ No single-owner files — knowledge is well spread across the team. Nobody is a single point of failure.</div></div>`;
|
|
130
|
+
const sevColor = (p) => (p >= 0.9 ? "#e11d48" : p >= 0.75 ? "#f97316" : p >= 0.6 ? "#eab308" : "#22c55e");
|
|
131
|
+
const sevWord = (p) => (p >= 0.9 ? "critical" : p >= 0.75 ? "high" : p >= 0.6 ? "watch" : "ok");
|
|
132
|
+
const critical = files.filter((f) => f.pct >= 0.9).length;
|
|
133
|
+
const rows = files.map((f) => {
|
|
134
|
+
const c = deg.has(f.file) ? deg.get(f.file).size : 0;
|
|
135
|
+
const col = sevColor(f.pct), pctN = Math.round(f.pct * 100);
|
|
136
|
+
return `<div class="rmbar" title="${esc(f.file)} — ${pctN}% by one author${c ? `, coupled to ${c} file(s)` : ""}">
|
|
137
|
+
<span class="rmbf">${esc(base(f.file))}</span>
|
|
138
|
+
<span class="rmtrack"><span class="rmfill" style="width:${pctN}%;background:${col}"></span></span>
|
|
139
|
+
<span class="rmval" style="color:${col}">${pctN}%</span>
|
|
140
|
+
<span class="rmcouple">${c ? `↔${c}` : ""}</span>
|
|
141
|
+
</div>`;
|
|
142
|
+
}).join("");
|
|
142
143
|
return `<div class="riskmap">
|
|
143
|
-
<div class="rmhead"
|
|
144
|
-
<div class="rmsub"
|
|
145
|
-
<div class="
|
|
146
|
-
<div class="rmleg"><span><i style="background:#
|
|
147
|
-
${riskList}
|
|
144
|
+
<div class="rmhead">🔑 Key-person risk — <b>if one person is away, what's exposed</b></div>
|
|
145
|
+
<div class="rmsub">Each bar is a file. <b>Longer & redder = more of it was written by a single person</b> — so it's riskier if they leave. <b>↔N</b> = it changes together with N other files. Worst on top. Measured from git history — nothing invented.</div>
|
|
146
|
+
<div class="rmbars">${rows}</div>
|
|
147
|
+
<div class="rmleg"><span><i style="background:#e11d48"></i>critical ≥90%</span><span><i style="background:#f97316"></i>high ≥75%</span><span><i style="background:#eab308"></i>watch ≥60%</span><span class="rmsummary">${files.length} owned file(s)${critical ? ` · <b style="color:#be123c">${critical} critical</b>` : ""}</span></div>
|
|
148
148
|
</div>`;
|
|
149
149
|
}
|
|
150
150
|
|
package/public/index.html
CHANGED
|
@@ -93,21 +93,18 @@
|
|
|
93
93
|
.riskmap{padding:18px 30px;border-bottom:1px solid #eef0f2;background:linear-gradient(180deg,#fcfcfd,#fff)}
|
|
94
94
|
.riskmap .rmhead{font-size:14.5px;color:#0b0b0f}
|
|
95
95
|
.riskmap .rmsub{font-size:11.5px;color:#8b8f98;margin:4px 0 10px;line-height:1.5}
|
|
96
|
-
.riskmap .
|
|
97
|
-
.riskmap .
|
|
96
|
+
.riskmap .rmbars{display:flex;flex-direction:column;gap:7px;margin:2px 0 4px}
|
|
97
|
+
.riskmap .rmbar{display:flex;align-items:center;gap:10px;font-size:12.5px}
|
|
98
|
+
.riskmap .rmbf{flex:0 0 38%;max-width:38%;font-family:ui-monospace,Menlo,monospace;color:#0b0b0f;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
99
|
+
.riskmap .rmtrack{flex:1;height:14px;background:#f1f2f4;border-radius:8px;overflow:hidden}
|
|
100
|
+
.riskmap .rmfill{display:block;height:100%;border-radius:8px;transition:width .6s cubic-bezier(.2,.8,.2,1)}
|
|
101
|
+
.riskmap .rmval{flex:0 0 40px;text-align:right;font-weight:680;font-variant-numeric:tabular-nums}
|
|
102
|
+
.riskmap .rmcouple{flex:0 0 30px;font-size:11px;color:#7c83f6;font-weight:600}
|
|
103
|
+
@media (max-width:560px){.riskmap .rmbf{flex-basis:46%;max-width:46%}}
|
|
104
|
+
.riskmap .rmleg{display:flex;flex-wrap:wrap;gap:14px;margin-top:12px;font-size:11.5px;color:#5b6068}
|
|
98
105
|
.riskmap .rmleg span{display:inline-flex;align-items:center;gap:6px}
|
|
99
106
|
.riskmap .rmleg i{width:11px;height:11px;border-radius:50%;display:inline-block}
|
|
100
|
-
.riskmap .rmleg i.dash{width:18px;height:0;border-radius:0;border-top:2px dashed #e11d48}
|
|
101
107
|
.riskmap .rmsummary{margin-left:auto;color:#8b8f98}
|
|
102
|
-
.riskmap .rmsvgwrap{position:relative;cursor:zoom-in}
|
|
103
|
-
.riskmap .rmexpand{position:absolute;top:10px;right:12px;font-size:11px;color:#5b6068;background:#fff;border:1px solid #ececef;border-radius:8px;padding:3px 9px;opacity:.85}
|
|
104
|
-
.riskmap .rmlist{margin-top:14px;border-top:1px solid #eef0f2;padding-top:12px}
|
|
105
|
-
.riskmap .rmlt{font-size:12.5px;font-weight:680;color:#be123c;margin-bottom:8px}
|
|
106
|
-
.riskmap .rmok{color:#15803d;font-weight:560;font-size:13px;border:0;padding-top:0}
|
|
107
|
-
.riskmap .rmli{display:flex;align-items:baseline;gap:9px;padding:6px 0;font-size:13px;flex-wrap:wrap}
|
|
108
|
-
.riskmap .rmdot{width:10px;height:10px;border-radius:50%;flex-shrink:0;align-self:center}
|
|
109
|
-
.riskmap .rmli b{color:#0b0b0f;font-family:ui-monospace,Menlo,monospace}
|
|
110
|
-
.riskmap .rmwhy{color:#5b6068}
|
|
111
108
|
.rmmodal{display:none;position:fixed;inset:0;z-index:9999;background:rgba(11,11,20,.62);align-items:center;justify-content:center;padding:16px}
|
|
112
109
|
.rmmodal-box{background:#fff;border-radius:16px;max-width:1100px;width:100%;max-height:92vh;overflow:auto;box-shadow:0 24px 80px rgba(0,0,0,.4)}
|
|
113
110
|
.rmmodal-bar{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid #eef0f2;font-size:13px;color:#5b6068;position:sticky;top:0;background:#fff}
|
package/public/report.html
CHANGED
|
@@ -46,21 +46,18 @@
|
|
|
46
46
|
.riskmap{padding:18px 30px;border-bottom:1px solid #eef0f2;background:linear-gradient(180deg,#fcfcfd,#fff)}
|
|
47
47
|
.riskmap .rmhead{font-size:14.5px;color:#0b0b0f}
|
|
48
48
|
.riskmap .rmsub{font-size:11.5px;color:#8b8f98;margin:4px 0 10px;line-height:1.5}
|
|
49
|
-
.riskmap .
|
|
50
|
-
.riskmap .
|
|
49
|
+
.riskmap .rmbars{display:flex;flex-direction:column;gap:7px;margin:2px 0 4px}
|
|
50
|
+
.riskmap .rmbar{display:flex;align-items:center;gap:10px;font-size:12.5px}
|
|
51
|
+
.riskmap .rmbf{flex:0 0 38%;max-width:38%;font-family:ui-monospace,Menlo,monospace;color:#0b0b0f;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
52
|
+
.riskmap .rmtrack{flex:1;height:14px;background:#f1f2f4;border-radius:8px;overflow:hidden}
|
|
53
|
+
.riskmap .rmfill{display:block;height:100%;border-radius:8px;transition:width .6s cubic-bezier(.2,.8,.2,1)}
|
|
54
|
+
.riskmap .rmval{flex:0 0 40px;text-align:right;font-weight:680;font-variant-numeric:tabular-nums}
|
|
55
|
+
.riskmap .rmcouple{flex:0 0 30px;font-size:11px;color:#7c83f6;font-weight:600}
|
|
56
|
+
@media (max-width:560px){.riskmap .rmbf{flex-basis:46%;max-width:46%}}
|
|
57
|
+
.riskmap .rmleg{display:flex;flex-wrap:wrap;gap:14px;margin-top:12px;font-size:11.5px;color:#5b6068}
|
|
51
58
|
.riskmap .rmleg span{display:inline-flex;align-items:center;gap:6px}
|
|
52
59
|
.riskmap .rmleg i{width:11px;height:11px;border-radius:50%;display:inline-block}
|
|
53
|
-
.riskmap .rmleg i.dash{width:18px;height:0;border-radius:0;border-top:2px dashed #e11d48}
|
|
54
60
|
.riskmap .rmsummary{margin-left:auto;color:#8b8f98}
|
|
55
|
-
.riskmap .rmsvgwrap{position:relative;cursor:zoom-in}
|
|
56
|
-
.riskmap .rmexpand{position:absolute;top:10px;right:12px;font-size:11px;color:#5b6068;background:#fff;border:1px solid #ececef;border-radius:8px;padding:3px 9px;opacity:.85}
|
|
57
|
-
.riskmap .rmlist{margin-top:14px;border-top:1px solid #eef0f2;padding-top:12px}
|
|
58
|
-
.riskmap .rmlt{font-size:12.5px;font-weight:680;color:#be123c;margin-bottom:8px}
|
|
59
|
-
.riskmap .rmok{color:#15803d;font-weight:560;font-size:13px;border:0;padding-top:0}
|
|
60
|
-
.riskmap .rmli{display:flex;align-items:baseline;gap:9px;padding:6px 0;font-size:13px;flex-wrap:wrap}
|
|
61
|
-
.riskmap .rmdot{width:10px;height:10px;border-radius:50%;flex-shrink:0;align-self:center}
|
|
62
|
-
.riskmap .rmli b{color:#0b0b0f;font-family:ui-monospace,Menlo,monospace}
|
|
63
|
-
.riskmap .rmwhy{color:#5b6068}
|
|
64
61
|
.rmmodal{display:none;position:fixed;inset:0;z-index:9999;background:rgba(11,11,20,.62);align-items:center;justify-content:center;padding:16px}
|
|
65
62
|
.rmmodal-box{background:#fff;border-radius:16px;max-width:1100px;width:100%;max-height:92vh;overflow:auto;box-shadow:0 24px 80px rgba(0,0,0,.4)}
|
|
66
63
|
.rmmodal-bar{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid #eef0f2;font-size:13px;color:#5b6068;position:sticky;top:0;background:#fff}
|