@mneme-ai/core 0.9.0 → 0.11.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/ghost.d.ts +80 -0
- package/dist/insights/ghost.d.ts.map +1 -0
- package/dist/insights/ghost.js +138 -0
- package/dist/insights/ghost.js.map +1 -0
- package/dist/insights/ghost.test.d.ts +2 -0
- package/dist/insights/ghost.test.d.ts.map +1 -0
- package/dist/insights/ghost.test.js +141 -0
- package/dist/insights/ghost.test.js.map +1 -0
- package/dist/insights/index.d.ts +8 -0
- package/dist/insights/index.d.ts.map +1 -1
- package/dist/insights/index.js +8 -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/premortem.d.ts +73 -0
- package/dist/insights/premortem.d.ts.map +1 -0
- package/dist/insights/premortem.js +209 -0
- package/dist/insights/premortem.js.map +1 -0
- package/dist/insights/premortem.test.d.ts +2 -0
- package/dist/insights/premortem.test.d.ts.map +1 -0
- package/dist/insights/premortem.test.js +169 -0
- package/dist/insights/premortem.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/time-machine.d.ts +70 -0
- package/dist/insights/time-machine.d.ts.map +1 -0
- package/dist/insights/time-machine.js +177 -0
- package/dist/insights/time-machine.js.map +1 -0
- package/dist/insights/time-machine.test.d.ts +2 -0
- package/dist/insights/time-machine.test.d.ts.map +1 -0
- package/dist/insights/time-machine.test.js +141 -0
- package/dist/insights/time-machine.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/dist/util/index.d.ts +4 -0
- package/dist/util/index.d.ts.map +1 -1
- package/dist/util/index.js +26 -0
- package/dist/util/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme correlation-matrix` — find HIDDEN coupling between files.
|
|
3
|
+
*
|
|
4
|
+
* Static analysis catches imports. This catches *behavioral coupling*:
|
|
5
|
+
* "every time file X is touched, file Y has a bug fix within N days,
|
|
6
|
+
* even though X and Y don't import each other."
|
|
7
|
+
*
|
|
8
|
+
* Why this is novel: most tools see static dependency graphs (X imports
|
|
9
|
+
* Y). Behavioral graphs reveal architectural smells that imports don't:
|
|
10
|
+
* a config table that EVERY service silently depends on, an undocumented
|
|
11
|
+
* shared state, an order-of-operations contract.
|
|
12
|
+
*
|
|
13
|
+
* Output: ranked file-pair coupling with statistical significance score.
|
|
14
|
+
*
|
|
15
|
+
* Pure analysis. No LLM.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Build the file-pair coupling matrix from commit history.
|
|
19
|
+
*
|
|
20
|
+
* Algorithm: for each commit, mark every pair (file_i, file_j) as co-touched.
|
|
21
|
+
* Aggregate counts; compute Jaccard + lift per pair.
|
|
22
|
+
*/
|
|
23
|
+
export function correlationMatrix(commits, opts = {}) {
|
|
24
|
+
const minTouches = opts.minFileTouches ?? 3;
|
|
25
|
+
const minCo = opts.minCoOccurrences ?? 2;
|
|
26
|
+
const topN = opts.topN ?? 20;
|
|
27
|
+
const minLift = opts.minLift ?? 1.5;
|
|
28
|
+
// Step 1: per-file count.
|
|
29
|
+
const fileCount = new Map();
|
|
30
|
+
for (const c of commits) {
|
|
31
|
+
for (const f of c.files ?? [])
|
|
32
|
+
fileCount.set(f, (fileCount.get(f) ?? 0) + 1);
|
|
33
|
+
}
|
|
34
|
+
// Filter to files with enough activity.
|
|
35
|
+
const eligible = new Set();
|
|
36
|
+
for (const [f, n] of fileCount)
|
|
37
|
+
if (n >= minTouches)
|
|
38
|
+
eligible.add(f);
|
|
39
|
+
// Step 2: per-pair co-occurrence.
|
|
40
|
+
const pairKey = (a, b) => (a < b ? `${a}|${b}` : `${b}|${a}`);
|
|
41
|
+
const pairCount = new Map();
|
|
42
|
+
for (const c of commits) {
|
|
43
|
+
const files = (c.files ?? []).filter((f) => eligible.has(f));
|
|
44
|
+
for (let i = 0; i < files.length; i++) {
|
|
45
|
+
for (let j = i + 1; j < files.length; j++) {
|
|
46
|
+
const k = pairKey(files[i], files[j]);
|
|
47
|
+
pairCount.set(k, (pairCount.get(k) ?? 0) + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const totalCommits = commits.length;
|
|
52
|
+
const pairs = [];
|
|
53
|
+
for (const [k, n] of pairCount) {
|
|
54
|
+
if (n < minCo)
|
|
55
|
+
continue;
|
|
56
|
+
const [a, b] = k.split("|");
|
|
57
|
+
const A = fileCount.get(a);
|
|
58
|
+
const B = fileCount.get(b);
|
|
59
|
+
const jaccard = n / (A + B - n);
|
|
60
|
+
// P(B|A) = n / A; P(B) = B / totalCommits; lift = (n/A) / (B/totalCommits)
|
|
61
|
+
const lift = totalCommits === 0 || A === 0 || B === 0 ? 0 : (n / A) / (B / totalCommits);
|
|
62
|
+
if (lift < minLift)
|
|
63
|
+
continue;
|
|
64
|
+
pairs.push({
|
|
65
|
+
fileA: a,
|
|
66
|
+
fileB: b,
|
|
67
|
+
countA: A,
|
|
68
|
+
countB: B,
|
|
69
|
+
coOccurrences: n,
|
|
70
|
+
jaccard,
|
|
71
|
+
lift,
|
|
72
|
+
tier: classifyCouplingTier(jaccard, lift),
|
|
73
|
+
interpretation: buildCouplingInterpretation(jaccard, lift, n),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
pairs.sort((a, b) => b.lift - a.lift || b.jaccard - a.jaccard);
|
|
77
|
+
return pairs.slice(0, topN);
|
|
78
|
+
}
|
|
79
|
+
export function classifyCouplingTier(jaccard, lift) {
|
|
80
|
+
// 'tight' = perfect jaccard (≥ 0.9) OR (jaccard ≥ 0.6 AND lift ≥ 5).
|
|
81
|
+
// Real codebases rarely hit lift ≥ 5; jaccard 1.0 is a stronger signal
|
|
82
|
+
// than lift alone — fully co-touched files deserve the 'tight' label.
|
|
83
|
+
if (jaccard >= 0.9 || (jaccard >= 0.6 && lift >= 5))
|
|
84
|
+
return "tight";
|
|
85
|
+
if (jaccard >= 0.4 || lift >= 3)
|
|
86
|
+
return "strong";
|
|
87
|
+
if (jaccard >= 0.2 || lift >= 2)
|
|
88
|
+
return "moderate";
|
|
89
|
+
return "weak";
|
|
90
|
+
}
|
|
91
|
+
function buildCouplingInterpretation(jaccard, lift, co) {
|
|
92
|
+
if (jaccard >= 0.6) {
|
|
93
|
+
return `Tight behavioral coupling (Jaccard ${jaccard.toFixed(2)}). These files almost always change together. Likely candidates for a single module.`;
|
|
94
|
+
}
|
|
95
|
+
if (lift >= 5) {
|
|
96
|
+
return `${lift.toFixed(1)}× more likely than random to be touched together (${co} co-occurrences). Hidden dependency worth investigating.`;
|
|
97
|
+
}
|
|
98
|
+
if (jaccard >= 0.3) {
|
|
99
|
+
return `Moderate coupling (Jaccard ${jaccard.toFixed(2)}). Watch for accidental shared state.`;
|
|
100
|
+
}
|
|
101
|
+
return `Weak signal — ${co} shared touches with lift ${lift.toFixed(1)}×.`;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=correlation-matrix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlation-matrix.js","sourceRoot":"","sources":["../../src/quant/correlation-matrix.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlation-matrix.test.d.ts","sourceRoot":"","sources":["../../src/quant/correlation-matrix.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { correlationMatrix, classifyCouplingTier } from "./correlation-matrix.js";
|
|
3
|
+
const cmt = (hash, date, files) => ({
|
|
4
|
+
hash,
|
|
5
|
+
shortHash: hash.slice(0, 7),
|
|
6
|
+
authorName: "alice",
|
|
7
|
+
authorEmail: "a@x",
|
|
8
|
+
authorDate: `${date}T00:00:00Z`,
|
|
9
|
+
committerDate: `${date}T00:00:00Z`,
|
|
10
|
+
subject: "x",
|
|
11
|
+
body: "",
|
|
12
|
+
parents: [],
|
|
13
|
+
files,
|
|
14
|
+
});
|
|
15
|
+
describe("classifyCouplingTier", () => {
|
|
16
|
+
it("'tight' when both jaccard ≥ 0.6 + lift ≥ 5", () => {
|
|
17
|
+
expect(classifyCouplingTier(0.7, 6)).toBe("tight");
|
|
18
|
+
});
|
|
19
|
+
it("'strong' when one of jaccard ≥ 0.4 or lift ≥ 3", () => {
|
|
20
|
+
expect(classifyCouplingTier(0.5, 1)).toBe("strong");
|
|
21
|
+
expect(classifyCouplingTier(0.1, 4)).toBe("strong");
|
|
22
|
+
});
|
|
23
|
+
it("'moderate' for middling values", () => {
|
|
24
|
+
expect(classifyCouplingTier(0.25, 2)).toBe("moderate");
|
|
25
|
+
});
|
|
26
|
+
it("'weak' otherwise", () => {
|
|
27
|
+
expect(classifyCouplingTier(0.05, 1.2)).toBe("weak");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe("correlationMatrix — basic detection", () => {
|
|
31
|
+
it("returns empty when there are no co-occurring file pairs", () => {
|
|
32
|
+
const commits = [
|
|
33
|
+
cmt("a1", "2024-01-01", ["src/a.ts"]),
|
|
34
|
+
cmt("a2", "2024-01-02", ["src/b.ts"]),
|
|
35
|
+
cmt("a3", "2024-01-03", ["src/c.ts"]),
|
|
36
|
+
];
|
|
37
|
+
expect(correlationMatrix(commits)).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
it("detects a tightly-coupled pair when both files always change together", () => {
|
|
40
|
+
// 10 co-touched commits + 20 background commits on unrelated files.
|
|
41
|
+
// Background commits lower the baseline rate of x and y so that
|
|
42
|
+
// co-occurrence has lift > 1.5 (otherwise lift = 1 and is filtered out).
|
|
43
|
+
const co = Array.from({ length: 10 }, (_, i) => cmt(`c${i}`.padEnd(7, "x"), `2024-01-${String(i + 1).padStart(2, "0")}`, ["src/x.ts", "src/y.ts"]));
|
|
44
|
+
const bg = Array.from({ length: 20 }, (_, i) => cmt(`bg${i}`.padEnd(7, "x"), `2024-02-${String((i % 28) + 1).padStart(2, "0")}`, [`src/other${i}.ts`]));
|
|
45
|
+
const pairs = correlationMatrix([...co, ...bg], { minFileTouches: 3, minCoOccurrences: 2 });
|
|
46
|
+
const xy = pairs.find((p) => (p.fileA === "src/x.ts" && p.fileB === "src/y.ts") ||
|
|
47
|
+
(p.fileA === "src/y.ts" && p.fileB === "src/x.ts"));
|
|
48
|
+
expect(xy).toBeDefined();
|
|
49
|
+
expect(xy.jaccard).toBeCloseTo(1, 3);
|
|
50
|
+
expect(xy.tier).toBe("tight");
|
|
51
|
+
});
|
|
52
|
+
it("respects minFileTouches threshold", () => {
|
|
53
|
+
const commits = [
|
|
54
|
+
cmt("a1", "2024-01-01", ["src/x.ts", "src/y.ts"]),
|
|
55
|
+
cmt("a2", "2024-01-02", ["src/x.ts", "src/y.ts"]),
|
|
56
|
+
];
|
|
57
|
+
expect(correlationMatrix(commits, { minFileTouches: 5 })).toEqual([]);
|
|
58
|
+
expect(correlationMatrix(commits, { minFileTouches: 1, minCoOccurrences: 1, minLift: 0 })).toHaveLength(1);
|
|
59
|
+
});
|
|
60
|
+
it("respects minCoOccurrences threshold", () => {
|
|
61
|
+
const commits = [
|
|
62
|
+
cmt("a1", "2024-01-01", ["src/x.ts", "src/y.ts"]),
|
|
63
|
+
cmt("a2", "2024-01-02", ["src/x.ts"]),
|
|
64
|
+
cmt("a3", "2024-01-03", ["src/y.ts"]),
|
|
65
|
+
cmt("a4", "2024-01-04", ["src/x.ts"]),
|
|
66
|
+
];
|
|
67
|
+
expect(correlationMatrix(commits, { minFileTouches: 1, minCoOccurrences: 3 })).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
it("respects minLift threshold (filters weak pairs)", () => {
|
|
70
|
+
// Build a noisy history where x and y co-occur but at random level
|
|
71
|
+
const commits = [];
|
|
72
|
+
for (let i = 0; i < 20; i++)
|
|
73
|
+
commits.push(cmt(`x${i}`.padEnd(7, "y"), `2024-01-${String(i + 1).padStart(2, "0")}`, ["src/x.ts"]));
|
|
74
|
+
for (let i = 0; i < 20; i++)
|
|
75
|
+
commits.push(cmt(`y${i}`.padEnd(7, "x"), `2024-02-${String(i + 1).padStart(2, "0")}`, ["src/y.ts"]));
|
|
76
|
+
commits.push(cmt("co1", "2024-03-01", ["src/x.ts", "src/y.ts"]));
|
|
77
|
+
commits.push(cmt("co2", "2024-03-02", ["src/x.ts", "src/y.ts"]));
|
|
78
|
+
// 2 co-occurrences but x has 22 total, y has 22 total, so jaccard tiny + lift low
|
|
79
|
+
expect(correlationMatrix(commits, { minLift: 5 })).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe("correlationMatrix — sort + tiers", () => {
|
|
83
|
+
it("sorts by lift desc", () => {
|
|
84
|
+
const commits = [
|
|
85
|
+
// Pair 1: TIGHT (5 co-occurrences, files appear in nothing else).
|
|
86
|
+
cmt("a1", "2024-01-01", ["src/tight-a.ts", "src/tight-b.ts"]),
|
|
87
|
+
cmt("a2", "2024-01-02", ["src/tight-a.ts", "src/tight-b.ts"]),
|
|
88
|
+
cmt("a3", "2024-01-03", ["src/tight-a.ts", "src/tight-b.ts"]),
|
|
89
|
+
cmt("a4", "2024-01-04", ["src/tight-a.ts", "src/tight-b.ts"]),
|
|
90
|
+
cmt("a5", "2024-01-05", ["src/tight-a.ts", "src/tight-b.ts"]),
|
|
91
|
+
// Pair 2: LOOSER (3 co, but both files also touched alone — diluted lift).
|
|
92
|
+
cmt("b1", "2024-02-01", ["src/loose-a.ts", "src/loose-b.ts"]),
|
|
93
|
+
cmt("b2", "2024-02-02", ["src/loose-a.ts", "src/loose-b.ts"]),
|
|
94
|
+
cmt("b3", "2024-02-03", ["src/loose-a.ts", "src/loose-b.ts"]),
|
|
95
|
+
cmt("b4", "2024-02-04", ["src/loose-a.ts"]),
|
|
96
|
+
cmt("b5", "2024-02-05", ["src/loose-a.ts"]),
|
|
97
|
+
cmt("b6", "2024-02-06", ["src/loose-b.ts"]),
|
|
98
|
+
cmt("b7", "2024-02-07", ["src/loose-b.ts"]),
|
|
99
|
+
// Background — 15 commits on unrelated files lower the global baseline,
|
|
100
|
+
// so even the tight pair has lift > 1.5 (otherwise lift = 1 and filtered).
|
|
101
|
+
...Array.from({ length: 15 }, (_, i) => cmt(`bg${i}`.padEnd(7, "x"), `2024-03-${String(i + 1).padStart(2, "0")}`, [`src/other${i}.ts`])),
|
|
102
|
+
];
|
|
103
|
+
const pairs = correlationMatrix(commits, { minFileTouches: 3, minCoOccurrences: 2 });
|
|
104
|
+
expect(pairs[0].fileA).toMatch(/tight-/);
|
|
105
|
+
expect(pairs[0].lift).toBeGreaterThan(pairs[pairs.length - 1].lift);
|
|
106
|
+
});
|
|
107
|
+
it("respects topN", () => {
|
|
108
|
+
const commits = [];
|
|
109
|
+
// 10 file pairs, each with 4 co-occurrences
|
|
110
|
+
for (let p = 0; p < 10; p++) {
|
|
111
|
+
for (let i = 0; i < 4; i++) {
|
|
112
|
+
commits.push(cmt(`p${p}c${i}`.padEnd(7, "x"), `2024-${String(p + 1).padStart(2, "0")}-0${i + 1}`, [`src/p${p}-a.ts`, `src/p${p}-b.ts`]));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
expect(correlationMatrix(commits, { minFileTouches: 1, minCoOccurrences: 2, topN: 5, minLift: 0 }).length).toBeLessThanOrEqual(5);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=correlation-matrix.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlation-matrix.test.js","sourceRoot":"","sources":["../../src/quant/correlation-matrix.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme drawdown` — find the worst "losing streaks" in repo history.
|
|
3
|
+
*
|
|
4
|
+
* In finance, a drawdown is a peak-to-trough decline. In a codebase, it's
|
|
5
|
+
* a stretch of bug-fix-only commits with no feature progress — pure
|
|
6
|
+
* firefighting. Surfacing these is gold for retrospectives:
|
|
7
|
+
*
|
|
8
|
+
* "We spent 3 weeks last September shipping nothing but fixes."
|
|
9
|
+
*
|
|
10
|
+
* Pure data analysis. No LLM. Tested via TDD against synthetic histories.
|
|
11
|
+
*/
|
|
12
|
+
import type { Commit } from "../types.js";
|
|
13
|
+
export interface Drawdown {
|
|
14
|
+
/** Start commit (peak — last feat before the drawdown). */
|
|
15
|
+
startHash: string;
|
|
16
|
+
startDate: string;
|
|
17
|
+
/** End commit (trough — last fix in the streak). */
|
|
18
|
+
endHash: string;
|
|
19
|
+
endDate: string;
|
|
20
|
+
/** Number of consecutive non-feat commits in the streak. */
|
|
21
|
+
length: number;
|
|
22
|
+
/** Days between start and end. */
|
|
23
|
+
durationDays: number;
|
|
24
|
+
/** Sample fix subjects from inside the streak (up to 3). */
|
|
25
|
+
sampleFixes: string[];
|
|
26
|
+
/** Severity tier from length × duration. */
|
|
27
|
+
tier: "minor" | "moderate" | "severe" | "critical";
|
|
28
|
+
}
|
|
29
|
+
/** Decide whether a commit subject is a "fix" (counts toward drawdown). */
|
|
30
|
+
export declare function isFixCommit(c: Commit): boolean;
|
|
31
|
+
/** Decide whether a commit subject is a "feature" (resets the streak). */
|
|
32
|
+
export declare function isFeatCommit(c: Commit): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Detect drawdowns — runs of ≥ minLength consecutive non-feat commits
|
|
35
|
+
* where ≥ fixRatio of those commits are explicit fixes.
|
|
36
|
+
*/
|
|
37
|
+
export declare function detectDrawdowns(commits: Commit[], opts?: {
|
|
38
|
+
minLength?: number;
|
|
39
|
+
fixRatio?: number;
|
|
40
|
+
topN?: number;
|
|
41
|
+
}): Drawdown[];
|
|
42
|
+
export declare function classifyDrawdown(length: number, durationDays: number): Drawdown["tier"];
|
|
43
|
+
export interface DrawdownSummary {
|
|
44
|
+
total: number;
|
|
45
|
+
longestStreak: number;
|
|
46
|
+
totalFixingDays: number;
|
|
47
|
+
/** Fraction of the repo's lifespan spent in drawdown. */
|
|
48
|
+
drawdownFraction: number;
|
|
49
|
+
}
|
|
50
|
+
export declare function summarizeDrawdowns(commits: Commit[], drawdowns: Drawdown[]): DrawdownSummary;
|
|
51
|
+
//# sourceMappingURL=drawdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawdown.d.ts","sourceRoot":"","sources":["../../src/quant/drawdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,QAAQ;IACvB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,4CAA4C;IAC5C,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;CACpD;AAKD,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,QAAQ,EAAE,CA4CZ;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAKvF;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,eAAe,CAiB5F"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme drawdown` — find the worst "losing streaks" in repo history.
|
|
3
|
+
*
|
|
4
|
+
* In finance, a drawdown is a peak-to-trough decline. In a codebase, it's
|
|
5
|
+
* a stretch of bug-fix-only commits with no feature progress — pure
|
|
6
|
+
* firefighting. Surfacing these is gold for retrospectives:
|
|
7
|
+
*
|
|
8
|
+
* "We spent 3 weeks last September shipping nothing but fixes."
|
|
9
|
+
*
|
|
10
|
+
* Pure data analysis. No LLM. Tested via TDD against synthetic histories.
|
|
11
|
+
*/
|
|
12
|
+
const FEAT_RE = /^feat[(:]/i;
|
|
13
|
+
const FIX_RE = /^(fix|hotfix|bug|revert|patch)[(:]/i;
|
|
14
|
+
/** Decide whether a commit subject is a "fix" (counts toward drawdown). */
|
|
15
|
+
export function isFixCommit(c) {
|
|
16
|
+
return FIX_RE.test(c.subject);
|
|
17
|
+
}
|
|
18
|
+
/** Decide whether a commit subject is a "feature" (resets the streak). */
|
|
19
|
+
export function isFeatCommit(c) {
|
|
20
|
+
return FEAT_RE.test(c.subject);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Detect drawdowns — runs of ≥ minLength consecutive non-feat commits
|
|
24
|
+
* where ≥ fixRatio of those commits are explicit fixes.
|
|
25
|
+
*/
|
|
26
|
+
export function detectDrawdowns(commits, opts = {}) {
|
|
27
|
+
const minLength = opts.minLength ?? 3;
|
|
28
|
+
const fixRatio = opts.fixRatio ?? 0.5;
|
|
29
|
+
const topN = opts.topN ?? 10;
|
|
30
|
+
const sorted = [...commits].sort((a, b) => a.authorDate.localeCompare(b.authorDate));
|
|
31
|
+
const out = [];
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i < sorted.length) {
|
|
34
|
+
if (isFeatCommit(sorted[i])) {
|
|
35
|
+
i += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// Walk forward while non-feat.
|
|
39
|
+
let j = i;
|
|
40
|
+
let fixCount = 0;
|
|
41
|
+
while (j < sorted.length && !isFeatCommit(sorted[j])) {
|
|
42
|
+
if (isFixCommit(sorted[j]))
|
|
43
|
+
fixCount += 1;
|
|
44
|
+
j += 1;
|
|
45
|
+
}
|
|
46
|
+
const length = j - i;
|
|
47
|
+
const ratio = length === 0 ? 0 : fixCount / length;
|
|
48
|
+
if (length >= minLength && ratio >= fixRatio) {
|
|
49
|
+
const start = sorted[i];
|
|
50
|
+
const end = sorted[j - 1];
|
|
51
|
+
const dur = (new Date(end.authorDate).getTime() - new Date(start.authorDate).getTime()) / 86_400_000;
|
|
52
|
+
const samples = sorted.slice(i, j).filter(isFixCommit).slice(0, 3).map((c) => c.subject);
|
|
53
|
+
out.push({
|
|
54
|
+
startHash: start.shortHash || start.hash.slice(0, 7),
|
|
55
|
+
startDate: start.authorDate.slice(0, 10),
|
|
56
|
+
endHash: end.shortHash || end.hash.slice(0, 7),
|
|
57
|
+
endDate: end.authorDate.slice(0, 10),
|
|
58
|
+
length,
|
|
59
|
+
durationDays: Math.round(dur * 10) / 10,
|
|
60
|
+
sampleFixes: samples,
|
|
61
|
+
tier: classifyDrawdown(length, dur),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
i = j === i ? i + 1 : j;
|
|
65
|
+
}
|
|
66
|
+
// Sort by severity (length × duration), desc.
|
|
67
|
+
out.sort((a, b) => b.length * Math.max(b.durationDays, 1) - a.length * Math.max(a.durationDays, 1));
|
|
68
|
+
return out.slice(0, topN);
|
|
69
|
+
}
|
|
70
|
+
export function classifyDrawdown(length, durationDays) {
|
|
71
|
+
if (length >= 15 || durationDays >= 30)
|
|
72
|
+
return "critical";
|
|
73
|
+
if (length >= 8 || durationDays >= 14)
|
|
74
|
+
return "severe";
|
|
75
|
+
if (length >= 5 || durationDays >= 7)
|
|
76
|
+
return "moderate";
|
|
77
|
+
return "minor";
|
|
78
|
+
}
|
|
79
|
+
export function summarizeDrawdowns(commits, drawdowns) {
|
|
80
|
+
if (commits.length === 0) {
|
|
81
|
+
return { total: 0, longestStreak: 0, totalFixingDays: 0, drawdownFraction: 0 };
|
|
82
|
+
}
|
|
83
|
+
const sorted = [...commits].sort((a, b) => a.authorDate.localeCompare(b.authorDate));
|
|
84
|
+
const repoSpan = (new Date(sorted[sorted.length - 1].authorDate).getTime() -
|
|
85
|
+
new Date(sorted[0].authorDate).getTime()) /
|
|
86
|
+
86_400_000;
|
|
87
|
+
const totalFixingDays = drawdowns.reduce((s, d) => s + d.durationDays, 0);
|
|
88
|
+
const longest = drawdowns.reduce((m, d) => Math.max(m, d.length), 0);
|
|
89
|
+
return {
|
|
90
|
+
total: drawdowns.length,
|
|
91
|
+
longestStreak: longest,
|
|
92
|
+
totalFixingDays: Math.round(totalFixingDays * 10) / 10,
|
|
93
|
+
drawdownFraction: repoSpan === 0 ? 0 : totalFixingDays / repoSpan,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=drawdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawdown.js","sourceRoot":"","sources":["../../src/quant/drawdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawdown.test.d.ts","sourceRoot":"","sources":["../../src/quant/drawdown.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { detectDrawdowns, summarizeDrawdowns, isFeatCommit, isFixCommit, classifyDrawdown, } from "./drawdown.js";
|
|
3
|
+
const cmt = (hash, date, subject) => ({
|
|
4
|
+
hash,
|
|
5
|
+
shortHash: hash.slice(0, 7),
|
|
6
|
+
authorName: "alice",
|
|
7
|
+
authorEmail: "alice@x",
|
|
8
|
+
authorDate: `${date}T00:00:00Z`,
|
|
9
|
+
committerDate: `${date}T00:00:00Z`,
|
|
10
|
+
subject,
|
|
11
|
+
body: "",
|
|
12
|
+
parents: [],
|
|
13
|
+
files: [],
|
|
14
|
+
});
|
|
15
|
+
describe("isFeatCommit / isFixCommit — Conventional-Commit subject classifiers", () => {
|
|
16
|
+
it("matches feat( and feat:", () => {
|
|
17
|
+
expect(isFeatCommit(cmt("a", "2024-01-01", "feat: thing"))).toBe(true);
|
|
18
|
+
expect(isFeatCommit(cmt("a", "2024-01-01", "feat(auth): thing"))).toBe(true);
|
|
19
|
+
expect(isFeatCommit(cmt("a", "2024-01-01", "feat scoped without colon"))).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it("matches fix/hotfix/bug/revert/patch", () => {
|
|
22
|
+
expect(isFixCommit(cmt("a", "2024-01-01", "fix: x"))).toBe(true);
|
|
23
|
+
expect(isFixCommit(cmt("a", "2024-01-01", "hotfix: x"))).toBe(true);
|
|
24
|
+
expect(isFixCommit(cmt("a", "2024-01-01", "bug: x"))).toBe(true);
|
|
25
|
+
expect(isFixCommit(cmt("a", "2024-01-01", "revert: x"))).toBe(true);
|
|
26
|
+
expect(isFixCommit(cmt("a", "2024-01-01", "feat: x"))).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("classifyDrawdown — tier from length × duration", () => {
|
|
30
|
+
it("critical at 15+ commits or 30+ days", () => {
|
|
31
|
+
expect(classifyDrawdown(15, 5)).toBe("critical");
|
|
32
|
+
expect(classifyDrawdown(5, 30)).toBe("critical");
|
|
33
|
+
});
|
|
34
|
+
it("severe at 8+ or 14+", () => {
|
|
35
|
+
expect(classifyDrawdown(8, 5)).toBe("severe");
|
|
36
|
+
expect(classifyDrawdown(5, 14)).toBe("severe");
|
|
37
|
+
});
|
|
38
|
+
it("moderate at 5+ or 7+", () => {
|
|
39
|
+
expect(classifyDrawdown(5, 1)).toBe("moderate");
|
|
40
|
+
expect(classifyDrawdown(3, 7)).toBe("moderate");
|
|
41
|
+
});
|
|
42
|
+
it("minor for short streaks", () => {
|
|
43
|
+
expect(classifyDrawdown(3, 2)).toBe("minor");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("detectDrawdowns — simple cases", () => {
|
|
47
|
+
it("returns empty when no fix commits", () => {
|
|
48
|
+
const commits = [
|
|
49
|
+
cmt("a1", "2024-01-01", "feat: A"),
|
|
50
|
+
cmt("a2", "2024-01-02", "feat: B"),
|
|
51
|
+
cmt("a3", "2024-01-03", "feat: C"),
|
|
52
|
+
];
|
|
53
|
+
expect(detectDrawdowns(commits)).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
it("returns empty when streak shorter than minLength", () => {
|
|
56
|
+
const commits = [
|
|
57
|
+
cmt("a1", "2024-01-01", "feat: A"),
|
|
58
|
+
cmt("a2", "2024-01-02", "fix: x"),
|
|
59
|
+
cmt("a3", "2024-01-03", "fix: y"),
|
|
60
|
+
cmt("a4", "2024-01-04", "feat: B"),
|
|
61
|
+
];
|
|
62
|
+
expect(detectDrawdowns(commits, { minLength: 3 })).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
it("detects a single drawdown of 5 fixes between two feats", () => {
|
|
65
|
+
const commits = [
|
|
66
|
+
cmt("a1", "2024-01-01", "feat: A"),
|
|
67
|
+
cmt("a2", "2024-01-02", "fix: 1"),
|
|
68
|
+
cmt("a3", "2024-01-03", "fix: 2"),
|
|
69
|
+
cmt("a4", "2024-01-04", "fix: 3"),
|
|
70
|
+
cmt("a5", "2024-01-05", "fix: 4"),
|
|
71
|
+
cmt("a6", "2024-01-06", "fix: 5"),
|
|
72
|
+
cmt("a7", "2024-01-07", "feat: B"),
|
|
73
|
+
];
|
|
74
|
+
const drawdowns = detectDrawdowns(commits, { minLength: 3 });
|
|
75
|
+
expect(drawdowns).toHaveLength(1);
|
|
76
|
+
expect(drawdowns[0].length).toBe(5);
|
|
77
|
+
expect(drawdowns[0].tier).toBe("moderate");
|
|
78
|
+
expect(drawdowns[0].sampleFixes).toHaveLength(3);
|
|
79
|
+
});
|
|
80
|
+
it("detects multiple separate drawdowns", () => {
|
|
81
|
+
const commits = [
|
|
82
|
+
cmt("a1", "2024-01-01", "feat: A"),
|
|
83
|
+
cmt("a2", "2024-01-02", "fix: 1"),
|
|
84
|
+
cmt("a3", "2024-01-03", "fix: 2"),
|
|
85
|
+
cmt("a4", "2024-01-04", "fix: 3"),
|
|
86
|
+
cmt("a5", "2024-01-05", "feat: B"),
|
|
87
|
+
cmt("a6", "2024-01-06", "fix: 4"),
|
|
88
|
+
cmt("a7", "2024-01-07", "fix: 5"),
|
|
89
|
+
cmt("a8", "2024-01-08", "fix: 6"),
|
|
90
|
+
cmt("a9", "2024-01-09", "fix: 7"),
|
|
91
|
+
cmt("a10", "2024-01-10", "feat: C"),
|
|
92
|
+
];
|
|
93
|
+
const drawdowns = detectDrawdowns(commits, { minLength: 3 });
|
|
94
|
+
expect(drawdowns).toHaveLength(2);
|
|
95
|
+
});
|
|
96
|
+
it("respects fixRatio threshold (mixed chore/fix streaks)", () => {
|
|
97
|
+
const commits = [
|
|
98
|
+
cmt("a1", "2024-01-01", "feat: A"),
|
|
99
|
+
cmt("a2", "2024-01-02", "fix: 1"),
|
|
100
|
+
cmt("a3", "2024-01-03", "chore: bump deps"),
|
|
101
|
+
cmt("a4", "2024-01-04", "chore: lint config"),
|
|
102
|
+
cmt("a5", "2024-01-05", "feat: B"),
|
|
103
|
+
];
|
|
104
|
+
// 1/3 fix ratio — below default 0.5
|
|
105
|
+
expect(detectDrawdowns(commits, { minLength: 3, fixRatio: 0.5 })).toEqual([]);
|
|
106
|
+
expect(detectDrawdowns(commits, { minLength: 3, fixRatio: 0.3 })).toHaveLength(1);
|
|
107
|
+
});
|
|
108
|
+
it("sorts drawdowns by severity descending", () => {
|
|
109
|
+
const commits = [
|
|
110
|
+
// Drawdown 1: 4 fixes
|
|
111
|
+
cmt("a1", "2024-01-01", "feat: A"),
|
|
112
|
+
cmt("a2", "2024-01-02", "fix: 1"),
|
|
113
|
+
cmt("a3", "2024-01-03", "fix: 2"),
|
|
114
|
+
cmt("a4", "2024-01-04", "fix: 3"),
|
|
115
|
+
cmt("a5", "2024-01-05", "fix: 4"),
|
|
116
|
+
cmt("a6", "2024-01-06", "feat: B"),
|
|
117
|
+
// Drawdown 2: 8 fixes (more severe)
|
|
118
|
+
cmt("b1", "2024-02-01", "fix: 1"),
|
|
119
|
+
cmt("b2", "2024-02-02", "fix: 2"),
|
|
120
|
+
cmt("b3", "2024-02-03", "fix: 3"),
|
|
121
|
+
cmt("b4", "2024-02-04", "fix: 4"),
|
|
122
|
+
cmt("b5", "2024-02-05", "fix: 5"),
|
|
123
|
+
cmt("b6", "2024-02-06", "fix: 6"),
|
|
124
|
+
cmt("b7", "2024-02-07", "fix: 7"),
|
|
125
|
+
cmt("b8", "2024-02-08", "fix: 8"),
|
|
126
|
+
cmt("b9", "2024-02-09", "feat: C"),
|
|
127
|
+
];
|
|
128
|
+
const drawdowns = detectDrawdowns(commits, { minLength: 3 });
|
|
129
|
+
expect(drawdowns[0].length).toBe(8);
|
|
130
|
+
expect(drawdowns[1].length).toBe(4);
|
|
131
|
+
});
|
|
132
|
+
it("respects topN cap", () => {
|
|
133
|
+
const commits = [];
|
|
134
|
+
for (let i = 0; i < 30; i++) {
|
|
135
|
+
const day = String(i + 1).padStart(2, "0");
|
|
136
|
+
commits.push(cmt(`f${i}`, `2024-01-${day}`, "feat: x"));
|
|
137
|
+
commits.push(cmt(`x${i}`, `2024-01-${day}`, "fix: 1"));
|
|
138
|
+
commits.push(cmt(`y${i}`, `2024-01-${day}`, "fix: 2"));
|
|
139
|
+
commits.push(cmt(`z${i}`, `2024-01-${day}`, "fix: 3"));
|
|
140
|
+
}
|
|
141
|
+
expect(detectDrawdowns(commits, { minLength: 3, topN: 5 }).length).toBeLessThanOrEqual(5);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe("summarizeDrawdowns — repo-level metrics", () => {
|
|
145
|
+
it("computes drawdown fraction over repo lifespan", () => {
|
|
146
|
+
const commits = [
|
|
147
|
+
cmt("a1", "2024-01-01", "feat: A"),
|
|
148
|
+
cmt("a2", "2024-01-08", "fix: 1"), // 7 days into lifespan
|
|
149
|
+
cmt("a3", "2024-01-15", "fix: 2"),
|
|
150
|
+
cmt("a4", "2024-01-22", "fix: 3"),
|
|
151
|
+
cmt("a5", "2024-02-01", "feat: B"), // 31 days total
|
|
152
|
+
];
|
|
153
|
+
const drawdowns = detectDrawdowns(commits, { minLength: 3 });
|
|
154
|
+
const summary = summarizeDrawdowns(commits, drawdowns);
|
|
155
|
+
expect(summary.total).toBe(1);
|
|
156
|
+
expect(summary.longestStreak).toBe(3);
|
|
157
|
+
expect(summary.drawdownFraction).toBeGreaterThan(0);
|
|
158
|
+
expect(summary.drawdownFraction).toBeLessThan(1);
|
|
159
|
+
});
|
|
160
|
+
it("returns zero metrics for empty commits", () => {
|
|
161
|
+
const summary = summarizeDrawdowns([], []);
|
|
162
|
+
expect(summary.total).toBe(0);
|
|
163
|
+
expect(summary.drawdownFraction).toBe(0);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=drawdown.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawdown.test.js","sourceRoot":"","sources":["../../src/quant/drawdown.test.ts"],"names":[],"mappings":"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"}
|