@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
|
@@ -93,4 +93,33 @@ export function tierOf(commitCount, lastTouchIso, now) {
|
|
|
93
93
|
return "stale";
|
|
94
94
|
return "occasional";
|
|
95
95
|
}
|
|
96
|
+
export function whoKnowsVerdict(candidates) {
|
|
97
|
+
if (candidates.length === 0) {
|
|
98
|
+
return { confidencePct: 0, totalCommits: 0 };
|
|
99
|
+
}
|
|
100
|
+
const sorted = [...candidates].sort((a, b) => b.score - a.score);
|
|
101
|
+
const top = sorted[0];
|
|
102
|
+
const total = candidates.reduce((s, c) => s + c.commitCount, 0);
|
|
103
|
+
const pct = total === 0 ? 0 : Math.round((top.commitCount / total) * 100);
|
|
104
|
+
const backup = sorted[1];
|
|
105
|
+
let risk;
|
|
106
|
+
if (top.tier === "stale") {
|
|
107
|
+
risk = `Top expert is STALE — last touched ${formatDaysAgo(top.lastTouch)} ago. Consider asking ${backup?.name ?? "someone with recent activity"} instead.`;
|
|
108
|
+
}
|
|
109
|
+
else if (pct < 30 && candidates.length >= 3) {
|
|
110
|
+
risk = `No single dominant expert — ${candidates.length} people share the work. Knowledge is well-distributed but no one will know everything.`;
|
|
111
|
+
}
|
|
112
|
+
else if (top.commitCount === 1) {
|
|
113
|
+
risk = "Top expert touched the topic only once — treat as weak signal.";
|
|
114
|
+
}
|
|
115
|
+
return { topExpert: top, confidencePct: pct, backup, risk, totalCommits: total };
|
|
116
|
+
}
|
|
117
|
+
function formatDaysAgo(iso) {
|
|
118
|
+
const d = (Date.now() - new Date(iso).getTime()) / 86_400_000;
|
|
119
|
+
if (d < 30)
|
|
120
|
+
return `${Math.round(d)}d`;
|
|
121
|
+
if (d < 365)
|
|
122
|
+
return `${Math.round(d / 30)}mo`;
|
|
123
|
+
return `${(d / 365).toFixed(1)}y`;
|
|
124
|
+
}
|
|
96
125
|
//# sourceMappingURL=who-knows.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"who-knows.js","sourceRoot":"","sources":["../../src/insights/who-knows.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"who-knows.js","sourceRoot":"","sources":["../../src/insights/who-knows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA8BH,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAiB,EAAE,IAAqB;IAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IAEnC,qDAAqD;IACrD,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACjD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,6EAA6E;IAC7E,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IAS/D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK;YAAE,SAAS;QACtD,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE;gBACf,IAAI,EAAE,CAAC,CAAC,UAAU;gBAClB,KAAK,EAAE,CAAC,CAAC,WAAW;gBACpB,WAAW,EAAE,CAAC;gBACd,SAAS,EAAE,CAAC,CAAC,UAAU;gBACvB,KAAK,EAAE,IAAI,GAAG,EAAE;aACjB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;QAC5B,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,SAAS;YAAE,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,UAAU,CAAC;QAC3D,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;YAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC;YACxB,wDAAwD;YACxD,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAsB,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC9D,UAAU,CAAC,IAAI,CAAC;YACd,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI;YAC1B,KAAK;YACL,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IAED,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC7C,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,cAAc,CAAC,WAAmB,EAAE,YAAoB,EAAE,GAAS;IACjF,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IAC3F,yFAAyF;IACzF,MAAM,OAAO,GAAG,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC;IAC/E,8DAA8D;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;IAC1C,OAAO,MAAM,GAAG,OAAO,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,WAAmB,EAAE,YAAoB,EAAE,GAAS;IACzE,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9E,IAAI,WAAW,IAAI,EAAE,IAAI,OAAO,IAAI,WAAW;QAAE,OAAO,YAAY,CAAC;IACrE,IAAI,WAAW,IAAI,CAAC,IAAI,OAAO,IAAI,WAAW;QAAE,OAAO,QAAQ,CAAC;IAChE,IAAI,OAAO,GAAG,UAAU;QAAE,OAAO,OAAO,CAAC;IACzC,OAAO,YAAY,CAAC;AACtB,CAAC;AAoBD,MAAM,UAAU,eAAe,CAAC,UAA6B;IAC3D,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IAC/C,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IACjE,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;IACvB,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAChE,MAAM,GAAG,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAEzB,IAAI,IAAwB,CAAC;IAC7B,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACzB,IAAI,GAAG,sCAAsC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,yBAAyB,MAAM,EAAE,IAAI,IAAI,8BAA8B,WAAW,CAAC;IAC9J,CAAC;SAAM,IAAI,GAAG,GAAG,EAAE,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC9C,IAAI,GAAG,+BAA+B,UAAU,CAAC,MAAM,wFAAwF,CAAC;IAClJ,CAAC;SAAM,IAAI,GAAG,CAAC,WAAW,KAAK,CAAC,EAAE,CAAC;QACjC,IAAI,GAAG,gEAAgE,CAAC;IAC1E,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;AACnF,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,UAAU,CAAC;IAC9D,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;IACvC,IAAI,CAAC,GAAG,GAAG;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;IAC9C,OAAO,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AACpC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { scoreCandidate, tierOf } from "./who-knows.js";
|
|
2
|
+
import { scoreCandidate, tierOf, whoKnowsVerdict } from "./who-knows.js";
|
|
3
3
|
const today = new Date("2026-05-04T00:00:00Z");
|
|
4
4
|
const daysAgo = (n) => new Date(today.getTime() - n * 86_400_000).toISOString();
|
|
5
5
|
describe("scoreCandidate — pure scoring function", () => {
|
|
@@ -44,4 +44,66 @@ describe("tierOf — readable expert tier", () => {
|
|
|
44
44
|
expect(tierOf(1, daysAgo(40), today)).toBe("occasional");
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
|
+
describe("whoKnowsVerdict — turns a list of candidates into a single verdict", () => {
|
|
48
|
+
const cand = (overrides) => ({
|
|
49
|
+
name: "alice",
|
|
50
|
+
email: "alice@x",
|
|
51
|
+
commitCount: 10,
|
|
52
|
+
lastTouch: daysAgo(5),
|
|
53
|
+
filesTouched: 20,
|
|
54
|
+
score: 3.0,
|
|
55
|
+
tier: "active",
|
|
56
|
+
...overrides,
|
|
57
|
+
});
|
|
58
|
+
it("returns empty verdict for empty candidate list", () => {
|
|
59
|
+
const v = whoKnowsVerdict([]);
|
|
60
|
+
expect(v.topExpert).toBeUndefined();
|
|
61
|
+
expect(v.confidencePct).toBe(0);
|
|
62
|
+
expect(v.totalCommits).toBe(0);
|
|
63
|
+
});
|
|
64
|
+
it("picks the highest-score candidate as topExpert", () => {
|
|
65
|
+
const v = whoKnowsVerdict([
|
|
66
|
+
cand({ name: "alice", score: 4.0, commitCount: 15 }),
|
|
67
|
+
cand({ name: "bob", score: 2.0, commitCount: 5 }),
|
|
68
|
+
]);
|
|
69
|
+
expect(v.topExpert?.name).toBe("alice");
|
|
70
|
+
expect(v.backup?.name).toBe("bob");
|
|
71
|
+
});
|
|
72
|
+
it("computes confidencePct as share of total commits", () => {
|
|
73
|
+
const v = whoKnowsVerdict([
|
|
74
|
+
cand({ name: "alice", score: 4.0, commitCount: 15 }),
|
|
75
|
+
cand({ name: "bob", score: 2.0, commitCount: 5 }),
|
|
76
|
+
]);
|
|
77
|
+
expect(v.confidencePct).toBe(75);
|
|
78
|
+
});
|
|
79
|
+
it("flags risk when top expert is stale", () => {
|
|
80
|
+
const v = whoKnowsVerdict([
|
|
81
|
+
cand({ name: "alice", tier: "stale", lastTouch: daysAgo(300), commitCount: 20 }),
|
|
82
|
+
cand({ name: "bob", tier: "active", lastTouch: daysAgo(5), commitCount: 5 }),
|
|
83
|
+
]);
|
|
84
|
+
expect(v.risk?.toLowerCase()).toContain("stale");
|
|
85
|
+
expect(v.risk).toContain("bob"); // suggests backup
|
|
86
|
+
});
|
|
87
|
+
it("flags risk when no dominant expert (well-distributed)", () => {
|
|
88
|
+
const v = whoKnowsVerdict([
|
|
89
|
+
cand({ name: "alice", commitCount: 4, score: 1.5 }),
|
|
90
|
+
cand({ name: "bob", commitCount: 4, score: 1.4 }),
|
|
91
|
+
cand({ name: "carol", commitCount: 4, score: 1.3 }),
|
|
92
|
+
cand({ name: "dave", commitCount: 4, score: 1.2 }),
|
|
93
|
+
]);
|
|
94
|
+
expect(v.confidencePct).toBe(25);
|
|
95
|
+
expect(v.risk?.toLowerCase()).toContain("distributed");
|
|
96
|
+
});
|
|
97
|
+
it("flags risk when top expert touched once", () => {
|
|
98
|
+
const v = whoKnowsVerdict([cand({ name: "alice", commitCount: 1 })]);
|
|
99
|
+
expect(v.risk?.toLowerCase()).toContain("once");
|
|
100
|
+
});
|
|
101
|
+
it("no risk when top expert is definitive + recent", () => {
|
|
102
|
+
const v = whoKnowsVerdict([
|
|
103
|
+
cand({ name: "alice", tier: "definitive", commitCount: 20, lastTouch: daysAgo(5), score: 5 }),
|
|
104
|
+
cand({ name: "bob", commitCount: 2, score: 1, tier: "occasional" }),
|
|
105
|
+
]);
|
|
106
|
+
expect(v.risk).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
47
109
|
//# sourceMappingURL=who-knows.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"who-knows.test.js","sourceRoot":"","sources":["../../src/insights/who-knows.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"who-knows.test.js","sourceRoot":"","sources":["../../src/insights/who-knows.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme alpha` — Kelly criterion for technical debt allocation.
|
|
3
|
+
*
|
|
4
|
+
* Each refactor/TD item is a "bet" with (expected payoff, variance).
|
|
5
|
+
* Kelly optimal fraction maximizes long-run growth: f* = edge / variance.
|
|
6
|
+
* In practice, we use FRACTIONAL Kelly (×0.25) to limit blow-up risk —
|
|
7
|
+
* the same lever Edward Thorp used to win Wall Street.
|
|
8
|
+
*
|
|
9
|
+
* Pure math + small heuristics for estimating edge/variance from history.
|
|
10
|
+
* No LLM. The CLI takes a JSON list of items OR auto-extracts from
|
|
11
|
+
* `mneme regret` + `mneme bus-factor` + `mneme paradox` data.
|
|
12
|
+
*/
|
|
13
|
+
export interface DebtItem {
|
|
14
|
+
/** Stable id — drives sorting + reporting. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Human-readable name shown in output. */
|
|
17
|
+
name: string;
|
|
18
|
+
/**
|
|
19
|
+
* Edge — expected return as a fraction. Positive = improves codebase
|
|
20
|
+
* (smaller diff sizes, fewer regrets, better velocity). Negative =
|
|
21
|
+
* outright wasteful (the item costs more than it returns).
|
|
22
|
+
*/
|
|
23
|
+
edge: number;
|
|
24
|
+
/**
|
|
25
|
+
* Variance — squared volatility of the outcome. Higher = riskier bet,
|
|
26
|
+
* more dispersion in possible outcomes. Tiny number (0..1) — interpret
|
|
27
|
+
* as fraction-squared.
|
|
28
|
+
*/
|
|
29
|
+
variance: number;
|
|
30
|
+
/** Estimated dev-days to complete. */
|
|
31
|
+
effortDays: number;
|
|
32
|
+
}
|
|
33
|
+
export interface KellyAllocation extends DebtItem {
|
|
34
|
+
/** Raw Kelly fraction f* = edge / variance. May be > 1 or negative. */
|
|
35
|
+
rawKelly: number;
|
|
36
|
+
/** Fractional Kelly (clamped to [0, 0.5] after multiplier). */
|
|
37
|
+
kellyFraction: number;
|
|
38
|
+
/** Allocated dev-days for this item out of the budget. */
|
|
39
|
+
allocatedDays: number;
|
|
40
|
+
/** Sort tier for display. */
|
|
41
|
+
tier: "skip" | "small" | "core" | "outsized";
|
|
42
|
+
}
|
|
43
|
+
export interface KellyResult {
|
|
44
|
+
items: KellyAllocation[];
|
|
45
|
+
totalAllocated: number;
|
|
46
|
+
budgetDays: number;
|
|
47
|
+
reserveDays: number;
|
|
48
|
+
/** Kelly multiplier used. 0.25 by default — conservative. */
|
|
49
|
+
kellyMultiplier: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Compute Kelly-optimal allocation for a list of debt items.
|
|
53
|
+
*
|
|
54
|
+
* Algorithm:
|
|
55
|
+
* 1. raw_kelly_i = edge_i / variance_i (the canonical Kelly formula)
|
|
56
|
+
* 2. fractional_kelly_i = raw_kelly_i × multiplier (default 0.25)
|
|
57
|
+
* 3. clamp to [0, 0.5] — never bet > 50% of budget on one item
|
|
58
|
+
* 4. zero out negative-edge items (don't hold a losing bet)
|
|
59
|
+
* 5. normalize so allocations sum to ≤ budgetDays (leave reserve)
|
|
60
|
+
* 6. allocate dev-days proportionally
|
|
61
|
+
*/
|
|
62
|
+
export declare function kellyAllocate(items: DebtItem[], opts?: {
|
|
63
|
+
budgetDays: number;
|
|
64
|
+
multiplier?: number;
|
|
65
|
+
reserveFraction?: number;
|
|
66
|
+
}): KellyResult;
|
|
67
|
+
export declare function classifyTier(kellyFraction: number, edge: number): KellyAllocation["tier"];
|
|
68
|
+
/**
|
|
69
|
+
* Estimate edge from historical data: a refactor's edge is the difference
|
|
70
|
+
* in (regret rate, bug rate, churn velocity) between BEFORE and AFTER
|
|
71
|
+
* similar past refactors in the same module.
|
|
72
|
+
*
|
|
73
|
+
* The CLI calls this with regret data + commit-coach signals; here we
|
|
74
|
+
* just expose the algorithm so it's testable in isolation.
|
|
75
|
+
*/
|
|
76
|
+
export declare function estimateEdge(opts: {
|
|
77
|
+
pastRegretRate: number;
|
|
78
|
+
pastChurnPerDay: number;
|
|
79
|
+
postRefactorRegretRate: number;
|
|
80
|
+
postRefactorChurnPerDay: number;
|
|
81
|
+
}): number;
|
|
82
|
+
/**
|
|
83
|
+
* Estimate variance — historical variance of payoff across similar past
|
|
84
|
+
* refactors. Higher when past outcomes were inconsistent.
|
|
85
|
+
*/
|
|
86
|
+
export declare function estimateVariance(historicalPayoffs: number[]): number;
|
|
87
|
+
//# sourceMappingURL=alpha.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alpha.d.ts","sourceRoot":"","sources":["../../src/quant/alpha.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,QAAQ;IACvB,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,QAAQ;IAC/C,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC;CAC9C;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,QAAQ,EAAE,EACjB,IAAI,GAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAuB,GAC/F,WAAW,CA6Cb;AAED,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAMzF;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;CACjC,GAAG,MAAM,CAKT;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,EAAE,GAAG,MAAM,CAMpE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme alpha` — Kelly criterion for technical debt allocation.
|
|
3
|
+
*
|
|
4
|
+
* Each refactor/TD item is a "bet" with (expected payoff, variance).
|
|
5
|
+
* Kelly optimal fraction maximizes long-run growth: f* = edge / variance.
|
|
6
|
+
* In practice, we use FRACTIONAL Kelly (×0.25) to limit blow-up risk —
|
|
7
|
+
* the same lever Edward Thorp used to win Wall Street.
|
|
8
|
+
*
|
|
9
|
+
* Pure math + small heuristics for estimating edge/variance from history.
|
|
10
|
+
* No LLM. The CLI takes a JSON list of items OR auto-extracts from
|
|
11
|
+
* `mneme regret` + `mneme bus-factor` + `mneme paradox` data.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Compute Kelly-optimal allocation for a list of debt items.
|
|
15
|
+
*
|
|
16
|
+
* Algorithm:
|
|
17
|
+
* 1. raw_kelly_i = edge_i / variance_i (the canonical Kelly formula)
|
|
18
|
+
* 2. fractional_kelly_i = raw_kelly_i × multiplier (default 0.25)
|
|
19
|
+
* 3. clamp to [0, 0.5] — never bet > 50% of budget on one item
|
|
20
|
+
* 4. zero out negative-edge items (don't hold a losing bet)
|
|
21
|
+
* 5. normalize so allocations sum to ≤ budgetDays (leave reserve)
|
|
22
|
+
* 6. allocate dev-days proportionally
|
|
23
|
+
*/
|
|
24
|
+
export function kellyAllocate(items, opts = { budgetDays: 25 }) {
|
|
25
|
+
const multiplier = opts.multiplier ?? 0.25;
|
|
26
|
+
const reserveFrac = opts.reserveFraction ?? 0.2;
|
|
27
|
+
const budget = Math.max(0, opts.budgetDays);
|
|
28
|
+
// 1. Raw + fractional Kelly per item.
|
|
29
|
+
const computed = items.map((item) => {
|
|
30
|
+
// Avoid division-by-zero — variance of 0 is unrealistic; treat as floor.
|
|
31
|
+
const v = Math.max(item.variance, 1e-6);
|
|
32
|
+
const rawKelly = item.edge / v;
|
|
33
|
+
let frac = rawKelly * multiplier;
|
|
34
|
+
if (frac < 0)
|
|
35
|
+
frac = 0; // never bet on a losing item
|
|
36
|
+
if (frac > 0.5)
|
|
37
|
+
frac = 0.5; // never bet > 50% on one item
|
|
38
|
+
return { ...item, rawKelly, kellyFraction: frac };
|
|
39
|
+
});
|
|
40
|
+
// 2. Normalize fractions to sum ≤ (1 - reserveFrac).
|
|
41
|
+
const totalFrac = computed.reduce((s, c) => s + c.kellyFraction, 0);
|
|
42
|
+
const targetTotal = 1 - reserveFrac;
|
|
43
|
+
const scale = totalFrac > targetTotal ? targetTotal / totalFrac : 1;
|
|
44
|
+
// 3. Allocate dev-days.
|
|
45
|
+
let allocatedSum = 0;
|
|
46
|
+
const allocations = computed.map((c) => {
|
|
47
|
+
const adjusted = c.kellyFraction * scale;
|
|
48
|
+
const allocatedDays = Math.round(budget * adjusted * 10) / 10;
|
|
49
|
+
allocatedSum += allocatedDays;
|
|
50
|
+
return {
|
|
51
|
+
...c,
|
|
52
|
+
kellyFraction: adjusted,
|
|
53
|
+
allocatedDays,
|
|
54
|
+
tier: classifyTier(adjusted, c.edge),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
// 4. Sort by allocated days desc — biggest bets first.
|
|
58
|
+
allocations.sort((a, b) => b.allocatedDays - a.allocatedDays);
|
|
59
|
+
return {
|
|
60
|
+
items: allocations,
|
|
61
|
+
totalAllocated: Math.round(allocatedSum * 10) / 10,
|
|
62
|
+
budgetDays: budget,
|
|
63
|
+
reserveDays: Math.round((budget - allocatedSum) * 10) / 10,
|
|
64
|
+
kellyMultiplier: multiplier,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function classifyTier(kellyFraction, edge) {
|
|
68
|
+
if (edge < 0)
|
|
69
|
+
return "skip";
|
|
70
|
+
if (kellyFraction >= 0.2)
|
|
71
|
+
return "outsized";
|
|
72
|
+
if (kellyFraction >= 0.1)
|
|
73
|
+
return "core";
|
|
74
|
+
if (kellyFraction > 0)
|
|
75
|
+
return "small";
|
|
76
|
+
return "skip";
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Estimate edge from historical data: a refactor's edge is the difference
|
|
80
|
+
* in (regret rate, bug rate, churn velocity) between BEFORE and AFTER
|
|
81
|
+
* similar past refactors in the same module.
|
|
82
|
+
*
|
|
83
|
+
* The CLI calls this with regret data + commit-coach signals; here we
|
|
84
|
+
* just expose the algorithm so it's testable in isolation.
|
|
85
|
+
*/
|
|
86
|
+
export function estimateEdge(opts) {
|
|
87
|
+
const regretImprovement = opts.pastRegretRate - opts.postRefactorRegretRate;
|
|
88
|
+
const churnImprovement = (opts.pastChurnPerDay - opts.postRefactorChurnPerDay) / Math.max(opts.pastChurnPerDay, 0.001);
|
|
89
|
+
// Weighted: regret matters more (bugs hurt more than churn).
|
|
90
|
+
return 0.7 * regretImprovement + 0.3 * churnImprovement;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Estimate variance — historical variance of payoff across similar past
|
|
94
|
+
* refactors. Higher when past outcomes were inconsistent.
|
|
95
|
+
*/
|
|
96
|
+
export function estimateVariance(historicalPayoffs) {
|
|
97
|
+
if (historicalPayoffs.length < 2)
|
|
98
|
+
return 0.1; // unknown — assume mid-range
|
|
99
|
+
const mean = historicalPayoffs.reduce((s, x) => s + x, 0) / historicalPayoffs.length;
|
|
100
|
+
const variance = historicalPayoffs.reduce((s, x) => s + (x - mean) * (x - mean), 0) / historicalPayoffs.length;
|
|
101
|
+
return Math.max(variance, 1e-6);
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=alpha.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alpha.js","sourceRoot":"","sources":["../../src/quant/alpha.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alpha.test.d.ts","sourceRoot":"","sources":["../../src/quant/alpha.test.ts"],"names":[],"mappings":""}
|
|
@@ -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"}
|