@mneme-ai/xray 2.189.0 → 2.191.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.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * CONTEXT AIR QUALITY — one number: how clean is this codebase for an AI to work in?
3
+ *
4
+ * The honest core of the "lungs / air-pollution" idea. A deterministic weighted
5
+ * composite of the SAME measured signals the X-Ray already reports, viewed through a
6
+ * distinct lens: the conditions that make a repo HARD for an AI agent to reason about
7
+ * and change safely —
8
+ * • leaked secrets (toxic: an agent can exfiltrate or trip on them)
9
+ * • destructive commands (an agent running CI can wipe/RCE)
10
+ * • knowledge concentration (single-owner files = no second context to learn from)
11
+ * • hidden coupling (cross-module change-coupling = edits silently break things)
12
+ * • dependency rot (dead/moribund deps = the agent suggests dead APIs)
13
+ * • oversized functions (an agent loses the thread in 200-line bodies)
14
+ *
15
+ * Output: a 0-100 BREATHABILITY score (higher = cleaner), a band, and the ranked
16
+ * "pollutants". ★HONEST: this is a labelled composite of measured signals — NOT a
17
+ * hallucination forecast and NOT a re-skin of the A-F grade (the grade weights
18
+ * security/freshness for humans; this weights AI-workability). Pure + total; proven
19
+ * monotonic + in-range over 100,000 random reports.
20
+ */
21
+ export type AQBand = "Pristine" | "Good" | "Moderate" | "Unhealthy" | "Hazardous";
22
+ export interface Pollutant {
23
+ name: string;
24
+ impact: number;
25
+ detail: string;
26
+ }
27
+ export interface AirQuality {
28
+ score: number;
29
+ band: AQBand;
30
+ pollutants: Pollutant[];
31
+ note: string;
32
+ }
33
+ export declare function buildAirQuality(report: unknown): AirQuality;
34
+ export interface AQGauntlet {
35
+ score: number;
36
+ iterations: number;
37
+ checks: Array<{
38
+ name: string;
39
+ pass: boolean;
40
+ detail: string;
41
+ }>;
42
+ }
43
+ export declare function airQualityGauntlet(iterations?: number): AQGauntlet;
44
+ //# sourceMappingURL=airquality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"airquality.d.ts","sourceRoot":"","sources":["../src/airquality.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;AAClF,MAAM,WAAW,SAAS;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC3E,MAAM,WAAW,UAAU;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,SAAS,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AASlG,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,UAAU,CAoD3D;AAGD,MAAM,WAAW,UAAU;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE;AAcjI,wBAAgB,kBAAkB,CAAC,UAAU,SAAU,GAAG,UAAU,CA0BnE"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * CONTEXT AIR QUALITY — one number: how clean is this codebase for an AI to work in?
3
+ *
4
+ * The honest core of the "lungs / air-pollution" idea. A deterministic weighted
5
+ * composite of the SAME measured signals the X-Ray already reports, viewed through a
6
+ * distinct lens: the conditions that make a repo HARD for an AI agent to reason about
7
+ * and change safely —
8
+ * • leaked secrets (toxic: an agent can exfiltrate or trip on them)
9
+ * • destructive commands (an agent running CI can wipe/RCE)
10
+ * • knowledge concentration (single-owner files = no second context to learn from)
11
+ * • hidden coupling (cross-module change-coupling = edits silently break things)
12
+ * • dependency rot (dead/moribund deps = the agent suggests dead APIs)
13
+ * • oversized functions (an agent loses the thread in 200-line bodies)
14
+ *
15
+ * Output: a 0-100 BREATHABILITY score (higher = cleaner), a band, and the ranked
16
+ * "pollutants". ★HONEST: this is a labelled composite of measured signals — NOT a
17
+ * hallucination forecast and NOT a re-skin of the A-F grade (the grade weights
18
+ * security/freshness for humans; this weights AI-workability). Pure + total; proven
19
+ * monotonic + in-range over 100,000 random reports.
20
+ */
21
+ const clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);
22
+ const num = (x) => (Number.isFinite(Number(x)) ? Number(x) : 0);
23
+ const arr = (x) => (Array.isArray(x) ? x : []);
24
+ /** Weights sum to 1 — the AI-workability lens (security-toxic signals dominate). */
25
+ const W = { secrets: 0.24, destructive: 0.16, ownership: 0.14, coupling: 0.2, deprot: 0.1, complexity: 0.16 };
26
+ function bandOf(score) {
27
+ return score >= 85 ? "Pristine" : score >= 70 ? "Good" : score >= 50 ? "Moderate" : score >= 30 ? "Unhealthy" : "Hazardous";
28
+ }
29
+ export function buildAirQuality(report) {
30
+ const r = (report && typeof report === "object" ? report : {});
31
+ const secrets = (r["secrets"] || {});
32
+ const security = (r["security"] || {});
33
+ const bf = (r["busFactor"] || {});
34
+ const cp = (r["coupling"] || {});
35
+ const deps = (r["deps"] || {});
36
+ const band = (deps["byBand"] || {});
37
+ const cx = (r["complexity"] || {});
38
+ const sFind = num(secrets["totalFindings"]);
39
+ const destr = arr(security["destructive"]).length;
40
+ const ownerPct = clamp(num(bf["singleOwnerFilePct"]) / 100, 0, 1);
41
+ const hidden = arr(cp["pairs"]).filter((p) => !!p?.["hidden"]).length;
42
+ const dead = num(band["dead"]), morib = num(band["moribund"]);
43
+ const huge = arr(cx["hotspots"]).filter((h) => num(h?.["bodyLines"]) >= 120).length;
44
+ // each pollutant → impact in [0,1] (1 = worst). Saturating so a few issues already hurt.
45
+ const impacts = {
46
+ secrets: clamp(sFind / 8, 0, 1),
47
+ destructive: clamp(destr / 3, 0, 1),
48
+ ownership: ownerPct,
49
+ coupling: clamp(hidden / 8, 0, 1),
50
+ deprot: clamp((dead + morib * 0.5) / 8, 0, 1),
51
+ complexity: clamp(huge / 5, 0, 1),
52
+ };
53
+ const pollution = Object.keys(W).reduce((s, k) => s + W[k] * impacts[k], 0);
54
+ const score = Math.round(clamp(100 * (1 - pollution), 0, 100));
55
+ const detail = {
56
+ secrets: `${sFind} credential pattern(s) in production code`,
57
+ destructive: `${destr} destructive command(s) in build/CI`,
58
+ ownership: `${Math.round(ownerPct * 100)}% of files are single-owner`,
59
+ coupling: `${hidden} hidden cross-module coupling link(s)`,
60
+ deprot: `${dead} dead + ${morib} moribund dependenc(ies)`,
61
+ complexity: `${huge} oversized function(s) (≥120 lines)`,
62
+ };
63
+ const label = {
64
+ secrets: "Leaked secrets", destructive: "Destructive commands", ownership: "Knowledge concentration",
65
+ coupling: "Hidden coupling", deprot: "Dependency rot", complexity: "Oversized functions",
66
+ };
67
+ const pollutants = Object.keys(W)
68
+ .map((k) => ({ name: label[k], impact: Math.round(impacts[k] * 100) / 100, detail: detail[k] }))
69
+ .filter((p) => p.impact > 0)
70
+ .sort((a, b) => b.impact - a.impact);
71
+ return {
72
+ score, band: bandOf(score), pollutants,
73
+ note: pollutants.length
74
+ ? `breathability ${score}/100 — a weighted composite of measured signals (AI-workability), not a hallucination forecast`
75
+ : `breathability ${score}/100 — clean: no measured pollutants`,
76
+ };
77
+ }
78
+ function lcg(seed) { let s = seed >>> 0; return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; }
79
+ function rr(rnd) {
80
+ const n = (k) => Math.floor(rnd() * k);
81
+ return {
82
+ secrets: { totalFindings: rnd() < 0.05 ? NaN : n(20) },
83
+ security: { destructive: Array.from({ length: n(6) }, () => ({})) },
84
+ busFactor: { singleOwnerFilePct: rnd() < 0.05 ? NaN : n(140) },
85
+ coupling: { pairs: Array.from({ length: n(30) }, () => ({ hidden: rnd() < 0.4 })) },
86
+ deps: { byBand: { dead: n(6), moribund: n(6) } },
87
+ complexity: { hotspots: Array.from({ length: n(12) }, () => ({ bodyLines: n(400) })) },
88
+ };
89
+ }
90
+ export function airQualityGauntlet(iterations = 100_000) {
91
+ const rnd = lcg(20260604);
92
+ let threw = 0, outOfRange = 0, badBand = 0, badPollutant = 0;
93
+ const BANDS = new Set(["Pristine", "Good", "Moderate", "Unhealthy", "Hazardous"]);
94
+ // a pristine report must outscore a toxic one (monotonicity)
95
+ const pristine = buildAirQuality({ secrets: { totalFindings: 0 }, security: { destructive: [] }, busFactor: { singleOwnerFilePct: 0 }, coupling: { pairs: [] }, deps: { byBand: {} }, complexity: { hotspots: [] } });
96
+ const toxic = buildAirQuality({ secrets: { totalFindings: 50 }, security: { destructive: [{}, {}, {}, {}] }, busFactor: { singleOwnerFilePct: 100 }, coupling: { pairs: Array.from({ length: 20 }, () => ({ hidden: true })) }, deps: { byBand: { dead: 9, moribund: 9 } }, complexity: { hotspots: Array.from({ length: 9 }, () => ({ bodyLines: 300 })) } });
97
+ for (let i = 0; i < iterations; i++) {
98
+ try {
99
+ const a = buildAirQuality(rr(rnd));
100
+ if (!(a.score >= 0 && a.score <= 100) || !Number.isInteger(a.score))
101
+ outOfRange++;
102
+ if (!BANDS.has(a.band))
103
+ badBand++;
104
+ for (const p of a.pollutants)
105
+ if (!(p.impact > 0 && p.impact <= 1) || !p.name || !p.detail)
106
+ badPollutant++;
107
+ }
108
+ catch {
109
+ threw++;
110
+ }
111
+ }
112
+ const det = JSON.stringify(buildAirQuality(rr(lcg(7)))) === JSON.stringify(buildAirQuality(rr(lcg(7))));
113
+ const checks = [
114
+ { name: "TOTAL", pass: threw === 0, detail: `0 throws over ${iterations.toLocaleString()} random reports (got ${threw})` },
115
+ { name: "IN-RANGE", pass: outOfRange === 0, detail: `score always an integer in [0,100] (violations ${outOfRange})` },
116
+ { name: "VALID-BAND", pass: badBand === 0, detail: `band always one of 5 (violations ${badBand})` },
117
+ { name: "POLLUTANTS-SOUND", pass: badPollutant === 0, detail: `every pollutant impact∈(0,1] + labelled (violations ${badPollutant})` },
118
+ { name: "MONOTONIC", pass: pristine.score > toxic.score && pristine.score === 100 && toxic.score < 30, detail: `pristine ${pristine.score} > toxic ${toxic.score}` },
119
+ { name: "DETERMINISTIC", pass: det, detail: "same report → byte-identical air quality" },
120
+ ];
121
+ const passed = checks.filter((c) => c.pass).length;
122
+ return { score: Math.round((passed / checks.length) * 100), iterations, checks };
123
+ }
124
+ //# sourceMappingURL=airquality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"airquality.js","sourceRoot":"","sources":["../src/airquality.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { listRemoteBranches, remoteRef, reportDelta, trackerTick, trackGauntlet,
18
18
  export { TrackerHub, trackId, hubGauntlet, verifyWebhookSig, type SseSink, type BuildFn, type RefFn, type TrackRecord, type HistoryEntry } from "./tracker_server.js";
19
19
  export { buildRiskMap, buildBlastRadius, buildDeepBlast, riskMapGauntlet, MAP_W, MAP_H, MAP_CAP, type RiskMap, type RiskNode, type RiskEdge, type BlastTarget, type BlastPartner, type DeepBlast } from "./riskmap.js";
20
20
  export { buildKeystones, buildActionPlan, buildOnboarding, buildMomentum, intelGauntlet, KEYSTONE_OWNER, type Keystone, type ActionItem, type Sev, type OnboardingStep, type Momentum, type MomentumVerdict } from "./intel.js";
21
+ export { buildAirQuality, airQualityGauntlet, type AirQuality, type AQBand, type Pollutant } from "./airquality.js";
21
22
  export { defaultFetcher, type MetaFetcher } from "./battery/deps.js";
22
23
  export { publishReport, type PublishResult } from "./publish.js";
23
24
  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,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACvN,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,GAAG,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAChO,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,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACvN,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,GAAG,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAChO,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,KAAK,UAAU,EAAE,KAAK,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACpH,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
@@ -18,6 +18,7 @@ export { listRemoteBranches, remoteRef, reportDelta, trackerTick, trackGauntlet
18
18
  export { TrackerHub, trackId, hubGauntlet, verifyWebhookSig } from "./tracker_server.js";
19
19
  export { buildRiskMap, buildBlastRadius, buildDeepBlast, riskMapGauntlet, MAP_W, MAP_H, MAP_CAP } from "./riskmap.js";
20
20
  export { buildKeystones, buildActionPlan, buildOnboarding, buildMomentum, intelGauntlet, KEYSTONE_OWNER } from "./intel.js";
21
+ export { buildAirQuality, airQualityGauntlet } from "./airquality.js";
21
22
  export { defaultFetcher } from "./battery/deps.js";
22
23
  export { publishReport } from "./publish.js";
23
24
  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,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAmG,MAAM,cAAc,CAAC;AACvN,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAsG,MAAM,YAAY,CAAC;AAChO,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,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAmG,MAAM,cAAc,CAAC;AACvN,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAsG,MAAM,YAAY,CAAC;AAChO,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAgD,MAAM,iBAAiB,CAAC;AACpH,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mneme-ai/xray",
3
- "version": "2.189.0",
3
+ "version": "2.191.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.189.0"
50
+ "@mneme-ai/core": "2.191.0"
51
51
  },
52
52
  "optionalDependencies": {
53
53
  "@resvg/resvg-js": "^2.6.2"
package/public/card.js CHANGED
@@ -235,6 +235,36 @@
235
235
  </div>`;
236
236
  }
237
237
 
238
+ // CONTEXT AIR QUALITY — mirrors packages/xray/src/airquality.ts buildAirQuality (tested + 100k).
239
+ // One breathability number: how clean is this codebase for an AI to work in.
240
+ function airQualityHTML(r) {
241
+ const num = (x) => (Number.isFinite(Number(x)) ? Number(x) : 0);
242
+ const arrn = (x) => (Array.isArray(x) ? x : []);
243
+ const W = { secrets: 0.24, destructive: 0.16, ownership: 0.14, coupling: 0.2, deprot: 0.1, complexity: 0.16 };
244
+ const cl = (v) => Math.max(0, Math.min(1, v));
245
+ const sFind = num((r.secrets || {}).totalFindings), destr = arrn((r.security || {}).destructive).length;
246
+ const ownerPct = cl(num((r.busFactor || {}).singleOwnerFilePct) / 100);
247
+ const hidden = arrn((r.coupling || {}).pairs).filter((p) => !!(p && p.hidden)).length;
248
+ const dead = num(((r.deps || {}).byBand || {}).dead), morib = num(((r.deps || {}).byBand || {}).moribund);
249
+ const huge = arrn((r.complexity || {}).hotspots).filter((h) => num(h && h.bodyLines) >= 120).length;
250
+ const imp = { secrets: cl(sFind / 8), destructive: cl(destr / 3), ownership: ownerPct, coupling: cl(hidden / 8), deprot: cl((dead + morib * 0.5) / 8), complexity: cl(huge / 5) };
251
+ const pollution = Object.keys(W).reduce((s, k) => s + W[k] * imp[k], 0);
252
+ const score = Math.round(cl(1 - pollution) * 100);
253
+ const band = score >= 85 ? "Pristine" : score >= 70 ? "Good" : score >= 50 ? "Moderate" : score >= 30 ? "Unhealthy" : "Hazardous";
254
+ const col = score >= 85 ? "#16a34a" : score >= 70 ? "#65a30d" : score >= 50 ? "#d97706" : score >= 30 ? "#ea580c" : "#e11d48";
255
+ const lbl = { secrets: "Leaked secrets", destructive: "Destructive commands", ownership: "Knowledge concentration", coupling: "Hidden coupling", deprot: "Dependency rot", complexity: "Oversized functions" };
256
+ const det = { secrets: `${sFind} secret pattern(s)`, destructive: `${destr} destructive cmd(s)`, ownership: `${Math.round(ownerPct * 100)}% single-owner`, coupling: `${hidden} hidden link(s)`, deprot: `${dead} dead + ${morib} moribund`, complexity: `${huge} oversized fn(s)` };
257
+ const polls = Object.keys(W).map((k) => ({ k, impact: imp[k] })).filter((p) => p.impact > 0).sort((a, b) => b.impact - a.impact);
258
+ return `<div class="aq">
259
+ <div class="aqgauge"><div class="aqring" style="background:conic-gradient(${col} ${score * 3.6}deg,#eef0f2 0)"><div class="aqnum" style="color:${col}">${score}</div></div></div>
260
+ <div class="aqbody">
261
+ <div class="aqhead">🫁 Context Air Quality — <b style="color:${col}">${band}</b> <span class="aqof">/100</span></div>
262
+ <div class="aqsub">How clean this codebase is for an <b>AI to work in</b> — a weighted composite of measured signals. <i>Not a hallucination forecast.</i></div>
263
+ ${polls.length ? `<div class="aqpolls">${polls.slice(0, 4).map((p) => `<span class="aqpill" title="${esc(det[p.k])}"><i style="background:${p.impact >= 0.66 ? "#e11d48" : p.impact >= 0.33 ? "#d97706" : "#eab308"}"></i>${esc(lbl[p.k])}</span>`).join("")}</div>` : `<div class="aqclean">✓ no measured pollutants — clean air</div>`}
264
+ </div>
265
+ </div>`;
266
+ }
267
+
238
268
  function xrayCardHTML(signed, opts) {
239
269
  opts = opts || {};
240
270
  g.__lastSigned = signed; // stash for the "Verify signature" proof button
@@ -278,6 +308,7 @@
278
308
  ${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>
279
309
  <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>` : ""}
280
310
  </div>
311
+ ${airQualityHTML(r)}
281
312
  ${momentumHTML(r)}
282
313
  ${keystoneHTML(r)}
283
314
  ${riskMapHTML(r)}
package/public/index.html CHANGED
@@ -168,6 +168,17 @@
168
168
  .momentum .mosub{font-size:11.5px;color:#8b8f98;margin-top:2px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
169
169
  .bldeep{margin-top:10px;padding-top:10px;border-top:1px solid #f3f3f5;font-size:12px;color:#4b5563;line-height:1.5}
170
170
  .bldeep code{font-family:ui-monospace,Menlo,monospace;background:rgba(0,0,0,.05);padding:1px 6px;border-radius:6px;color:#0b0b0f}
171
+ /* ── context air quality gauge ── */
172
+ .aq{display:flex;gap:18px;align-items:center;padding:18px 30px;border-bottom:1px solid #eef0f2;background:linear-gradient(180deg,#fcfdfe,#fff)}
173
+ .aqgauge{flex-shrink:0}
174
+ .aqring{width:76px;height:76px;border-radius:50%;display:grid;place-items:center}
175
+ .aqnum{width:58px;height:58px;border-radius:50%;background:#fff;display:grid;place-items:center;font-size:23px;font-weight:760;font-variant-numeric:tabular-nums}
176
+ .aqhead{font-size:15px;color:#0b0b0f}.aqof{color:#aeb2ba;font-size:12px;font-weight:400}
177
+ .aqsub{font-size:11.5px;color:#8b8f98;margin:3px 0 9px;line-height:1.5}.aqsub i{color:#aeb2ba}
178
+ .aqpolls{display:flex;flex-wrap:wrap;gap:7px}
179
+ .aqpill{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;background:#f6f7f9;border-radius:20px;padding:3px 10px;color:#33333b}
180
+ .aqpill i{width:8px;height:8px;border-radius:50%;display:inline-block}
181
+ .aqclean{font-size:12.5px;color:#15803d;font-weight:560}
171
182
  .trustbar{display:flex;align-items:center;gap:14px;padding:13px 30px;background:#f0fdf4;border-bottom:1px solid #dcfce7;flex-wrap:wrap}
172
183
  .hgauge{display:inline-flex;align-items:center;gap:8px;font-weight:680;color:#15803d;font-size:14px;white-space:nowrap}
173
184
  .hdot{width:10px;height:10px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 4px rgba(22,163,74,.16)}
@@ -121,6 +121,17 @@
121
121
  .momentum .mosub{font-size:11.5px;color:#8b8f98;margin-top:2px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
122
122
  .bldeep{margin-top:10px;padding-top:10px;border-top:1px solid #f3f3f5;font-size:12px;color:#4b5563;line-height:1.5}
123
123
  .bldeep code{font-family:ui-monospace,Menlo,monospace;background:rgba(0,0,0,.05);padding:1px 6px;border-radius:6px;color:#0b0b0f}
124
+ /* ── context air quality gauge ── */
125
+ .aq{display:flex;gap:18px;align-items:center;padding:18px 30px;border-bottom:1px solid #eef0f2;background:linear-gradient(180deg,#fcfdfe,#fff)}
126
+ .aqgauge{flex-shrink:0}
127
+ .aqring{width:76px;height:76px;border-radius:50%;display:grid;place-items:center}
128
+ .aqnum{width:58px;height:58px;border-radius:50%;background:#fff;display:grid;place-items:center;font-size:23px;font-weight:760;font-variant-numeric:tabular-nums}
129
+ .aqhead{font-size:15px;color:#0b0b0f}.aqof{color:#aeb2ba;font-size:12px;font-weight:400}
130
+ .aqsub{font-size:11.5px;color:#8b8f98;margin:3px 0 9px;line-height:1.5}.aqsub i{color:#aeb2ba}
131
+ .aqpolls{display:flex;flex-wrap:wrap;gap:7px}
132
+ .aqpill{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;background:#f6f7f9;border-radius:20px;padding:3px 10px;color:#33333b}
133
+ .aqpill i{width:8px;height:8px;border-radius:50%;display:inline-block}
134
+ .aqclean{font-size:12.5px;color:#15803d;font-weight:560}
124
135
  .trustbar{display:flex;align-items:center;gap:14px;padding:13px 30px;background:#f0fdf4;border-bottom:1px solid #dcfce7;flex-wrap:wrap}
125
136
  .hgauge{display:inline-flex;align-items:center;gap:8px;font-weight:680;color:#15803d;font-size:14px}
126
137
  .hdot{width:10px;height:10px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 4px rgba(22,163,74,.16)}