@mneme-ai/core 0.8.4 → 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.
Files changed (272) hide show
  1. package/dist/entities/go-parser.d.ts +47 -0
  2. package/dist/entities/go-parser.d.ts.map +1 -0
  3. package/dist/entities/go-parser.js +315 -0
  4. package/dist/entities/go-parser.js.map +1 -0
  5. package/dist/entities/go-parser.test.d.ts +2 -0
  6. package/dist/entities/go-parser.test.d.ts.map +1 -0
  7. package/dist/entities/go-parser.test.js +147 -0
  8. package/dist/entities/go-parser.test.js.map +1 -0
  9. package/dist/entities/index.d.ts +1 -0
  10. package/dist/entities/index.d.ts.map +1 -1
  11. package/dist/entities/index.js +1 -0
  12. package/dist/entities/index.js.map +1 -1
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/indexer/indexer.d.ts +12 -0
  18. package/dist/indexer/indexer.d.ts.map +1 -1
  19. package/dist/indexer/indexer.js +28 -1
  20. package/dist/indexer/indexer.js.map +1 -1
  21. package/dist/insights/bus-factor.d.ts +58 -0
  22. package/dist/insights/bus-factor.d.ts.map +1 -0
  23. package/dist/insights/bus-factor.js +117 -0
  24. package/dist/insights/bus-factor.js.map +1 -0
  25. package/dist/insights/bus-factor.test.d.ts +2 -0
  26. package/dist/insights/bus-factor.test.d.ts.map +1 -0
  27. package/dist/insights/bus-factor.test.js +149 -0
  28. package/dist/insights/bus-factor.test.js.map +1 -0
  29. package/dist/insights/commit-coach.d.ts +80 -0
  30. package/dist/insights/commit-coach.d.ts.map +1 -0
  31. package/dist/insights/commit-coach.js +230 -0
  32. package/dist/insights/commit-coach.js.map +1 -0
  33. package/dist/insights/commit-coach.test.d.ts +2 -0
  34. package/dist/insights/commit-coach.test.d.ts.map +1 -0
  35. package/dist/insights/commit-coach.test.js +163 -0
  36. package/dist/insights/commit-coach.test.js.map +1 -0
  37. package/dist/insights/crystal-ball.d.ts +76 -0
  38. package/dist/insights/crystal-ball.d.ts.map +1 -0
  39. package/dist/insights/crystal-ball.js +219 -0
  40. package/dist/insights/crystal-ball.js.map +1 -0
  41. package/dist/insights/crystal-ball.test.d.ts +2 -0
  42. package/dist/insights/crystal-ball.test.d.ts.map +1 -0
  43. package/dist/insights/crystal-ball.test.js +157 -0
  44. package/dist/insights/crystal-ball.test.js.map +1 -0
  45. package/dist/insights/decisions.d.ts +38 -0
  46. package/dist/insights/decisions.d.ts.map +1 -0
  47. package/dist/insights/decisions.js +125 -0
  48. package/dist/insights/decisions.js.map +1 -0
  49. package/dist/insights/decisions.test.d.ts +2 -0
  50. package/dist/insights/decisions.test.d.ts.map +1 -0
  51. package/dist/insights/decisions.test.js +141 -0
  52. package/dist/insights/decisions.test.js.map +1 -0
  53. package/dist/insights/dream.d.ts +71 -0
  54. package/dist/insights/dream.d.ts.map +1 -0
  55. package/dist/insights/dream.js +235 -0
  56. package/dist/insights/dream.js.map +1 -0
  57. package/dist/insights/dream.test.d.ts +2 -0
  58. package/dist/insights/dream.test.d.ts.map +1 -0
  59. package/dist/insights/dream.test.js +127 -0
  60. package/dist/insights/dream.test.js.map +1 -0
  61. package/dist/insights/index.d.ts +21 -0
  62. package/dist/insights/index.d.ts.map +1 -0
  63. package/dist/insights/index.js +21 -0
  64. package/dist/insights/index.js.map +1 -0
  65. package/dist/insights/obsidian.d.ts +42 -0
  66. package/dist/insights/obsidian.d.ts.map +1 -0
  67. package/dist/insights/obsidian.js +263 -0
  68. package/dist/insights/obsidian.js.map +1 -0
  69. package/dist/insights/obsidian.test.d.ts +2 -0
  70. package/dist/insights/obsidian.test.d.ts.map +1 -0
  71. package/dist/insights/obsidian.test.js +241 -0
  72. package/dist/insights/obsidian.test.js.map +1 -0
  73. package/dist/insights/paradox.d.ts +36 -0
  74. package/dist/insights/paradox.d.ts.map +1 -0
  75. package/dist/insights/paradox.js +201 -0
  76. package/dist/insights/paradox.js.map +1 -0
  77. package/dist/insights/paradox.test.d.ts +2 -0
  78. package/dist/insights/paradox.test.d.ts.map +1 -0
  79. package/dist/insights/paradox.test.js +88 -0
  80. package/dist/insights/paradox.test.js.map +1 -0
  81. package/dist/insights/regret.d.ts +57 -0
  82. package/dist/insights/regret.d.ts.map +1 -0
  83. package/dist/insights/regret.js +137 -0
  84. package/dist/insights/regret.js.map +1 -0
  85. package/dist/insights/regret.test.d.ts +2 -0
  86. package/dist/insights/regret.test.d.ts.map +1 -0
  87. package/dist/insights/regret.test.js +153 -0
  88. package/dist/insights/regret.test.js.map +1 -0
  89. package/dist/insights/stack-trace.d.ts +40 -0
  90. package/dist/insights/stack-trace.d.ts.map +1 -0
  91. package/dist/insights/stack-trace.js +127 -0
  92. package/dist/insights/stack-trace.js.map +1 -0
  93. package/dist/insights/stack-trace.test.d.ts +2 -0
  94. package/dist/insights/stack-trace.test.d.ts.map +1 -0
  95. package/dist/insights/stack-trace.test.js +103 -0
  96. package/dist/insights/stack-trace.test.js.map +1 -0
  97. package/dist/insights/story.d.ts +34 -0
  98. package/dist/insights/story.d.ts.map +1 -0
  99. package/dist/insights/story.js +100 -0
  100. package/dist/insights/story.js.map +1 -0
  101. package/dist/insights/story.test.d.ts +2 -0
  102. package/dist/insights/story.test.d.ts.map +1 -0
  103. package/dist/insights/story.test.js +99 -0
  104. package/dist/insights/story.test.js.map +1 -0
  105. package/dist/insights/suggest.d.ts +29 -0
  106. package/dist/insights/suggest.d.ts.map +1 -0
  107. package/dist/insights/suggest.js +93 -0
  108. package/dist/insights/suggest.js.map +1 -0
  109. package/dist/insights/suggest.test.d.ts +2 -0
  110. package/dist/insights/suggest.test.d.ts.map +1 -0
  111. package/dist/insights/suggest.test.js +71 -0
  112. package/dist/insights/suggest.test.js.map +1 -0
  113. package/dist/insights/who-knows.d.ts +66 -0
  114. package/dist/insights/who-knows.d.ts.map +1 -0
  115. package/dist/insights/who-knows.js +125 -0
  116. package/dist/insights/who-knows.js.map +1 -0
  117. package/dist/insights/who-knows.test.d.ts +2 -0
  118. package/dist/insights/who-knows.test.d.ts.map +1 -0
  119. package/dist/insights/who-knows.test.js +109 -0
  120. package/dist/insights/who-knows.test.js.map +1 -0
  121. package/dist/quant/alpha.d.ts +87 -0
  122. package/dist/quant/alpha.d.ts.map +1 -0
  123. package/dist/quant/alpha.js +103 -0
  124. package/dist/quant/alpha.js.map +1 -0
  125. package/dist/quant/alpha.test.d.ts +2 -0
  126. package/dist/quant/alpha.test.d.ts.map +1 -0
  127. package/dist/quant/alpha.test.js +147 -0
  128. package/dist/quant/alpha.test.js.map +1 -0
  129. package/dist/quant/backtest.d.ts +57 -0
  130. package/dist/quant/backtest.d.ts.map +1 -0
  131. package/dist/quant/backtest.js +90 -0
  132. package/dist/quant/backtest.js.map +1 -0
  133. package/dist/quant/backtest.test.d.ts +2 -0
  134. package/dist/quant/backtest.test.d.ts.map +1 -0
  135. package/dist/quant/backtest.test.js +133 -0
  136. package/dist/quant/backtest.test.js.map +1 -0
  137. package/dist/quant/black-swan.d.ts +45 -0
  138. package/dist/quant/black-swan.d.ts.map +1 -0
  139. package/dist/quant/black-swan.js +112 -0
  140. package/dist/quant/black-swan.js.map +1 -0
  141. package/dist/quant/black-swan.test.d.ts +2 -0
  142. package/dist/quant/black-swan.test.d.ts.map +1 -0
  143. package/dist/quant/black-swan.test.js +131 -0
  144. package/dist/quant/black-swan.test.js.map +1 -0
  145. package/dist/quant/correlation-matrix.d.ts +54 -0
  146. package/dist/quant/correlation-matrix.d.ts.map +1 -0
  147. package/dist/quant/correlation-matrix.js +103 -0
  148. package/dist/quant/correlation-matrix.js.map +1 -0
  149. package/dist/quant/correlation-matrix.test.d.ts +2 -0
  150. package/dist/quant/correlation-matrix.test.d.ts.map +1 -0
  151. package/dist/quant/correlation-matrix.test.js +118 -0
  152. package/dist/quant/correlation-matrix.test.js.map +1 -0
  153. package/dist/quant/drawdown.d.ts +51 -0
  154. package/dist/quant/drawdown.d.ts.map +1 -0
  155. package/dist/quant/drawdown.js +96 -0
  156. package/dist/quant/drawdown.js.map +1 -0
  157. package/dist/quant/drawdown.test.d.ts +2 -0
  158. package/dist/quant/drawdown.test.d.ts.map +1 -0
  159. package/dist/quant/drawdown.test.js +166 -0
  160. package/dist/quant/drawdown.test.js.map +1 -0
  161. package/dist/quant/greek.d.ts +55 -0
  162. package/dist/quant/greek.d.ts.map +1 -0
  163. package/dist/quant/greek.js +157 -0
  164. package/dist/quant/greek.js.map +1 -0
  165. package/dist/quant/greek.test.d.ts +2 -0
  166. package/dist/quant/greek.test.d.ts.map +1 -0
  167. package/dist/quant/greek.test.js +138 -0
  168. package/dist/quant/greek.test.js.map +1 -0
  169. package/dist/quant/implied-volatility.d.ts +65 -0
  170. package/dist/quant/implied-volatility.d.ts.map +1 -0
  171. package/dist/quant/implied-volatility.js +149 -0
  172. package/dist/quant/implied-volatility.js.map +1 -0
  173. package/dist/quant/implied-volatility.test.d.ts +2 -0
  174. package/dist/quant/implied-volatility.test.d.ts.map +1 -0
  175. package/dist/quant/implied-volatility.test.js +127 -0
  176. package/dist/quant/implied-volatility.test.js.map +1 -0
  177. package/dist/quant/index.d.ts +28 -0
  178. package/dist/quant/index.d.ts.map +1 -0
  179. package/dist/quant/index.js +28 -0
  180. package/dist/quant/index.js.map +1 -0
  181. package/dist/quant/insider-trading.d.ts +56 -0
  182. package/dist/quant/insider-trading.d.ts.map +1 -0
  183. package/dist/quant/insider-trading.js +129 -0
  184. package/dist/quant/insider-trading.js.map +1 -0
  185. package/dist/quant/insider-trading.test.d.ts +2 -0
  186. package/dist/quant/insider-trading.test.d.ts.map +1 -0
  187. package/dist/quant/insider-trading.test.js +130 -0
  188. package/dist/quant/insider-trading.test.js.map +1 -0
  189. package/dist/quant/moneyball.d.ts +48 -0
  190. package/dist/quant/moneyball.d.ts.map +1 -0
  191. package/dist/quant/moneyball.js +110 -0
  192. package/dist/quant/moneyball.js.map +1 -0
  193. package/dist/quant/moneyball.test.d.ts +2 -0
  194. package/dist/quant/moneyball.test.d.ts.map +1 -0
  195. package/dist/quant/moneyball.test.js +137 -0
  196. package/dist/quant/moneyball.test.js.map +1 -0
  197. package/dist/quant/tax-loss-harvest.d.ts +59 -0
  198. package/dist/quant/tax-loss-harvest.d.ts.map +1 -0
  199. package/dist/quant/tax-loss-harvest.js +126 -0
  200. package/dist/quant/tax-loss-harvest.js.map +1 -0
  201. package/dist/quant/tax-loss-harvest.test.d.ts +2 -0
  202. package/dist/quant/tax-loss-harvest.test.d.ts.map +1 -0
  203. package/dist/quant/tax-loss-harvest.test.js +126 -0
  204. package/dist/quant/tax-loss-harvest.test.js.map +1 -0
  205. package/dist/retrieve/index.d.ts +2 -0
  206. package/dist/retrieve/index.d.ts.map +1 -1
  207. package/dist/retrieve/index.js +2 -0
  208. package/dist/retrieve/index.js.map +1 -1
  209. package/dist/retrieve/intent.d.ts +32 -0
  210. package/dist/retrieve/intent.d.ts.map +1 -0
  211. package/dist/retrieve/intent.js +104 -0
  212. package/dist/retrieve/intent.js.map +1 -0
  213. package/dist/retrieve/intent.test.d.ts +2 -0
  214. package/dist/retrieve/intent.test.d.ts.map +1 -0
  215. package/dist/retrieve/intent.test.js +106 -0
  216. package/dist/retrieve/intent.test.js.map +1 -0
  217. package/dist/retrieve/search.d.ts +30 -0
  218. package/dist/retrieve/search.d.ts.map +1 -1
  219. package/dist/retrieve/search.js +48 -0
  220. package/dist/retrieve/search.js.map +1 -1
  221. package/dist/retrieve/search.test.js +84 -1
  222. package/dist/retrieve/search.test.js.map +1 -1
  223. package/dist/retrieve/synthesize.d.ts +57 -0
  224. package/dist/retrieve/synthesize.d.ts.map +1 -0
  225. package/dist/retrieve/synthesize.js +191 -0
  226. package/dist/retrieve/synthesize.js.map +1 -0
  227. package/dist/retrieve/synthesize.test.d.ts +2 -0
  228. package/dist/retrieve/synthesize.test.d.ts.map +1 -0
  229. package/dist/retrieve/synthesize.test.js +127 -0
  230. package/dist/retrieve/synthesize.test.js.map +1 -0
  231. package/dist/store/schema.d.ts +2 -2
  232. package/dist/store/schema.d.ts.map +1 -1
  233. package/dist/store/schema.js +60 -2
  234. package/dist/store/schema.js.map +1 -1
  235. package/dist/store/sqlite.d.ts +2 -0
  236. package/dist/store/sqlite.d.ts.map +1 -1
  237. package/dist/store/sqlite.js +24 -0
  238. package/dist/store/sqlite.js.map +1 -1
  239. package/dist/store/sqlite.test.js +1 -1
  240. package/dist/util/index.d.ts +2 -0
  241. package/dist/util/index.d.ts.map +1 -1
  242. package/dist/util/index.js +2 -1
  243. package/dist/util/index.js.map +1 -1
  244. package/dist/util/redact.d.ts +58 -0
  245. package/dist/util/redact.d.ts.map +1 -0
  246. package/dist/util/redact.js +129 -0
  247. package/dist/util/redact.js.map +1 -0
  248. package/dist/util/redact.test.d.ts +2 -0
  249. package/dist/util/redact.test.d.ts.map +1 -0
  250. package/dist/util/redact.test.js +148 -0
  251. package/dist/util/redact.test.js.map +1 -0
  252. package/dist/wisdom/calibrator.d.ts +43 -0
  253. package/dist/wisdom/calibrator.d.ts.map +1 -0
  254. package/dist/wisdom/calibrator.js +120 -0
  255. package/dist/wisdom/calibrator.js.map +1 -0
  256. package/dist/wisdom/feedback.d.ts +45 -0
  257. package/dist/wisdom/feedback.d.ts.map +1 -0
  258. package/dist/wisdom/feedback.js +116 -0
  259. package/dist/wisdom/feedback.js.map +1 -0
  260. package/dist/wisdom/index.d.ts +15 -0
  261. package/dist/wisdom/index.d.ts.map +1 -0
  262. package/dist/wisdom/index.js +15 -0
  263. package/dist/wisdom/index.js.map +1 -0
  264. package/dist/wisdom/types.d.ts +67 -0
  265. package/dist/wisdom/types.d.ts.map +1 -0
  266. package/dist/wisdom/types.js +20 -0
  267. package/dist/wisdom/types.js.map +1 -0
  268. package/dist/wisdom/wisdom.test.d.ts +2 -0
  269. package/dist/wisdom/wisdom.test.d.ts.map +1 -0
  270. package/dist/wisdom/wisdom.test.js +144 -0
  271. package/dist/wisdom/wisdom.test.js.map +1 -0
  272. package/package.json +1 -1
@@ -0,0 +1,153 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { detectRegrets, summarizeRegrets } from "./regret.js";
3
+ const cmt = (hash, date, subject, body = "", files = []) => ({
4
+ hash,
5
+ shortHash: hash.slice(0, 7),
6
+ authorName: "alice",
7
+ authorEmail: "alice@x",
8
+ authorDate: `${date}T12:00:00Z`,
9
+ committerDate: `${date}T12:00:00Z`,
10
+ subject,
11
+ body,
12
+ parents: [],
13
+ files,
14
+ });
15
+ describe("detectRegrets — revert (strongest signal)", () => {
16
+ it("detects explicit revert that names the shipped commit hash", () => {
17
+ const commits = [
18
+ cmt("a1b2c3d", "2024-08-01", "feat: enable HTTP/3", "", ["src/edge.ts"]),
19
+ cmt("e4f5g6h", "2024-08-03", "Revert \"feat: enable HTTP/3\"", "Reverts a1b2c3d — broke mobile", ["src/edge.ts"]),
20
+ ];
21
+ const regrets = detectRegrets(commits, { windowDays: 7 });
22
+ expect(regrets).toHaveLength(1);
23
+ expect(regrets[0].kind).toBe("revert");
24
+ expect(regrets[0].daysToFix).toBeCloseTo(2, 0);
25
+ });
26
+ it("detects revert from shared-files heuristic when subject is generic", () => {
27
+ const commits = [
28
+ cmt("a1", "2024-08-01", "feat: new caching", "", ["src/cache.ts"]),
29
+ cmt("a2", "2024-08-04", "revert caching change", "", ["src/cache.ts"]),
30
+ ];
31
+ expect(detectRegrets(commits)[0].kind).toBe("revert");
32
+ });
33
+ });
34
+ describe("detectRegrets — hotfix and fix kinds", () => {
35
+ it("flags hotfix when follow-up has 'hotfix' marker AND shares files", () => {
36
+ const commits = [
37
+ cmt("a1", "2024-08-01", "refactor: simplify session middleware", "", ["src/auth.ts"]),
38
+ cmt("a2", "2024-08-01", "hotfix: CSRF check disappeared in refactor", "", ["src/auth.ts"]),
39
+ ];
40
+ expect(detectRegrets(commits)[0].kind).toBe("hotfix");
41
+ });
42
+ it("flags 'fix' kind when follow-up is a generic fix touching same files", () => {
43
+ const commits = [
44
+ cmt("a1", "2024-08-01", "perf: cache user lookup", "", ["src/users.ts"]),
45
+ cmt("a2", "2024-08-04", "fix: cache invalidation race", "", ["src/users.ts"]),
46
+ ];
47
+ expect(detectRegrets(commits)[0].kind).toBe("fix");
48
+ });
49
+ it("doesn't double-flag commits that ARE themselves fixes", () => {
50
+ const commits = [
51
+ cmt("a1", "2024-08-01", "fix: typo", "", ["README.md"]),
52
+ cmt("a2", "2024-08-02", "fix: another typo", "", ["README.md"]),
53
+ ];
54
+ // Neither is a regret — both are fix-shaped subjects.
55
+ expect(detectRegrets(commits)).toEqual([]);
56
+ });
57
+ });
58
+ describe("detectRegrets — window enforcement", () => {
59
+ it("respects the windowDays cutoff", () => {
60
+ const commits = [
61
+ cmt("a1", "2024-08-01", "feat: thing", "", ["x.ts"]),
62
+ cmt("a2", "2024-08-15", "fix: thing broke", "", ["x.ts"]), // 14 days later
63
+ ];
64
+ expect(detectRegrets(commits, { windowDays: 7 })).toEqual([]);
65
+ expect(detectRegrets(commits, { windowDays: 30 })).toHaveLength(1);
66
+ });
67
+ it("respects minDaysToFix lower bound (filter out same-day micro-fixes)", () => {
68
+ const commits = [
69
+ cmt("a1", "2024-08-01", "feat: thing", "", ["x.ts"]),
70
+ cmt("a2", "2024-08-01", "fix: typo", "", ["x.ts"]),
71
+ ];
72
+ expect(detectRegrets(commits, { minDaysToFix: 1 })).toEqual([]);
73
+ expect(detectRegrets(commits, { minDaysToFix: 0 })).toHaveLength(1);
74
+ });
75
+ });
76
+ describe("detectRegrets — only one regret per shipped commit", () => {
77
+ it("returns the first follow-up only (avoids double-counting)", () => {
78
+ const commits = [
79
+ cmt("a1", "2024-08-01", "feat: thing", "", ["x.ts"]),
80
+ cmt("a2", "2024-08-02", "fix: first attempt at fix", "", ["x.ts"]),
81
+ cmt("a3", "2024-08-04", "fix: second attempt", "", ["x.ts"]),
82
+ ];
83
+ const regrets = detectRegrets(commits);
84
+ expect(regrets).toHaveLength(1);
85
+ expect(regrets[0].followup.shortHash).toBe("a2");
86
+ });
87
+ });
88
+ describe("detectRegrets — output ordering and content", () => {
89
+ it("sorts results by ship date descending (newest regret first)", () => {
90
+ const commits = [
91
+ cmt("old1", "2024-01-01", "feat: a", "", ["x.ts"]),
92
+ cmt("old2", "2024-01-02", "fix: a broke", "", ["x.ts"]),
93
+ cmt("new1", "2024-08-01", "feat: b", "", ["y.ts"]),
94
+ cmt("new2", "2024-08-03", "fix: b broke", "", ["y.ts"]),
95
+ ];
96
+ const regrets = detectRegrets(commits);
97
+ expect(regrets).toHaveLength(2);
98
+ expect(regrets[0].shipped.shortHash).toBe("new1");
99
+ expect(regrets[1].shipped.shortHash).toBe("old1");
100
+ });
101
+ it("attaches a lesson string when the follow-up body has explanatory text", () => {
102
+ const commits = [
103
+ cmt("a1", "2024-08-01", "feat: thing", "", ["x.ts"]),
104
+ cmt("a2", "2024-08-02", "fix: thing crashed", "the cache invalidation was racing with the eviction sweep", ["x.ts"]),
105
+ ];
106
+ const r = detectRegrets(commits)[0];
107
+ expect(r.lesson).toContain("cache invalidation");
108
+ });
109
+ });
110
+ describe("summarizeRegrets — repo-level metrics", () => {
111
+ it("computes regretRate over non-fix shipped commits", () => {
112
+ const commits = [
113
+ cmt("a1", "2024-08-01", "feat: A", "", ["x.ts"]),
114
+ cmt("a2", "2024-08-02", "fix: A broke", "", ["x.ts"]),
115
+ cmt("a3", "2024-08-03", "feat: B", "", ["y.ts"]),
116
+ cmt("a4", "2024-08-04", "feat: C", "", ["z.ts"]),
117
+ ];
118
+ const regrets = detectRegrets(commits);
119
+ const summary = summarizeRegrets(commits, regrets);
120
+ // Non-fix shipped: a1, a3, a4 → 3 candidates. 1 regret. → 1/3 ≈ 0.33
121
+ expect(summary.totalShipped).toBe(3);
122
+ expect(summary.totalRegrets).toBe(1);
123
+ expect(summary.regretRate).toBeCloseTo(1 / 3, 2);
124
+ });
125
+ it("counts byKind correctly", () => {
126
+ const commits = [
127
+ cmt("a1", "2024-08-01", "feat: A", "", ["x.ts"]),
128
+ cmt("a2", "2024-08-02", "Revert \"feat: A\"", "", ["x.ts"]),
129
+ cmt("a3", "2024-08-03", "feat: B", "", ["y.ts"]),
130
+ cmt("a4", "2024-08-04", "hotfix: B explosion", "", ["y.ts"]),
131
+ ];
132
+ const regrets = detectRegrets(commits);
133
+ const summary = summarizeRegrets(commits, regrets);
134
+ expect(summary.byKind.revert).toBe(1);
135
+ expect(summary.byKind.hotfix).toBe(1);
136
+ });
137
+ it("reports averageDaysToFix", () => {
138
+ const commits = [
139
+ cmt("a1", "2024-08-01", "feat: A", "", ["x.ts"]),
140
+ cmt("a2", "2024-08-03", "fix: A", "", ["x.ts"]), // 2 days
141
+ cmt("a3", "2024-08-10", "feat: B", "", ["y.ts"]),
142
+ cmt("a4", "2024-08-14", "fix: B", "", ["y.ts"]), // 4 days
143
+ ];
144
+ const summary = summarizeRegrets(commits, detectRegrets(commits));
145
+ expect(summary.averageDaysToFix).toBeCloseTo(3, 1);
146
+ });
147
+ it("returns 0 metrics when there are no regrets", () => {
148
+ const summary = summarizeRegrets([cmt("a", "2024-08-01", "feat: stable", "", ["x"])], []);
149
+ expect(summary.regretRate).toBe(0);
150
+ expect(summary.averageDaysToFix).toBe(0);
151
+ });
152
+ });
153
+ //# sourceMappingURL=regret.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regret.test.js","sourceRoot":"","sources":["../../src/insights/regret.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `stack-trace` — given an error / stack trace, find the commits that
3
+ * touched each frame and any past incidents at the same locations.
4
+ *
5
+ * Why this exists: when a prod bug fires, the most useful question is
6
+ * "have we seen this here before?" Mneme already indexes git + incidents.
7
+ * Parsing a stack trace and querying both is a small composition of pieces
8
+ * we already have.
9
+ *
10
+ * Supported formats (frame parser):
11
+ * - JS/TS: " at functionName (path/to/file.ts:42:15)"
12
+ * - Python: " File "path/to/file.py", line 42, in functionName"
13
+ * - Go: "goroutine 1 [running]: ... path/to/file.go:42 +0x..."
14
+ * - Java: " at com.example.Foo.bar(Foo.java:42)"
15
+ *
16
+ * Pure parsing — no I/O. Composing with retrieval lives in the CLI command.
17
+ */
18
+ export interface StackFrame {
19
+ /** File path as it appears in the trace. May be absolute, relative, or module-qualified. */
20
+ file: string;
21
+ /** Line number where the frame is. 0 when absent. */
22
+ line: number;
23
+ /** Function/method name when extractable; may be empty. */
24
+ function?: string;
25
+ /** Programming language detected from the frame format. */
26
+ language: "js" | "python" | "go" | "java" | "unknown";
27
+ }
28
+ /**
29
+ * Parse a multi-line stack trace into ordered frames. Frames are deduped
30
+ * when the same (file, line) appears consecutively. Languages that share
31
+ * patterns (e.g. .ts files might also match the python file regex if
32
+ * weirdly formatted) are disambiguated by extension first.
33
+ */
34
+ export declare function parseStackTrace(text: string): StackFrame[];
35
+ /**
36
+ * Heuristic: detect whether the trace appears to be from a particular
37
+ * language. Useful when displaying frame analysis ("Python traceback").
38
+ */
39
+ export declare function detectLanguage(text: string): StackFrame["language"];
40
+ //# sourceMappingURL=stack-trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-trace.d.ts","sourceRoot":"","sources":["../../src/insights/stack-trace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,UAAU;IACzB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,QAAQ,EAAE,IAAI,GAAG,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;CACvD;AA2DD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAyB1D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CASnE"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `stack-trace` — given an error / stack trace, find the commits that
3
+ * touched each frame and any past incidents at the same locations.
4
+ *
5
+ * Why this exists: when a prod bug fires, the most useful question is
6
+ * "have we seen this here before?" Mneme already indexes git + incidents.
7
+ * Parsing a stack trace and querying both is a small composition of pieces
8
+ * we already have.
9
+ *
10
+ * Supported formats (frame parser):
11
+ * - JS/TS: " at functionName (path/to/file.ts:42:15)"
12
+ * - Python: " File "path/to/file.py", line 42, in functionName"
13
+ * - Go: "goroutine 1 [running]: ... path/to/file.go:42 +0x..."
14
+ * - Java: " at com.example.Foo.bar(Foo.java:42)"
15
+ *
16
+ * Pure parsing — no I/O. Composing with retrieval lives in the CLI command.
17
+ */
18
+ const PATTERNS = [
19
+ // JS/TS — V8 / Node.js / browser
20
+ // " at functionName (path/to/file.ts:42:15)"
21
+ // " at path/to/file.ts:42:15"
22
+ {
23
+ language: "js",
24
+ re: /\bat\s+(?:(?<fn>[\w$.<>[\]\s]+?)\s+\()?(?<file>[^():\n]+\.[a-zA-Z]+):(?<line>\d+)(?::\d+)?\)?/g,
25
+ map: (m) => {
26
+ const file = m.groups?.file?.trim();
27
+ if (!file)
28
+ return null;
29
+ return {
30
+ file,
31
+ line: Number(m.groups?.line ?? 0),
32
+ function: m.groups?.fn?.trim() || undefined,
33
+ language: "js",
34
+ };
35
+ },
36
+ },
37
+ // Python — CPython tracebacks
38
+ // ' File "path/to/file.py", line 42, in functionName'
39
+ {
40
+ language: "python",
41
+ re: /File\s+"(?<file>[^"]+\.py[i]?)",\s+line\s+(?<line>\d+)(?:,\s+in\s+(?<fn>\w+))?/g,
42
+ map: (m) => ({
43
+ file: m.groups.file,
44
+ line: Number(m.groups.line),
45
+ function: m.groups?.fn || undefined,
46
+ language: "python",
47
+ }),
48
+ },
49
+ // Go — runtime panic format
50
+ // " /home/x/main.go:42 +0x..."
51
+ // "main.foo(0x0, 0x0)"
52
+ // " /home/x/foo.go:42"
53
+ {
54
+ language: "go",
55
+ re: /(?<file>[^\s():]+\.go):(?<line>\d+)/g,
56
+ map: (m) => ({
57
+ file: m.groups.file,
58
+ line: Number(m.groups.line),
59
+ language: "go",
60
+ }),
61
+ },
62
+ // Java
63
+ // " at com.example.Foo.bar(Foo.java:42)"
64
+ {
65
+ language: "java",
66
+ re: /\bat\s+(?<fn>[\w.$]+)\((?<file>[^()]+\.java):(?<line>\d+)\)/g,
67
+ map: (m) => ({
68
+ file: m.groups.file,
69
+ line: Number(m.groups.line),
70
+ function: m.groups?.fn || undefined,
71
+ language: "java",
72
+ }),
73
+ },
74
+ ];
75
+ /**
76
+ * Parse a multi-line stack trace into ordered frames. Frames are deduped
77
+ * when the same (file, line) appears consecutively. Languages that share
78
+ * patterns (e.g. .ts files might also match the python file regex if
79
+ * weirdly formatted) are disambiguated by extension first.
80
+ */
81
+ export function parseStackTrace(text) {
82
+ if (!text || !text.trim())
83
+ return [];
84
+ const seen = new Set();
85
+ const frames = [];
86
+ for (const p of PATTERNS) {
87
+ const re = new RegExp(p.re.source, p.re.flags);
88
+ let m;
89
+ while ((m = re.exec(text)) !== null) {
90
+ const frame = p.map(m);
91
+ if (!frame)
92
+ continue;
93
+ // Skip language-mismatch (e.g. js regex matching a Java line).
94
+ const ext = frame.file.split(".").pop()?.toLowerCase();
95
+ if (frame.language === "js" && ext && !["js", "ts", "jsx", "tsx", "mjs", "cjs"].includes(ext))
96
+ continue;
97
+ const key = `${frame.file}:${frame.line}`;
98
+ if (seen.has(key))
99
+ continue;
100
+ seen.add(key);
101
+ frames.push(frame);
102
+ }
103
+ }
104
+ // Sort by appearance order in original text (so most-recent-frame-first
105
+ // for languages where the trace is bottom-up like Python).
106
+ // Detection: count file paths — the first frame in the input is what we keep first.
107
+ return frames.sort((a, b) => text.indexOf(a.file) - text.indexOf(b.file));
108
+ }
109
+ /**
110
+ * Heuristic: detect whether the trace appears to be from a particular
111
+ * language. Useful when displaying frame analysis ("Python traceback").
112
+ */
113
+ export function detectLanguage(text) {
114
+ const t = text.toLowerCase();
115
+ if (t.includes("traceback") || t.includes('file "') || t.includes(".py"))
116
+ return t.includes(".py") ? "python" : "unknown";
117
+ if (t.includes("goroutine") || /\.go:\d+/.test(t))
118
+ return "go";
119
+ if (/\.java:\d+/.test(t))
120
+ return "java";
121
+ if (/\b(typeerror|referenceerror|syntaxerror|rangeerror)\b/.test(t))
122
+ return "js";
123
+ if (/\.[jt]sx?:\d+/.test(t))
124
+ return "js";
125
+ return "unknown";
126
+ }
127
+ //# sourceMappingURL=stack-trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-trace.js","sourceRoot":"","sources":["../../src/insights/stack-trace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stack-trace.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-trace.test.d.ts","sourceRoot":"","sources":["../../src/insights/stack-trace.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseStackTrace, detectLanguage } from "./stack-trace.js";
3
+ describe("parseStackTrace — JavaScript/TypeScript", () => {
4
+ it("parses V8-style frames with function name", () => {
5
+ const trace = `TypeError: Cannot read property 'amount' of undefined
6
+ at parseAmount (src/payment.ts:42:15)
7
+ at processCharge (src/payment.ts:78:9)
8
+ at Object.<anonymous> (src/index.ts:5:1)`;
9
+ const frames = parseStackTrace(trace);
10
+ expect(frames).toHaveLength(3);
11
+ expect(frames[0]).toMatchObject({ file: "src/payment.ts", line: 42, function: "parseAmount", language: "js" });
12
+ expect(frames[1].function).toBe("processCharge");
13
+ });
14
+ it("parses anonymous frames (no function name)", () => {
15
+ const trace = `Error: kaboom
16
+ at /app/dist/main.js:1:42`;
17
+ const frames = parseStackTrace(trace);
18
+ expect(frames[0]?.file).toBe("/app/dist/main.js");
19
+ expect(frames[0]?.function).toBeUndefined();
20
+ });
21
+ });
22
+ describe("parseStackTrace — Python", () => {
23
+ it("parses CPython tracebacks", () => {
24
+ const trace = `Traceback (most recent call last):
25
+ File "/app/main.py", line 42, in process_charge
26
+ amount = parse_amount(payload["amount"])
27
+ File "/app/payment.py", line 17, in parse_amount
28
+ return Decimal(value)
29
+ ValueError: not a number`;
30
+ const frames = parseStackTrace(trace);
31
+ expect(frames.length).toBeGreaterThanOrEqual(2);
32
+ const main = frames.find((f) => f.file.endsWith("main.py"));
33
+ expect(main).toMatchObject({ line: 42, function: "process_charge", language: "python" });
34
+ });
35
+ });
36
+ describe("parseStackTrace — Go", () => {
37
+ it("parses Go panic file:line references", () => {
38
+ const trace = `goroutine 1 [running]:
39
+ main.foo(0x0)
40
+ /home/dev/app/main.go:42 +0x12
41
+ runtime.main()
42
+ /usr/local/go/src/runtime/proc.go:250`;
43
+ const frames = parseStackTrace(trace);
44
+ expect(frames.length).toBeGreaterThanOrEqual(2);
45
+ expect(frames.find((f) => f.file === "/home/dev/app/main.go")?.line).toBe(42);
46
+ });
47
+ });
48
+ describe("parseStackTrace — Java", () => {
49
+ it("parses Java stack frames with class.method(File.java:line)", () => {
50
+ const trace = `Exception in thread "main" java.lang.NullPointerException
51
+ at com.example.Foo.bar(Foo.java:42)
52
+ at com.example.App.main(App.java:7)`;
53
+ const frames = parseStackTrace(trace);
54
+ expect(frames).toHaveLength(2);
55
+ expect(frames[0]).toMatchObject({ file: "Foo.java", line: 42, language: "java" });
56
+ expect(frames[0].function).toContain("Foo.bar");
57
+ });
58
+ });
59
+ describe("parseStackTrace — edge cases", () => {
60
+ it("returns empty array for empty input", () => {
61
+ expect(parseStackTrace("")).toEqual([]);
62
+ expect(parseStackTrace(" \n \n")).toEqual([]);
63
+ });
64
+ it("dedupes consecutive identical (file, line) frames", () => {
65
+ const trace = `at a (foo.ts:42:1)
66
+ at a (foo.ts:42:1)
67
+ at a (foo.ts:42:1)`;
68
+ expect(parseStackTrace(trace)).toHaveLength(1);
69
+ });
70
+ it("filters out non-matching language extensions", () => {
71
+ // ".java" file should not match the JS regex even if the format looks similar.
72
+ const trace = `at com.x.Foo.bar(Foo.java:42)`;
73
+ const frames = parseStackTrace(trace);
74
+ expect(frames.every((f) => f.language === "java")).toBe(true);
75
+ });
76
+ it("handles a mixed trace (multiple languages in one input)", () => {
77
+ const trace = `TypeError: kaboom
78
+ at handler (src/api.ts:42:1)
79
+ And in the worker:
80
+ File "/app/worker.py", line 7, in run_job`;
81
+ const frames = parseStackTrace(trace);
82
+ expect(frames.find((f) => f.language === "js")).toBeDefined();
83
+ expect(frames.find((f) => f.language === "python")).toBeDefined();
84
+ });
85
+ });
86
+ describe("detectLanguage — heuristic top-level detection", () => {
87
+ it("python (Traceback header)", () => {
88
+ expect(detectLanguage("Traceback (most recent call last):\n File \"x.py\", line 1, in f")).toBe("python");
89
+ });
90
+ it("go (goroutine header)", () => {
91
+ expect(detectLanguage("goroutine 1 [running]:\n/x/main.go:1 +0x")).toBe("go");
92
+ });
93
+ it("java (.java:N pattern)", () => {
94
+ expect(detectLanguage("at com.x.Foo.bar(Foo.java:42)")).toBe("java");
95
+ });
96
+ it("js (TypeError keyword)", () => {
97
+ expect(detectLanguage("TypeError: Cannot read property 'x' of undefined")).toBe("js");
98
+ });
99
+ it("unknown when no marker", () => {
100
+ expect(detectLanguage("just some random text")).toBe("unknown");
101
+ });
102
+ });
103
+ //# sourceMappingURL=stack-trace.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-trace.test.js","sourceRoot":"","sources":["../../src/insights/stack-trace.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `story <topic>` — narrative generation. Take a topic, find every commit
3
+ * that touched it, group into "acts" (initial / refactor / incidents /
4
+ * stable), and emit a structured timeline. The LLM (when available) can
5
+ * polish the act labels and write a short prose summary.
6
+ *
7
+ * Pure data extraction in this file. The CLI command renders + optionally
8
+ * pipes through an LLM for prose.
9
+ */
10
+ import type { Commit } from "../types.js";
11
+ export interface StoryAct {
12
+ /** Stable id used for the section header. */
13
+ id: "initial" | "refactor" | "incident" | "evolution" | "stable";
14
+ /** Human-readable title — "Act I — The Beginning" etc. */
15
+ title: string;
16
+ /** Commits in chronological order (oldest first). */
17
+ commits: Commit[];
18
+ /** Date range of the act. */
19
+ fromDate: string;
20
+ toDate: string;
21
+ }
22
+ export interface Story {
23
+ topic: string;
24
+ acts: StoryAct[];
25
+ totalCommits: number;
26
+ spanDays: number;
27
+ }
28
+ /**
29
+ * Build a Story from a list of commits matching the topic. Commits should
30
+ * already be filtered by relevance (e.g. via FTS) and sorted chronologically
31
+ * by the caller.
32
+ */
33
+ export declare function buildStory(topic: string, commits: Commit[]): Story;
34
+ //# sourceMappingURL=story.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/insights/story.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,QAAQ;IACvB,6CAA6C;IAC7C,EAAE,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;IACjE,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAqElE"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * `story <topic>` — narrative generation. Take a topic, find every commit
3
+ * that touched it, group into "acts" (initial / refactor / incidents /
4
+ * stable), and emit a structured timeline. The LLM (when available) can
5
+ * polish the act labels and write a short prose summary.
6
+ *
7
+ * Pure data extraction in this file. The CLI command renders + optionally
8
+ * pipes through an LLM for prose.
9
+ */
10
+ const REFACTOR_KEYWORDS = /\b(refactor|rewrite|migrate|switch(?:ed)?|replace[ds]?|deprecate)\b/i;
11
+ const INCIDENT_KEYWORDS = /\b(incident|outage|hotfix|revert|rollback|broken|critical|emergency|p[01])\b/i;
12
+ /**
13
+ * Build a Story from a list of commits matching the topic. Commits should
14
+ * already be filtered by relevance (e.g. via FTS) and sorted chronologically
15
+ * by the caller.
16
+ */
17
+ export function buildStory(topic, commits) {
18
+ const sorted = [...commits].sort((a, b) => a.authorDate.localeCompare(b.authorDate));
19
+ if (sorted.length === 0) {
20
+ return { topic, acts: [], totalCommits: 0, spanDays: 0 };
21
+ }
22
+ const acts = [];
23
+ // Act I — Initial: the very first commit on the topic.
24
+ const initial = sorted[0];
25
+ acts.push({
26
+ id: "initial",
27
+ title: "Act I — The Beginning",
28
+ commits: [initial],
29
+ fromDate: initial.authorDate.slice(0, 10),
30
+ toDate: initial.authorDate.slice(0, 10),
31
+ });
32
+ const remaining = sorted.slice(1);
33
+ let currentFlavor = null;
34
+ let currentBucket = [];
35
+ const flush = () => {
36
+ if (currentBucket.length === 0 || !currentFlavor)
37
+ return;
38
+ acts.push({
39
+ id: currentFlavor,
40
+ title: titleFor(currentFlavor, acts.length),
41
+ commits: [...currentBucket],
42
+ fromDate: currentBucket[0].authorDate.slice(0, 10),
43
+ toDate: currentBucket[currentBucket.length - 1].authorDate.slice(0, 10),
44
+ });
45
+ currentBucket = [];
46
+ currentFlavor = null;
47
+ };
48
+ for (const c of remaining) {
49
+ const flavor = detectFlavor(c);
50
+ if (flavor !== currentFlavor)
51
+ flush();
52
+ currentFlavor = flavor;
53
+ currentBucket.push(c);
54
+ }
55
+ flush();
56
+ // Final act — if the most recent commit is older than 90 days, consider
57
+ // the topic "stable" (no recent change).
58
+ const last = sorted[sorted.length - 1];
59
+ const ageDays = (Date.now() - new Date(last.authorDate).getTime()) / 86_400_000;
60
+ if (ageDays > 90 && acts[acts.length - 1].id !== "stable") {
61
+ acts.push({
62
+ id: "stable",
63
+ title: `${acts.length === 0 ? "Act I" : romanFor(acts.length + 1)} — The Stable State`,
64
+ commits: [last],
65
+ fromDate: last.authorDate.slice(0, 10),
66
+ toDate: new Date().toISOString().slice(0, 10),
67
+ });
68
+ }
69
+ const span = Math.round((new Date(last.authorDate).getTime() - new Date(initial.authorDate).getTime()) / 86_400_000);
70
+ return {
71
+ topic,
72
+ acts,
73
+ totalCommits: sorted.length,
74
+ spanDays: span,
75
+ };
76
+ }
77
+ function detectFlavor(c) {
78
+ const text = `${c.subject}\n${c.body || ""}`;
79
+ if (INCIDENT_KEYWORDS.test(text))
80
+ return "incident";
81
+ if (REFACTOR_KEYWORDS.test(text))
82
+ return "refactor";
83
+ return "evolution";
84
+ }
85
+ function titleFor(flavor, actIndex) {
86
+ const roman = romanFor(actIndex + 1);
87
+ switch (flavor) {
88
+ case "refactor":
89
+ return `${roman} — The Refactor`;
90
+ case "incident":
91
+ return `${roman} — Incidents Strike`;
92
+ case "evolution":
93
+ return `${roman} — Steady Evolution`;
94
+ }
95
+ }
96
+ function romanFor(n) {
97
+ const roman = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"];
98
+ return n <= 10 ? `Act ${roman[n - 1]}` : `Act ${n}`;
99
+ }
100
+ //# sourceMappingURL=story.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"story.js","sourceRoot":"","sources":["../../src/insights/story.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=story.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"story.test.d.ts","sourceRoot":"","sources":["../../src/insights/story.test.ts"],"names":[],"mappings":""}