@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,147 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { kellyAllocate, classifyTier, estimateEdge, estimateVariance } from "./alpha.js";
|
|
3
|
+
const item = (overrides) => ({
|
|
4
|
+
id: "x",
|
|
5
|
+
name: "X",
|
|
6
|
+
edge: 0.1,
|
|
7
|
+
variance: 0.05,
|
|
8
|
+
effortDays: 5,
|
|
9
|
+
...overrides,
|
|
10
|
+
});
|
|
11
|
+
describe("kellyAllocate — basic invariants", () => {
|
|
12
|
+
it("never allocates negative days", () => {
|
|
13
|
+
const r = kellyAllocate([
|
|
14
|
+
item({ id: "good", edge: 0.2, variance: 0.05 }),
|
|
15
|
+
item({ id: "bad", edge: -0.1, variance: 0.05 }),
|
|
16
|
+
], { budgetDays: 20 });
|
|
17
|
+
for (const a of r.items)
|
|
18
|
+
expect(a.allocatedDays).toBeGreaterThanOrEqual(0);
|
|
19
|
+
expect(r.items.find((a) => a.id === "bad").allocatedDays).toBe(0);
|
|
20
|
+
expect(r.items.find((a) => a.id === "bad").tier).toBe("skip");
|
|
21
|
+
});
|
|
22
|
+
it("raw Kelly is edge / variance", () => {
|
|
23
|
+
const r = kellyAllocate([item({ id: "x", edge: 0.2, variance: 0.05 })], { budgetDays: 100 });
|
|
24
|
+
expect(r.items[0].rawKelly).toBeCloseTo(4, 6);
|
|
25
|
+
});
|
|
26
|
+
it("applies fractional Kelly multiplier (default 0.25)", () => {
|
|
27
|
+
const r = kellyAllocate([item({ id: "x", edge: 0.2, variance: 0.1 })], { budgetDays: 100 });
|
|
28
|
+
// raw kelly = 2.0, × 0.25 = 0.5 (clamped to ceiling)
|
|
29
|
+
expect(r.items[0].kellyFraction).toBeLessThanOrEqual(0.5);
|
|
30
|
+
});
|
|
31
|
+
it("clamps any single item to ≤ 50% of budget", () => {
|
|
32
|
+
const r = kellyAllocate([item({ edge: 1.0, variance: 0.001 })], { budgetDays: 100, multiplier: 1 });
|
|
33
|
+
expect(r.items[0].kellyFraction).toBeLessThanOrEqual(0.5);
|
|
34
|
+
});
|
|
35
|
+
it("totalAllocated never exceeds budgetDays", () => {
|
|
36
|
+
const items = [];
|
|
37
|
+
for (let i = 0; i < 10; i++) {
|
|
38
|
+
items.push(item({ id: `i${i}`, edge: 0.3, variance: 0.05 }));
|
|
39
|
+
}
|
|
40
|
+
const r = kellyAllocate(items, { budgetDays: 25 });
|
|
41
|
+
expect(r.totalAllocated).toBeLessThanOrEqual(25);
|
|
42
|
+
});
|
|
43
|
+
it("reserves at least reserveFraction of the budget by default", () => {
|
|
44
|
+
const items = Array.from({ length: 5 }, (_, i) => item({ id: `i${i}`, edge: 0.5, variance: 0.05 }));
|
|
45
|
+
const r = kellyAllocate(items, { budgetDays: 25, reserveFraction: 0.2 });
|
|
46
|
+
expect(r.totalAllocated).toBeLessThanOrEqual(25 * 0.8 + 0.5); // float wiggle
|
|
47
|
+
expect(r.reserveDays).toBeGreaterThanOrEqual(25 * 0.2 - 0.5);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe("kellyAllocate — sorting + tiers", () => {
|
|
51
|
+
it("sorts by allocated days desc", () => {
|
|
52
|
+
const r = kellyAllocate([
|
|
53
|
+
item({ id: "small", edge: 0.05, variance: 0.05 }),
|
|
54
|
+
item({ id: "big", edge: 0.5, variance: 0.05 }),
|
|
55
|
+
item({ id: "mid", edge: 0.2, variance: 0.05 }),
|
|
56
|
+
], { budgetDays: 25 });
|
|
57
|
+
expect(r.items[0].id).toBe("big");
|
|
58
|
+
expect(r.items[r.items.length - 1].id).toBe("small");
|
|
59
|
+
});
|
|
60
|
+
it("classifies tiers — outsized > core > small > skip", () => {
|
|
61
|
+
expect(classifyTier(0.3, 0.2)).toBe("outsized");
|
|
62
|
+
expect(classifyTier(0.15, 0.2)).toBe("core");
|
|
63
|
+
expect(classifyTier(0.05, 0.2)).toBe("small");
|
|
64
|
+
expect(classifyTier(0.0, 0.2)).toBe("skip");
|
|
65
|
+
expect(classifyTier(0.5, -0.1)).toBe("skip"); // negative edge always skip
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe("kellyAllocate — edge cases", () => {
|
|
69
|
+
it("handles empty input", () => {
|
|
70
|
+
const r = kellyAllocate([], { budgetDays: 25 });
|
|
71
|
+
expect(r.items).toEqual([]);
|
|
72
|
+
expect(r.totalAllocated).toBe(0);
|
|
73
|
+
expect(r.reserveDays).toBe(25);
|
|
74
|
+
});
|
|
75
|
+
it("handles zero variance (avoids division by zero)", () => {
|
|
76
|
+
const r = kellyAllocate([item({ id: "x", edge: 0.1, variance: 0 })], { budgetDays: 100 });
|
|
77
|
+
expect(Number.isFinite(r.items[0].allocatedDays)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
it("zero-budget case allocates nothing", () => {
|
|
80
|
+
const r = kellyAllocate([item({ edge: 0.5, variance: 0.05 })], { budgetDays: 0 });
|
|
81
|
+
expect(r.totalAllocated).toBe(0);
|
|
82
|
+
expect(r.items[0].allocatedDays).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
it("more permissive multiplier (0.5) allocates more aggressively than default (0.25)", () => {
|
|
85
|
+
// Use a low raw-Kelly so neither multiplier hits the per-item 0.5 ceiling.
|
|
86
|
+
const items = [item({ edge: 0.05, variance: 0.1 })];
|
|
87
|
+
const conservative = kellyAllocate(items, { budgetDays: 100, multiplier: 0.25 });
|
|
88
|
+
const aggressive = kellyAllocate(items, { budgetDays: 100, multiplier: 0.5 });
|
|
89
|
+
expect(aggressive.totalAllocated).toBeGreaterThan(conservative.totalAllocated);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe("estimateEdge — derives expected return from historical signals", () => {
|
|
93
|
+
it("positive edge when post-refactor improves regret + churn", () => {
|
|
94
|
+
const e = estimateEdge({
|
|
95
|
+
pastRegretRate: 0.3,
|
|
96
|
+
pastChurnPerDay: 5,
|
|
97
|
+
postRefactorRegretRate: 0.1,
|
|
98
|
+
postRefactorChurnPerDay: 3,
|
|
99
|
+
});
|
|
100
|
+
expect(e).toBeGreaterThan(0);
|
|
101
|
+
});
|
|
102
|
+
it("negative edge when post-refactor is worse", () => {
|
|
103
|
+
const e = estimateEdge({
|
|
104
|
+
pastRegretRate: 0.1,
|
|
105
|
+
pastChurnPerDay: 5,
|
|
106
|
+
postRefactorRegretRate: 0.3,
|
|
107
|
+
postRefactorChurnPerDay: 8,
|
|
108
|
+
});
|
|
109
|
+
expect(e).toBeLessThan(0);
|
|
110
|
+
});
|
|
111
|
+
it("regret improvement weighted higher than churn improvement", () => {
|
|
112
|
+
const regretOnly = estimateEdge({
|
|
113
|
+
pastRegretRate: 0.5,
|
|
114
|
+
pastChurnPerDay: 5,
|
|
115
|
+
postRefactorRegretRate: 0,
|
|
116
|
+
postRefactorChurnPerDay: 5,
|
|
117
|
+
});
|
|
118
|
+
const churnOnly = estimateEdge({
|
|
119
|
+
pastRegretRate: 0.3,
|
|
120
|
+
pastChurnPerDay: 10,
|
|
121
|
+
postRefactorRegretRate: 0.3,
|
|
122
|
+
postRefactorChurnPerDay: 5,
|
|
123
|
+
});
|
|
124
|
+
expect(regretOnly).toBeGreaterThan(churnOnly);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe("estimateVariance — sample variance of payoffs", () => {
|
|
128
|
+
it("zero (floor) for unknown / single sample", () => {
|
|
129
|
+
expect(estimateVariance([])).toBe(0.1);
|
|
130
|
+
expect(estimateVariance([0.2])).toBe(0.1);
|
|
131
|
+
});
|
|
132
|
+
it("computes variance correctly for multiple payoffs", () => {
|
|
133
|
+
const v = estimateVariance([0.1, 0.2, 0.3]);
|
|
134
|
+
// mean = 0.2, deviations: -0.1, 0, 0.1; variance = (0.01 + 0 + 0.01) / 3
|
|
135
|
+
expect(v).toBeCloseTo(0.00667, 4);
|
|
136
|
+
});
|
|
137
|
+
it("non-negative for any input", () => {
|
|
138
|
+
const inputs = [
|
|
139
|
+
[0, 0, 0],
|
|
140
|
+
[-0.5, 0.5],
|
|
141
|
+
[1, 1, 1, 1],
|
|
142
|
+
];
|
|
143
|
+
for (const arr of inputs)
|
|
144
|
+
expect(estimateVariance(arr)).toBeGreaterThanOrEqual(0);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=alpha.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alpha.test.js","sourceRoot":"","sources":["../../src/quant/alpha.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
export interface BacktestSample {
|
|
18
|
+
/** The thing being predicted on — a commit, file, etc. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Did the predictor say "trouble incoming"? */
|
|
21
|
+
predicted: boolean;
|
|
22
|
+
/** Did trouble actually happen within the validation window? */
|
|
23
|
+
actual: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface BacktestResult {
|
|
26
|
+
/** Total samples evaluated. */
|
|
27
|
+
n: number;
|
|
28
|
+
/** Confusion matrix counts. */
|
|
29
|
+
truePositives: number;
|
|
30
|
+
falsePositives: number;
|
|
31
|
+
trueNegatives: number;
|
|
32
|
+
falseNegatives: number;
|
|
33
|
+
/** Standard metrics. */
|
|
34
|
+
precision: number;
|
|
35
|
+
recall: number;
|
|
36
|
+
f1: number;
|
|
37
|
+
/** Base rate of positive outcomes (how often trouble happens randomly). */
|
|
38
|
+
baseRate: number;
|
|
39
|
+
/** Lift over base rate — precision / baseRate. */
|
|
40
|
+
lift: number;
|
|
41
|
+
/** Verdict label for the report. */
|
|
42
|
+
verdict: "no-edge" | "weak" | "real-edge" | "strong-edge";
|
|
43
|
+
/** Plain-English conclusion. */
|
|
44
|
+
conclusion: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Compute classification metrics + verdict from a list of (predicted,
|
|
48
|
+
* actual) samples. Pure math — no I/O, deterministic.
|
|
49
|
+
*/
|
|
50
|
+
export declare function backtest(samples: BacktestSample[]): BacktestResult;
|
|
51
|
+
export declare function classifyVerdict(lift: number, precision: number, recall: number, n: number): BacktestResult["verdict"];
|
|
52
|
+
/**
|
|
53
|
+
* Aggregate a backtest result into a one-line markdown badge for the
|
|
54
|
+
* README / docs. Format: "F1 = 0.67 · 2.4× lift · n=14".
|
|
55
|
+
*/
|
|
56
|
+
export declare function badge(result: BacktestResult): string;
|
|
57
|
+
//# sourceMappingURL=backtest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backtest.d.ts","sourceRoot":"","sources":["../../src/quant/backtest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,SAAS,EAAE,OAAO,CAAC;IACnB,gEAAgE;IAChE,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,+BAA+B;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,OAAO,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC;IAC1D,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,cAAc,CAkClE;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,CAAC,EAAE,MAAM,GACR,cAAc,CAAC,SAAS,CAAC,CAM3B;AAsBD;;;GAGG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEpD"}
|
|
@@ -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 @@
|
|
|
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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"}
|