@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.
Files changed (181) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/insights/bus-factor.d.ts +58 -0
  6. package/dist/insights/bus-factor.d.ts.map +1 -0
  7. package/dist/insights/bus-factor.js +117 -0
  8. package/dist/insights/bus-factor.js.map +1 -0
  9. package/dist/insights/bus-factor.test.d.ts +2 -0
  10. package/dist/insights/bus-factor.test.d.ts.map +1 -0
  11. package/dist/insights/bus-factor.test.js +149 -0
  12. package/dist/insights/bus-factor.test.js.map +1 -0
  13. package/dist/insights/commit-coach.d.ts +80 -0
  14. package/dist/insights/commit-coach.d.ts.map +1 -0
  15. package/dist/insights/commit-coach.js +230 -0
  16. package/dist/insights/commit-coach.js.map +1 -0
  17. package/dist/insights/commit-coach.test.d.ts +2 -0
  18. package/dist/insights/commit-coach.test.d.ts.map +1 -0
  19. package/dist/insights/commit-coach.test.js +163 -0
  20. package/dist/insights/commit-coach.test.js.map +1 -0
  21. package/dist/insights/crystal-ball.d.ts +76 -0
  22. package/dist/insights/crystal-ball.d.ts.map +1 -0
  23. package/dist/insights/crystal-ball.js +219 -0
  24. package/dist/insights/crystal-ball.js.map +1 -0
  25. package/dist/insights/crystal-ball.test.d.ts +2 -0
  26. package/dist/insights/crystal-ball.test.d.ts.map +1 -0
  27. package/dist/insights/crystal-ball.test.js +157 -0
  28. package/dist/insights/crystal-ball.test.js.map +1 -0
  29. package/dist/insights/ghost.d.ts +80 -0
  30. package/dist/insights/ghost.d.ts.map +1 -0
  31. package/dist/insights/ghost.js +138 -0
  32. package/dist/insights/ghost.js.map +1 -0
  33. package/dist/insights/ghost.test.d.ts +2 -0
  34. package/dist/insights/ghost.test.d.ts.map +1 -0
  35. package/dist/insights/ghost.test.js +141 -0
  36. package/dist/insights/ghost.test.js.map +1 -0
  37. package/dist/insights/index.d.ts +8 -0
  38. package/dist/insights/index.d.ts.map +1 -1
  39. package/dist/insights/index.js +8 -0
  40. package/dist/insights/index.js.map +1 -1
  41. package/dist/insights/paradox.d.ts +36 -0
  42. package/dist/insights/paradox.d.ts.map +1 -0
  43. package/dist/insights/paradox.js +201 -0
  44. package/dist/insights/paradox.js.map +1 -0
  45. package/dist/insights/paradox.test.d.ts +2 -0
  46. package/dist/insights/paradox.test.d.ts.map +1 -0
  47. package/dist/insights/paradox.test.js +88 -0
  48. package/dist/insights/paradox.test.js.map +1 -0
  49. package/dist/insights/premortem.d.ts +73 -0
  50. package/dist/insights/premortem.d.ts.map +1 -0
  51. package/dist/insights/premortem.js +209 -0
  52. package/dist/insights/premortem.js.map +1 -0
  53. package/dist/insights/premortem.test.d.ts +2 -0
  54. package/dist/insights/premortem.test.d.ts.map +1 -0
  55. package/dist/insights/premortem.test.js +169 -0
  56. package/dist/insights/premortem.test.js.map +1 -0
  57. package/dist/insights/regret.d.ts +57 -0
  58. package/dist/insights/regret.d.ts.map +1 -0
  59. package/dist/insights/regret.js +137 -0
  60. package/dist/insights/regret.js.map +1 -0
  61. package/dist/insights/regret.test.d.ts +2 -0
  62. package/dist/insights/regret.test.d.ts.map +1 -0
  63. package/dist/insights/regret.test.js +153 -0
  64. package/dist/insights/regret.test.js.map +1 -0
  65. package/dist/insights/time-machine.d.ts +70 -0
  66. package/dist/insights/time-machine.d.ts.map +1 -0
  67. package/dist/insights/time-machine.js +177 -0
  68. package/dist/insights/time-machine.js.map +1 -0
  69. package/dist/insights/time-machine.test.d.ts +2 -0
  70. package/dist/insights/time-machine.test.d.ts.map +1 -0
  71. package/dist/insights/time-machine.test.js +141 -0
  72. package/dist/insights/time-machine.test.js.map +1 -0
  73. package/dist/insights/who-knows.d.ts +18 -0
  74. package/dist/insights/who-knows.d.ts.map +1 -1
  75. package/dist/insights/who-knows.js +29 -0
  76. package/dist/insights/who-knows.js.map +1 -1
  77. package/dist/insights/who-knows.test.js +63 -1
  78. package/dist/insights/who-knows.test.js.map +1 -1
  79. package/dist/quant/alpha.d.ts +87 -0
  80. package/dist/quant/alpha.d.ts.map +1 -0
  81. package/dist/quant/alpha.js +103 -0
  82. package/dist/quant/alpha.js.map +1 -0
  83. package/dist/quant/alpha.test.d.ts +2 -0
  84. package/dist/quant/alpha.test.d.ts.map +1 -0
  85. package/dist/quant/alpha.test.js +147 -0
  86. package/dist/quant/alpha.test.js.map +1 -0
  87. package/dist/quant/backtest.d.ts +57 -0
  88. package/dist/quant/backtest.d.ts.map +1 -0
  89. package/dist/quant/backtest.js +90 -0
  90. package/dist/quant/backtest.js.map +1 -0
  91. package/dist/quant/backtest.test.d.ts +2 -0
  92. package/dist/quant/backtest.test.d.ts.map +1 -0
  93. package/dist/quant/backtest.test.js +133 -0
  94. package/dist/quant/backtest.test.js.map +1 -0
  95. package/dist/quant/black-swan.d.ts +45 -0
  96. package/dist/quant/black-swan.d.ts.map +1 -0
  97. package/dist/quant/black-swan.js +112 -0
  98. package/dist/quant/black-swan.js.map +1 -0
  99. package/dist/quant/black-swan.test.d.ts +2 -0
  100. package/dist/quant/black-swan.test.d.ts.map +1 -0
  101. package/dist/quant/black-swan.test.js +131 -0
  102. package/dist/quant/black-swan.test.js.map +1 -0
  103. package/dist/quant/correlation-matrix.d.ts +54 -0
  104. package/dist/quant/correlation-matrix.d.ts.map +1 -0
  105. package/dist/quant/correlation-matrix.js +103 -0
  106. package/dist/quant/correlation-matrix.js.map +1 -0
  107. package/dist/quant/correlation-matrix.test.d.ts +2 -0
  108. package/dist/quant/correlation-matrix.test.d.ts.map +1 -0
  109. package/dist/quant/correlation-matrix.test.js +118 -0
  110. package/dist/quant/correlation-matrix.test.js.map +1 -0
  111. package/dist/quant/drawdown.d.ts +51 -0
  112. package/dist/quant/drawdown.d.ts.map +1 -0
  113. package/dist/quant/drawdown.js +96 -0
  114. package/dist/quant/drawdown.js.map +1 -0
  115. package/dist/quant/drawdown.test.d.ts +2 -0
  116. package/dist/quant/drawdown.test.d.ts.map +1 -0
  117. package/dist/quant/drawdown.test.js +166 -0
  118. package/dist/quant/drawdown.test.js.map +1 -0
  119. package/dist/quant/greek.d.ts +55 -0
  120. package/dist/quant/greek.d.ts.map +1 -0
  121. package/dist/quant/greek.js +157 -0
  122. package/dist/quant/greek.js.map +1 -0
  123. package/dist/quant/greek.test.d.ts +2 -0
  124. package/dist/quant/greek.test.d.ts.map +1 -0
  125. package/dist/quant/greek.test.js +138 -0
  126. package/dist/quant/greek.test.js.map +1 -0
  127. package/dist/quant/implied-volatility.d.ts +65 -0
  128. package/dist/quant/implied-volatility.d.ts.map +1 -0
  129. package/dist/quant/implied-volatility.js +149 -0
  130. package/dist/quant/implied-volatility.js.map +1 -0
  131. package/dist/quant/implied-volatility.test.d.ts +2 -0
  132. package/dist/quant/implied-volatility.test.d.ts.map +1 -0
  133. package/dist/quant/implied-volatility.test.js +127 -0
  134. package/dist/quant/implied-volatility.test.js.map +1 -0
  135. package/dist/quant/index.d.ts +28 -0
  136. package/dist/quant/index.d.ts.map +1 -0
  137. package/dist/quant/index.js +28 -0
  138. package/dist/quant/index.js.map +1 -0
  139. package/dist/quant/insider-trading.d.ts +56 -0
  140. package/dist/quant/insider-trading.d.ts.map +1 -0
  141. package/dist/quant/insider-trading.js +129 -0
  142. package/dist/quant/insider-trading.js.map +1 -0
  143. package/dist/quant/insider-trading.test.d.ts +2 -0
  144. package/dist/quant/insider-trading.test.d.ts.map +1 -0
  145. package/dist/quant/insider-trading.test.js +130 -0
  146. package/dist/quant/insider-trading.test.js.map +1 -0
  147. package/dist/quant/moneyball.d.ts +48 -0
  148. package/dist/quant/moneyball.d.ts.map +1 -0
  149. package/dist/quant/moneyball.js +110 -0
  150. package/dist/quant/moneyball.js.map +1 -0
  151. package/dist/quant/moneyball.test.d.ts +2 -0
  152. package/dist/quant/moneyball.test.d.ts.map +1 -0
  153. package/dist/quant/moneyball.test.js +137 -0
  154. package/dist/quant/moneyball.test.js.map +1 -0
  155. package/dist/quant/tax-loss-harvest.d.ts +59 -0
  156. package/dist/quant/tax-loss-harvest.d.ts.map +1 -0
  157. package/dist/quant/tax-loss-harvest.js +126 -0
  158. package/dist/quant/tax-loss-harvest.js.map +1 -0
  159. package/dist/quant/tax-loss-harvest.test.d.ts +2 -0
  160. package/dist/quant/tax-loss-harvest.test.d.ts.map +1 -0
  161. package/dist/quant/tax-loss-harvest.test.js +126 -0
  162. package/dist/quant/tax-loss-harvest.test.js.map +1 -0
  163. package/dist/retrieve/synthesize.d.ts.map +1 -1
  164. package/dist/retrieve/synthesize.js +56 -25
  165. package/dist/retrieve/synthesize.js.map +1 -1
  166. package/dist/retrieve/synthesize.test.js +26 -15
  167. package/dist/retrieve/synthesize.test.js.map +1 -1
  168. package/dist/store/schema.d.ts +2 -2
  169. package/dist/store/schema.d.ts.map +1 -1
  170. package/dist/store/schema.js +6 -2
  171. package/dist/store/schema.js.map +1 -1
  172. package/dist/store/sqlite.d.ts +2 -0
  173. package/dist/store/sqlite.d.ts.map +1 -1
  174. package/dist/store/sqlite.js +24 -0
  175. package/dist/store/sqlite.js.map +1 -1
  176. package/dist/store/sqlite.test.js +1 -1
  177. package/dist/util/index.d.ts +4 -0
  178. package/dist/util/index.d.ts.map +1 -1
  179. package/dist/util/index.js +26 -0
  180. package/dist/util/index.js.map +1 -1
  181. package/package.json +1 -1
@@ -0,0 +1,90 @@
1
+ /**
2
+ * `mneme backtest` — validate insight commands retroactively.
3
+ *
4
+ * The killer property: every prediction Mneme makes ("this is risky") can
5
+ * be replayed against actual history to compute precision, recall, F1, and
6
+ * lift over a random baseline. This turns "we have an AI tool" into
7
+ * "we have an AI tool with measured edge against the past".
8
+ *
9
+ * Backtest works for any binary predictor: given a set of (commit,
10
+ * prediction) pairs and a window in which to count "trouble" outcomes,
11
+ * compute the standard classification metrics.
12
+ *
13
+ * Pure data analysis — no LLM. The actual replay (re-running a command at
14
+ * a frozen point in history) lives in the CLI command, but the metric
15
+ * math is here and unit-testable.
16
+ */
17
+ /**
18
+ * Compute classification metrics + verdict from a list of (predicted,
19
+ * actual) samples. Pure math — no I/O, deterministic.
20
+ */
21
+ export function backtest(samples) {
22
+ const n = samples.length;
23
+ let tp = 0;
24
+ let fp = 0;
25
+ let tn = 0;
26
+ let fn = 0;
27
+ for (const s of samples) {
28
+ if (s.predicted && s.actual)
29
+ tp += 1;
30
+ else if (s.predicted && !s.actual)
31
+ fp += 1;
32
+ else if (!s.predicted && !s.actual)
33
+ tn += 1;
34
+ else
35
+ fn += 1;
36
+ }
37
+ const precision = tp + fp === 0 ? 0 : tp / (tp + fp);
38
+ const recall = tp + fn === 0 ? 0 : tp / (tp + fn);
39
+ const f1 = precision + recall === 0 ? 0 : (2 * precision * recall) / (precision + recall);
40
+ const baseRate = n === 0 ? 0 : (tp + fn) / n;
41
+ const lift = baseRate === 0 ? 0 : precision / baseRate;
42
+ const verdict = classifyVerdict(lift, precision, recall, n);
43
+ return {
44
+ n,
45
+ truePositives: tp,
46
+ falsePositives: fp,
47
+ trueNegatives: tn,
48
+ falseNegatives: fn,
49
+ precision,
50
+ recall,
51
+ f1,
52
+ baseRate,
53
+ lift,
54
+ verdict,
55
+ conclusion: buildConclusion(verdict, n, precision, recall, lift),
56
+ };
57
+ }
58
+ export function classifyVerdict(lift, precision, recall, n) {
59
+ if (n < 5)
60
+ return "no-edge"; // sample too small
61
+ if (lift >= 2.5 && precision >= 0.6 && recall >= 0.5)
62
+ return "strong-edge";
63
+ if (lift >= 1.5 && precision >= 0.4)
64
+ return "real-edge";
65
+ if (lift >= 1.1)
66
+ return "weak";
67
+ return "no-edge";
68
+ }
69
+ function buildConclusion(verdict, n, precision, recall, lift) {
70
+ switch (verdict) {
71
+ case "no-edge":
72
+ if (n < 5)
73
+ return `Sample size too small (n=${n}) to draw conclusions.`;
74
+ return `No detectable edge over random — precision ${(precision * 100).toFixed(0)}%, lift ${lift.toFixed(2)}×.`;
75
+ case "weak":
76
+ return `Weak edge — beats random by ${((lift - 1) * 100).toFixed(0)}% but precision is still ${(precision * 100).toFixed(0)}%. Use as a soft prior.`;
77
+ case "real-edge":
78
+ return `Real predictive edge — precision ${(precision * 100).toFixed(0)}%, ${lift.toFixed(1)}× over random.`;
79
+ case "strong-edge":
80
+ return `Strong edge — precision ${(precision * 100).toFixed(0)}%, recall ${(recall * 100).toFixed(0)}%, ${lift.toFixed(1)}× over random. Trust this predictor.`;
81
+ }
82
+ }
83
+ /**
84
+ * Aggregate a backtest result into a one-line markdown badge for the
85
+ * README / docs. Format: "F1 = 0.67 · 2.4× lift · n=14".
86
+ */
87
+ export function badge(result) {
88
+ return `F1 = ${result.f1.toFixed(2)} · ${result.lift.toFixed(1)}× lift · n=${result.n}`;
89
+ }
90
+ //# sourceMappingURL=backtest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backtest.js","sourceRoot":"","sources":["../../src/quant/backtest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=backtest.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backtest.test.d.ts","sourceRoot":"","sources":["../../src/quant/backtest.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,133 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { backtest, classifyVerdict, badge } from "./backtest.js";
3
+ describe("backtest — confusion matrix counts", () => {
4
+ it("perfect predictor — all TP, no FP/FN", () => {
5
+ const r = backtest([
6
+ { id: "a", predicted: true, actual: true },
7
+ { id: "b", predicted: true, actual: true },
8
+ { id: "c", predicted: false, actual: false },
9
+ { id: "d", predicted: false, actual: false },
10
+ ]);
11
+ expect(r.truePositives).toBe(2);
12
+ expect(r.falsePositives).toBe(0);
13
+ expect(r.trueNegatives).toBe(2);
14
+ expect(r.falseNegatives).toBe(0);
15
+ expect(r.precision).toBe(1);
16
+ expect(r.recall).toBe(1);
17
+ expect(r.f1).toBe(1);
18
+ });
19
+ it("worst predictor — all FP and FN", () => {
20
+ const r = backtest([
21
+ { id: "a", predicted: true, actual: false },
22
+ { id: "b", predicted: true, actual: false },
23
+ { id: "c", predicted: false, actual: true },
24
+ { id: "d", predicted: false, actual: true },
25
+ ]);
26
+ expect(r.truePositives).toBe(0);
27
+ expect(r.precision).toBe(0);
28
+ expect(r.recall).toBe(0);
29
+ expect(r.f1).toBe(0);
30
+ });
31
+ it("balanced realistic case", () => {
32
+ // 14 samples, 6 predicted high-risk, 4 of those actually had incidents
33
+ // 8 predicted clean, 2 actually had incidents
34
+ // Total positives: 6 (4 + 2)
35
+ const samples = [
36
+ { id: "1", predicted: true, actual: true },
37
+ { id: "2", predicted: true, actual: true },
38
+ { id: "3", predicted: true, actual: true },
39
+ { id: "4", predicted: true, actual: true },
40
+ { id: "5", predicted: true, actual: false },
41
+ { id: "6", predicted: true, actual: false },
42
+ { id: "7", predicted: false, actual: true },
43
+ { id: "8", predicted: false, actual: true },
44
+ { id: "9", predicted: false, actual: false },
45
+ { id: "10", predicted: false, actual: false },
46
+ { id: "11", predicted: false, actual: false },
47
+ { id: "12", predicted: false, actual: false },
48
+ { id: "13", predicted: false, actual: false },
49
+ { id: "14", predicted: false, actual: false },
50
+ ];
51
+ const r = backtest(samples);
52
+ expect(r.precision).toBeCloseTo(4 / 6, 4);
53
+ expect(r.recall).toBeCloseTo(4 / 6, 4);
54
+ expect(r.f1).toBeCloseTo(4 / 6, 4);
55
+ expect(r.baseRate).toBeCloseTo(6 / 14, 4);
56
+ expect(r.lift).toBeCloseTo((4 / 6) / (6 / 14), 4);
57
+ });
58
+ });
59
+ describe("backtest — edge cases", () => {
60
+ it("empty input returns zeros + no-edge verdict", () => {
61
+ const r = backtest([]);
62
+ expect(r.n).toBe(0);
63
+ expect(r.precision).toBe(0);
64
+ expect(r.recall).toBe(0);
65
+ expect(r.f1).toBe(0);
66
+ expect(r.verdict).toBe("no-edge");
67
+ });
68
+ it("no positive predictions → precision = 0 (avoids div by zero)", () => {
69
+ const r = backtest([
70
+ { id: "1", predicted: false, actual: false },
71
+ { id: "2", predicted: false, actual: true },
72
+ ]);
73
+ expect(r.precision).toBe(0);
74
+ });
75
+ it("no actual positives → recall = 0", () => {
76
+ const r = backtest([
77
+ { id: "1", predicted: true, actual: false },
78
+ { id: "2", predicted: false, actual: false },
79
+ ]);
80
+ expect(r.recall).toBe(0);
81
+ });
82
+ });
83
+ describe("classifyVerdict — tier from lift × precision × recall", () => {
84
+ it("strong-edge for high precision + recall + 2.5× lift", () => {
85
+ expect(classifyVerdict(3, 0.7, 0.6, 20)).toBe("strong-edge");
86
+ });
87
+ it("real-edge for moderate metrics", () => {
88
+ expect(classifyVerdict(1.8, 0.5, 0.4, 20)).toBe("real-edge");
89
+ });
90
+ it("weak for lift ≥ 1.1 only", () => {
91
+ expect(classifyVerdict(1.2, 0.3, 0.3, 20)).toBe("weak");
92
+ });
93
+ it("no-edge for too-small samples", () => {
94
+ expect(classifyVerdict(5, 0.9, 0.9, 4)).toBe("no-edge");
95
+ });
96
+ it("no-edge when no lift over random", () => {
97
+ expect(classifyVerdict(0.9, 0.3, 0.3, 20)).toBe("no-edge");
98
+ });
99
+ });
100
+ describe("badge — one-line summary string", () => {
101
+ it("includes F1, lift, and n", () => {
102
+ const r = backtest([
103
+ { id: "1", predicted: true, actual: true },
104
+ { id: "2", predicted: true, actual: false },
105
+ { id: "3", predicted: false, actual: false },
106
+ { id: "4", predicted: false, actual: false },
107
+ { id: "5", predicted: false, actual: false },
108
+ ]);
109
+ const b = badge(r);
110
+ expect(b).toMatch(/F1 = \d/);
111
+ expect(b).toMatch(/× lift/);
112
+ expect(b).toMatch(/n=5/);
113
+ });
114
+ });
115
+ describe("backtest — conclusion text adapts to verdict", () => {
116
+ it("no-edge mentions sample size for small n", () => {
117
+ const r = backtest([{ id: "1", predicted: true, actual: true }]);
118
+ expect(r.conclusion.toLowerCase()).toContain("sample");
119
+ });
120
+ it("strong-edge says 'trust' or 'real predictive'", () => {
121
+ // build samples with strong edge: lift ≥ 2.5, precision ≥ 0.6, recall ≥ 0.5
122
+ const samples = [
123
+ ...Array.from({ length: 8 }, (_, i) => ({ id: `tp${i}`, predicted: true, actual: true })),
124
+ ...Array.from({ length: 2 }, (_, i) => ({ id: `fp${i}`, predicted: true, actual: false })),
125
+ ...Array.from({ length: 4 }, (_, i) => ({ id: `fn${i}`, predicted: false, actual: true })),
126
+ ...Array.from({ length: 36 }, (_, i) => ({ id: `tn${i}`, predicted: false, actual: false })),
127
+ ];
128
+ const r = backtest(samples);
129
+ expect(r.verdict).toBe("strong-edge");
130
+ expect(r.conclusion.toLowerCase()).toMatch(/trust|strong/);
131
+ });
132
+ });
133
+ //# sourceMappingURL=backtest.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backtest.test.js","sourceRoot":"","sources":["../../src/quant/backtest.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `mneme black-swan` — find rare-but-catastrophic file patterns.
3
+ *
4
+ * Inspired by Taleb: real risk lives in the tail. Files touched rarely
5
+ * but tied to high-severity incidents are far more dangerous than
6
+ * frequently-touched files with proportional bug counts.
7
+ *
8
+ * tail_risk = log(avg_severity + 1) × (1 / max(touch_frequency, 1))
9
+ *
10
+ * Files surface ranked by tail_risk — the silent assassins that look
11
+ * stable but explode when touched.
12
+ *
13
+ * Pure store-backed analysis. No LLM.
14
+ */
15
+ import type { MnemeStore } from "../store/sqlite.js";
16
+ export interface BlackSwanCandidate {
17
+ filePath: string;
18
+ /** Total commits that ever touched this file. */
19
+ touchCount: number;
20
+ /** Days since the last touch. */
21
+ daysSinceTouch: number;
22
+ /** Number of incidents associated with this file. */
23
+ incidentCount: number;
24
+ /** Mean severity (1=low, 5=critical). */
25
+ avgSeverity: number;
26
+ /** Tail-risk score — see formula above. */
27
+ tailRisk: number;
28
+ /** Tier label for output. */
29
+ tier: "deceptive-calm" | "elevated" | "watch" | "background";
30
+ /** A 1-line operational recommendation. */
31
+ recommendation: string;
32
+ }
33
+ /**
34
+ * Walk every indexed file, compute touch frequency + linked-incident
35
+ * severity, and rank by tail risk. Conservative defaults: files with
36
+ * < incidents are skipped (no tail without trouble).
37
+ */
38
+ export declare function findBlackSwans(store: MnemeStore, opts?: {
39
+ topN?: number;
40
+ minIncidents?: number;
41
+ maxTouches?: number;
42
+ now?: Date;
43
+ }): BlackSwanCandidate[];
44
+ export declare function classifyBlackSwanTier(tailRisk: number, touches: number, avgSeverity: number): BlackSwanCandidate["tier"];
45
+ //# sourceMappingURL=black-swan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"black-swan.d.ts","sourceRoot":"","sources":["../../src/quant/black-swan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,IAAI,EAAE,gBAAgB,GAAG,UAAU,GAAG,OAAO,GAAG,YAAY,CAAC;IAC7D,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAC;CACxB;AAUD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,EACjB,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAA;CAAO,GACnF,kBAAkB,EAAE,CAiEtB;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAClB,kBAAkB,CAAC,MAAM,CAAC,CAK5B"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * `mneme black-swan` — find rare-but-catastrophic file patterns.
3
+ *
4
+ * Inspired by Taleb: real risk lives in the tail. Files touched rarely
5
+ * but tied to high-severity incidents are far more dangerous than
6
+ * frequently-touched files with proportional bug counts.
7
+ *
8
+ * tail_risk = log(avg_severity + 1) × (1 / max(touch_frequency, 1))
9
+ *
10
+ * Files surface ranked by tail_risk — the silent assassins that look
11
+ * stable but explode when touched.
12
+ *
13
+ * Pure store-backed analysis. No LLM.
14
+ */
15
+ const SEVERITY_RANK = {
16
+ critical: 5,
17
+ high: 4,
18
+ medium: 3,
19
+ low: 2,
20
+ info: 1,
21
+ };
22
+ /**
23
+ * Walk every indexed file, compute touch frequency + linked-incident
24
+ * severity, and rank by tail risk. Conservative defaults: files with
25
+ * < incidents are skipped (no tail without trouble).
26
+ */
27
+ export function findBlackSwans(store, opts = {}) {
28
+ const topN = opts.topN ?? 10;
29
+ const minIncidents = opts.minIncidents ?? 1;
30
+ const maxTouches = opts.maxTouches ?? 30;
31
+ const now = opts.now ?? new Date();
32
+ // Pull file → (touch count, last touch).
33
+ const fileRows = store.db
34
+ .prepare(`SELECT
35
+ fc.path AS path,
36
+ COUNT(*) AS touches,
37
+ MAX(c.author_date) AS last_touch
38
+ FROM file_changes fc
39
+ JOIN commits c ON c.hash = fc.commit_hash
40
+ GROUP BY fc.path
41
+ HAVING touches <= ?`)
42
+ .all(maxTouches);
43
+ // Pull file → incidents (parsed from incidents.affected_files).
44
+ const incidentRows = store.db
45
+ .prepare(`SELECT severity, affected_files FROM incidents`)
46
+ .all();
47
+ // Build file → { incidentCount, severities[] }
48
+ const incidentsByFile = new Map();
49
+ for (const r of incidentRows) {
50
+ if (!r.affected_files)
51
+ continue;
52
+ let files = [];
53
+ try {
54
+ const parsed = JSON.parse(r.affected_files);
55
+ if (Array.isArray(parsed))
56
+ files = parsed.filter((x) => typeof x === "string");
57
+ }
58
+ catch {
59
+ // Fallback: comma-separated paths
60
+ files = r.affected_files.split(",").map((s) => s.trim()).filter(Boolean);
61
+ }
62
+ const sev = SEVERITY_RANK[r.severity?.toLowerCase()] ?? 3;
63
+ for (const f of files) {
64
+ if (!incidentsByFile.has(f))
65
+ incidentsByFile.set(f, []);
66
+ incidentsByFile.get(f).push(sev);
67
+ }
68
+ }
69
+ const candidates = [];
70
+ for (const r of fileRows) {
71
+ const sevs = incidentsByFile.get(r.path) ?? [];
72
+ if (sevs.length < minIncidents)
73
+ continue;
74
+ const avgSeverity = sevs.reduce((s, x) => s + x, 0) / sevs.length;
75
+ const daysSinceTouch = (now.getTime() - new Date(r.last_touch).getTime()) / 86_400_000;
76
+ const tailRisk = Math.log(avgSeverity + 1) * (1 / Math.max(r.touches, 1));
77
+ candidates.push({
78
+ filePath: r.path,
79
+ touchCount: r.touches,
80
+ daysSinceTouch: Math.round(daysSinceTouch),
81
+ incidentCount: sevs.length,
82
+ avgSeverity: Math.round(avgSeverity * 10) / 10,
83
+ tailRisk,
84
+ tier: classifyBlackSwanTier(tailRisk, r.touches, avgSeverity),
85
+ recommendation: buildBlackSwanRecommendation(r.touches, avgSeverity, daysSinceTouch),
86
+ });
87
+ }
88
+ candidates.sort((a, b) => b.tailRisk - a.tailRisk);
89
+ return candidates.slice(0, topN);
90
+ }
91
+ export function classifyBlackSwanTier(tailRisk, touches, avgSeverity) {
92
+ if (tailRisk >= 0.8 && touches <= 3 && avgSeverity >= 4)
93
+ return "deceptive-calm";
94
+ if (tailRisk >= 0.5 && avgSeverity >= 3)
95
+ return "elevated";
96
+ if (tailRisk >= 0.2)
97
+ return "watch";
98
+ return "background";
99
+ }
100
+ function buildBlackSwanRecommendation(touches, avgSeverity, daysSinceTouch) {
101
+ if (touches <= 2 && avgSeverity >= 4) {
102
+ return "Mandatory pair-program + canary deploy. This file LOOKS stable but its track record is catastrophic.";
103
+ }
104
+ if (avgSeverity >= 4) {
105
+ return "Code-freeze without 2 reviewers + load test required. High tail risk on edits.";
106
+ }
107
+ if (daysSinceTouch > 365) {
108
+ return "Untouched for 1+ year. Schedule a review session before the next change.";
109
+ }
110
+ return "Monitor closely. Run mneme blast on any commit that touches this file.";
111
+ }
112
+ //# sourceMappingURL=black-swan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"black-swan.js","sourceRoot":"","sources":["../../src/quant/black-swan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=black-swan.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"black-swan.test.d.ts","sourceRoot":"","sources":["../../src/quant/black-swan.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { MnemeStore } from "../store/sqlite.js";
6
+ import { findBlackSwans, classifyBlackSwanTier } from "./black-swan.js";
7
+ let tmpDir;
8
+ let store;
9
+ beforeEach(() => {
10
+ tmpDir = mkdtempSync(join(tmpdir(), "mneme-bs-"));
11
+ store = new MnemeStore(join(tmpDir, "mneme.db"));
12
+ });
13
+ afterEach(() => {
14
+ store.close();
15
+ rmSync(tmpDir, { recursive: true, force: true });
16
+ });
17
+ const cmt = (hash, date, files) => ({
18
+ hash,
19
+ shortHash: hash.slice(0, 7),
20
+ authorName: "alice",
21
+ authorEmail: "a@x",
22
+ authorDate: `${date}T00:00:00Z`,
23
+ committerDate: `${date}T00:00:00Z`,
24
+ subject: "commit",
25
+ body: "",
26
+ parents: [],
27
+ files,
28
+ });
29
+ function seedCommits(commits) {
30
+ store.upsertCommits(commits);
31
+ for (const c of commits) {
32
+ store.upsertFileChanges(c.files.map((f) => ({
33
+ commitHash: c.hash,
34
+ path: f,
35
+ changeKind: "M",
36
+ insertions: 1,
37
+ deletions: 0,
38
+ })));
39
+ }
40
+ }
41
+ function seedIncident(severity, affectedFiles) {
42
+ store.db
43
+ .prepare(`INSERT INTO incidents (id, source, title, occurred_at, severity, affected_files)
44
+ VALUES (?, 'manual', ?, ?, ?, ?)`)
45
+ .run(`inc-${Math.random()}`, "test", "2024-12-01T00:00:00Z", severity, JSON.stringify(affectedFiles));
46
+ }
47
+ describe("classifyBlackSwanTier", () => {
48
+ it('"deceptive-calm" for high tail risk + few touches + critical severity', () => {
49
+ expect(classifyBlackSwanTier(1.5, 2, 4.5)).toBe("deceptive-calm");
50
+ });
51
+ it('"elevated" for moderate tail risk + medium severity', () => {
52
+ expect(classifyBlackSwanTier(0.6, 5, 3.0)).toBe("elevated");
53
+ });
54
+ it('"watch" for low tail risk', () => {
55
+ expect(classifyBlackSwanTier(0.3, 10, 2.0)).toBe("watch");
56
+ });
57
+ it('"background" for tiny tail risk', () => {
58
+ expect(classifyBlackSwanTier(0.05, 30, 1.5)).toBe("background");
59
+ });
60
+ });
61
+ describe("findBlackSwans — basic detection", () => {
62
+ it("returns empty array when no incidents are linked to files", () => {
63
+ seedCommits([cmt("a1", "2024-01-01", ["src/x.ts"])]);
64
+ expect(findBlackSwans(store)).toEqual([]);
65
+ });
66
+ it("flags a low-touch + high-severity file as deceptive-calm", () => {
67
+ seedCommits([
68
+ cmt("a1", "2024-01-01", ["src/refund.ts"]),
69
+ cmt("a2", "2024-06-01", ["src/refund.ts"]),
70
+ ]);
71
+ seedIncident("critical", ["src/refund.ts"]);
72
+ seedIncident("critical", ["src/refund.ts"]);
73
+ const candidates = findBlackSwans(store, { now: new Date("2024-12-01T00:00:00Z") });
74
+ const refund = candidates.find((c) => c.filePath === "src/refund.ts");
75
+ expect(refund).toBeDefined();
76
+ expect(refund.incidentCount).toBe(2);
77
+ expect(refund.avgSeverity).toBe(5);
78
+ expect(refund.tier).toBe("deceptive-calm");
79
+ });
80
+ it("respects maxTouches filter — high-traffic files are NOT black swans", () => {
81
+ const commits = Array.from({ length: 100 }, (_, i) => cmt(`a${i}`.padEnd(7, "x"), `2024-${String(((i % 12) + 1)).padStart(2, "0")}-01`, ["src/hot.ts"]));
82
+ seedCommits(commits);
83
+ seedIncident("critical", ["src/hot.ts"]);
84
+ expect(findBlackSwans(store, { maxTouches: 30 }).find((c) => c.filePath === "src/hot.ts")).toBeUndefined();
85
+ });
86
+ it("respects minIncidents filter (default 1)", () => {
87
+ seedCommits([cmt("a1", "2024-01-01", ["src/never-broke.ts"])]);
88
+ // No incident seeded — should be excluded
89
+ expect(findBlackSwans(store).find((c) => c.filePath === "src/never-broke.ts")).toBeUndefined();
90
+ });
91
+ it("sorts by tailRisk descending", () => {
92
+ seedCommits([
93
+ cmt("a1", "2024-01-01", ["src/calm.ts"]),
94
+ cmt("a2", "2024-02-01", ["src/calm.ts"]),
95
+ cmt("b1", "2024-01-01", ["src/medium.ts"]),
96
+ cmt("b2", "2024-02-01", ["src/medium.ts"]),
97
+ cmt("b3", "2024-03-01", ["src/medium.ts"]),
98
+ cmt("b4", "2024-04-01", ["src/medium.ts"]),
99
+ cmt("b5", "2024-05-01", ["src/medium.ts"]),
100
+ ]);
101
+ seedIncident("critical", ["src/calm.ts"]); // severity 5, 2 touches → high tail
102
+ seedIncident("medium", ["src/medium.ts"]); // severity 3, 5 touches → low tail
103
+ const candidates = findBlackSwans(store);
104
+ expect(candidates[0].filePath).toBe("src/calm.ts");
105
+ });
106
+ it("respects topN cap", () => {
107
+ for (let i = 0; i < 30; i++) {
108
+ seedCommits([cmt(`x${i}`.padEnd(7, "x"), "2024-01-01", [`src/f${i}.ts`])]);
109
+ seedIncident("critical", [`src/f${i}.ts`]);
110
+ }
111
+ expect(findBlackSwans(store, { topN: 5 }).length).toBeLessThanOrEqual(5);
112
+ });
113
+ });
114
+ describe("findBlackSwans — recommendation text", () => {
115
+ it("recommends pair-program + canary for low-touch + critical files", () => {
116
+ seedCommits([cmt("a1", "2024-01-01", ["src/refund.ts"])]);
117
+ seedIncident("critical", ["src/refund.ts"]);
118
+ const c = findBlackSwans(store)[0];
119
+ expect(c.recommendation.toLowerCase()).toMatch(/pair-program|canary/);
120
+ });
121
+ it("recommends review session for files untouched > 1 year", () => {
122
+ seedCommits([
123
+ cmt("a1", "2022-01-01", ["src/legacy.ts"]),
124
+ cmt("a2", "2022-02-01", ["src/legacy.ts"]),
125
+ ]);
126
+ seedIncident("medium", ["src/legacy.ts"]);
127
+ const c = findBlackSwans(store, { now: new Date("2024-12-01T00:00:00Z") }).find((x) => x.filePath === "src/legacy.ts");
128
+ expect(c.recommendation.toLowerCase()).toMatch(/review|untouched/);
129
+ });
130
+ });
131
+ //# sourceMappingURL=black-swan.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"black-swan.test.js","sourceRoot":"","sources":["../../src/quant/black-swan.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
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
+ import type { Commit } from "../types.js";
18
+ export interface CouplingPair {
19
+ fileA: string;
20
+ fileB: string;
21
+ /** Total commits that touched A. */
22
+ countA: number;
23
+ /** Total commits that touched B. */
24
+ countB: number;
25
+ /** Commits that touched A AND B together. */
26
+ coOccurrences: number;
27
+ /** Jaccard similarity = co_occurrences / (A + B - co_occurrences). */
28
+ jaccard: number;
29
+ /** Lift = P(B|A) / P(B) — how much MORE likely B is touched given A. */
30
+ lift: number;
31
+ /** Tier label. */
32
+ tier: "tight" | "strong" | "moderate" | "weak";
33
+ /** Plain-English interpretation. */
34
+ interpretation: string;
35
+ }
36
+ export interface CorrelationOptions {
37
+ /** Minimum total touches per file to consider. */
38
+ minFileTouches?: number;
39
+ /** Minimum co-occurrences to surface a pair. */
40
+ minCoOccurrences?: number;
41
+ /** Top-N pairs to return. */
42
+ topN?: number;
43
+ /** Skip pairs where lift below this. */
44
+ minLift?: number;
45
+ }
46
+ /**
47
+ * Build the file-pair coupling matrix from commit history.
48
+ *
49
+ * Algorithm: for each commit, mark every pair (file_i, file_j) as co-touched.
50
+ * Aggregate counts; compute Jaccard + lift per pair.
51
+ */
52
+ export declare function correlationMatrix(commits: Commit[], opts?: CorrelationOptions): CouplingPair[];
53
+ export declare function classifyCouplingTier(jaccard: number, lift: number): CouplingPair["tier"];
54
+ //# sourceMappingURL=correlation-matrix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation-matrix.d.ts","sourceRoot":"","sources":["../../src/quant/correlation-matrix.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;IAC/C,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,kBAAuB,GAAG,YAAY,EAAE,CAwDlG;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAQxF"}