@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,141 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { buildGhostReport } from "./ghost.js";
3
+ const NOW = new Date("2026-05-05").getTime();
4
+ function mk(p) {
5
+ return {
6
+ hash: p.hash,
7
+ shortHash: p.hash.slice(0, 7),
8
+ authorName: "Test",
9
+ authorEmail: "t@e.com",
10
+ authorDate: p.date,
11
+ committerDate: p.date,
12
+ subject: p.subject,
13
+ body: p.body ?? "",
14
+ files: p.files ?? [],
15
+ parents: [],
16
+ };
17
+ }
18
+ function mkChange(commitHash, path) {
19
+ return { commitHash, path, changeKind: "M", insertions: 5, deletions: 0 };
20
+ }
21
+ describe("buildGhostReport", () => {
22
+ it("returns empty when no commits or changes", () => {
23
+ const r = buildGhostReport([], [], { nowMs: NOW });
24
+ expect(r.ghostFiles).toHaveLength(0);
25
+ expect(r.staleTodos).toHaveLength(0);
26
+ expect(r.totalFiles).toBe(0);
27
+ });
28
+ it("identifies a long-untouched file as a ghost", () => {
29
+ const c = mk({ hash: "a1", date: "2024-01-01", subject: "add legacy thing" });
30
+ const ch = [mkChange("a1", "src/legacy.ts")];
31
+ const r = buildGhostReport([c], ch, { nowMs: NOW, staleDays: 180, minGhostliness: 0.4 });
32
+ const ghost = r.ghostFiles.find((g) => g.path === "src/legacy.ts");
33
+ expect(ghost).toBeDefined();
34
+ expect(ghost.daysSinceLastTouch).toBeGreaterThan(180);
35
+ });
36
+ it("does not flag actively-edited files as ghosts", () => {
37
+ const commits = Array.from({ length: 12 }, (_, i) => mk({
38
+ hash: `c${i}`,
39
+ date: `2026-04-${(i + 1).toString().padStart(2, "0")}`,
40
+ subject: `evolve module`,
41
+ files: ["src/active.ts"],
42
+ }));
43
+ const changes = commits.map((c) => mkChange(c.hash, "src/active.ts"));
44
+ const r = buildGhostReport(commits, changes, { nowMs: NOW, minGhostliness: 0.4 });
45
+ const ghost = r.ghostFiles.find((g) => g.path === "src/active.ts");
46
+ expect(ghost).toBeUndefined();
47
+ });
48
+ it("boosts ghostliness via TODO density", () => {
49
+ const c1 = mk({ hash: "a1", date: "2024-01-01", subject: "scaffold" });
50
+ const c2 = mk({ hash: "a2", date: "2024-01-15", subject: "TODO: finish error handling" });
51
+ const ch = [mkChange("a1", "src/half.ts"), mkChange("a2", "src/half.ts")];
52
+ const r = buildGhostReport([c1, c2], ch, {
53
+ nowMs: NOW,
54
+ staleDays: 180,
55
+ todoCounts: new Map([["src/half.ts", 5]]),
56
+ minGhostliness: 0.4,
57
+ });
58
+ const ghost = r.ghostFiles.find((g) => g.path === "src/half.ts");
59
+ expect(ghost).toBeDefined();
60
+ expect(ghost.todoCount).toBe(5);
61
+ });
62
+ it("detects stale TODOs from commit history", () => {
63
+ const c1 = mk({ hash: "a1", date: "2024-01-01", subject: "TODO: add validation" });
64
+ const c2 = mk({ hash: "a2", date: "2024-06-01", subject: "minor formatting" });
65
+ const c3 = mk({ hash: "a3", date: "2024-12-01", subject: "another tweak" });
66
+ const ch = [
67
+ mkChange("a1", "src/feat.ts"),
68
+ mkChange("a2", "src/feat.ts"),
69
+ mkChange("a3", "src/feat.ts"),
70
+ ];
71
+ const r = buildGhostReport([c1, c2, c3], ch, {
72
+ nowMs: NOW,
73
+ todoStaleDays: 90,
74
+ });
75
+ expect(r.staleTodos.length).toBeGreaterThanOrEqual(1);
76
+ expect(r.staleTodos[0].ignoredCount).toBeGreaterThanOrEqual(1);
77
+ });
78
+ it("ghostliness is bounded to 0..1", () => {
79
+ const commits = [
80
+ mk({ hash: "a1", date: "2020-01-01", subject: "ancient" }),
81
+ ];
82
+ const ch = [mkChange("a1", "src/dust.ts")];
83
+ const r = buildGhostReport(commits, ch, {
84
+ nowMs: NOW,
85
+ todoCounts: new Map([["src/dust.ts", 100]]),
86
+ minGhostliness: 0,
87
+ });
88
+ for (const g of r.ghostFiles) {
89
+ expect(g.ghostliness).toBeGreaterThanOrEqual(0);
90
+ expect(g.ghostliness).toBeLessThanOrEqual(1);
91
+ }
92
+ });
93
+ it("orders ghostFiles by ghostliness descending", () => {
94
+ const commits = [
95
+ mk({ hash: "old", date: "2020-01-01", subject: "very old" }),
96
+ mk({ hash: "med", date: "2024-08-01", subject: "medium age" }),
97
+ mk({ hash: "fresh", date: "2026-04-01", subject: "fresh" }),
98
+ ];
99
+ const ch = [
100
+ mkChange("old", "src/old.ts"),
101
+ mkChange("med", "src/med.ts"),
102
+ mkChange("fresh", "src/fresh.ts"),
103
+ ];
104
+ const r = buildGhostReport(commits, ch, {
105
+ nowMs: NOW,
106
+ staleDays: 180,
107
+ minGhostliness: 0,
108
+ });
109
+ for (let i = 1; i < r.ghostFiles.length; i++) {
110
+ expect(r.ghostFiles[i - 1].ghostliness).toBeGreaterThanOrEqual(r.ghostFiles[i].ghostliness);
111
+ }
112
+ });
113
+ it("reports averageGhostliness across all files", () => {
114
+ const commits = [
115
+ mk({ hash: "a1", date: "2020-01-01", subject: "ancient" }),
116
+ mk({ hash: "a2", date: "2026-04-01", subject: "fresh" }),
117
+ ];
118
+ const ch = [mkChange("a1", "src/a.ts"), mkChange("a2", "src/b.ts")];
119
+ const r = buildGhostReport(commits, ch, { nowMs: NOW, minGhostliness: 0 });
120
+ expect(r.averageGhostliness).toBeGreaterThan(0);
121
+ expect(r.averageGhostliness).toBeLessThanOrEqual(1);
122
+ });
123
+ it("produces a non-empty reason for every ghost", () => {
124
+ const commits = [
125
+ mk({ hash: "a1", date: "2020-01-01", subject: "ancient" }),
126
+ ];
127
+ const ch = [mkChange("a1", "src/dusty.ts")];
128
+ const r = buildGhostReport(commits, ch, { nowMs: NOW, minGhostliness: 0 });
129
+ for (const g of r.ghostFiles) {
130
+ expect(g.reason.length).toBeGreaterThan(0);
131
+ }
132
+ });
133
+ it("does not flag stale TODO unless ignored at least once", () => {
134
+ const c1 = mk({ hash: "a1", date: "2020-01-01", subject: "TODO: clean this up" });
135
+ const ch = [mkChange("a1", "src/only.ts")];
136
+ const r = buildGhostReport([c1], ch, { nowMs: NOW, todoStaleDays: 90 });
137
+ // The TODO commit is the only touch — ignoredCount = 0, should not appear
138
+ expect(r.staleTodos.find((t) => t.filePath === "src/only.ts")).toBeUndefined();
139
+ });
140
+ });
141
+ //# sourceMappingURL=ghost.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ghost.test.js","sourceRoot":"","sources":["../../src/insights/ghost.test.ts"],"names":[],"mappings":"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"}
@@ -13,4 +13,12 @@ export * from "./story.js";
13
13
  export * from "./dream.js";
14
14
  export * from "./suggest.js";
15
15
  export * from "./obsidian.js";
16
+ export * from "./regret.js";
17
+ export * from "./bus-factor.js";
18
+ export * from "./paradox.js";
19
+ export * from "./commit-coach.js";
20
+ export * from "./crystal-ball.js";
21
+ export * from "./time-machine.js";
22
+ export * from "./premortem.js";
23
+ export * from "./ghost.js";
16
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
@@ -13,4 +13,12 @@ export * from "./story.js";
13
13
  export * from "./dream.js";
14
14
  export * from "./suggest.js";
15
15
  export * from "./obsidian.js";
16
+ export * from "./regret.js";
17
+ export * from "./bus-factor.js";
18
+ export * from "./paradox.js";
19
+ export * from "./commit-coach.js";
20
+ export * from "./crystal-ball.js";
21
+ export * from "./time-machine.js";
22
+ export * from "./premortem.js";
23
+ export * from "./ghost.js";
16
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `mneme paradox` — find architectural flip-flops in commit history.
3
+ *
4
+ * A "flip-flop" is a chain of decisions on the same topic where the
5
+ * direction reverses: A → B → A. This usually means the team forgot
6
+ * (or never wrote down) WHY they made the original decision.
7
+ *
8
+ * Implementation: walk decisions chronologically, group by topic
9
+ * (heuristically clustered by key terms), and detect ABA-style
10
+ * chains. Conservative — only emit when confidence is high that
11
+ * the decisions are actually about the same thing.
12
+ *
13
+ * Pure data analysis. No LLM. The CLI renders.
14
+ */
15
+ import type { ExtractedDecision } from "./decisions.js";
16
+ export interface FlipFlop {
17
+ /** Topic label — extracted from the shared keyword(s). */
18
+ topic: string;
19
+ /** The chain of decisions, oldest first. */
20
+ chain: ExtractedDecision[];
21
+ /** How many direction reversals (0 = no flip, 1 = one flip, 2 = ABA). */
22
+ flips: number;
23
+ /** Months between first and last decision in the chain. */
24
+ spanMonths: number;
25
+ /** A 1-line interpretation. */
26
+ question: string;
27
+ }
28
+ /**
29
+ * Detect flip-flops across a list of decisions.
30
+ *
31
+ * Returns groups with ≥ 1 flip — the actual paradoxes worth investigating.
32
+ * Groups with 2+ decisions but no flips (just continuous progress) are
33
+ * suppressed.
34
+ */
35
+ export declare function detectParadoxes(decisions: ExtractedDecision[]): FlipFlop[];
36
+ //# sourceMappingURL=paradox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paradox.d.ts","sourceRoot":"","sources":["../../src/insights/paradox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,WAAW,QAAQ;IACvB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAqJD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,iBAAiB,EAAE,GAAG,QAAQ,EAAE,CA2B1E"}
@@ -0,0 +1,201 @@
1
+ /**
2
+ * `mneme paradox` — find architectural flip-flops in commit history.
3
+ *
4
+ * A "flip-flop" is a chain of decisions on the same topic where the
5
+ * direction reverses: A → B → A. This usually means the team forgot
6
+ * (or never wrote down) WHY they made the original decision.
7
+ *
8
+ * Implementation: walk decisions chronologically, group by topic
9
+ * (heuristically clustered by key terms), and detect ABA-style
10
+ * chains. Conservative — only emit when confidence is high that
11
+ * the decisions are actually about the same thing.
12
+ *
13
+ * Pure data analysis. No LLM. The CLI renders.
14
+ */
15
+ /**
16
+ * Stop-words that should NOT count as "topic" — too generic.
17
+ * Add to this set when noise topics appear in real-world repos.
18
+ */
19
+ const TOPIC_STOPWORDS = new Set([
20
+ "the", "a", "an", "to", "from", "for", "with", "of", "in", "on", "at",
21
+ "and", "or", "but", "is", "are", "was", "were",
22
+ "use", "used", "using", "make", "made",
23
+ "new", "old", "all", "more", "less",
24
+ "code", "thing", "stuff", "function", "method", "class", "module",
25
+ "version", "feature", "patch",
26
+ ]);
27
+ /**
28
+ * Extract topic keywords from a decision summary. Keeps only the
29
+ * meaningful nouns/identifiers (filtering verbs and stop-words).
30
+ */
31
+ function extractTopicKeywords(summary) {
32
+ const lower = summary.toLowerCase();
33
+ // Tokenize: split on non-alphanumeric, keep words ≥ 3 chars.
34
+ const tokens = lower
35
+ .replace(/[^\w\s./-]+/g, " ")
36
+ .split(/\s+/)
37
+ .filter((t) => t.length >= 3 && !TOPIC_STOPWORDS.has(t));
38
+ // Also exclude transition verbs that won't help cluster.
39
+ const VERB_BLOCKLIST = new Set([
40
+ "decided", "switch", "switched", "switching", "from",
41
+ "replace", "replaced", "with", "over", "instead",
42
+ "adopt", "adopted", "adopting",
43
+ "deprecate", "deprecated", "deprecating",
44
+ "migrate", "migrated", "migrating",
45
+ "reject", "rejected",
46
+ "choose", "chose", "chosen",
47
+ ]);
48
+ return tokens.filter((t) => !VERB_BLOCKLIST.has(t));
49
+ }
50
+ /**
51
+ * Group decisions by overlapping topic keywords. Two decisions belong
52
+ * to the same group if they share ≥ 1 meaningful keyword.
53
+ */
54
+ function groupByTopic(decisions) {
55
+ const decisionKeywords = decisions.map((d) => ({
56
+ decision: d,
57
+ keywords: new Set(extractTopicKeywords(d.summary)),
58
+ }));
59
+ // Union-find by shared keyword.
60
+ const groups = [];
61
+ for (const item of decisionKeywords) {
62
+ let placed = false;
63
+ for (const g of groups) {
64
+ // Share at least one keyword?
65
+ let shared = false;
66
+ for (const k of item.keywords) {
67
+ if (g.keywords.has(k)) {
68
+ shared = true;
69
+ break;
70
+ }
71
+ }
72
+ if (shared) {
73
+ g.decisions.push(item.decision);
74
+ for (const k of item.keywords)
75
+ g.keywords.add(k);
76
+ placed = true;
77
+ break;
78
+ }
79
+ }
80
+ if (!placed) {
81
+ groups.push({ keywords: new Set(item.keywords), decisions: [item.decision] });
82
+ }
83
+ }
84
+ // Pick a label per group — the keyword that appears in the most decisions
85
+ // of the group (the "common thread"). Ties broken by length.
86
+ const out = new Map();
87
+ for (const g of groups) {
88
+ if (g.decisions.length < 2)
89
+ continue; // need at least 2 decisions to flip
90
+ const label = pickLabel(g.decisions);
91
+ if (out.has(label)) {
92
+ // Merge into existing — multiple groups can hash to the same label.
93
+ out.get(label).push(...g.decisions);
94
+ }
95
+ else {
96
+ out.set(label, g.decisions);
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+ /** Label = the keyword that occurs in the most decisions of the chain. */
102
+ function pickLabel(decisions) {
103
+ const freq = new Map();
104
+ for (const d of decisions) {
105
+ const kws = new Set(extractTopicKeywords(d.summary));
106
+ for (const k of kws)
107
+ freq.set(k, (freq.get(k) ?? 0) + 1);
108
+ }
109
+ if (freq.size === 0)
110
+ return "(unnamed)";
111
+ return [...freq.entries()].sort((a, b) => b[1] - a[1] || b[0].length - a[0].length)[0][0];
112
+ }
113
+ /**
114
+ * Extract the *destination* keywords of a decision — the noun being
115
+ * switched/decided TO. This is the "side" that the decision is choosing.
116
+ *
117
+ * For "switched from X to Y" we want Y.
118
+ * For "decided to use Z" we want Z.
119
+ * For "adopted W" we want W.
120
+ */
121
+ function destinationKeywords(d) {
122
+ const summary = d.summary.toLowerCase();
123
+ // Match the LAST " to X" clause (closest to end of string).
124
+ // Pattern: "to" surrounded by spaces, then everything to end.
125
+ const toMatch = summary.match(/\bto\s+(.+)$/);
126
+ if (toMatch) {
127
+ return new Set(extractTopicKeywords(toMatch[1]));
128
+ }
129
+ // No "to" — fall back to keywords from the whole summary.
130
+ return new Set(extractTopicKeywords(summary));
131
+ }
132
+ /**
133
+ * Count flips in a chronologically-sorted decision chain.
134
+ *
135
+ * A "flip" is an ABA pattern — at index i, a destination keyword X appears,
136
+ * disappears in i+1, and reappears in i+2. The team chose X, switched away,
137
+ * then went back. That's exactly the "we keep flip-flopping" anti-pattern.
138
+ */
139
+ function countFlips(chain) {
140
+ if (chain.length < 3)
141
+ return 0;
142
+ const dests = chain.map(destinationKeywords);
143
+ let flips = 0;
144
+ for (let i = 0; i < dests.length - 2; i++) {
145
+ const a = dests[i];
146
+ const b = dests[i + 1];
147
+ const c = dests[i + 2];
148
+ let flipFound = false;
149
+ for (const k of a) {
150
+ if (!b.has(k) && c.has(k)) {
151
+ flipFound = true;
152
+ break;
153
+ }
154
+ }
155
+ if (flipFound)
156
+ flips += 1;
157
+ }
158
+ return flips;
159
+ }
160
+ /**
161
+ * Detect flip-flops across a list of decisions.
162
+ *
163
+ * Returns groups with ≥ 1 flip — the actual paradoxes worth investigating.
164
+ * Groups with 2+ decisions but no flips (just continuous progress) are
165
+ * suppressed.
166
+ */
167
+ export function detectParadoxes(decisions) {
168
+ if (decisions.length < 2)
169
+ return [];
170
+ const sorted = [...decisions].sort((a, b) => a.date.localeCompare(b.date));
171
+ const groups = groupByTopic(sorted);
172
+ const flipFlops = [];
173
+ for (const [topic, chain] of groups) {
174
+ if (chain.length < 3)
175
+ continue; // ABA needs 3 decisions minimum
176
+ const flips = countFlips(chain);
177
+ if (flips === 0)
178
+ continue;
179
+ const fromDate = new Date(chain[0].date);
180
+ const toDate = new Date(chain[chain.length - 1].date);
181
+ const spanMonths = (toDate.getTime() - fromDate.getTime()) / (30 * 86_400_000);
182
+ flipFlops.push({
183
+ topic,
184
+ chain,
185
+ flips,
186
+ spanMonths: Math.round(spanMonths * 10) / 10,
187
+ question: buildQuestion(topic, chain, flips),
188
+ });
189
+ }
190
+ flipFlops.sort((a, b) => b.flips - a.flips || b.spanMonths - a.spanMonths);
191
+ return flipFlops;
192
+ }
193
+ function buildQuestion(topic, chain, flips) {
194
+ const months = Math.round((new Date(chain[chain.length - 1].date).getTime() - new Date(chain[0].date).getTime()) /
195
+ (30 * 86_400_000));
196
+ if (flips >= 2) {
197
+ return `${flips} reversals in ${months} months — write an ADR before the next change.`;
198
+ }
199
+ return `One reversal in ${months} months — was the prior decision a mistake, or did context change?`;
200
+ }
201
+ //# sourceMappingURL=paradox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paradox.js","sourceRoot":"","sources":["../../src/insights/paradox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=paradox.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paradox.test.d.ts","sourceRoot":"","sources":["../../src/insights/paradox.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { detectParadoxes } from "./paradox.js";
3
+ const dec = (date, summary, hash = `h${date.replace(/-/g, "")}`, kind = "switched") => ({
4
+ commitHash: hash,
5
+ shortHash: hash.slice(0, 7),
6
+ date,
7
+ author: "alice",
8
+ summary,
9
+ kind,
10
+ confidence: 0.9,
11
+ });
12
+ describe("detectParadoxes — basic flip detection", () => {
13
+ it("detects a clean ABA flip (Redis → in-memory → Redis)", () => {
14
+ const decisions = [
15
+ dec("2024-03-15", "decided to use Redis for caching"),
16
+ dec("2024-08-20", "switched from Redis to in-memory cache"),
17
+ dec("2025-02-10", "switched from in-memory back to Redis"),
18
+ ];
19
+ const paradoxes = detectParadoxes(decisions);
20
+ expect(paradoxes.length).toBeGreaterThanOrEqual(1);
21
+ const redis = paradoxes.find((p) => /redis|cache/i.test(p.topic));
22
+ expect(redis).toBeDefined();
23
+ expect(redis.flips).toBeGreaterThanOrEqual(1);
24
+ expect(redis.chain).toHaveLength(3);
25
+ });
26
+ it("does NOT flip on continuous progress (different targets each time)", () => {
27
+ const decisions = [
28
+ dec("2024-01-01", "adopted React"),
29
+ dec("2024-06-01", "switched from React to Solid"),
30
+ dec("2024-12-01", "switched from Solid to Svelte"),
31
+ ];
32
+ // Each decision picks a NEW target — no return to React. No flip.
33
+ const paradoxes = detectParadoxes(decisions);
34
+ const ui = paradoxes.find((p) => /react|solid|svelte/i.test(p.topic));
35
+ expect(ui?.flips ?? 0).toBe(0);
36
+ });
37
+ it("returns empty array when there are fewer than 2 decisions", () => {
38
+ expect(detectParadoxes([])).toEqual([]);
39
+ expect(detectParadoxes([dec("2024-01-01", "decided")])).toEqual([]);
40
+ });
41
+ it("requires at least 3 decisions to count as a flip-flop chain", () => {
42
+ const decisions = [
43
+ dec("2024-01-01", "use Redis cache"),
44
+ dec("2024-06-01", "switched from Redis to in-memory cache"),
45
+ ];
46
+ expect(detectParadoxes(decisions)).toEqual([]);
47
+ });
48
+ });
49
+ describe("detectParadoxes — span and metadata", () => {
50
+ it("computes spanMonths between first and last decision", () => {
51
+ const decisions = [
52
+ dec("2024-01-01", "use Redis cache"),
53
+ dec("2024-06-01", "switched from Redis to memcache"),
54
+ dec("2025-01-01", "back to Redis cache"),
55
+ ];
56
+ const p = detectParadoxes(decisions)[0];
57
+ expect(p.spanMonths).toBeGreaterThan(11);
58
+ expect(p.spanMonths).toBeLessThan(13);
59
+ });
60
+ it("attaches a question that prompts an ADR for repeated reversals", () => {
61
+ const decisions = [
62
+ dec("2024-01-01", "passport oauth"),
63
+ dec("2024-06-01", "switched from passport to custom auth"),
64
+ dec("2025-01-01", "switched back to passport"),
65
+ ];
66
+ const p = detectParadoxes(decisions)[0];
67
+ expect(p.question.toLowerCase()).toMatch(/reversal|adr|mistake|context/);
68
+ });
69
+ });
70
+ describe("detectParadoxes — sort order", () => {
71
+ it("sorts paradoxes by flip count desc, then by span", () => {
72
+ const decisions = [
73
+ // topic A: 2 flips (ABABA-ish)
74
+ dec("2024-01-01", "use Redis cache fast"),
75
+ dec("2024-04-01", "switched from Redis cache to memcached fast"),
76
+ dec("2024-08-01", "back to Redis cache"),
77
+ dec("2024-12-01", "memcached cache again"),
78
+ dec("2025-04-01", "Redis cache again"),
79
+ // topic B: 1 flip
80
+ dec("2024-02-01", "use webpack bundler"),
81
+ dec("2024-07-01", "switched from webpack bundler to vite bundler"),
82
+ dec("2024-12-01", "back to webpack bundler"),
83
+ ];
84
+ const paradoxes = detectParadoxes(decisions);
85
+ expect(paradoxes[0].flips).toBeGreaterThanOrEqual(paradoxes[paradoxes.length - 1].flips);
86
+ });
87
+ });
88
+ //# sourceMappingURL=paradox.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paradox.test.js","sourceRoot":"","sources":["../../src/insights/paradox.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `mneme premortem <intent>` — predict what will go wrong with a proposed
3
+ * change *before* you write it, by mining your repo's history of similar
4
+ * past attempts and what happened to them.
5
+ *
6
+ * Premortem inverts the postmortem: instead of "what went wrong?", we ask
7
+ * "what is likely to go wrong, given what's gone wrong before in this repo
8
+ * when we tried something like this?"
9
+ *
10
+ * What makes this novel:
11
+ * - Most "AI coding" tools give generic advice ("watch out for race
12
+ * conditions"). Premortem gives advice grounded in YOUR repo's actual
13
+ * failure history.
14
+ * - Estimates a regret probability based on hit rate of similar past
15
+ * intents that ended in revert/hotfix/incident.
16
+ * - Surfaces 3 concrete risks with citations to the past commits that
17
+ * caused them — actionable, not abstract.
18
+ *
19
+ * Pure data extraction — no LLM. The CLI renders.
20
+ */
21
+ import type { Commit } from "../types.js";
22
+ export type RiskKind = "revert" | "hotfix" | "incident" | "rewrite";
23
+ export interface PastAttempt {
24
+ /** The commit that introduced the past attempt. */
25
+ attempt: Commit;
26
+ /** The follow-up that signals regret (if any). */
27
+ regret?: Commit;
28
+ /** What went wrong, classified by signal in the regret commit. */
29
+ riskKind: RiskKind | "none";
30
+ /** Days from attempt to regret (or 0 if no regret). */
31
+ daysToRegret: number;
32
+ /** Similarity score of this past attempt to the current intent (0..1). */
33
+ similarity: number;
34
+ }
35
+ export interface Risk {
36
+ /** Short label e.g. "cache invalidation regression". */
37
+ label: string;
38
+ /** Specific commits that exhibited this risk. */
39
+ evidence: Commit[];
40
+ /** How often this risk fired, normalized 0..1 (capped at 1). */
41
+ weight: number;
42
+ }
43
+ export interface PremortemResult {
44
+ intent: string;
45
+ /** Past attempts in this repo that look like the proposed intent. */
46
+ pastAttempts: PastAttempt[];
47
+ /**
48
+ * Probability that this kind of change will be regretted, computed as
49
+ * regret_count / total_similar_attempts. Range 0..1.
50
+ */
51
+ regretProbability: number;
52
+ /** Top concrete risks distilled from past attempts. */
53
+ topRisks: Risk[];
54
+ /** Verdict label — "low / medium / high / very_high". */
55
+ verdict: "low" | "medium" | "high" | "very_high";
56
+ /** Short verdict prose. */
57
+ summary: string;
58
+ }
59
+ /**
60
+ * Score similarity between an intent string and a commit, by token overlap
61
+ * + a small bonus for files-mentioned matching commit-touched files.
62
+ */
63
+ export declare function scoreSimilarity(intent: string, c: Commit): number;
64
+ /**
65
+ * Build a premortem from an intent string and the commit history. Caller
66
+ * passes the full commit history (newest first or oldest first — we sort).
67
+ */
68
+ export declare function buildPremortem(intent: string, commits: Commit[], opts?: {
69
+ similarityFloor?: number;
70
+ windowDays?: number;
71
+ maxAttempts?: number;
72
+ }): PremortemResult;
73
+ //# sourceMappingURL=premortem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"premortem.d.ts","sourceRoot":"","sources":["../../src/insights/premortem.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAEpE,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC5B,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,IAAI;IACnB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjB,yDAAyD;IACzD,OAAO,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACjD,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAaD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAkBjE;AAsBD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE;IACJ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,GACL,eAAe,CA8GjB"}