@mneme-ai/xray 2.176.0 → 2.177.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/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export { xrayGauntlet, type XRayGauntlet } from "./gauntlet.js";
16
16
  export { isAllowedPublicUrl, isSafeBranch } from "./clone.js";
17
17
  export { listRemoteBranches, remoteRef, reportDelta, trackerTick, trackGauntlet, type RemoteBranch, type ReportDelta, type Drift, type TrackState, type TickResult } from "./track.js";
18
18
  export { TrackerHub, trackId, hubGauntlet, verifyWebhookSig, type SseSink, type BuildFn, type RefFn, type TrackRecord, type HistoryEntry } from "./tracker_server.js";
19
+ export { buildRiskMap, riskMapGauntlet, MAP_W, MAP_H, MAP_CAP, type RiskMap, type RiskNode, type RiskEdge } from "./riskmap.js";
19
20
  export { defaultFetcher, type MetaFetcher } from "./battery/deps.js";
20
21
  export { publishReport, type PublishResult } from "./publish.js";
21
22
  export { createXRayServer } from "./server.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACvL,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACtK,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAC3G,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACvL,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACtK,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAChI,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAC3G,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ export { xrayGauntlet } from "./gauntlet.js";
16
16
  export { isAllowedPublicUrl, isSafeBranch } from "./clone.js";
17
17
  export { listRemoteBranches, remoteRef, reportDelta, trackerTick, trackGauntlet } from "./track.js";
18
18
  export { TrackerHub, trackId, hubGauntlet, verifyWebhookSig } from "./tracker_server.js";
19
+ export { buildRiskMap, riskMapGauntlet, MAP_W, MAP_H, MAP_CAP } from "./riskmap.js";
19
20
  export { defaultFetcher } from "./battery/deps.js";
20
21
  export { publishReport } from "./publish.js";
21
22
  export { createXRayServer } from "./server.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAqB,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,YAAY,EAAoB,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAqB,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAqF,MAAM,YAAY,CAAC;AACvL,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAA+E,MAAM,qBAAqB,CAAC;AACtK,OAAO,EAAE,cAAc,EAAoB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAsB,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAkC,MAAM,aAAa,CAAC;AAC3G,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAoB,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAqB,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,YAAY,EAAoB,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAqB,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAqF,MAAM,YAAY,CAAC;AACvL,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAA+E,MAAM,qBAAqB,CAAC;AACtK,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAA8C,MAAM,cAAc,CAAC;AAChI,OAAO,EAAE,cAAc,EAAoB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAsB,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAkC,MAAM,aAAa,CAAC;AAC3G,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAoB,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * RISK MAP — the "if this person leaves, what breaks?" constellation.
3
+ *
4
+ * A 2D network the CEO grasps in two seconds: each node is a FILE the analysis
5
+ * actually flagged; node colour = key-person risk (single-owner share), node size
6
+ * = churn/size, edges = files that change together (temporal coupling). High-risk
7
+ * single-owner files sit near the centre.
8
+ *
9
+ * ★ACCURACY CONTRACT (so it can't be disputed): every node + edge is taken
10
+ * VERBATIM from the signed X-Ray report — fragileFiles (single-owner %), hotspots
11
+ * (churn × size), coupling pairs. NOTHING is invented; a file with no measured
12
+ * signal is not drawn. The layout is DETERMINISTIC (a golden-angle spiral, no
13
+ * randomness) so the same report always renders the same map.
14
+ *
15
+ * Pure + total: missing/garbage fields never throw; coordinates are always finite
16
+ * and clamped inside the viewBox. Proven over 100,000 random reports (the gauntlet).
17
+ */
18
+ export interface RiskNode {
19
+ id: number;
20
+ file: string;
21
+ risk: number;
22
+ size: number;
23
+ ownerPct: number;
24
+ churn: number;
25
+ x: number;
26
+ y: number;
27
+ }
28
+ export interface RiskEdge {
29
+ a: number;
30
+ b: number;
31
+ weight: number;
32
+ hidden: boolean;
33
+ }
34
+ export interface RiskMap {
35
+ nodes: RiskNode[];
36
+ edges: RiskEdge[];
37
+ maxRisk: number;
38
+ W: number;
39
+ H: number;
40
+ note: string;
41
+ }
42
+ export declare const MAP_W = 920, MAP_H = 460, MAP_CAP = 24;
43
+ /** Build the deterministic risk constellation from a signed report. Total. */
44
+ export declare function buildRiskMap(report: unknown): RiskMap;
45
+ export interface GauntletCheck {
46
+ name: string;
47
+ pass: boolean;
48
+ detail: string;
49
+ }
50
+ export interface RiskMapGauntlet {
51
+ score: number;
52
+ iterations: number;
53
+ checks: GauntletCheck[];
54
+ }
55
+ export declare function riskMapGauntlet(iterations?: number): RiskMapGauntlet;
56
+ //# sourceMappingURL=riskmap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"riskmap.d.ts","sourceRoot":"","sources":["../src/riskmap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,QAAQ;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;AACzI,MAAM,WAAW,QAAQ;IAAG,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE;AACnF,MAAM,WAAW,OAAO;IAAG,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAEtH,eAAO,MAAM,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,OAAO,KAAK,CAAC;AAKpD,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CA+CrD;AAGD,MAAM,WAAW,aAAa;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC9E,MAAM,WAAW,eAAe;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,EAAE,CAAA;CAAE;AAgB/F,wBAAgB,eAAe,CAAC,UAAU,SAAU,GAAG,eAAe,CA0BrE"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * RISK MAP — the "if this person leaves, what breaks?" constellation.
3
+ *
4
+ * A 2D network the CEO grasps in two seconds: each node is a FILE the analysis
5
+ * actually flagged; node colour = key-person risk (single-owner share), node size
6
+ * = churn/size, edges = files that change together (temporal coupling). High-risk
7
+ * single-owner files sit near the centre.
8
+ *
9
+ * ★ACCURACY CONTRACT (so it can't be disputed): every node + edge is taken
10
+ * VERBATIM from the signed X-Ray report — fragileFiles (single-owner %), hotspots
11
+ * (churn × size), coupling pairs. NOTHING is invented; a file with no measured
12
+ * signal is not drawn. The layout is DETERMINISTIC (a golden-angle spiral, no
13
+ * randomness) so the same report always renders the same map.
14
+ *
15
+ * Pure + total: missing/garbage fields never throw; coordinates are always finite
16
+ * and clamped inside the viewBox. Proven over 100,000 random reports (the gauntlet).
17
+ */
18
+ export const MAP_W = 920, MAP_H = 460, MAP_CAP = 24;
19
+ const clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);
20
+ const num = (x) => (Number.isFinite(Number(x)) ? Number(x) : 0);
21
+ const str = (x) => (typeof x === "string" ? x : "");
22
+ /** Build the deterministic risk constellation from a signed report. Total. */
23
+ export function buildRiskMap(report) {
24
+ const r = (report && typeof report === "object" ? report : {});
25
+ const bf = (r["busFactor"] || {});
26
+ const hs = (r["hotspots"] || {});
27
+ const cx = (r["complexity"] || {});
28
+ const cp = (r["coupling"] || {});
29
+ // 1) gather per-file signals from the report (verbatim)
30
+ const byFile = new Map();
31
+ const touch = (file) => { const f = str(file); if (!f)
32
+ return null; if (!byFile.has(f))
33
+ byFile.set(f, { ownerPct: 0, churn: 0, loc: 0 }); return byFile.get(f); };
34
+ for (const x of (Array.isArray(bf["fragileFiles"]) ? bf["fragileFiles"] : [])) {
35
+ const n = touch(str(x?.["file"]));
36
+ if (n)
37
+ n.ownerPct = Math.max(n.ownerPct, clamp(num(x?.["topAuthorShare"]), 0, 1));
38
+ }
39
+ for (const x of (Array.isArray(hs["hotspots"]) ? hs["hotspots"] : [])) {
40
+ const n = touch(str(x?.["file"]));
41
+ if (n) {
42
+ n.churn = Math.max(n.churn, num(x?.["changes"]));
43
+ n.loc = Math.max(n.loc, num(x?.["loc"]));
44
+ }
45
+ }
46
+ for (const x of (Array.isArray(cx["hotspots"]) ? cx["hotspots"] : [])) {
47
+ const n = touch(str(x?.["file"]));
48
+ if (n)
49
+ n.loc = Math.max(n.loc, num(x?.["bodyLines"]));
50
+ }
51
+ const pairs = (Array.isArray(cp["pairs"]) ? cp["pairs"] : []);
52
+ for (const p of pairs) {
53
+ touch(str(p?.["a"]));
54
+ touch(str(p?.["b"]));
55
+ }
56
+ // 2) score + cap (worst-first: risk then size)
57
+ const maxChurn = Math.max(1, ...[...byFile.values()].map((v) => v.churn));
58
+ const maxLoc = Math.max(1, ...[...byFile.values()].map((v) => v.loc));
59
+ let entries = [...byFile.entries()].map(([file, v]) => {
60
+ const size = clamp(Math.max(v.churn / maxChurn, v.loc / maxLoc), 0, 1);
61
+ const risk = clamp(v.ownerPct, 0, 1);
62
+ return { file, risk, size, ownerPct: v.ownerPct, churn: v.churn };
63
+ });
64
+ entries.sort((a, b) => (b.risk - a.risk) || (b.size - a.size) || a.file.localeCompare(b.file));
65
+ entries = entries.slice(0, MAP_CAP);
66
+ const indexOf = new Map(entries.map((e, i) => [e.file, i]));
67
+ // 3) DETERMINISTIC golden-angle spiral — highest-risk (index 0) nearest centre
68
+ const cxC = MAP_W / 2, cyC = MAP_H / 2, GOLD = 2.399963229728653;
69
+ const nodes = entries.map((e, i) => {
70
+ const ang = i * GOLD;
71
+ const rad = 26 + 30 * Math.sqrt(i);
72
+ const x = clamp(cxC + rad * Math.cos(ang), 40, MAP_W - 40);
73
+ const y = clamp(cyC + rad * Math.sin(ang) * 0.56, 36, MAP_H - 36);
74
+ return { id: i, file: e.file, risk: e.risk, size: e.size, ownerPct: e.ownerPct, churn: e.churn, x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 };
75
+ });
76
+ // 4) edges = coupling pairs whose BOTH files are nodes (verbatim confidence)
77
+ const edges = [];
78
+ for (const p of pairs) {
79
+ const a = indexOf.get(str(p?.["a"])), b = indexOf.get(str(p?.["b"]));
80
+ if (a === undefined || b === undefined || a === b)
81
+ continue;
82
+ edges.push({ a, b, weight: clamp(num(p?.["confidence"]), 0, 1), hidden: !!p?.["hidden"] });
83
+ }
84
+ const maxRisk = nodes.reduce((m, n) => Math.max(m, n.risk), 0);
85
+ return { nodes, edges, maxRisk, W: MAP_W, H: MAP_H, note: nodes.length ? `${nodes.length} flagged file(s), ${edges.length} coupling link(s)` : "no per-file risk signals in this repo" };
86
+ }
87
+ /** seeded LCG → deterministic, reproducible "random" reports for the stress test */
88
+ function lcg(seed) { let s = seed >>> 0; return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; }
89
+ function randomReport(rnd) {
90
+ const nF = Math.floor(rnd() * 40), nH = Math.floor(rnd() * 40), nP = Math.floor(rnd() * 40);
91
+ const file = () => `src/${Math.floor(rnd() * 1000)}/${Math.floor(rnd() * 1000)}.ts`;
92
+ return {
93
+ busFactor: { fragileFiles: Array.from({ length: nF }, () => ({ file: rnd() < 0.05 ? "" : file(), topAuthorShare: rnd() < 0.05 ? NaN : rnd() * 1.2, commits: Math.floor(rnd() * 100) })) },
94
+ hotspots: { hotspots: Array.from({ length: nH }, () => ({ file: file(), changes: rnd() < 0.05 ? -5 : Math.floor(rnd() * 500), loc: Math.floor(rnd() * 3000) })) },
95
+ complexity: { hotspots: Array.from({ length: Math.floor(rnd() * 20) }, () => ({ file: file(), bodyLines: Math.floor(rnd() * 800) })) },
96
+ coupling: { pairs: Array.from({ length: nP }, () => ({ a: file(), b: rnd() < 0.1 ? "" : file(), confidence: rnd() < 0.05 ? NaN : rnd(), hidden: rnd() < 0.3 })) },
97
+ };
98
+ }
99
+ export function riskMapGauntlet(iterations = 100_000) {
100
+ const rnd = lcg(1234567);
101
+ let threw = 0, badCoord = 0, badRange = 0, badEdge = 0, overCap = 0;
102
+ for (let i = 0; i < iterations; i++) {
103
+ let m;
104
+ try {
105
+ m = buildRiskMap(randomReport(rnd));
106
+ }
107
+ catch {
108
+ threw++;
109
+ continue;
110
+ }
111
+ if (m.nodes.length > MAP_CAP)
112
+ overCap++;
113
+ for (const n of m.nodes) {
114
+ if (!Number.isFinite(n.x) || !Number.isFinite(n.y) || n.x < 0 || n.x > MAP_W || n.y < 0 || n.y > MAP_H)
115
+ badCoord++;
116
+ if (!(n.risk >= 0 && n.risk <= 1) || !(n.size >= 0 && n.size <= 1))
117
+ badRange++;
118
+ }
119
+ for (const e of m.edges)
120
+ if (e.a < 0 || e.b < 0 || e.a >= m.nodes.length || e.b >= m.nodes.length || e.a === e.b)
121
+ badEdge++;
122
+ }
123
+ // determinism: a fixed report renders identically twice
124
+ const fixed = randomReport(lcg(42));
125
+ const det = JSON.stringify(buildRiskMap(fixed)) === JSON.stringify(buildRiskMap(fixed));
126
+ const checks = [
127
+ { name: "TOTAL", pass: threw === 0, detail: `0 throws over ${iterations.toLocaleString()} random reports (got ${threw})` },
128
+ { name: "COORDS-IN-BOX", pass: badCoord === 0, detail: `every node coordinate finite + inside ${MAP_W}×${MAP_H} (violations ${badCoord})` },
129
+ { name: "RANGE-0-1", pass: badRange === 0, detail: `risk + size always in [0,1] (violations ${badRange})` },
130
+ { name: "EDGES-VALID", pass: badEdge === 0, detail: `every edge references two distinct existing nodes (violations ${badEdge})` },
131
+ { name: "CAP", pass: overCap === 0, detail: `node count never exceeds ${MAP_CAP} (violations ${overCap})` },
132
+ { name: "DETERMINISTIC", pass: det, detail: "same report → byte-identical map" },
133
+ ];
134
+ const passed = checks.filter((c) => c.pass).length;
135
+ return { score: Math.round((passed / checks.length) * 100), iterations, checks };
136
+ }
137
+ //# sourceMappingURL=riskmap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"riskmap.js","sourceRoot":"","sources":["../src/riskmap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,EAAE,KAAK,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,CAAC;AACpD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAU,EAAE,EAAU,EAAU,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7F,MAAM,GAAG,GAAG,CAAC,CAAU,EAAU,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjF,MAAM,GAAG,GAAG,CAAC,CAAU,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAErE,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAC,MAAe;IAC1C,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAA4B,CAAC;IAC1F,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,CAA4B,CAAC;IAC7D,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,CAA4B,CAAC;IAC5D,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,CAA4B,CAAC;IAC9D,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,CAA4B,CAAC;IAE5D,wDAAwD;IACxD,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4D,CAAC;IACnF,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC;IAC3K,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAmC,EAAE,CAAC;QAAC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAAC,IAAI,CAAC;YAAE,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IAC1O,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAmC,EAAE,CAAC;QAAC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC;YAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAAC,CAAC;IAAC,CAAC;IACtP,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAmC,EAAE,CAAC;QAAC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAAC,IAAI,CAAC;YAAE,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IACtM,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAmC,CAAC;IAChG,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IAEtE,+CAA+C;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,IAAI,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;QACpD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,QAAQ,EAAE,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACvE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IACpE,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/F,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAE5D,+EAA+E;IAC/E,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,GAAG,CAAC,EAAE,IAAI,GAAG,iBAAiB,CAAC;IACjE,MAAM,KAAK,GAAe,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC7C,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC;QACrB,MAAM,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,GAAG,EAAE,CAAC,CAAC;QAC3D,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,EAAE,KAAK,GAAG,EAAE,CAAC,CAAC;QAClE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC;IAC3J,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACrE,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QAC5D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,qBAAqB,KAAK,CAAC,MAAM,mBAAmB,CAAC,CAAC,CAAC,uCAAuC,EAAE,CAAC;AAC3L,CAAC;AAMD,oFAAoF;AACpF,SAAS,GAAG,CAAC,IAAY,IAAI,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;AAEpH,SAAS,YAAY,CAAC,GAAiB;IACrC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5F,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;IACpF,OAAO;QACL,SAAS,EAAE,EAAE,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE;QACzL,QAAQ,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE;QACjK,UAAU,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE;QACtI,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,EAAE;KAClK,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,UAAU,GAAG,OAAO;IAClD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,IAAI,KAAK,GAAG,CAAC,EAAE,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC;IACpE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,CAAU,CAAC;QACf,IAAI,CAAC;YAAC,CAAC,GAAG,YAAY,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,KAAK,EAAE,CAAC;YAAC,SAAS;QAAC,CAAC;QACzE,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,OAAO;YAAE,OAAO,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK;gBAAE,QAAQ,EAAE,CAAC;YACnH,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;gBAAE,QAAQ,EAAE,CAAC;QACjF,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK;YAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;gBAAE,OAAO,EAAE,CAAC;IAC9H,CAAC;IACD,wDAAwD;IACxD,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IACxF,MAAM,MAAM,GAAoB;QAC9B,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,KAAK,CAAC,EAAE,MAAM,EAAE,iBAAiB,UAAU,CAAC,cAAc,EAAE,wBAAwB,KAAK,GAAG,EAAE;QAC1H,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,KAAK,CAAC,EAAE,MAAM,EAAE,yCAAyC,KAAK,IAAI,KAAK,gBAAgB,QAAQ,GAAG,EAAE;QAC3I,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,KAAK,CAAC,EAAE,MAAM,EAAE,2CAA2C,QAAQ,GAAG,EAAE;QAC3G,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,EAAE,iEAAiE,OAAO,GAAG,EAAE;QACjI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,EAAE,4BAA4B,OAAO,gBAAgB,OAAO,GAAG,EAAE;QAC3G,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,kCAAkC,EAAE;KACjF,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IACnD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AACnF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mneme-ai/xray",
3
- "version": "2.176.0",
3
+ "version": "2.177.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
@@ -93,8 +93,52 @@
93
93
  else if (age.vitality === "active") { tone = "ok"; head = `✅ Healthy & actively maintained — safe to build on`; }
94
94
  else { tone = "neutral"; head = `Reviewed — ${T.length} thing${T.length === 1 ? "" : "s"} to know below`; }
95
95
 
96
+ // the concrete RISK ITEMS (what exactly + where) — listed, scrollable if long
97
+ const risks = [];
98
+ (sec.hits || []).forEach((h) => risks.push({ g: "Secret", icon: "🔑", t: `${esc(h.kind)} — ${esc(h.file)}:${h.line}` }));
99
+ (su.destructive || []).forEach((d) => risks.push({ g: "Destructive CI", icon: "💥", t: `${esc(d.where)} — ${esc(String(d.command).slice(0, 80))}` }));
100
+ (dep.atRisk || []).filter((d) => d.band === "dead" || d.band === "moribund").forEach((d) => risks.push({ g: "Dying dep", icon: "📦", t: `${esc(d.name)} (${esc(d.band)})${d.successor ? ` → ${esc(d.successor)}` : ""}` }));
101
+ (dep.licenseFlags || []).forEach((l) => risks.push({ g: "License", icon: "⚖️", t: `${esc(l.name)} — ${esc(l.license)}` }));
102
+ (bf.fragileFiles || []).forEach((f) => risks.push({ g: "Single-owner", icon: "👤", t: `${esc(f.file)} — one author owns ${Math.round((f.topAuthorShare || 0) * 100)}%` }));
103
+ (su.injectionWhere || []).forEach((w) => risks.push({ g: "Prompt-injection", icon: "🧪", t: esc(w) }));
104
+
96
105
  const top = T.slice(0, 5);
97
- return { tone, head, kind, takeaways: top };
106
+ return { tone, head, kind, takeaways: top, risks };
107
+ }
108
+
109
+ // RISK MAP — mirrors packages/xray/src/riskmap.ts (the tested + 100k-stressed source
110
+ // of truth). Every node/edge is verbatim from the signed report; nothing invented.
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
+ const riskColor = (t) => (t < 0.5 ? mix("#16a34a", "#d97706", t / 0.5) : mix("#d97706", "#e11d48", (t - 0.5) / 0.5));
113
+ function riskMapHTML(r) {
114
+ const num = (x) => (Number.isFinite(Number(x)) ? Number(x) : 0), strv = (x) => (typeof x === "string" ? x : "");
115
+ const bf = r.busFactor || {}, hs = r.hotspots || {}, cx = r.complexity || {}, cp = r.coupling || {};
116
+ const W = 920, H = 460, CAP = 24, byFile = new Map();
117
+ const touch = (f) => { f = strv(f); if (!f) return null; if (!byFile.has(f)) byFile.set(f, { o: 0, c: 0, l: 0 }); return byFile.get(f); };
118
+ (bf.fragileFiles || []).forEach((x) => { const n = touch(x && x.file); if (n) n.o = Math.max(n.o, Math.min(1, Math.max(0, num(x.topAuthorShare)))); });
119
+ (hs.hotspots || []).forEach((x) => { const n = touch(x && x.file); if (n) { n.c = Math.max(n.c, num(x.changes)); n.l = Math.max(n.l, num(x.loc)); } });
120
+ (cx.hotspots || []).forEach((x) => { const n = touch(x && x.file); if (n) n.l = Math.max(n.l, num(x.bodyLines)); });
121
+ const pairs = cp.pairs || []; pairs.forEach((p) => { touch(p && p.a); touch(p && p.b); });
122
+ if (!byFile.size) return "";
123
+ const mc = Math.max(1, ...[...byFile.values()].map((v) => v.c)), ml = Math.max(1, ...[...byFile.values()].map((v) => v.l));
124
+ let es = [...byFile.entries()].map(([file, v]) => ({ file, risk: Math.min(1, Math.max(0, v.o)), size: Math.min(1, Math.max(v.c / mc, v.l / ml)), churn: v.c }));
125
+ es.sort((a, b) => (b.risk - a.risk) || (b.size - a.size) || a.file.localeCompare(b.file));
126
+ es = es.slice(0, CAP);
127
+ const idx = new Map(es.map((e, i) => [e.file, i]));
128
+ const cxC = W / 2, cyC = H / 2, GOLD = 2.399963229728653;
129
+ const nodes = es.map((e, i) => { const ang = i * GOLD, rad = 26 + 30 * Math.sqrt(i); return { ...e, i, x: Math.min(W - 40, Math.max(40, cxC + rad * Math.cos(ang))), y: Math.min(H - 36, Math.max(36, cyC + rad * Math.sin(ang) * 0.56)) }; });
130
+ const base = (f) => { const p = String(f).split("/"); return p[p.length - 1]; };
131
+ const edges = [];
132
+ pairs.forEach((p) => { const a = idx.get(strv(p && p.a)), b = idx.get(strv(p && p.b)); if (a === undefined || b === undefined || a === b) return; edges.push({ a, b, w: Math.min(1, Math.max(0, num(p.confidence))), hidden: !!(p && p.hidden) }); });
133
+ const edgeSvg = edges.map((e) => { const A = nodes[e.a], B = nodes[e.b], mx = (A.x + B.x) / 2, my = (A.y + B.y) / 2 - 24; return `<path d="M${A.x.toFixed(1)} ${A.y.toFixed(1)} Q${mx.toFixed(1)} ${my.toFixed(1)} ${B.x.toFixed(1)} ${B.y.toFixed(1)}" fill="none" stroke="${e.hidden ? "#e11d48" : "#9aa0ad"}" stroke-width="${(0.6 + e.w * 2.2).toFixed(2)}" stroke-opacity="${(0.18 + e.w * 0.5).toFixed(2)}"${e.hidden ? ' stroke-dasharray="5 4"' : ""}/>`; }).join("");
134
+ const nodeSvg = nodes.map((n) => { const rad = 7 + n.size * 20, col = riskColor(n.risk), showLabel = n.i < 8 || n.risk >= 0.7; return `<g><circle cx="${n.x}" cy="${n.y}" r="${(rad + 7).toFixed(1)}" fill="${col}" opacity="0.14"/><circle cx="${n.x}" cy="${n.y}" r="${rad.toFixed(1)}" fill="${col}" opacity="0.92"/>${showLabel ? `<text x="${n.x}" y="${(n.y + rad + 12).toFixed(1)}" text-anchor="middle" font-size="11" fill="#5b6068" font-family="ui-monospace,Menlo,monospace">${esc(base(n.file)).slice(0, 22)}${n.ownerPct >= 0.5 ? ` ${Math.round(n.ownerPct * 100)}%` : ""}</text>` : ""}</g>`; }).join("");
135
+ const owned = nodes.filter((n) => n.risk >= 0.6).length;
136
+ return `<div class="riskmap">
137
+ <div class="rmhead">🗺 Risk map — <b>if an owner leaves, the red nodes lose their only expert</b></div>
138
+ <div class="rmsub">${nodes.length} flagged file(s) · ${edges.length} coupling link(s)${owned ? ` · ${owned} single-owner hotspot(s)` : ""} — node colour = key-person risk · size = churn · line = files that change together. Every node is verbatim from the signed report.</div>
139
+ <svg class="rmsvg" viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="repository risk map">${edgeSvg}${nodeSvg}</svg>
140
+ <div class="rmleg"><span><i style="background:#16a34a"></i>shared / low risk</span><span><i style="background:#d97706"></i>concentrated</span><span><i style="background:#e11d48"></i>single-owner (key-person)</span><span><i class="dash"></i>hidden cross-dir coupling</span></div>
141
+ </div>`;
98
142
  }
99
143
 
100
144
  function xrayCardHTML(signed, opts) {
@@ -136,7 +180,10 @@
136
180
  <div class="vhead">${esc(vd.head)}</div>
137
181
  <div class="vkind">${esc(vd.kind)} · what this means for you ↓</div>
138
182
  <ul class="vlist">${vd.takeaways.map((t) => `<li class="vt-${t.t}">${t.x}</li>`).join("")}</ul>
183
+ ${vd.risks.length ? `<details class="vrisks"${vd.risks.length <= 6 ? " open" : ""}><summary>📋 ${vd.risks.length} flagged item${vd.risks.length > 1 ? "s" : ""} — exactly what &amp; where</summary>
184
+ <div class="vrlist">${vd.risks.map((k) => `<div class="vr"><span class="vrg">${k.icon} ${k.g}</span><span class="vrt">${k.t}</span></div>`).join("")}</div></details>` : ""}
139
185
  </div>
186
+ ${riskMapHTML(r)}
140
187
  <div class="membrane">
141
188
  <div class="mp"><span class="mpk">① CAPABILITY</span><span class="mpv">${s.signalsRun} deterministic signals · ${(sec.filesScanned || 0).toLocaleString()} files scanned</span></div>
142
189
  <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
@@ -79,6 +79,23 @@
79
79
  .v-warn{background:#fffaf0} .v-warn .vhead{color:#b45309}
80
80
  .v-ok{background:#f0fdf4} .v-ok .vhead{color:#15803d}
81
81
  .v-neutral{background:#fafafb}
82
+ .verdict .vrisks{margin-top:12px;border-top:1px dashed #e5e7eb;padding-top:10px}
83
+ .verdict .vrisks summary{cursor:pointer;font-size:12.5px;font-weight:600;color:#33333b;list-style:none}
84
+ .verdict .vrisks summary::-webkit-details-marker{display:none}
85
+ .verdict .vrlist{margin-top:8px;max-height:188px;overflow-y:auto;border:1px solid #ececef;border-radius:10px;background:#fff}
86
+ .verdict .vrlist::-webkit-scrollbar{width:9px} .verdict .vrlist::-webkit-scrollbar-thumb{background:#d3d6dd;border-radius:8px;border:2px solid #fff} .verdict .vrlist::-webkit-scrollbar-track{background:transparent}
87
+ .verdict .vr{display:flex;gap:10px;padding:7px 12px;border-bottom:1px solid #f3f3f5;font-size:12.5px;align-items:baseline}
88
+ .verdict .vr:last-child{border-bottom:0}
89
+ .verdict .vrg{flex-shrink:0;min-width:104px;font-weight:600;color:#8b8f98}
90
+ .verdict .vrt{color:#33333b;font-family:ui-monospace,Menlo,monospace;word-break:break-all}
91
+ .riskmap{padding:18px 30px;border-bottom:1px solid #eef0f2;background:linear-gradient(180deg,#fcfcfd,#fff)}
92
+ .riskmap .rmhead{font-size:14.5px;color:#0b0b0f}
93
+ .riskmap .rmsub{font-size:11.5px;color:#8b8f98;margin:4px 0 10px;line-height:1.5}
94
+ .riskmap .rmsvg{width:100%;height:auto;display:block;border:1px solid #f0f1f3;border-radius:12px;background:radial-gradient(120% 120% at 50% 30%,#fbfbfe,#fff)}
95
+ .riskmap .rmleg{display:flex;flex-wrap:wrap;gap:14px;margin-top:10px;font-size:11.5px;color:#5b6068}
96
+ .riskmap .rmleg span{display:inline-flex;align-items:center;gap:6px}
97
+ .riskmap .rmleg i{width:11px;height:11px;border-radius:50%;display:inline-block}
98
+ .riskmap .rmleg i.dash{width:18px;height:0;border-radius:0;border-top:2px dashed #e11d48}
82
99
  .trustbar{display:flex;align-items:center;gap:14px;padding:13px 30px;background:#f0fdf4;border-bottom:1px solid #dcfce7;flex-wrap:wrap}
83
100
  .hgauge{display:inline-flex;align-items:center;gap:8px;font-weight:680;color:#15803d;font-size:14px;white-space:nowrap}
84
101
  .hdot{width:10px;height:10px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 4px rgba(22,163,74,.16)}
@@ -206,25 +223,26 @@
206
223
  .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)}
207
224
  .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}
208
225
  /* SHOWCASE — selling points; the graphic sits to the SIDE (grid areas, no markup move) */
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}
226
+ /* centered header; then cards (left, 3-across, readable) + graphic (right column, in the white space) */
227
+ .showcase{max-width:1200px;margin:60px auto 0;padding:0 24px;
228
+ display:grid;column-gap:48px;row-gap:6px;align-items:center;
229
+ grid-template-columns:minmax(0,1fr) 340px;grid-template-areas:"title title" "sub sub" "cards art"}
230
+ .showcase .sctitle{grid-area:title;text-align:center;max-width:760px;margin:0 auto;font-size:29px;font-weight:700;letter-spacing:-.02em;color:var(--ink);line-height:1.25}
213
231
  .showcase .sctitle .hl{color:var(--a)}
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}
232
+ .showcase .scsub{grid-area:sub;text-align:center;max-width:660px;margin:10px auto 22px;font-size:15px;color:var(--sub);line-height:1.6}
233
+ .showcase .sc3{grid-area:cards;display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
218
234
  .showcase .sccard{background:var(--soft);border:1px solid var(--line);border-radius:16px;padding:18px}
219
235
  .showcase .scico{font-size:24px;margin-bottom:8px}
220
236
  .showcase .sccard b{display:block;font-size:14.5px;color:var(--ink);margin-bottom:6px}
221
237
  .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}
238
+ .showcase .bigart{grid-area:art;display:flex;justify-content:center;align-self:center;opacity:.9}
239
+ .showcase .bigart svg{width:100%;height:auto;max-width:320px}
240
+ @media(max-width:1080px){ /* below this the graphic hides + cards go full-width 3-across (readability wins over decoration) */
241
+ .showcase{grid-template-columns:1fr;grid-template-areas:"title" "sub" "cards";max-width:1000px}
242
+ .showcase .bigart{display:none}
243
+ .showcase .sc3{grid-template-columns:repeat(3,1fr)}
226
244
  }
227
- @media(max-width:560px){ .showcase .sc3{grid-template-columns:1fr} }
245
+ @media(max-width:720px){ .showcase .sc3{grid-template-columns:1fr;max-width:460px;margin:0 auto} }
228
246
  .picker{display:none;margin-top:12px;border:1px solid var(--line);border-radius:10px;background:#fff;overflow:hidden}
229
247
  .picker.on{display:block}
230
248
  .pkbar{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--line2);font-size:12.5px}
@@ -32,6 +32,23 @@
32
32
  .v-warn{background:#fffaf0} .v-warn .vhead{color:#b45309}
33
33
  .v-ok{background:#f0fdf4} .v-ok .vhead{color:#15803d}
34
34
  .v-neutral{background:#fafafb}
35
+ .verdict .vrisks{margin-top:12px;border-top:1px dashed #e5e7eb;padding-top:10px}
36
+ .verdict .vrisks summary{cursor:pointer;font-size:12.5px;font-weight:600;color:#33333b;list-style:none}
37
+ .verdict .vrisks summary::-webkit-details-marker{display:none}
38
+ .verdict .vrlist{margin-top:8px;max-height:188px;overflow-y:auto;border:1px solid #ececef;border-radius:10px;background:#fff}
39
+ .verdict .vrlist::-webkit-scrollbar{width:9px} .verdict .vrlist::-webkit-scrollbar-thumb{background:#d3d6dd;border-radius:8px;border:2px solid #fff} .verdict .vrlist::-webkit-scrollbar-track{background:transparent}
40
+ .verdict .vr{display:flex;gap:10px;padding:7px 12px;border-bottom:1px solid #f3f3f5;font-size:12.5px;align-items:baseline}
41
+ .verdict .vr:last-child{border-bottom:0}
42
+ .verdict .vrg{flex-shrink:0;min-width:104px;font-weight:600;color:#8b8f98}
43
+ .verdict .vrt{color:#33333b;font-family:ui-monospace,Menlo,monospace;word-break:break-all}
44
+ .riskmap{padding:18px 30px;border-bottom:1px solid #eef0f2;background:linear-gradient(180deg,#fcfcfd,#fff)}
45
+ .riskmap .rmhead{font-size:14.5px;color:#0b0b0f}
46
+ .riskmap .rmsub{font-size:11.5px;color:#8b8f98;margin:4px 0 10px;line-height:1.5}
47
+ .riskmap .rmsvg{width:100%;height:auto;display:block;border:1px solid #f0f1f3;border-radius:12px;background:radial-gradient(120% 120% at 50% 30%,#fbfbfe,#fff)}
48
+ .riskmap .rmleg{display:flex;flex-wrap:wrap;gap:14px;margin-top:10px;font-size:11.5px;color:#5b6068}
49
+ .riskmap .rmleg span{display:inline-flex;align-items:center;gap:6px}
50
+ .riskmap .rmleg i{width:11px;height:11px;border-radius:50%;display:inline-block}
51
+ .riskmap .rmleg i.dash{width:18px;height:0;border-radius:0;border-top:2px dashed #e11d48}
35
52
  .trustbar{display:flex;align-items:center;gap:14px;padding:13px 30px;background:#f0fdf4;border-bottom:1px solid #dcfce7;flex-wrap:wrap}
36
53
  .hgauge{display:inline-flex;align-items:center;gap:8px;font-weight:680;color:#15803d;font-size:14px}
37
54
  .hdot{width:10px;height:10px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 4px rgba(22,163,74,.16)}