@mneme-ai/core 0.9.0 → 0.10.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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/insights/bus-factor.d.ts +58 -0
- package/dist/insights/bus-factor.d.ts.map +1 -0
- package/dist/insights/bus-factor.js +117 -0
- package/dist/insights/bus-factor.js.map +1 -0
- package/dist/insights/bus-factor.test.d.ts +2 -0
- package/dist/insights/bus-factor.test.d.ts.map +1 -0
- package/dist/insights/bus-factor.test.js +149 -0
- package/dist/insights/bus-factor.test.js.map +1 -0
- package/dist/insights/commit-coach.d.ts +80 -0
- package/dist/insights/commit-coach.d.ts.map +1 -0
- package/dist/insights/commit-coach.js +230 -0
- package/dist/insights/commit-coach.js.map +1 -0
- package/dist/insights/commit-coach.test.d.ts +2 -0
- package/dist/insights/commit-coach.test.d.ts.map +1 -0
- package/dist/insights/commit-coach.test.js +163 -0
- package/dist/insights/commit-coach.test.js.map +1 -0
- package/dist/insights/crystal-ball.d.ts +76 -0
- package/dist/insights/crystal-ball.d.ts.map +1 -0
- package/dist/insights/crystal-ball.js +219 -0
- package/dist/insights/crystal-ball.js.map +1 -0
- package/dist/insights/crystal-ball.test.d.ts +2 -0
- package/dist/insights/crystal-ball.test.d.ts.map +1 -0
- package/dist/insights/crystal-ball.test.js +157 -0
- package/dist/insights/crystal-ball.test.js.map +1 -0
- package/dist/insights/index.d.ts +5 -0
- package/dist/insights/index.d.ts.map +1 -1
- package/dist/insights/index.js +5 -0
- package/dist/insights/index.js.map +1 -1
- package/dist/insights/paradox.d.ts +36 -0
- package/dist/insights/paradox.d.ts.map +1 -0
- package/dist/insights/paradox.js +201 -0
- package/dist/insights/paradox.js.map +1 -0
- package/dist/insights/paradox.test.d.ts +2 -0
- package/dist/insights/paradox.test.d.ts.map +1 -0
- package/dist/insights/paradox.test.js +88 -0
- package/dist/insights/paradox.test.js.map +1 -0
- package/dist/insights/regret.d.ts +57 -0
- package/dist/insights/regret.d.ts.map +1 -0
- package/dist/insights/regret.js +137 -0
- package/dist/insights/regret.js.map +1 -0
- package/dist/insights/regret.test.d.ts +2 -0
- package/dist/insights/regret.test.d.ts.map +1 -0
- package/dist/insights/regret.test.js +153 -0
- package/dist/insights/regret.test.js.map +1 -0
- package/dist/insights/who-knows.d.ts +18 -0
- package/dist/insights/who-knows.d.ts.map +1 -1
- package/dist/insights/who-knows.js +29 -0
- package/dist/insights/who-knows.js.map +1 -1
- package/dist/insights/who-knows.test.js +63 -1
- package/dist/insights/who-knows.test.js.map +1 -1
- package/dist/quant/alpha.d.ts +87 -0
- package/dist/quant/alpha.d.ts.map +1 -0
- package/dist/quant/alpha.js +103 -0
- package/dist/quant/alpha.js.map +1 -0
- package/dist/quant/alpha.test.d.ts +2 -0
- package/dist/quant/alpha.test.d.ts.map +1 -0
- package/dist/quant/alpha.test.js +147 -0
- package/dist/quant/alpha.test.js.map +1 -0
- package/dist/quant/backtest.d.ts +57 -0
- package/dist/quant/backtest.d.ts.map +1 -0
- package/dist/quant/backtest.js +90 -0
- package/dist/quant/backtest.js.map +1 -0
- package/dist/quant/backtest.test.d.ts +2 -0
- package/dist/quant/backtest.test.d.ts.map +1 -0
- package/dist/quant/backtest.test.js +133 -0
- package/dist/quant/backtest.test.js.map +1 -0
- package/dist/quant/black-swan.d.ts +45 -0
- package/dist/quant/black-swan.d.ts.map +1 -0
- package/dist/quant/black-swan.js +112 -0
- package/dist/quant/black-swan.js.map +1 -0
- package/dist/quant/black-swan.test.d.ts +2 -0
- package/dist/quant/black-swan.test.d.ts.map +1 -0
- package/dist/quant/black-swan.test.js +131 -0
- package/dist/quant/black-swan.test.js.map +1 -0
- package/dist/quant/correlation-matrix.d.ts +54 -0
- package/dist/quant/correlation-matrix.d.ts.map +1 -0
- package/dist/quant/correlation-matrix.js +103 -0
- package/dist/quant/correlation-matrix.js.map +1 -0
- package/dist/quant/correlation-matrix.test.d.ts +2 -0
- package/dist/quant/correlation-matrix.test.d.ts.map +1 -0
- package/dist/quant/correlation-matrix.test.js +118 -0
- package/dist/quant/correlation-matrix.test.js.map +1 -0
- package/dist/quant/drawdown.d.ts +51 -0
- package/dist/quant/drawdown.d.ts.map +1 -0
- package/dist/quant/drawdown.js +96 -0
- package/dist/quant/drawdown.js.map +1 -0
- package/dist/quant/drawdown.test.d.ts +2 -0
- package/dist/quant/drawdown.test.d.ts.map +1 -0
- package/dist/quant/drawdown.test.js +166 -0
- package/dist/quant/drawdown.test.js.map +1 -0
- package/dist/quant/greek.d.ts +55 -0
- package/dist/quant/greek.d.ts.map +1 -0
- package/dist/quant/greek.js +157 -0
- package/dist/quant/greek.js.map +1 -0
- package/dist/quant/greek.test.d.ts +2 -0
- package/dist/quant/greek.test.d.ts.map +1 -0
- package/dist/quant/greek.test.js +138 -0
- package/dist/quant/greek.test.js.map +1 -0
- package/dist/quant/implied-volatility.d.ts +65 -0
- package/dist/quant/implied-volatility.d.ts.map +1 -0
- package/dist/quant/implied-volatility.js +149 -0
- package/dist/quant/implied-volatility.js.map +1 -0
- package/dist/quant/implied-volatility.test.d.ts +2 -0
- package/dist/quant/implied-volatility.test.d.ts.map +1 -0
- package/dist/quant/implied-volatility.test.js +127 -0
- package/dist/quant/implied-volatility.test.js.map +1 -0
- package/dist/quant/index.d.ts +28 -0
- package/dist/quant/index.d.ts.map +1 -0
- package/dist/quant/index.js +28 -0
- package/dist/quant/index.js.map +1 -0
- package/dist/quant/insider-trading.d.ts +56 -0
- package/dist/quant/insider-trading.d.ts.map +1 -0
- package/dist/quant/insider-trading.js +129 -0
- package/dist/quant/insider-trading.js.map +1 -0
- package/dist/quant/insider-trading.test.d.ts +2 -0
- package/dist/quant/insider-trading.test.d.ts.map +1 -0
- package/dist/quant/insider-trading.test.js +130 -0
- package/dist/quant/insider-trading.test.js.map +1 -0
- package/dist/quant/moneyball.d.ts +48 -0
- package/dist/quant/moneyball.d.ts.map +1 -0
- package/dist/quant/moneyball.js +110 -0
- package/dist/quant/moneyball.js.map +1 -0
- package/dist/quant/moneyball.test.d.ts +2 -0
- package/dist/quant/moneyball.test.d.ts.map +1 -0
- package/dist/quant/moneyball.test.js +137 -0
- package/dist/quant/moneyball.test.js.map +1 -0
- package/dist/quant/tax-loss-harvest.d.ts +59 -0
- package/dist/quant/tax-loss-harvest.d.ts.map +1 -0
- package/dist/quant/tax-loss-harvest.js +126 -0
- package/dist/quant/tax-loss-harvest.js.map +1 -0
- package/dist/quant/tax-loss-harvest.test.d.ts +2 -0
- package/dist/quant/tax-loss-harvest.test.d.ts.map +1 -0
- package/dist/quant/tax-loss-harvest.test.js +126 -0
- package/dist/quant/tax-loss-harvest.test.js.map +1 -0
- package/dist/retrieve/synthesize.d.ts.map +1 -1
- package/dist/retrieve/synthesize.js +56 -25
- package/dist/retrieve/synthesize.js.map +1 -1
- package/dist/retrieve/synthesize.test.js +26 -15
- package/dist/retrieve/synthesize.test.js.map +1 -1
- package/dist/store/schema.d.ts +2 -2
- package/dist/store/schema.d.ts.map +1 -1
- package/dist/store/schema.js +6 -2
- package/dist/store/schema.js.map +1 -1
- package/dist/store/sqlite.d.ts +2 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +24 -0
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/sqlite.test.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { detectInsiderTrading, classifyInsiderTier } from "./insider-trading.js";
|
|
3
|
+
const cmt = (hash, author, date, subject, files) => ({
|
|
4
|
+
hash,
|
|
5
|
+
shortHash: hash.slice(0, 7),
|
|
6
|
+
authorName: author,
|
|
7
|
+
authorEmail: `${author}@x`,
|
|
8
|
+
authorDate: `${date}T00:00:00Z`,
|
|
9
|
+
committerDate: `${date}T00:00:00Z`,
|
|
10
|
+
subject,
|
|
11
|
+
body: "",
|
|
12
|
+
parents: [],
|
|
13
|
+
files,
|
|
14
|
+
});
|
|
15
|
+
describe("classifyInsiderTier", () => {
|
|
16
|
+
it("high-pattern at 5+", () => expect(classifyInsiderTier(5)).toBe("high-pattern"));
|
|
17
|
+
it("elevated at 3-4", () => expect(classifyInsiderTier(3)).toBe("elevated"));
|
|
18
|
+
it("watch at 2", () => expect(classifyInsiderTier(2)).toBe("watch"));
|
|
19
|
+
it("low at <2", () => expect(classifyInsiderTier(1)).toBe("low"));
|
|
20
|
+
});
|
|
21
|
+
describe("detectInsiderTrading — same author + same files + fix follow-up", () => {
|
|
22
|
+
it("flags a single insider pattern (alice ships then alice fixes)", () => {
|
|
23
|
+
const commits = [
|
|
24
|
+
cmt("a1", "alice", "2024-08-01", "feat(stripe): add webhook", ["src/stripe.ts"]),
|
|
25
|
+
cmt("a2", "alice", "2024-08-03", "fix: stripe webhook crashed", ["src/stripe.ts"]),
|
|
26
|
+
];
|
|
27
|
+
const profiles = detectInsiderTrading(commits, { minPatterns: 1 });
|
|
28
|
+
expect(profiles).toHaveLength(1);
|
|
29
|
+
expect(profiles[0].authorName).toBe("alice");
|
|
30
|
+
expect(profiles[0].patternCount).toBe(1);
|
|
31
|
+
});
|
|
32
|
+
it("does NOT flag when author is different", () => {
|
|
33
|
+
const commits = [
|
|
34
|
+
cmt("a1", "alice", "2024-08-01", "feat(stripe): add webhook", ["src/stripe.ts"]),
|
|
35
|
+
cmt("b1", "bob", "2024-08-03", "fix: stripe webhook crashed", ["src/stripe.ts"]),
|
|
36
|
+
];
|
|
37
|
+
expect(detectInsiderTrading(commits, { minPatterns: 1 })).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
it("does NOT flag when files do not overlap", () => {
|
|
40
|
+
const commits = [
|
|
41
|
+
cmt("a1", "alice", "2024-08-01", "feat: stripe", ["src/stripe.ts"]),
|
|
42
|
+
cmt("a2", "alice", "2024-08-03", "fix: orders broken", ["src/orders.ts"]),
|
|
43
|
+
];
|
|
44
|
+
expect(detectInsiderTrading(commits, { minPatterns: 1 })).toEqual([]);
|
|
45
|
+
});
|
|
46
|
+
it("respects windowDays", () => {
|
|
47
|
+
const commits = [
|
|
48
|
+
cmt("a1", "alice", "2024-08-01", "feat: stripe", ["src/x.ts"]),
|
|
49
|
+
cmt("a2", "alice", "2024-09-15", "fix: stripe broke", ["src/x.ts"]), // 45 days later
|
|
50
|
+
];
|
|
51
|
+
expect(detectInsiderTrading(commits, { windowDays: 14, minPatterns: 1 })).toEqual([]);
|
|
52
|
+
expect(detectInsiderTrading(commits, { windowDays: 60, minPatterns: 1 })).toHaveLength(1);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("detectInsiderTrading — minPatterns threshold and aggregation", () => {
|
|
56
|
+
it("requires ≥ minPatterns to flag (default 2)", () => {
|
|
57
|
+
const commits = [
|
|
58
|
+
cmt("a1", "alice", "2024-08-01", "feat: A", ["src/x.ts"]),
|
|
59
|
+
cmt("a2", "alice", "2024-08-02", "fix: A", ["src/x.ts"]),
|
|
60
|
+
];
|
|
61
|
+
expect(detectInsiderTrading(commits)).toEqual([]); // only 1 pattern, default min = 2
|
|
62
|
+
});
|
|
63
|
+
it("aggregates all patterns by author", () => {
|
|
64
|
+
const commits = [
|
|
65
|
+
cmt("a1", "alice", "2024-08-01", "feat: A", ["src/x.ts"]),
|
|
66
|
+
cmt("a2", "alice", "2024-08-02", "fix: A", ["src/x.ts"]),
|
|
67
|
+
cmt("a3", "alice", "2024-08-10", "feat: B", ["src/y.ts"]),
|
|
68
|
+
cmt("a4", "alice", "2024-08-11", "fix: B", ["src/y.ts"]),
|
|
69
|
+
cmt("a5", "alice", "2024-08-20", "feat: C", ["src/z.ts"]),
|
|
70
|
+
cmt("a6", "alice", "2024-08-22", "fix: C broke", ["src/z.ts"]),
|
|
71
|
+
];
|
|
72
|
+
const profiles = detectInsiderTrading(commits, { minPatterns: 2 });
|
|
73
|
+
expect(profiles).toHaveLength(1);
|
|
74
|
+
expect(profiles[0].patternCount).toBe(3);
|
|
75
|
+
expect(profiles[0].affectedFiles.sort()).toEqual(["src/x.ts", "src/y.ts", "src/z.ts"]);
|
|
76
|
+
});
|
|
77
|
+
it("samples are capped at 3", () => {
|
|
78
|
+
const commits = [];
|
|
79
|
+
for (let i = 0; i < 10; i++) {
|
|
80
|
+
commits.push(cmt(`f${i}`, "alice", `2024-08-${String((i * 2) + 1).padStart(2, "0")}`, `feat: ${i}`, [`f${i}.ts`]));
|
|
81
|
+
commits.push(cmt(`x${i}`, "alice", `2024-08-${String((i * 2) + 2).padStart(2, "0")}`, `fix: ${i}`, [`f${i}.ts`]));
|
|
82
|
+
}
|
|
83
|
+
const profiles = detectInsiderTrading(commits, { minPatterns: 2 });
|
|
84
|
+
expect(profiles[0].samples).toHaveLength(3);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("detectInsiderTrading — pairing suggestion", () => {
|
|
88
|
+
it("suggests another author who has touched the same files", () => {
|
|
89
|
+
const commits = [
|
|
90
|
+
cmt("a1", "alice", "2024-08-01", "feat: A", ["src/x.ts"]),
|
|
91
|
+
cmt("a2", "alice", "2024-08-02", "fix: A", ["src/x.ts"]),
|
|
92
|
+
cmt("a3", "alice", "2024-08-10", "feat: B", ["src/x.ts"]),
|
|
93
|
+
cmt("a4", "alice", "2024-08-11", "fix: B", ["src/x.ts"]),
|
|
94
|
+
cmt("b1", "bob", "2024-09-01", "refactor", ["src/x.ts"]),
|
|
95
|
+
cmt("b2", "bob", "2024-09-05", "refactor 2", ["src/x.ts"]),
|
|
96
|
+
];
|
|
97
|
+
const profile = detectInsiderTrading(commits, { minPatterns: 2 })[0];
|
|
98
|
+
expect(profile?.pairSuggestion).toBe("bob");
|
|
99
|
+
});
|
|
100
|
+
it("returns undefined pair when nobody else has touched the files", () => {
|
|
101
|
+
const commits = [
|
|
102
|
+
cmt("a1", "alice", "2024-08-01", "feat: A", ["src/solo.ts"]),
|
|
103
|
+
cmt("a2", "alice", "2024-08-02", "fix: A", ["src/solo.ts"]),
|
|
104
|
+
cmt("a3", "alice", "2024-08-10", "feat: B", ["src/solo.ts"]),
|
|
105
|
+
cmt("a4", "alice", "2024-08-11", "fix: B", ["src/solo.ts"]),
|
|
106
|
+
];
|
|
107
|
+
const profile = detectInsiderTrading(commits, { minPatterns: 2 })[0];
|
|
108
|
+
expect(profile?.pairSuggestion).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe("detectInsiderTrading — sort order", () => {
|
|
112
|
+
it("sorts profiles by patternCount desc", () => {
|
|
113
|
+
const commits = [];
|
|
114
|
+
// alice — 3 patterns
|
|
115
|
+
for (let i = 0; i < 3; i++) {
|
|
116
|
+
commits.push(cmt(`a${i}`, "alice", `2024-0${i + 1}-01`, `feat: ${i}`, [`a${i}.ts`]));
|
|
117
|
+
commits.push(cmt(`af${i}`, "alice", `2024-0${i + 1}-03`, `fix: ${i}`, [`a${i}.ts`]));
|
|
118
|
+
}
|
|
119
|
+
// bob — 5 patterns
|
|
120
|
+
for (let i = 0; i < 5; i++) {
|
|
121
|
+
commits.push(cmt(`b${i}`, "bob", `2024-0${i + 1}-15`, `feat: ${i}`, [`b${i}.ts`]));
|
|
122
|
+
commits.push(cmt(`bf${i}`, "bob", `2024-0${i + 1}-17`, `fix: ${i}`, [`b${i}.ts`]));
|
|
123
|
+
}
|
|
124
|
+
const profiles = detectInsiderTrading(commits, { minPatterns: 2 });
|
|
125
|
+
expect(profiles[0].authorName).toBe("bob");
|
|
126
|
+
expect(profiles[0].patternCount).toBe(5);
|
|
127
|
+
expect(profiles[1].authorName).toBe("alice");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
//# sourceMappingURL=insider-trading.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insider-trading.test.js","sourceRoot":"","sources":["../../src/quant/insider-trading.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme moneyball` — find undervalued contributors.
|
|
3
|
+
*
|
|
4
|
+
* The Billy Beane angle on engineering: in baseball, big-volume hitters
|
|
5
|
+
* get the contracts but on-base-percentage wins games. In codebases:
|
|
6
|
+
* loud LOC-per-day contributors get promoted, but the people whose
|
|
7
|
+
* commits *unblock everyone else* are the real value.
|
|
8
|
+
*
|
|
9
|
+
* Metric: each commit is "valuable" by how many SUBSEQUENT commits touch
|
|
10
|
+
* the same files. People whose commits frequently appear upstream of
|
|
11
|
+
* many others' work are unblocking. People with high LOC volume but few
|
|
12
|
+
* downstream touches are noise generators.
|
|
13
|
+
*
|
|
14
|
+
* value_score = log(downstream_commits + 1) × distinct_collaborators
|
|
15
|
+
* over_undervalued = value_score / commit_count (per-commit ROI)
|
|
16
|
+
*
|
|
17
|
+
* Pure analysis. No LLM.
|
|
18
|
+
*/
|
|
19
|
+
import type { Commit } from "../types.js";
|
|
20
|
+
export interface ContributorScore {
|
|
21
|
+
authorName: string;
|
|
22
|
+
authorEmail: string;
|
|
23
|
+
/** Total commits authored. */
|
|
24
|
+
commitCount: number;
|
|
25
|
+
/** Total downstream commits — sum of how many later commits touched the same files. */
|
|
26
|
+
downstreamReach: number;
|
|
27
|
+
/** Number of DISTINCT other authors whose work followed theirs on the same files. */
|
|
28
|
+
collaborators: number;
|
|
29
|
+
/** Aggregate value score (see formula in module doc). */
|
|
30
|
+
valueScore: number;
|
|
31
|
+
/** Per-commit ROI = valueScore / commitCount — the moneyball metric. */
|
|
32
|
+
perCommitROI: number;
|
|
33
|
+
/** Tier label. */
|
|
34
|
+
tier: "moneyball" | "balanced" | "loud" | "passive";
|
|
35
|
+
/** A 1-line interpretation. */
|
|
36
|
+
interpretation: string;
|
|
37
|
+
}
|
|
38
|
+
export interface MoneyballOptions {
|
|
39
|
+
/** Maximum days to count "downstream" relationships. Default 90. */
|
|
40
|
+
downstreamWindowDays?: number;
|
|
41
|
+
/** Minimum commits for a contributor to be ranked. Default 2. */
|
|
42
|
+
minCommits?: number;
|
|
43
|
+
/** Top-N to return. */
|
|
44
|
+
topN?: number;
|
|
45
|
+
}
|
|
46
|
+
export declare function moneyball(commits: Commit[], opts?: MoneyballOptions): ContributorScore[];
|
|
47
|
+
export declare function classifyMoneyballTier(commitCount: number, valueScore: number, perCommitROI: number): ContributorScore["tier"];
|
|
48
|
+
//# sourceMappingURL=moneyball.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moneyball.d.ts","sourceRoot":"","sources":["../../src/quant/moneyball.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,uFAAuF;IACvF,eAAe,EAAE,MAAM,CAAC;IACxB,qFAAqF;IACrF,aAAa,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB;IAClB,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IACpD,+BAA+B;IAC/B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,gBAAqB,GAAG,gBAAgB,EAAE,CA2E5F;AAED,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GACnB,gBAAgB,CAAC,MAAM,CAAC,CAS1B"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme moneyball` — find undervalued contributors.
|
|
3
|
+
*
|
|
4
|
+
* The Billy Beane angle on engineering: in baseball, big-volume hitters
|
|
5
|
+
* get the contracts but on-base-percentage wins games. In codebases:
|
|
6
|
+
* loud LOC-per-day contributors get promoted, but the people whose
|
|
7
|
+
* commits *unblock everyone else* are the real value.
|
|
8
|
+
*
|
|
9
|
+
* Metric: each commit is "valuable" by how many SUBSEQUENT commits touch
|
|
10
|
+
* the same files. People whose commits frequently appear upstream of
|
|
11
|
+
* many others' work are unblocking. People with high LOC volume but few
|
|
12
|
+
* downstream touches are noise generators.
|
|
13
|
+
*
|
|
14
|
+
* value_score = log(downstream_commits + 1) × distinct_collaborators
|
|
15
|
+
* over_undervalued = value_score / commit_count (per-commit ROI)
|
|
16
|
+
*
|
|
17
|
+
* Pure analysis. No LLM.
|
|
18
|
+
*/
|
|
19
|
+
export function moneyball(commits, opts = {}) {
|
|
20
|
+
const windowMs = (opts.downstreamWindowDays ?? 90) * 86_400_000;
|
|
21
|
+
const minCommits = opts.minCommits ?? 2;
|
|
22
|
+
const topN = opts.topN ?? 20;
|
|
23
|
+
const sorted = [...commits].sort((a, b) => a.authorDate.localeCompare(b.authorDate));
|
|
24
|
+
const byAuthor = new Map();
|
|
25
|
+
for (const c of sorted) {
|
|
26
|
+
const key = `${c.authorName}|${c.authorEmail}`;
|
|
27
|
+
if (!byAuthor.has(key))
|
|
28
|
+
byAuthor.set(key, {
|
|
29
|
+
authorName: c.authorName,
|
|
30
|
+
authorEmail: c.authorEmail,
|
|
31
|
+
commits: [],
|
|
32
|
+
downstream: 0,
|
|
33
|
+
collaboratorSet: new Set(),
|
|
34
|
+
});
|
|
35
|
+
byAuthor.get(key).commits.push(c);
|
|
36
|
+
}
|
|
37
|
+
// Step 2: for each commit, count downstream commits within window.
|
|
38
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
39
|
+
const upstream = sorted[i];
|
|
40
|
+
if (!upstream.files?.length)
|
|
41
|
+
continue;
|
|
42
|
+
const upstreamFiles = new Set(upstream.files);
|
|
43
|
+
const upstreamTime = new Date(upstream.authorDate).getTime();
|
|
44
|
+
const upstreamAuthor = `${upstream.authorName}|${upstream.authorEmail}`;
|
|
45
|
+
const bucket = byAuthor.get(upstreamAuthor);
|
|
46
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
47
|
+
const later = sorted[j];
|
|
48
|
+
const dt = new Date(later.authorDate).getTime() - upstreamTime;
|
|
49
|
+
if (dt > windowMs)
|
|
50
|
+
break;
|
|
51
|
+
if (!later.files?.some((f) => upstreamFiles.has(f)))
|
|
52
|
+
continue;
|
|
53
|
+
// Count this as a downstream touch on the upstream commit.
|
|
54
|
+
bucket.downstream += 1;
|
|
55
|
+
const laterAuthor = `${later.authorName}|${later.authorEmail}`;
|
|
56
|
+
if (laterAuthor !== upstreamAuthor) {
|
|
57
|
+
bucket.collaboratorSet.add(later.authorName);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Step 3: compute scores + tiers.
|
|
62
|
+
const scores = [];
|
|
63
|
+
for (const b of byAuthor.values()) {
|
|
64
|
+
if (b.commits.length < minCommits)
|
|
65
|
+
continue;
|
|
66
|
+
const valueScore = Math.log2(b.downstream + 1) * b.collaboratorSet.size;
|
|
67
|
+
const perCommitROI = b.commits.length === 0 ? 0 : valueScore / b.commits.length;
|
|
68
|
+
const tier = classifyMoneyballTier(b.commits.length, valueScore, perCommitROI);
|
|
69
|
+
scores.push({
|
|
70
|
+
authorName: b.authorName,
|
|
71
|
+
authorEmail: b.authorEmail,
|
|
72
|
+
commitCount: b.commits.length,
|
|
73
|
+
downstreamReach: b.downstream,
|
|
74
|
+
collaborators: b.collaboratorSet.size,
|
|
75
|
+
valueScore,
|
|
76
|
+
perCommitROI,
|
|
77
|
+
tier,
|
|
78
|
+
interpretation: buildInterpretation(tier, b.commits.length, b.downstream, b.collaboratorSet.size),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Sort by perCommitROI desc — undervalued contributors rise.
|
|
82
|
+
scores.sort((a, b) => b.perCommitROI - a.perCommitROI);
|
|
83
|
+
return scores.slice(0, topN);
|
|
84
|
+
}
|
|
85
|
+
export function classifyMoneyballTier(commitCount, valueScore, perCommitROI) {
|
|
86
|
+
// Moneyball: high ROI per commit AND modest commit count (low volume, high impact).
|
|
87
|
+
if (perCommitROI >= 1.5 && commitCount < 30)
|
|
88
|
+
return "moneyball";
|
|
89
|
+
// Balanced: high overall value but proportional commit count.
|
|
90
|
+
if (valueScore >= 5 && perCommitROI >= 0.5)
|
|
91
|
+
return "balanced";
|
|
92
|
+
// Loud: lots of commits, low ROI per commit (noise).
|
|
93
|
+
if (commitCount >= 30 && perCommitROI < 0.3)
|
|
94
|
+
return "loud";
|
|
95
|
+
// Passive: few commits, low downstream activity.
|
|
96
|
+
return "passive";
|
|
97
|
+
}
|
|
98
|
+
function buildInterpretation(tier, commitCount, downstream, collaborators) {
|
|
99
|
+
switch (tier) {
|
|
100
|
+
case "moneyball":
|
|
101
|
+
return `Undervalued — ${commitCount} commits unblocked ${collaborators} other contributors (${downstream} downstream touches). High ROI per commit.`;
|
|
102
|
+
case "balanced":
|
|
103
|
+
return `Solid contributor — ${commitCount} commits with ${downstream} downstream effects across ${collaborators} collaborators.`;
|
|
104
|
+
case "loud":
|
|
105
|
+
return `High volume, low impact — ${commitCount} commits but only ${downstream} downstream touches. Noise > signal.`;
|
|
106
|
+
case "passive":
|
|
107
|
+
return `Limited footprint — ${commitCount} commits, ${downstream} downstream effects. Either too new or working in isolation.`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=moneyball.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moneyball.js","sourceRoot":"","sources":["../../src/quant/moneyball.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moneyball.test.d.ts","sourceRoot":"","sources":["../../src/quant/moneyball.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { moneyball, classifyMoneyballTier } from "./moneyball.js";
|
|
3
|
+
const cmt = (hash, author, date, files) => ({
|
|
4
|
+
hash,
|
|
5
|
+
shortHash: hash.slice(0, 7),
|
|
6
|
+
authorName: author,
|
|
7
|
+
authorEmail: `${author}@x`,
|
|
8
|
+
authorDate: `${date}T00:00:00Z`,
|
|
9
|
+
committerDate: `${date}T00:00:00Z`,
|
|
10
|
+
subject: "commit",
|
|
11
|
+
body: "",
|
|
12
|
+
parents: [],
|
|
13
|
+
files,
|
|
14
|
+
});
|
|
15
|
+
describe("classifyMoneyballTier", () => {
|
|
16
|
+
it("'moneyball' when perCommitROI ≥ 1.5 + commit count < 30", () => {
|
|
17
|
+
expect(classifyMoneyballTier(10, 20, 2)).toBe("moneyball");
|
|
18
|
+
});
|
|
19
|
+
it("'balanced' when value ≥ 5 + ROI ≥ 0.5", () => {
|
|
20
|
+
expect(classifyMoneyballTier(20, 10, 0.5)).toBe("balanced");
|
|
21
|
+
});
|
|
22
|
+
it("'loud' when ≥30 commits and ROI < 0.3", () => {
|
|
23
|
+
expect(classifyMoneyballTier(50, 10, 0.2)).toBe("loud");
|
|
24
|
+
});
|
|
25
|
+
it("'passive' when nothing else applies", () => {
|
|
26
|
+
expect(classifyMoneyballTier(5, 1, 0.2)).toBe("passive");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("moneyball — basic invariants", () => {
|
|
30
|
+
it("returns empty for empty input", () => {
|
|
31
|
+
expect(moneyball([])).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
it("respects minCommits filter (default 2)", () => {
|
|
34
|
+
const commits = [cmt("a1", "alice", "2024-08-01", ["x.ts"])];
|
|
35
|
+
expect(moneyball(commits)).toEqual([]); // only 1 commit
|
|
36
|
+
expect(moneyball(commits, { minCommits: 1 })).toHaveLength(1);
|
|
37
|
+
});
|
|
38
|
+
it("downstream reach counts later commits touching same files", () => {
|
|
39
|
+
const commits = [
|
|
40
|
+
cmt("a1", "alice", "2024-08-01", ["src/x.ts"]),
|
|
41
|
+
cmt("a2", "alice", "2024-08-02", ["src/x.ts"]),
|
|
42
|
+
cmt("b1", "bob", "2024-08-03", ["src/x.ts"]),
|
|
43
|
+
cmt("c1", "carol", "2024-08-04", ["src/x.ts"]),
|
|
44
|
+
];
|
|
45
|
+
const scores = moneyball(commits, { minCommits: 1 });
|
|
46
|
+
const alice = scores.find((s) => s.authorName === "alice");
|
|
47
|
+
expect(alice.downstreamReach).toBeGreaterThan(0);
|
|
48
|
+
expect(alice.collaborators).toBeGreaterThanOrEqual(2); // bob + carol
|
|
49
|
+
});
|
|
50
|
+
it("ignores downstream commits beyond the time window", () => {
|
|
51
|
+
const commits = [
|
|
52
|
+
cmt("a1", "alice", "2024-08-01", ["x.ts"]),
|
|
53
|
+
cmt("a2", "alice", "2024-08-02", ["x.ts"]),
|
|
54
|
+
cmt("b1", "bob", "2026-01-01", ["x.ts"]), // 1.5 years later
|
|
55
|
+
];
|
|
56
|
+
const scores = moneyball(commits, { minCommits: 1, downstreamWindowDays: 90 });
|
|
57
|
+
const alice = scores.find((s) => s.authorName === "alice");
|
|
58
|
+
// bob is outside 90-day window — not counted as collaborator
|
|
59
|
+
expect(alice.collaborators).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe("moneyball — per-commit ROI sort + tier assignment", () => {
|
|
63
|
+
it("'moneyball' tier emerges for low-volume + high-impact authors", () => {
|
|
64
|
+
// alice: 3 commits, each unblocks 5 collaborators
|
|
65
|
+
const commits = [
|
|
66
|
+
cmt("a1", "alice", "2024-08-01", ["src/core.ts"]),
|
|
67
|
+
cmt("a2", "alice", "2024-08-02", ["src/core.ts"]),
|
|
68
|
+
cmt("a3", "alice", "2024-08-03", ["src/core.ts"]),
|
|
69
|
+
];
|
|
70
|
+
// 5 collaborators each making 3 follow-up commits
|
|
71
|
+
const followups = ["bob", "carol", "dave", "eve", "frank"];
|
|
72
|
+
for (const name of followups) {
|
|
73
|
+
for (let j = 0; j < 3; j++) {
|
|
74
|
+
commits.push(cmt(`${name[0]}${j}`.padEnd(7, "x"), name, `2024-08-${String(j + 5 + followups.indexOf(name) * 2).padStart(2, "0")}`, ["src/core.ts"]));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const scores = moneyball(commits, { minCommits: 1 });
|
|
78
|
+
const alice = scores.find((s) => s.authorName === "alice");
|
|
79
|
+
expect(alice.tier).toBe("moneyball");
|
|
80
|
+
});
|
|
81
|
+
it("'loud' tier for high commit count but low downstream effect", () => {
|
|
82
|
+
const commits = [];
|
|
83
|
+
// alice: 50 commits all on isolated files (no follow-ups)
|
|
84
|
+
for (let i = 0; i < 50; i++) {
|
|
85
|
+
const day = String(i + 1).padStart(2, "0");
|
|
86
|
+
const month = i < 28 ? "08" : "09";
|
|
87
|
+
const dayInMonth = i < 28 ? day : String(i - 27).padStart(2, "0");
|
|
88
|
+
commits.push(cmt(`a${i}`.padEnd(7, "x"), "alice", `2024-${month}-${dayInMonth}`, [`src/iso${i}.ts`]));
|
|
89
|
+
}
|
|
90
|
+
const scores = moneyball(commits, { minCommits: 1 });
|
|
91
|
+
const alice = scores.find((s) => s.authorName === "alice");
|
|
92
|
+
expect(alice.tier).toBe("loud");
|
|
93
|
+
expect(alice.perCommitROI).toBeLessThan(0.3);
|
|
94
|
+
});
|
|
95
|
+
it("sorts by perCommitROI desc — moneyball candidates first", () => {
|
|
96
|
+
const commits = [];
|
|
97
|
+
// bob: 50 commits on solo files (loud)
|
|
98
|
+
for (let i = 0; i < 50; i++) {
|
|
99
|
+
const day = String(i + 1).padStart(2, "0");
|
|
100
|
+
const month = i < 28 ? "08" : "09";
|
|
101
|
+
const dayInMonth = i < 28 ? day : String(i - 27).padStart(2, "0");
|
|
102
|
+
commits.push(cmt(`b${i}`.padEnd(7, "x"), "bob", `2024-${month}-${dayInMonth}`, [`src/iso${i}.ts`]));
|
|
103
|
+
}
|
|
104
|
+
// alice: 3 commits on shared file
|
|
105
|
+
commits.push(cmt("a1", "alice", "2024-08-01", ["src/core.ts"]));
|
|
106
|
+
commits.push(cmt("a2", "alice", "2024-08-02", ["src/core.ts"]));
|
|
107
|
+
commits.push(cmt("a3", "alice", "2024-08-03", ["src/core.ts"]));
|
|
108
|
+
// carol + dave + eve work downstream of alice
|
|
109
|
+
for (const name of ["carol", "dave", "eve", "frank", "george"]) {
|
|
110
|
+
const offset = ["carol", "dave", "eve", "frank", "george"].indexOf(name);
|
|
111
|
+
for (let j = 0; j < 4; j++) {
|
|
112
|
+
commits.push(cmt(`${name[0]}${j}`.padEnd(7, "x"), name, `2024-08-${String(j + 4 + offset * 4).padStart(2, "0")}`, ["src/core.ts"]));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const scores = moneyball(commits, { minCommits: 1 });
|
|
116
|
+
const aliceIdx = scores.findIndex((s) => s.authorName === "alice");
|
|
117
|
+
const bobIdx = scores.findIndex((s) => s.authorName === "bob");
|
|
118
|
+
expect(aliceIdx).toBeLessThan(bobIdx); // alice ranked higher (moneyball)
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe("moneyball — interpretation text", () => {
|
|
122
|
+
it("moneyball tier emphasizes high ROI", () => {
|
|
123
|
+
const commits = [
|
|
124
|
+
cmt("a1", "alice", "2024-08-01", ["x.ts"]),
|
|
125
|
+
cmt("a2", "alice", "2024-08-02", ["x.ts"]),
|
|
126
|
+
cmt("b1", "bob", "2024-08-03", ["x.ts"]),
|
|
127
|
+
cmt("c1", "carol", "2024-08-04", ["x.ts"]),
|
|
128
|
+
cmt("d1", "dave", "2024-08-05", ["x.ts"]),
|
|
129
|
+
cmt("e1", "eve", "2024-08-06", ["x.ts"]),
|
|
130
|
+
];
|
|
131
|
+
const profile = moneyball(commits, { minCommits: 1 }).find((s) => s.authorName === "alice");
|
|
132
|
+
if (profile.tier === "moneyball") {
|
|
133
|
+
expect(profile.interpretation.toLowerCase()).toMatch(/undervalued|unblocked/);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=moneyball.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moneyball.test.js","sourceRoot":"","sources":["../../src/quant/moneyball.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme tax-loss-harvest` — find dead code that can be deleted to "offset"
|
|
3
|
+
* technical debt.
|
|
4
|
+
*
|
|
5
|
+
* The metaphor: in finance, you sell losing positions to harvest tax losses,
|
|
6
|
+
* which offset gains elsewhere. In a codebase, deleting dead code reduces
|
|
7
|
+
* cognitive surface area, freeing up budget to absorb new debt elsewhere.
|
|
8
|
+
*
|
|
9
|
+
* Scoring per file:
|
|
10
|
+
* • staleness — days since last touch
|
|
11
|
+
* • commit count — fewer commits = less load-bearing
|
|
12
|
+
* • entity references — files with no entities or no inbound refs
|
|
13
|
+
* • risk if deleted — files with incidents historically = higher risk
|
|
14
|
+
*
|
|
15
|
+
* harvest_score = staleness × (1 / max(commits, 1)) × (1 - risk)
|
|
16
|
+
*
|
|
17
|
+
* Pure data analysis. No LLM. Output is a candidate list with risk gauges.
|
|
18
|
+
*/
|
|
19
|
+
import type { MnemeStore } from "../store/sqlite.js";
|
|
20
|
+
export interface HarvestCandidate {
|
|
21
|
+
filePath: string;
|
|
22
|
+
/** Days since last commit. */
|
|
23
|
+
daysSinceTouch: number;
|
|
24
|
+
/** Total commits ever touching this file. */
|
|
25
|
+
commitCount: number;
|
|
26
|
+
/** Number of entities (functions/classes) parsed from this file. 0 = likely dead. */
|
|
27
|
+
entityCount: number;
|
|
28
|
+
/** Number of past incidents that mentioned this file. */
|
|
29
|
+
incidentCount: number;
|
|
30
|
+
/** Composite harvest score — higher = better deletion candidate. */
|
|
31
|
+
harvestScore: number;
|
|
32
|
+
/** Risk tier of deletion. */
|
|
33
|
+
risk: "safe" | "low-risk" | "moderate" | "risky";
|
|
34
|
+
/** A 1-line action recommendation. */
|
|
35
|
+
recommendation: string;
|
|
36
|
+
}
|
|
37
|
+
export interface HarvestOptions {
|
|
38
|
+
/** Minimum days since last touch to consider. Default 180. */
|
|
39
|
+
minStaleDays?: number;
|
|
40
|
+
/** Maximum total commits to consider. Default 5 (low-load). */
|
|
41
|
+
maxCommits?: number;
|
|
42
|
+
/** Top-N candidates. */
|
|
43
|
+
topN?: number;
|
|
44
|
+
/** Override "now" for deterministic tests. */
|
|
45
|
+
now?: Date;
|
|
46
|
+
}
|
|
47
|
+
export declare function findHarvestCandidates(store: MnemeStore, opts?: HarvestOptions): HarvestCandidate[];
|
|
48
|
+
export declare function classifyHarvestRisk(incidents: number, entities: number, commits: number): HarvestCandidate["risk"];
|
|
49
|
+
export interface HarvestSummary {
|
|
50
|
+
candidateCount: number;
|
|
51
|
+
/** Sum of (lines saved if all deleted) — best-effort approximation. */
|
|
52
|
+
estimatedLinesSaved: number;
|
|
53
|
+
/** Net risk-adjusted savings — savings × (1 - avg risk). */
|
|
54
|
+
netSavings: number;
|
|
55
|
+
/** A 1-line summary. */
|
|
56
|
+
summary: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function summarizeHarvest(candidates: HarvestCandidate[]): HarvestSummary;
|
|
59
|
+
//# sourceMappingURL=tax-loss-harvest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tax-loss-harvest.d.ts","sourceRoot":"","sources":["../../src/quant/tax-loss-harvest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;IACjD,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,IAAI,GAAE,cAAmB,GACxB,gBAAgB,EAAE,CAsEpB;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,gBAAgB,CAAC,MAAM,CAAC,CAK1B;AAkBD,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,gBAAgB,EAAE,GAAG,cAAc,CAe/E"}
|