@mneme-ai/xray 2.174.0 → 2.176.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 +2 -2
- package/public/card.js +51 -0
- package/public/index.html +38 -16
- package/public/report.html +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mneme-ai/xray",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.176.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",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"mneme"
|
|
48
48
|
],
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@mneme-ai/core": "2.
|
|
50
|
+
"@mneme-ai/core": "2.175.0",
|
|
51
51
|
"@resvg/resvg-js": "^2.6.2"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
package/public/card.js
CHANGED
|
@@ -52,12 +52,58 @@
|
|
|
52
52
|
return A;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// VERDICT — turn the accurate metrics into a DECISION a human can act on:
|
|
56
|
+
// "should I trust / adopt / inherit this repo, and what do I do about it?"
|
|
57
|
+
// Every line is derived 100% from a signed metric (no new data, no AI guess).
|
|
58
|
+
function synthesizeVerdict(r) {
|
|
59
|
+
const num = (x) => (Number.isFinite(Number(x)) ? Number(x) : 0);
|
|
60
|
+
const dep = r.deps || {}, sec = r.secrets || {}, bf = r.busFactor || {}, age = r.age || {}, cx = r.complexity || {}, su = r.security || {}, hs = r.hotspots || {};
|
|
61
|
+
const dying = num((dep.byBand || {}).dead) + num((dep.byBand || {}).moribund);
|
|
62
|
+
const copyleft = num((dep.licenses || {})["strong-copyleft"]) + num((dep.licenses || {})["weak-copyleft"]);
|
|
63
|
+
const secrets = num(sec.totalFindings), destructive = (su.destructive || []).length;
|
|
64
|
+
const soloPct = num(bf.singleOwnerFilePct), topShare = num(bf.topContributorShare), busF = num(bf.busFactor);
|
|
65
|
+
const symbols = num(cx.totalSymbols), codeFiles = num(cx.filesAnalysed);
|
|
66
|
+
const isDocs = symbols < 30 && (codeFiles === 0 || symbols / Math.max(1, codeFiles) < 1.5);
|
|
67
|
+
const kind = isDocs ? "Docs / content repo" : "Code project";
|
|
68
|
+
|
|
69
|
+
// the takeaways — what it means + what to DO, severity-ranked, action-first
|
|
70
|
+
const T = [];
|
|
71
|
+
if (sec.worstVerdict === "BLOCK" && secrets > 0) T.push({ t: "bad", x: `🔴 ${secrets} live secret${secrets > 1 ? "s" : ""} in production code — rotate them now and add a pre-commit secret scan${sec.hits && sec.hits[0] ? ` (e.g. ${esc(sec.hits[0].file)}:${sec.hits[0].line})` : ""}.` });
|
|
72
|
+
else if (secrets > 0) T.push({ t: "warn", x: `🟠 ${secrets} credential-pattern match${secrets > 1 ? "es" : ""} to review in production code.` });
|
|
73
|
+
if (destructive > 0) T.push({ t: "bad", x: `🔴 ${destructive} destructive command${destructive > 1 ? "s" : ""} in build/CI (${esc((su.destructive[0] || {}).where || "ci")}) — audit before trusting this pipeline.` });
|
|
74
|
+
else if (num(su.injectionFindings) > 0) T.push({ t: "warn", x: `🟠 ${num(su.injectionFindings)} possible prompt-injection in docs — sanitize before feeding to an AI.` });
|
|
75
|
+
if (dying > 0) { const a = (dep.atRisk || [])[0]; T.push({ t: "warn", x: `🟠 ${dying} dependency${dying > 1 ? "ies" : "y"} dying/abandoned — plan a migration${a && a.successor ? ` (e.g. ${esc(a.name)} → ${esc(a.successor)})` : ""}.` }); }
|
|
76
|
+
if (copyleft > 0) T.push({ t: "warn", x: `🟠 ${copyleft} dependency${copyleft > 1 ? "ies" : "y"} with copyleft/unknown license — check before commercial use.` });
|
|
77
|
+
if (age.vitality === "archived" || age.dormant) T.push({ t: "warn", x: `🟠 No recent activity (${esc(age.vitality || "stalled")}) — may be unmaintained; pin a version if you depend on it.` });
|
|
78
|
+
else if (age.vitality === "active") T.push({ t: "ok", x: `✅ Actively maintained — ${num(age.totalCommits).toLocaleString()} commits over ${esc(age.lifespan || "its life")}, ${num(age.totalAuthors)} contributors. Low abandonment risk.` });
|
|
79
|
+
if (busF <= 1 && num(bf.authors) > 0) T.push({ t: "warn", x: `🟠 Key-person risk: one author owns ${topShare}% of commits${soloPct ? ` and ${soloPct}% of files have a single owner` : ""}. If they leave, those areas stall — spread reviews + document.` });
|
|
80
|
+
else if (soloPct >= 40) { const ff = (bf.fragileFiles || []).slice(0, 3).map((x) => esc(x.file)).join(", "); T.push({ t: "warn", x: `🟠 ${soloPct}% of files have a single owner — pair-review the fragile ones${ff ? `: ${ff}` : ""}.` }); }
|
|
81
|
+
if (secrets === 0 && destructive === 0 && dying === 0 && copyleft === 0) T.push({ t: "ok", x: `✅ Clean to adopt — no leaked secrets, no dying/risky-licensed deps, no destructive CI.` });
|
|
82
|
+
if ((hs.hotspots || [])[0] && !isDocs) { const h = hs.hotspots[0]; T.push({ t: "info", x: `ℹ️ If you change one thing first, it's <b>${esc(h.file)}</b> (highest churn×size)${h.expert ? ` — ${esc(h.expert)} knows it best` : ""}.` }); }
|
|
83
|
+
|
|
84
|
+
// the headline DECISION (worst-signal-wins)
|
|
85
|
+
const hasRed = secrets > 0 || destructive > 0;
|
|
86
|
+
const stale = age.vitality === "archived" || age.dormant;
|
|
87
|
+
const keyrisk = busF <= 1 || soloPct >= 60;
|
|
88
|
+
let tone, head;
|
|
89
|
+
if (hasRed) { tone = "bad"; head = "⚠️ Exposed risk — fix the red items before you trust this repo"; }
|
|
90
|
+
else if (stale) { tone = "warn"; head = "🪦 Looks unmaintained — risky to depend on without pinning"; }
|
|
91
|
+
else if (dying > 0) { tone = "warn"; head = "Adopt with care — aging dependencies need a migration plan"; }
|
|
92
|
+
else if (keyrisk) { tone = "warn"; head = "✅ Maintained — but ⚠️ concentrated in one person (key-person risk)"; }
|
|
93
|
+
else if (age.vitality === "active") { tone = "ok"; head = `✅ Healthy & actively maintained — safe to build on`; }
|
|
94
|
+
else { tone = "neutral"; head = `Reviewed — ${T.length} thing${T.length === 1 ? "" : "s"} to know below`; }
|
|
95
|
+
|
|
96
|
+
const top = T.slice(0, 5);
|
|
97
|
+
return { tone, head, kind, takeaways: top };
|
|
98
|
+
}
|
|
99
|
+
|
|
55
100
|
function xrayCardHTML(signed, opts) {
|
|
56
101
|
opts = opts || {};
|
|
57
102
|
const r = signed.report, s = r.summary;
|
|
58
103
|
const dep = r.deps, sec = r.secrets, bf = r.busFactor, age = r.age, cx = r.complexity;
|
|
59
104
|
const verified = signed.receipt ? '<span class="verified"><span class="dot"></span>Ed25519 — verifies offline</span>' : "unsigned";
|
|
60
105
|
const tri = triageOf(r);
|
|
106
|
+
const vd = synthesizeVerdict(r);
|
|
61
107
|
|
|
62
108
|
const depChips = (dep.atRisk || []).slice(0, 6).map((d) =>
|
|
63
109
|
`<span class="chip ${d.band === "dead" ? "bad" : "warn"}">${esc(d.name)} · ${d.band}${d.successor ? ` → ${esc(d.successor)}` : ""}</span>`).join("") || `<span class="chip">none dying</span>`;
|
|
@@ -86,6 +132,11 @@
|
|
|
86
132
|
${r.subject.kind === "git-url" ? `<a class="repourl" href="${esc(r.subject.ref)}" target="_blank" rel="noopener">${esc(r.subject.ref)} ↗</a>` : `<div class="repourl">${esc(r.subject.ref)}</div>`}
|
|
87
133
|
<div class="head">${esc(s.headline)} · ${s.signalsRun} signals · @ ${esc(String(r.subject.commitHash).slice(0, 10))}</div></div>
|
|
88
134
|
</div>
|
|
135
|
+
<div class="verdict v-${vd.tone}">
|
|
136
|
+
<div class="vhead">${esc(vd.head)}</div>
|
|
137
|
+
<div class="vkind">${esc(vd.kind)} · what this means for you ↓</div>
|
|
138
|
+
<ul class="vlist">${vd.takeaways.map((t) => `<li class="vt-${t.t}">${t.x}</li>`).join("")}</ul>
|
|
139
|
+
</div>
|
|
89
140
|
<div class="membrane">
|
|
90
141
|
<div class="mp"><span class="mpk">① CAPABILITY</span><span class="mpv">${s.signalsRun} deterministic signals · ${(sec.filesScanned || 0).toLocaleString()} files scanned</span></div>
|
|
91
142
|
<div class="mp"><span class="mpk">② ATTENTION</span><span class="mpv">${tri.length ? `${tri.length} signal(s) need attention` : `all signals clear`}</span></div>
|
package/public/index.html
CHANGED
|
@@ -70,6 +70,15 @@
|
|
|
70
70
|
.top .repourl{display:inline-block;font-size:12.5px;color:var(--a);text-decoration:none;margin-top:3px;word-break:break-all;font-family:ui-monospace,Menlo,monospace}
|
|
71
71
|
.top .repourl:hover{text-decoration:underline}
|
|
72
72
|
.top .head{color:var(--sub);font-size:14px;margin-top:4px}
|
|
73
|
+
.verdict{padding:18px 30px;border-bottom:1px solid #eef0f2}
|
|
74
|
+
.verdict .vhead{font-size:17px;font-weight:680;letter-spacing:-.01em;line-height:1.3;color:#0b0b0f}
|
|
75
|
+
.verdict .vkind{font-size:12px;color:#8b8f98;margin:3px 0 12px;font-weight:560}
|
|
76
|
+
.verdict .vlist{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:8px}
|
|
77
|
+
.verdict .vlist li{font-size:13.5px;line-height:1.5;color:#33333b}
|
|
78
|
+
.v-bad{background:#fff5f6} .v-bad .vhead{color:#be123c}
|
|
79
|
+
.v-warn{background:#fffaf0} .v-warn .vhead{color:#b45309}
|
|
80
|
+
.v-ok{background:#f0fdf4} .v-ok .vhead{color:#15803d}
|
|
81
|
+
.v-neutral{background:#fafafb}
|
|
73
82
|
.trustbar{display:flex;align-items:center;gap:14px;padding:13px 30px;background:#f0fdf4;border-bottom:1px solid #dcfce7;flex-wrap:wrap}
|
|
74
83
|
.hgauge{display:inline-flex;align-items:center;gap:8px;font-weight:680;color:#15803d;font-size:14px;white-space:nowrap}
|
|
75
84
|
.hdot{width:10px;height:10px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 4px rgba(22,163,74,.16)}
|
|
@@ -146,8 +155,10 @@
|
|
|
146
155
|
.listfoot{display:flex;justify-content:space-between;align-items:center;padding:14px 4px 0;font-size:13px}
|
|
147
156
|
.moreb{height:38px;padding:0 18px;border:1px solid var(--line);background:#fff;border-radius:10px;cursor:pointer;font-size:13px;font-weight:540;transition:border-color .15s}
|
|
148
157
|
.moreb:hover{border-color:var(--ink)}
|
|
149
|
-
.moreb:disabled{opacity:.4;cursor:not-allowed;border-color:var(--line)}
|
|
150
158
|
.pagenav{display:inline-flex;gap:8px}
|
|
159
|
+
.pagenav .moreb{background:var(--ink);color:#fff;border-color:var(--ink)}
|
|
160
|
+
.pagenav .moreb:hover{opacity:.88}
|
|
161
|
+
.pagenav .moreb:disabled{background:#fff;color:#c8ccd2;border-color:var(--line);opacity:1;cursor:not-allowed}
|
|
151
162
|
/* share + buttons */
|
|
152
163
|
.share{display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding:18px 30px;border-top:1px solid var(--line2)}
|
|
153
164
|
.badgeimg{height:20px}
|
|
@@ -195,23 +206,25 @@
|
|
|
195
206
|
.lscard .pill{display:inline-block;font-size:11px;background:var(--soft);border:1px solid var(--line);border-radius:20px;padding:1px 8px;color:var(--ink2)}
|
|
196
207
|
.lscard .lsnote{margin-top:10px;font-size:11.5px;color:var(--sub);line-height:1.5;border-top:1px solid var(--line2);padding-top:9px}
|
|
197
208
|
/* SHOWCASE — selling points; the graphic sits to the SIDE (grid areas, no markup move) */
|
|
198
|
-
.showcase{max-width:
|
|
199
|
-
display:grid;column-gap:
|
|
200
|
-
grid-template-columns:1fr
|
|
201
|
-
.showcase .sctitle{grid-area:title;font-size:
|
|
209
|
+
.showcase{max-width:1080px;margin:60px auto 0;padding:0 20px;
|
|
210
|
+
display:grid;column-gap:32px;row-gap:8px;align-items:center;
|
|
211
|
+
grid-template-columns:1fr 300px;grid-template-areas:"title title" "sub sub" "cards art"}
|
|
212
|
+
.showcase .sctitle{grid-area:title;text-align:center;font-size:29px;font-weight:700;letter-spacing:-.02em;color:var(--ink);margin:0;line-height:1.25}
|
|
202
213
|
.showcase .sctitle .hl{color:var(--a)}
|
|
203
|
-
.showcase .scsub{grid-area:sub;font-size:15px;color:var(--sub);margin:
|
|
204
|
-
.showcase .bigart{grid-area:art;display:flex;justify-content:center;align-self:center;opacity:.
|
|
205
|
-
.showcase .
|
|
206
|
-
.showcase .
|
|
207
|
-
.showcase .
|
|
208
|
-
.showcase .
|
|
209
|
-
.showcase .sccard
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
.showcase
|
|
213
|
-
.showcase .
|
|
214
|
+
.showcase .scsub{grid-area:sub;text-align:center;font-size:15px;color:var(--sub);max-width:680px;margin:6px auto 22px;line-height:1.6}
|
|
215
|
+
.showcase .bigart{grid-area:art;display:flex;justify-content:center;align-self:center;opacity:.92}
|
|
216
|
+
.showcase .bigart svg{width:100%;height:auto;max-width:300px}
|
|
217
|
+
.showcase .sc3{grid-area:cards;display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
|
|
218
|
+
.showcase .sccard{background:var(--soft);border:1px solid var(--line);border-radius:16px;padding:18px}
|
|
219
|
+
.showcase .scico{font-size:24px;margin-bottom:8px}
|
|
220
|
+
.showcase .sccard b{display:block;font-size:14.5px;color:var(--ink);margin-bottom:6px}
|
|
221
|
+
.showcase .sccard span{font-size:12.5px;color:var(--sub);line-height:1.5}
|
|
222
|
+
@media(max-width:880px){
|
|
223
|
+
.showcase{grid-template-columns:1fr;grid-template-areas:"title" "sub" "cards" "art"}
|
|
224
|
+
.showcase .sc3{grid-template-columns:1fr 1fr}
|
|
225
|
+
.showcase .bigart{margin-top:18px}
|
|
214
226
|
}
|
|
227
|
+
@media(max-width:560px){ .showcase .sc3{grid-template-columns:1fr} }
|
|
215
228
|
.picker{display:none;margin-top:12px;border:1px solid var(--line);border-radius:10px;background:#fff;overflow:hidden}
|
|
216
229
|
.picker.on{display:block}
|
|
217
230
|
.pkbar{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--line2);font-size:12.5px}
|
|
@@ -656,6 +669,14 @@ function renderLocalScan(r){
|
|
|
656
669
|
const sec=s.secrets, secCls=sec.totalFindings>0?"bad":"ok";
|
|
657
670
|
const langs=s.langs.map(([e,n])=>`<span class="pill">${esc(e)} ${n}</span>`).join(" ");
|
|
658
671
|
const g=s.git, vit = !g.has ? "" : (s.dormantDays>365?"dormant":s.dormantDays>120?"slowing":"active");
|
|
672
|
+
// VERDICT — turn the local numbers into a decision + what-to-do (same idea as the URL report)
|
|
673
|
+
const lt=[]; let ltone="ok", lhead="✅ Quick scan looks clean — see details below";
|
|
674
|
+
if(sec.totalFindings>0){ ltone="bad"; lhead="⚠️ Secret(s) in this folder — fix before sharing"; lt.push(`🔴 ${sec.totalFindings} credential${sec.totalFindings>1?"s":""} in production code — rotate + add a pre-commit secret scan.`); }
|
|
675
|
+
if(g.has && g.authors<=1){ if(ltone==="ok"){ltone="warn"; lhead="✅ Scanned — ⚠️ solo-owned (key-person risk)";} lt.push(`🟠 Bus factor 1 — one author made all ${g.commits} commits. If they leave, this stalls; add reviewers + docs.`); }
|
|
676
|
+
if(g.has && s.dormantDays>365){ if(ltone==="ok"){ltone="warn"; lhead="🪦 Looks unmaintained — last commit over a year ago";} lt.push(`🟠 No activity for ${s.dormantDays} days — may be stale; confirm it is still maintained.`); }
|
|
677
|
+
if(sec.totalFindings===0 && (!g.has || (g.authors>1 && s.dormantDays<=365))) lt.push(`✅ No leaked secrets${g.has?`, actively worked (${g.commits} commits, ${g.authors} authors)`:""} — clean for a quick local check.`);
|
|
678
|
+
if(s.deps.total>0) lt.push(`ℹ️ ${s.deps.total} dependencies declared — run the full report (public URL / bridge) to check which are dying or risky-licensed.`);
|
|
679
|
+
const lverdict = `<div class="verdict v-${ltone}" style="margin:0 -16px 12px;border-radius:0;border-bottom:1px solid #eef0f2"><div class="vhead" style="font-size:15px">${esc(lhead)}</div><ul class="vlist" style="margin-top:8px">${lt.slice(0,4).map(t=>`<li>${t}</li>`).join("")}</ul></div>`;
|
|
659
680
|
const gitRows = g.has ? `
|
|
660
681
|
<div class="lsrow"><span class="lsk">Bus factor</span><span class="lsv ${g.authors===1?"bad":""}"><b>${g.authors}</b> author${g.authors===1?" — single point of failure":"s"} · top author ${Math.round(g.topShare*100)}% of ${g.commits} commits</span></div>
|
|
661
682
|
<div class="lsrow"><span class="lsk">Vitality</span><span class="lsv ${vit==="dormant"?"bad":""}"><b>${vit}</b> · ${s.ageDays}d span · last activity ${s.dormantDays}d ago</span></div>`
|
|
@@ -663,6 +684,7 @@ function renderLocalScan(r){
|
|
|
663
684
|
out.innerHTML=`<div class="lscard">
|
|
664
685
|
<div class="lstop"><div class="lsgrade g-${("ABCDEF".includes(s.grade)?s.grade:"C")}">${esc(s.grade)}</div>
|
|
665
686
|
<div><div class="lsh"><b>📂 ${esc(r.folder)}</b></div><div class="muted" style="font-size:12px">scanned in your browser · ${r.files} files · nothing uploaded</div></div></div>
|
|
687
|
+
${lverdict}
|
|
666
688
|
<div class="lsrow"><span class="lsk">Secrets</span><span class="lsv ${secCls}"><b>${sec.totalFindings}</b> in production code${sec.excludedTestHits?` · ${sec.excludedTestHits} in tests (excluded)`:""}</span></div>
|
|
667
689
|
${sec.hits.length?`<div class="lshits">${sec.hits.slice(0,6).map(h=>`<div>🔴 ${esc(h.kind)} — ${esc(h.file)}:${h.line}</div>`).join("")}</div>`:`<div class="lsok">✓ no leaked credentials in production code</div>`}
|
|
668
690
|
${gitRows}
|
package/public/report.html
CHANGED
|
@@ -23,6 +23,15 @@
|
|
|
23
23
|
.g-A{background:linear-gradient(135deg,#1fb255,#15863f)}.g-B{background:linear-gradient(135deg,#7bb736,#5c8f1e)}
|
|
24
24
|
.g-C{background:linear-gradient(135deg,#eaa83a,#c9821a)}.g-D{background:linear-gradient(135deg,#f0742e,#d4571a)}.g-F{background:linear-gradient(135deg,#f43f5e,#be123c)}
|
|
25
25
|
.top .repo{font-size:22px;font-weight:640;letter-spacing:-.02em;color:var(--ink);word-break:break-all;line-height:1.2}.top .repobr{font-size:13px;font-weight:600;color:var(--a);background:var(--a-soft);border-radius:6px;padding:1px 7px;vertical-align:middle}.top .repourl{display:inline-block;font-size:12.5px;color:var(--a);text-decoration:none;margin-top:3px;word-break:break-all;font-family:ui-monospace,Menlo,monospace}.top .repourl:hover{text-decoration:underline}.top .head{color:var(--sub);font-size:14px;margin-top:4px}
|
|
26
|
+
.verdict{padding:18px 30px;border-bottom:1px solid #eef0f2}
|
|
27
|
+
.verdict .vhead{font-size:17px;font-weight:680;letter-spacing:-.01em;line-height:1.3;color:#0b0b0f}
|
|
28
|
+
.verdict .vkind{font-size:12px;color:#8b8f98;margin:3px 0 12px;font-weight:560}
|
|
29
|
+
.verdict .vlist{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:8px}
|
|
30
|
+
.verdict .vlist li{font-size:13.5px;line-height:1.5;color:#33333b}
|
|
31
|
+
.v-bad{background:#fff5f6} .v-bad .vhead{color:#be123c}
|
|
32
|
+
.v-warn{background:#fffaf0} .v-warn .vhead{color:#b45309}
|
|
33
|
+
.v-ok{background:#f0fdf4} .v-ok .vhead{color:#15803d}
|
|
34
|
+
.v-neutral{background:#fafafb}
|
|
26
35
|
.trustbar{display:flex;align-items:center;gap:14px;padding:13px 30px;background:#f0fdf4;border-bottom:1px solid #dcfce7;flex-wrap:wrap}
|
|
27
36
|
.hgauge{display:inline-flex;align-items:center;gap:8px;font-weight:680;color:#15803d;font-size:14px}
|
|
28
37
|
.hdot{width:10px;height:10px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 4px rgba(22,163,74,.16)}
|