@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,219 @@
1
+ /**
2
+ * `mneme crystal-ball` — predict the probability that the staged change
3
+ * will fail CI, BEFORE you push.
4
+ *
5
+ * Approach (Bayesian-ish over historical signals):
6
+ * 1. Compute a fingerprint of the staged diff:
7
+ * modules touched, file types, add/remove ratio, test changes Y/N.
8
+ * 2. Find historical commits with similar fingerprints.
9
+ * 3. Look at each one's fate within `windowDays`:
10
+ * - revert / hotfix / fix follow-up = "trouble"
11
+ * - else = "clean"
12
+ * 4. Output: prediction p(clean) = clean / total similar.
13
+ *
14
+ * Without integrated CI run history (which Mneme doesn't index by default),
15
+ * follow-up commits are our best proxy: a commit that needed a hotfix is
16
+ * indistinguishable from one that broke CI from the user's perspective.
17
+ *
18
+ * The output is honest about its limits: if `n` is small (< 5), we say
19
+ * "low signal" instead of pretending to be confident.
20
+ */
21
+ import { parseDiff } from "./commit-coach.js";
22
+ import { detectRegrets } from "./regret.js";
23
+ export function fingerprint(diff) {
24
+ const exts = new Set();
25
+ for (const f of diff.files) {
26
+ const m = f.match(/\.([a-z0-9]+)$/i);
27
+ if (m)
28
+ exts.add(m[1].toLowerCase());
29
+ }
30
+ const totalLines = diff.added + diff.removed;
31
+ const size = totalLines < 20 ? "tiny" : totalLines < 100 ? "small" : totalLines < 500 ? "medium" : "large";
32
+ const hasTests = diff.files.some((f) => /(\.test\.|\.spec\.|\/tests?\/|\/specs?\/)/.test(f));
33
+ return {
34
+ modules: [...new Set(diff.modules)].sort(),
35
+ extensions: [...exts].sort(),
36
+ shape: diff.shape,
37
+ size,
38
+ hasTests,
39
+ };
40
+ }
41
+ /**
42
+ * Score similarity between two fingerprints. Returns 0..1.
43
+ *
44
+ * Each dimension contributes a weight; the score is a weighted average:
45
+ * modules: 0.40 (Jaccard)
46
+ * extensions: 0.20 (Jaccard)
47
+ * shape: 0.20 (1.0 if equal, else 0.0)
48
+ * size: 0.10 (linear distance over 4 buckets)
49
+ * hasTests: 0.10 (1.0 if equal, else 0.0)
50
+ */
51
+ export function similarity(a, b) {
52
+ const jaccard = (xs, ys) => {
53
+ if (xs.length === 0 && ys.length === 0)
54
+ return 1;
55
+ const A = new Set(xs);
56
+ const B = new Set(ys);
57
+ let inter = 0;
58
+ for (const x of A)
59
+ if (B.has(x))
60
+ inter += 1;
61
+ const union = A.size + B.size - inter;
62
+ return union === 0 ? 0 : inter / union;
63
+ };
64
+ const modules = jaccard(a.modules, b.modules);
65
+ const exts = jaccard(a.extensions, b.extensions);
66
+ const shape = a.shape === b.shape ? 1 : 0;
67
+ const sizes = ["tiny", "small", "medium", "large"];
68
+ const sizeDist = Math.abs(sizes.indexOf(a.size) - sizes.indexOf(b.size));
69
+ const size = 1 - sizeDist / 3;
70
+ const tests = a.hasTests === b.hasTests ? 1 : 0;
71
+ return 0.4 * modules + 0.2 * exts + 0.2 * shape + 0.1 * size + 0.1 * tests;
72
+ }
73
+ const SIMILARITY_THRESHOLD = 0.5;
74
+ /**
75
+ * Predict CI/follow-up trouble probability for the given diff.
76
+ *
77
+ * @param store - the indexed store
78
+ * @param diffText - unified diff text (e.g. `git diff --staged` output)
79
+ * @param windowDays - how long after a past commit do we still count a
80
+ * follow-up as "trouble" (default 14)
81
+ */
82
+ export function predict(store, diffText, windowDays = 14) {
83
+ const parsed = parseDiff(diffText);
84
+ const fp = fingerprint(parsed);
85
+ // Pull all commits from the store + their files.
86
+ // Only consider recent-enough commits (skip ancient history).
87
+ const commitRows = store.db
88
+ .prepare(`SELECT * FROM commits ORDER BY author_date DESC LIMIT 1000`)
89
+ .all();
90
+ if (commitRows.length === 0) {
91
+ return {
92
+ fingerprint: fp,
93
+ similarN: 0,
94
+ cleanN: 0,
95
+ pClean: 0,
96
+ verdict: "unknown",
97
+ recommendation: "Index the repo first (mneme index) — no historical signal yet.",
98
+ };
99
+ }
100
+ const commits = [];
101
+ for (const r of commitRows) {
102
+ const hash = String(r.hash);
103
+ const files = store.db.prepare("SELECT path FROM file_changes WHERE commit_hash = ?").all(hash).map((x) => x.path);
104
+ commits.push({
105
+ hash,
106
+ shortHash: String(r.short_hash),
107
+ authorName: String(r.author_name),
108
+ authorEmail: String(r.author_email),
109
+ authorDate: String(r.author_date),
110
+ committerDate: String(r.committer_date),
111
+ subject: String(r.subject),
112
+ body: String(r.body || ""),
113
+ parents: r.parents ? String(r.parents).split(/\s+/).filter(Boolean) : [],
114
+ files,
115
+ });
116
+ }
117
+ // Compute similarity for each historical commit.
118
+ const scored = [];
119
+ for (const c of commits) {
120
+ const cFp = fingerprintFromCommit(c);
121
+ const sim = similarity(fp, cFp);
122
+ if (sim >= SIMILARITY_THRESHOLD)
123
+ scored.push({ commit: c, sim, fp: cFp });
124
+ }
125
+ scored.sort((a, b) => b.sim - a.sim);
126
+ // Top 50 most-similar commits are our base.
127
+ const similar = scored.slice(0, 50);
128
+ const similarN = similar.length;
129
+ if (similarN === 0) {
130
+ return {
131
+ fingerprint: fp,
132
+ similarN: 0,
133
+ cleanN: 0,
134
+ pClean: 0,
135
+ verdict: "unknown",
136
+ recommendation: "No similar past changes in this repo. Treat as exploratory — small commits, careful review.",
137
+ };
138
+ }
139
+ // Detect regrets ONLY among the similar commits (constrain corpus).
140
+ // Use only strong regret signals (revert/hotfix/fix) — the weaker
141
+ // 'sameFiles' kind is too noisy for failure prediction.
142
+ const similarHashes = new Set(similar.map((s) => s.commit.hash));
143
+ const allRegrets = detectRegrets(commits, { windowDays });
144
+ const troubledHashes = new Set(allRegrets
145
+ .filter((r) => r.kind !== "sameFiles" && similarHashes.has(r.shipped.hash))
146
+ .map((r) => r.shipped.hash));
147
+ const cleanN = similarN - troubledHashes.size;
148
+ const pClean = cleanN / similarN;
149
+ // Find most-similar past commit (top of scored list) and its outcome.
150
+ const top = similar[0];
151
+ const mostSimilar = {
152
+ hash: top.commit.shortHash || top.commit.hash.slice(0, 7),
153
+ subject: top.commit.subject,
154
+ date: top.commit.authorDate.slice(0, 10),
155
+ outcome: troubledHashes.has(top.commit.hash) ? "trouble" : "clean",
156
+ };
157
+ const verdict = similarN < 5
158
+ ? "unknown"
159
+ : pClean >= 0.85
160
+ ? "clear"
161
+ : pClean >= 0.6
162
+ ? "moderate"
163
+ : "risky";
164
+ const recommendation = buildRecommendation(verdict, similarN, troubledHashes.size, pClean);
165
+ return {
166
+ fingerprint: fp,
167
+ similarN,
168
+ cleanN,
169
+ pClean,
170
+ verdict,
171
+ mostSimilar,
172
+ recommendation,
173
+ };
174
+ }
175
+ function fingerprintFromCommit(c) {
176
+ // Reconstruct a parsed-diff-like view from commit metadata.
177
+ const files = c.files ?? [];
178
+ const modules = [
179
+ ...new Set(files.map((f) => {
180
+ const parts = f.split("/");
181
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
182
+ })),
183
+ ].sort();
184
+ const exts = new Set();
185
+ for (const f of files) {
186
+ const m = f.match(/\.([a-z0-9]+)$/i);
187
+ if (m)
188
+ exts.add(m[1].toLowerCase());
189
+ }
190
+ // Shape from subject conventions.
191
+ const subjectLower = c.subject.toLowerCase();
192
+ const shape = /^fix|^hotfix|^bug/.test(subjectLower)
193
+ ? "fix"
194
+ : /^refactor/.test(subjectLower)
195
+ ? "refactor"
196
+ : /^docs?|^chore/.test(subjectLower)
197
+ ? "docs"
198
+ : /^test/.test(subjectLower)
199
+ ? "test"
200
+ : /^perf/.test(subjectLower)
201
+ ? "perf"
202
+ : "feat";
203
+ // We don't have line counts; treat as "small" by default.
204
+ const hasTests = files.some((f) => /(\.test\.|\.spec\.|\/tests?\/)/.test(f));
205
+ return { modules, extensions: [...exts].sort(), shape, size: "small", hasTests };
206
+ }
207
+ function buildRecommendation(verdict, similarN, troubledN, pClean) {
208
+ switch (verdict) {
209
+ case "unknown":
210
+ return `Only ${similarN} similar past change${similarN === 1 ? "" : "s"} in history — too thin to predict. Run tests locally before pushing.`;
211
+ case "clear":
212
+ return `${(pClean * 100).toFixed(0)}% of ${similarN} similar past changes shipped cleanly. Looks like a routine change.`;
213
+ case "moderate":
214
+ return `${troubledN} of ${similarN} similar past changes needed a follow-up fix. Run lint + tests locally first.`;
215
+ case "risky":
216
+ return `${troubledN} of ${similarN} similar past changes needed a follow-up fix (${(pClean * 100).toFixed(0)}% clean rate). Strongly consider splitting this commit + extra review.`;
217
+ }
218
+ }
219
+ //# sourceMappingURL=crystal-ball.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crystal-ball.js","sourceRoot":"","sources":["../../src/insights/crystal-ball.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crystal-ball.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crystal-ball.test.d.ts","sourceRoot":"","sources":["../../src/insights/crystal-ball.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { MnemeStore } from "../store/sqlite.js";
6
+ import { fingerprint, similarity, predict } from "./crystal-ball.js";
7
+ import { parseDiff } from "./commit-coach.js";
8
+ let tmpDir;
9
+ let store;
10
+ beforeEach(() => {
11
+ tmpDir = mkdtempSync(join(tmpdir(), "mneme-cb-test-"));
12
+ store = new MnemeStore(join(tmpDir, "mneme.db"));
13
+ });
14
+ afterEach(() => {
15
+ store.close();
16
+ rmSync(tmpDir, { recursive: true, force: true });
17
+ });
18
+ const cmt = (hash, author, date, subject, files) => ({
19
+ hash,
20
+ shortHash: hash.slice(0, 7),
21
+ authorName: author,
22
+ authorEmail: `${author}@x`,
23
+ authorDate: `${date}T00:00:00Z`,
24
+ committerDate: `${date}T00:00:00Z`,
25
+ subject,
26
+ body: "",
27
+ parents: [],
28
+ files,
29
+ });
30
+ function seed(commits) {
31
+ store.upsertCommits(commits);
32
+ for (const c of commits) {
33
+ store.upsertFileChanges(c.files.map((f) => ({
34
+ commitHash: c.hash,
35
+ path: f,
36
+ changeKind: "M",
37
+ insertions: 1,
38
+ deletions: 0,
39
+ })));
40
+ }
41
+ }
42
+ describe("fingerprint", () => {
43
+ it("extracts modules, extensions, shape, size, hasTests", () => {
44
+ const diff = parseDiff(`diff --git a/src/auth/jwt.ts b/src/auth/jwt.ts
45
+ +const x = 1;`);
46
+ const fp = fingerprint(diff);
47
+ expect(fp.modules).toEqual(["src/auth"]);
48
+ expect(fp.extensions).toEqual(["ts"]);
49
+ expect(fp.size).toBe("tiny");
50
+ expect(fp.hasTests).toBe(false);
51
+ });
52
+ it("flags hasTests when *.test.ts present", () => {
53
+ const diff = parseDiff(`diff --git a/src/foo.test.ts b/src/foo.test.ts
54
+ +test('x', () => {});`);
55
+ expect(fingerprint(diff).hasTests).toBe(true);
56
+ });
57
+ it("size buckets are tiny/small/medium/large by line count", () => {
58
+ const tiny = fingerprint({ files: [], modules: [], added: 5, removed: 5, shape: "feat" });
59
+ const small = fingerprint({ files: [], modules: [], added: 50, removed: 30, shape: "feat" });
60
+ const medium = fingerprint({ files: [], modules: [], added: 200, removed: 200, shape: "feat" });
61
+ const large = fingerprint({ files: [], modules: [], added: 800, removed: 100, shape: "feat" });
62
+ expect(tiny.size).toBe("tiny");
63
+ expect(small.size).toBe("small");
64
+ expect(medium.size).toBe("medium");
65
+ expect(large.size).toBe("large");
66
+ });
67
+ });
68
+ describe("similarity", () => {
69
+ const fp = (overrides = {}) => ({
70
+ modules: ["src/auth"],
71
+ extensions: ["ts"],
72
+ shape: "feat",
73
+ size: "small",
74
+ hasTests: false,
75
+ ...overrides,
76
+ });
77
+ it("identical fingerprints score 1.0", () => {
78
+ expect(similarity(fp(), fp())).toBe(1);
79
+ });
80
+ it("module overlap dominates the score (40% weight)", () => {
81
+ const a = fp({ modules: ["src/auth"] });
82
+ const b = fp({ modules: ["src/payment"] });
83
+ // No module overlap → modules contributes 0
84
+ // Same exts (1.0 × 0.2), shape (0.2), size (0.1), tests (0.1) = 0.6
85
+ expect(similarity(a, b)).toBeCloseTo(0.6, 1);
86
+ });
87
+ it("size distance reduces score linearly", () => {
88
+ const a = fp({ size: "tiny" });
89
+ const b = fp({ size: "large" });
90
+ const s = similarity(a, b);
91
+ // Other dims identical (0.9 weight), size dim 0
92
+ expect(s).toBeCloseTo(0.9, 1);
93
+ });
94
+ });
95
+ describe("predict — empty repo edge case", () => {
96
+ it("returns 'unknown' verdict when repo is empty", () => {
97
+ const p = predict(store, "diff --git a/src/x.ts b/src/x.ts\n+const x = 1;");
98
+ expect(p.verdict).toBe("unknown");
99
+ expect(p.similarN).toBe(0);
100
+ expect(p.recommendation.toLowerCase()).toMatch(/index|signal/);
101
+ });
102
+ });
103
+ describe("predict — clean prediction", () => {
104
+ it("predicts 'clear' when most similar past changes shipped cleanly", () => {
105
+ const seedCommits = Array.from({ length: 20 }, (_, i) => cmt(`a${i}1234567`, "alice", `2024-08-${String((i % 28) + 1).padStart(2, "0")}`, `feat(auth): add thing ${i}`, ["src/auth/jwt.ts"]));
106
+ seed(seedCommits);
107
+ const p = predict(store, `diff --git a/src/auth/jwt.ts b/src/auth/jwt.ts
108
+ +const x = 1;`);
109
+ expect(p.similarN).toBeGreaterThan(0);
110
+ expect(p.verdict).toBe("clear");
111
+ expect(p.pClean).toBeGreaterThan(0.8);
112
+ });
113
+ });
114
+ describe("predict — risky prediction", () => {
115
+ it("predicts 'risky' when many similar past changes needed follow-up fixes", () => {
116
+ const commits = [];
117
+ // 10 pairs of (shipped, fix-2-days-later) on src/auth
118
+ for (let i = 0; i < 10; i++) {
119
+ const day = String(i * 3 + 1).padStart(2, "0");
120
+ const fixDay = String(i * 3 + 3).padStart(2, "0");
121
+ commits.push(cmt(`a${i}_____`, "alice", `2024-08-${day}`, `feat(auth): add thing ${i}`, ["src/auth/jwt.ts"]));
122
+ commits.push(cmt(`b${i}_____`, "alice", `2024-08-${fixDay}`, `fix: thing ${i} broke`, ["src/auth/jwt.ts"]));
123
+ }
124
+ seed(commits);
125
+ const p = predict(store, `diff --git a/src/auth/jwt.ts b/src/auth/jwt.ts
126
+ +const x = 1;`);
127
+ expect(p.similarN).toBeGreaterThanOrEqual(5);
128
+ expect(["risky", "moderate"]).toContain(p.verdict);
129
+ expect(p.pClean).toBeLessThan(0.7);
130
+ });
131
+ });
132
+ describe("predict — low signal verdict", () => {
133
+ it("returns 'unknown' when similar count is below 5", () => {
134
+ seed([
135
+ cmt("a1", "alice", "2024-08-01", "feat: thing", ["src/auth/jwt.ts"]),
136
+ cmt("a2", "alice", "2024-08-02", "feat: thing 2", ["src/auth/jwt.ts"]),
137
+ ]);
138
+ const p = predict(store, `diff --git a/src/auth/jwt.ts b/src/auth/jwt.ts
139
+ +const x = 1;`);
140
+ expect(["unknown", "clear"]).toContain(p.verdict);
141
+ if (p.similarN < 5)
142
+ expect(p.verdict).toBe("unknown");
143
+ });
144
+ });
145
+ describe("predict — most-similar reference", () => {
146
+ it("returns the most-similar past commit alongside the prediction", () => {
147
+ seed([
148
+ cmt("a1xxxxxx", "alice", "2024-08-01", "feat(auth): unique subject", ["src/auth/jwt.ts"]),
149
+ ...Array.from({ length: 10 }, (_, i) => cmt(`b${i}xxxxxx`, "alice", `2024-09-${String(i + 1).padStart(2, "0")}`, `feat: misc`, ["src/other/foo.ts"])),
150
+ ]);
151
+ const p = predict(store, `diff --git a/src/auth/jwt.ts b/src/auth/jwt.ts
152
+ +const x = 1;`);
153
+ expect(p.mostSimilar?.hash).toBeDefined();
154
+ expect(["clean", "trouble"]).toContain(p.mostSimilar?.outcome);
155
+ });
156
+ });
157
+ //# sourceMappingURL=crystal-ball.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crystal-ball.test.js","sourceRoot":"","sources":["../../src/insights/crystal-ball.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `decisions` — auto-extract architectural decisions (ADRs) from commit
3
+ * history. The classic "we should write ADRs" never works in practice;
4
+ * instead, extract them from the commit messages that *already exist*.
5
+ *
6
+ * The extractor is intentionally conservative — it triggers on a small set
7
+ * of high-precision patterns ("decided to", "switched from X to Y",
8
+ * "replaced X with Y", "deprecate X"). False positives are worse than
9
+ * false negatives here: an ADR list with garbage erodes trust faster than
10
+ * a thin one builds it.
11
+ */
12
+ import type { Commit } from "../types.js";
13
+ export interface ExtractedDecision {
14
+ /** Commit hash where the decision was recorded. */
15
+ commitHash: string;
16
+ shortHash: string;
17
+ date: string;
18
+ author: string;
19
+ /** A 1-line summary of the decision, drawn from the matched span. */
20
+ summary: string;
21
+ /** Optional rationale clause, when the message includes "because"/"so that". */
22
+ rationale?: string;
23
+ /** The pattern that fired — for transparency. */
24
+ kind: "decided-to" | "switched" | "replaced" | "deprecated" | "chose-over" | "instead-of" | "migrated" | "adopted" | "rejected";
25
+ /** Confidence score in [0, 1]; calibrated from pattern specificity. */
26
+ confidence: number;
27
+ }
28
+ /**
29
+ * Extract every decision in a single commit's text (subject + body + PR
30
+ * title + PR body). Returns 0..N decisions; one commit can record several.
31
+ */
32
+ export declare function extractDecisions(commit: Commit): ExtractedDecision[];
33
+ /**
34
+ * Render a list of decisions as a Markdown table. Useful for
35
+ * `mneme decisions --format markdown > docs/ADR.md`.
36
+ */
37
+ export declare function renderDecisionsAsMarkdown(decisions: ExtractedDecision[]): string;
38
+ //# sourceMappingURL=decisions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decisions.d.ts","sourceRoot":"","sources":["../../src/insights/decisions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,IAAI,EACA,YAAY,GACZ,UAAU,GACV,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,YAAY,GACZ,UAAU,GACV,SAAS,GACT,UAAU,CAAC;IACf,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;CACpB;AAyCD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAyCpE;AA2BD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAgBhF"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * `decisions` — auto-extract architectural decisions (ADRs) from commit
3
+ * history. The classic "we should write ADRs" never works in practice;
4
+ * instead, extract them from the commit messages that *already exist*.
5
+ *
6
+ * The extractor is intentionally conservative — it triggers on a small set
7
+ * of high-precision patterns ("decided to", "switched from X to Y",
8
+ * "replaced X with Y", "deprecate X"). False positives are worse than
9
+ * false negatives here: an ADR list with garbage erodes trust faster than
10
+ * a thin one builds it.
11
+ */
12
+ /**
13
+ * Patterns ordered by specificity. The first matching pattern wins.
14
+ * Each match exposes the decision text via the `text` group.
15
+ */
16
+ const PATTERNS = [
17
+ // Highest specificity: explicit "decided to ..."
18
+ { kind: "decided-to", re: /\b(?:we\s+)?decided\s+to\s+(?<text>[^.\n;]{8,200})/gi, confidence: 0.95 },
19
+ // Switched / migrated FROM A TO B
20
+ { kind: "switched", re: /\bswitch(?:ed)?\s+from\s+(?<from>[^.\n;]{2,80})\s+to\s+(?<text>[^.\n;]{2,120})/gi, confidence: 0.9 },
21
+ { kind: "migrated", re: /\bmigrat(?:e|ed)\s+from\s+(?<from>[^.\n;]{2,80})\s+to\s+(?<text>[^.\n;]{2,120})/gi, confidence: 0.9 },
22
+ // Replaced X with Y
23
+ { kind: "replaced", re: /\breplaced?\s+(?<from>[^.\n;]{2,80})\s+with\s+(?<text>[^.\n;]{2,120})/gi, confidence: 0.85 },
24
+ // Chose X over Y
25
+ { kind: "chose-over", re: /\bch(?:ose|ooses?)\s+(?<text>[^.\n;]{2,80})\s+over\s+(?<from>[^.\n;]{2,80})/gi, confidence: 0.85 },
26
+ // Use X instead of Y — text is lazy so it stops at "instead" instead of greedily eating it.
27
+ { kind: "instead-of", re: /\b(?:use|using|used)\s+(?<text>\S[^.\n;]*?)\s+instead\s+of\s+(?<from>[^.\n;]{2,80})/gi, confidence: 0.8 },
28
+ // Adopted X
29
+ { kind: "adopted", re: /\b(?:we\s+)?adopt(?:ed|ing)?\s+(?<text>[^.\n;]{4,120})/gi, confidence: 0.65 },
30
+ // Deprecated X / Deprecate X
31
+ { kind: "deprecated", re: /\bdeprecat(?:e|ed|ing)\s+(?<text>[^.\n;]{2,120})/gi, confidence: 0.75 },
32
+ // Rejected (the opposite — a decision NOT to do something)
33
+ { kind: "rejected", re: /\b(?:we\s+)?reject(?:ed|ing)?\s+(?<text>[^.\n;]{4,120})/gi, confidence: 0.6 },
34
+ ];
35
+ const RATIONALE_RE = /\b(?:because|so\s+that|in\s+order\s+to|due\s+to)\s+(?<rationale>[^.\n;]{4,200})/i;
36
+ /**
37
+ * Extract every decision in a single commit's text (subject + body + PR
38
+ * title + PR body). Returns 0..N decisions; one commit can record several.
39
+ */
40
+ export function extractDecisions(commit) {
41
+ const fullText = [commit.subject, commit.body, commit.prTitle, commit.prBody]
42
+ .filter(Boolean)
43
+ .join("\n");
44
+ if (!fullText)
45
+ return [];
46
+ const found = [];
47
+ const seen = new Set(); // dedupe by summary text
48
+ for (const p of PATTERNS) {
49
+ // Re-create regex per call to reset lastIndex.
50
+ const re = new RegExp(p.re.source, p.re.flags);
51
+ let m;
52
+ while ((m = re.exec(fullText)) !== null) {
53
+ const text = (m.groups?.text ?? "").trim();
54
+ const from = (m.groups?.from ?? "").trim();
55
+ // Min 2 chars — code identifiers like "Go", "DB", "JS", "Map" are real.
56
+ if (!text || text.length < 2)
57
+ continue;
58
+ const summary = buildSummary(p.kind, text, from);
59
+ if (seen.has(summary.toLowerCase()))
60
+ continue;
61
+ seen.add(summary.toLowerCase());
62
+ // Look for a rationale within ~120 chars after the match.
63
+ const tail = fullText.slice(m.index, m.index + m[0].length + 200);
64
+ const ratMatch = RATIONALE_RE.exec(tail);
65
+ const rationale = ratMatch?.groups?.rationale?.trim();
66
+ found.push({
67
+ commitHash: commit.hash,
68
+ shortHash: commit.shortHash || commit.hash.slice(0, 7),
69
+ date: commit.authorDate.slice(0, 10),
70
+ author: commit.authorName,
71
+ summary,
72
+ rationale: rationale || undefined,
73
+ kind: p.kind,
74
+ confidence: p.confidence,
75
+ });
76
+ }
77
+ }
78
+ return found;
79
+ }
80
+ function buildSummary(kind, text, from) {
81
+ const t = text.replace(/\s+/g, " ").replace(/^[,:\s]+|[,:\s]+$/g, "");
82
+ const f = from.replace(/\s+/g, " ").replace(/^[,:\s]+|[,:\s]+$/g, "");
83
+ switch (kind) {
84
+ case "decided-to":
85
+ return `decided to ${t}`;
86
+ case "switched":
87
+ return `switched from ${f} to ${t}`;
88
+ case "migrated":
89
+ return `migrated from ${f} to ${t}`;
90
+ case "replaced":
91
+ return `replaced ${f} with ${t}`;
92
+ case "chose-over":
93
+ return `chose ${t} over ${f}`;
94
+ case "instead-of":
95
+ return `using ${t} instead of ${f}`;
96
+ case "adopted":
97
+ return `adopted ${t}`;
98
+ case "deprecated":
99
+ return `deprecated ${t}`;
100
+ case "rejected":
101
+ return `rejected ${t}`;
102
+ }
103
+ }
104
+ /**
105
+ * Render a list of decisions as a Markdown table. Useful for
106
+ * `mneme decisions --format markdown > docs/ADR.md`.
107
+ */
108
+ export function renderDecisionsAsMarkdown(decisions) {
109
+ if (decisions.length === 0) {
110
+ return "# Architecture Decisions\n\n(no decisions extracted — try richer commit messages)\n";
111
+ }
112
+ const lines = [];
113
+ lines.push("# Architecture Decisions (auto-extracted)");
114
+ lines.push("");
115
+ lines.push("| Date | Author | Decision | Rationale | Source |");
116
+ lines.push("|---|---|---|---|---|");
117
+ for (const d of decisions) {
118
+ const decision = d.summary.replace(/\|/g, "\\|");
119
+ const rationale = (d.rationale ?? "").replace(/\|/g, "\\|");
120
+ lines.push(`| ${d.date} | ${d.author} | ${decision} | ${rationale} | \`${d.shortHash}\` |`);
121
+ }
122
+ lines.push("");
123
+ return lines.join("\n");
124
+ }
125
+ //# sourceMappingURL=decisions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decisions.js","sourceRoot":"","sources":["../../src/insights/decisions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAmCH;;;GAGG;AACH,MAAM,QAAQ,GAAc;IAC1B,iDAAiD;IACjD,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,sDAAsD,EAAE,UAAU,EAAE,IAAI,EAAE;IAEpG,kCAAkC;IAClC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,kFAAkF,EAAE,UAAU,EAAE,GAAG,EAAE;IAC7H,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,mFAAmF,EAAE,UAAU,EAAE,GAAG,EAAE;IAE9H,oBAAoB;IACpB,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,yEAAyE,EAAE,UAAU,EAAE,IAAI,EAAE;IAErH,iBAAiB;IACjB,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,+EAA+E,EAAE,UAAU,EAAE,IAAI,EAAE;IAE7H,4FAA4F;IAC5F,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,uFAAuF,EAAE,UAAU,EAAE,GAAG,EAAE;IAEpI,YAAY;IACZ,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,0DAA0D,EAAE,UAAU,EAAE,IAAI,EAAE;IAErG,6BAA6B;IAC7B,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,oDAAoD,EAAE,UAAU,EAAE,IAAI,EAAE;IAElG,2DAA2D;IAC3D,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,2DAA2D,EAAE,UAAU,EAAE,GAAG,EAAE;CACvG,CAAC;AAEF,MAAM,YAAY,GAAG,kFAAkF,CAAC;AAExG;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC;SAC1E,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IAEzB,MAAM,KAAK,GAAwB,EAAE,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC,CAAC,yBAAyB;IAEzD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,+CAA+C;QAC/C,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;QAC/C,IAAI,CAAyB,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3C,wEAAwE;YACxE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YACvC,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YACjD,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;gBAAE,SAAS;YAC9C,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;YAEhC,0DAA0D;YAC1D,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;YAClE,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzC,MAAM,SAAS,GAAG,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;YAEtD,KAAK,CAAC,IAAI,CAAC;gBACT,UAAU,EAAE,MAAM,CAAC,IAAI;gBACvB,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;gBACtD,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;gBACpC,MAAM,EAAE,MAAM,CAAC,UAAU;gBACzB,OAAO;gBACP,SAAS,EAAE,SAAS,IAAI,SAAS;gBACjC,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,UAAU,EAAE,CAAC,CAAC,UAAU;aACzB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,IAA+B,EAAE,IAAY,EAAE,IAAY;IAC/E,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;IACtE,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;IACtE,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,YAAY;YACf,OAAO,cAAc,CAAC,EAAE,CAAC;QAC3B,KAAK,UAAU;YACb,OAAO,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,KAAK,UAAU;YACb,OAAO,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,KAAK,UAAU;YACb,OAAO,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,KAAK,YAAY;YACf,OAAO,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,KAAK,YAAY;YACf,OAAO,SAAS,CAAC,eAAe,CAAC,EAAE,CAAC;QACtC,KAAK,SAAS;YACZ,OAAO,WAAW,CAAC,EAAE,CAAC;QACxB,KAAK,YAAY;YACf,OAAO,cAAc,CAAC,EAAE,CAAC;QAC3B,KAAK,UAAU;YACb,OAAO,YAAY,CAAC,EAAE,CAAC;IAC3B,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,SAA8B;IACtE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,qFAAqF,CAAC;IAC/F,CAAC;IACD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IACxD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;IAChE,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACpC,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC5D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,MAAM,QAAQ,MAAM,SAAS,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC;IAC9F,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=decisions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decisions.test.d.ts","sourceRoot":"","sources":["../../src/insights/decisions.test.ts"],"names":[],"mappings":""}