@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,230 @@
1
+ /**
2
+ * `mneme commit-coach` — pre-commit assistant.
3
+ *
4
+ * Reads a unified diff (from `git diff --staged` or stdin) and produces
5
+ * a structured advice payload covering:
6
+ * 1. Suggested commit message (style-matched to repo's history).
7
+ * 2. Recommended reviewers (top experts on touched files).
8
+ * 3. Scope warning (touching too many modules → suggest splitting).
9
+ * 4. Past-regret check (this pattern caused INC-X before).
10
+ *
11
+ * Pure data analysis. The CLI optionally calls an LLM to polish the
12
+ * commit message; everything else is heuristic + store queries.
13
+ */
14
+ import { detectRegrets } from "./regret.js";
15
+ /**
16
+ * Parse a unified diff string. Best-effort — handles standard `git diff`
17
+ * output. Skips binary diffs and file-mode changes.
18
+ */
19
+ export function parseDiff(diffText) {
20
+ const files = [];
21
+ let added = 0;
22
+ let removed = 0;
23
+ const lines = diffText.split("\n");
24
+ for (const line of lines) {
25
+ if (line.startsWith("diff --git")) {
26
+ // Format: diff --git a/path b/path
27
+ const m = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
28
+ if (m)
29
+ files.push(m[2]);
30
+ }
31
+ else if (line.startsWith("+") && !line.startsWith("+++")) {
32
+ added += 1;
33
+ }
34
+ else if (line.startsWith("-") && !line.startsWith("---")) {
35
+ removed += 1;
36
+ }
37
+ }
38
+ // Modules: first 2 path segments.
39
+ const modules = [
40
+ ...new Set(files.map((f) => {
41
+ const parts = f.split("/");
42
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
43
+ })),
44
+ ];
45
+ return {
46
+ files,
47
+ modules,
48
+ added,
49
+ removed,
50
+ shape: classifyShape(files, added, removed),
51
+ };
52
+ }
53
+ function classifyShape(files, added, removed) {
54
+ if (files.length === 0)
55
+ return "chore";
56
+ const allTests = files.every((f) => /(\.test\.|\.spec\.|\/tests?\/|\/specs?\/)/.test(f));
57
+ if (allTests)
58
+ return "test";
59
+ const allDocs = files.every((f) => /(\.md$|\/docs?\/)/i.test(f));
60
+ if (allDocs)
61
+ return "docs";
62
+ // Refactor: balanced add/remove (within 30%) AND no obvious test changes
63
+ if (removed > 0 && added > 0 && Math.abs(added - removed) / Math.max(added, removed) < 0.3 && removed > 5) {
64
+ return "refactor";
65
+ }
66
+ // Heavy removals → likely a fix or refactor
67
+ if (removed > added * 2)
68
+ return "fix";
69
+ // Default: feat for new code
70
+ return "feat";
71
+ }
72
+ /**
73
+ * Look up the top reviewer per touched file (the file's largest contributor).
74
+ * Aggregates across files to find the people most worth requesting review from.
75
+ */
76
+ export function recommendReviewers(store, files, topN = 3) {
77
+ if (files.length === 0)
78
+ return [];
79
+ // For each touched file, find the dominant author.
80
+ const placeholders = files.map(() => "?").join(",");
81
+ const rows = store.db
82
+ .prepare(`SELECT
83
+ fc.path AS path,
84
+ c.author_name AS author_name,
85
+ c.author_email AS author_email,
86
+ COUNT(*) AS touches
87
+ FROM file_changes fc
88
+ JOIN commits c ON c.hash = fc.commit_hash
89
+ WHERE fc.path IN (${placeholders})
90
+ GROUP BY fc.path, c.author_name, c.author_email
91
+ ORDER BY fc.path, touches DESC`)
92
+ .all(...files);
93
+ // Group by file → top author.
94
+ const fileTotals = new Map();
95
+ const fileTopAuthor = new Map();
96
+ for (const r of rows) {
97
+ fileTotals.set(r.path, (fileTotals.get(r.path) ?? 0) + r.touches);
98
+ if (!fileTopAuthor.has(r.path)) {
99
+ fileTopAuthor.set(r.path, { name: r.author_name, email: r.author_email, touches: r.touches });
100
+ }
101
+ }
102
+ const byAuthor = new Map();
103
+ for (const [file, top] of fileTopAuthor) {
104
+ const total = fileTotals.get(file) ?? 1;
105
+ const share = top.touches / total;
106
+ const key = `${top.name}|${top.email}`;
107
+ if (!byAuthor.has(key))
108
+ byAuthor.set(key, { name: top.name, email: top.email, ownedFiles: [], ownershipScore: 0 });
109
+ const b = byAuthor.get(key);
110
+ b.ownedFiles.push(file);
111
+ b.ownershipScore += share;
112
+ }
113
+ return [...byAuthor.values()]
114
+ .map((b) => ({
115
+ name: b.name,
116
+ email: b.email,
117
+ ownership: Math.round((b.ownershipScore / files.length) * 100),
118
+ ownedFiles: b.ownedFiles,
119
+ }))
120
+ .sort((a, b) => b.ownership - a.ownership)
121
+ .slice(0, topN);
122
+ }
123
+ /**
124
+ * Suggest a commit message based on the diff shape + style of recent
125
+ * commits in the repo. Returns a Conventional-Commits-style subject line.
126
+ */
127
+ export function suggestSubject(diff, recentSubjects) {
128
+ const usesConventional = recentSubjects.some((s) => /^[a-z]+(\([^)]+\))?:\s/.test(s));
129
+ const scope = diff.modules[0]?.replace(/^src\//, "") ?? "";
130
+ const scopeStr = scope ? `(${scope})` : "";
131
+ // Hint from filename if obvious
132
+ const hint = diff.files
133
+ .map((f) => f.split("/").pop().replace(/\.(ts|js|tsx|jsx|py|go|rb|md)$/, ""))
134
+ .filter((n) => n.length >= 3)
135
+ .slice(0, 2)
136
+ .join(" ");
137
+ if (!usesConventional) {
138
+ // Fall back to imperative subject
139
+ return `${diff.shape}: ${hint || "update " + (diff.modules[0] ?? "code")}`;
140
+ }
141
+ return `${diff.shape}${scopeStr}: ${hint || "update " + diff.modules[0]?.split("/").pop()}`;
142
+ }
143
+ /** Diagnose scope creep. Touching > 3 modules is usually too much for one commit. */
144
+ export function checkScope(diff) {
145
+ if (diff.modules.length === 0)
146
+ return { scopeOK: true, message: "No files staged." };
147
+ if (diff.modules.length === 1) {
148
+ return { scopeOK: true, message: `Focused on ${diff.modules[0]}.` };
149
+ }
150
+ if (diff.modules.length <= 3) {
151
+ return { scopeOK: true, message: `${diff.modules.length} modules touched: ${diff.modules.join(", ")}.` };
152
+ }
153
+ return {
154
+ scopeOK: false,
155
+ message: `Scope creep — ${diff.modules.length} modules touched (${diff.modules.slice(0, 3).join(", ")}, …). Consider splitting into ${diff.modules.length} commits.`,
156
+ };
157
+ }
158
+ /**
159
+ * Check past regrets: any commit touching the same files where the
160
+ * follow-up was a fix/revert? If so, surface as a warning.
161
+ */
162
+ export function checkPastRegrets(store, files) {
163
+ if (files.length === 0)
164
+ return [];
165
+ // Pull every commit that touched at least one of the files.
166
+ const placeholders = files.map(() => "?").join(",");
167
+ const commitRows = store.db
168
+ .prepare(`SELECT DISTINCT c.* FROM commits c
169
+ JOIN file_changes fc ON fc.commit_hash = c.hash
170
+ WHERE fc.path IN (${placeholders})
171
+ ORDER BY c.author_date DESC
172
+ LIMIT 200`)
173
+ .all(...files);
174
+ const commits = [];
175
+ for (const r of commitRows) {
176
+ const hash = String(r.hash);
177
+ const filesOfCommit = store.db
178
+ .prepare("SELECT path FROM file_changes WHERE commit_hash = ?")
179
+ .all(hash).map((x) => x.path);
180
+ commits.push({
181
+ hash,
182
+ shortHash: String(r.short_hash),
183
+ authorName: String(r.author_name),
184
+ authorEmail: String(r.author_email),
185
+ authorDate: String(r.author_date),
186
+ committerDate: String(r.committer_date),
187
+ subject: String(r.subject),
188
+ body: String(r.body || ""),
189
+ parents: r.parents ? String(r.parents).split(/\s+/).filter(Boolean) : [],
190
+ files: filesOfCommit,
191
+ });
192
+ }
193
+ const regrets = detectRegrets(commits, { windowDays: 14 });
194
+ // Convert to warnings — only for regrets where files overlap significantly.
195
+ const touchedSet = new Set(files);
196
+ const out = [];
197
+ for (const r of regrets) {
198
+ const overlap = (r.shipped.files ?? []).filter((f) => touchedSet.has(f));
199
+ if (overlap.length === 0)
200
+ continue;
201
+ out.push({
202
+ pattern: `Past change in ${overlap[0]} caused a ${r.kind} within ${r.daysToFix.toFixed(0)} days.`,
203
+ pastCommitHash: r.shipped.shortHash || r.shipped.hash.slice(0, 7),
204
+ pastDate: r.shipped.authorDate.slice(0, 10),
205
+ outcome: r.lesson || `${r.kind} within ${r.daysToFix.toFixed(0)} days`,
206
+ });
207
+ }
208
+ return out.slice(0, 3);
209
+ }
210
+ /** End-to-end coach: parse → recommend → diagnose. Pure-ish (reads the store). */
211
+ export function coach(store, diffText) {
212
+ const diff = parseDiff(diffText);
213
+ const reviewers = recommendReviewers(store, diff.files, 3);
214
+ const scope = checkScope(diff);
215
+ const warnings = checkPastRegrets(store, diff.files);
216
+ const recentRows = store.db
217
+ .prepare(`SELECT subject FROM commits ORDER BY author_date DESC LIMIT 30`)
218
+ .all();
219
+ const recentSubjects = recentRows.map((r) => r.subject);
220
+ const suggestedSubject = suggestSubject(diff, recentSubjects);
221
+ return {
222
+ diff,
223
+ suggestedSubject,
224
+ reviewers,
225
+ scopeOK: scope.scopeOK,
226
+ scopeMessage: scope.message,
227
+ warnings,
228
+ };
229
+ }
230
+ //# sourceMappingURL=commit-coach.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commit-coach.js","sourceRoot":"","sources":["../../src/insights/commit-coach.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=commit-coach.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commit-coach.test.d.ts","sourceRoot":"","sources":["../../src/insights/commit-coach.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,163 @@
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 { parseDiff, suggestSubject, checkScope, recommendReviewers, coach } from "./commit-coach.js";
7
+ let tmpDir;
8
+ let store;
9
+ beforeEach(() => {
10
+ tmpDir = mkdtempSync(join(tmpdir(), "mneme-coach-test-"));
11
+ store = new MnemeStore(join(tmpDir, "mneme.db"));
12
+ });
13
+ afterEach(() => {
14
+ store.close();
15
+ rmSync(tmpDir, { recursive: true, force: true });
16
+ });
17
+ const cmt = (hash, author, date, subject, files) => ({
18
+ hash,
19
+ shortHash: hash.slice(0, 7),
20
+ authorName: author,
21
+ authorEmail: `${author}@x`,
22
+ authorDate: `${date}T00:00:00Z`,
23
+ committerDate: `${date}T00:00:00Z`,
24
+ subject,
25
+ body: "",
26
+ parents: [],
27
+ files,
28
+ });
29
+ function seed(commits) {
30
+ store.upsertCommits(commits);
31
+ for (const c of commits) {
32
+ store.upsertFileChanges(c.files.map((f) => ({
33
+ commitHash: c.hash,
34
+ path: f,
35
+ changeKind: "M",
36
+ insertions: 1,
37
+ deletions: 0,
38
+ })));
39
+ }
40
+ }
41
+ const SAMPLE_DIFF = `diff --git a/src/auth/jwt.ts b/src/auth/jwt.ts
42
+ index abc..def 100644
43
+ --- a/src/auth/jwt.ts
44
+ +++ b/src/auth/jwt.ts
45
+ @@ -1,3 +1,5 @@
46
+ export function sign(payload) {
47
+ - return jwt.sign(payload, KEY);
48
+ + return jwt.sign(payload, KEY, { expiresIn: "15m" });
49
+ }
50
+ +export function refresh(token) { return jwt.refresh(token); }
51
+ diff --git a/src/auth/middleware.ts b/src/auth/middleware.ts
52
+ index 111..222 100644
53
+ --- a/src/auth/middleware.ts
54
+ +++ b/src/auth/middleware.ts
55
+ @@ -10,2 +10,3 @@
56
+ export const authGuard = () => {
57
+ + validateExpiry();
58
+ };
59
+ `;
60
+ describe("parseDiff", () => {
61
+ it("extracts file paths from `diff --git` headers", () => {
62
+ const d = parseDiff(SAMPLE_DIFF);
63
+ expect(d.files).toContain("src/auth/jwt.ts");
64
+ expect(d.files).toContain("src/auth/middleware.ts");
65
+ });
66
+ it("counts added and removed lines", () => {
67
+ const d = parseDiff(SAMPLE_DIFF);
68
+ expect(d.added).toBeGreaterThan(0);
69
+ expect(d.removed).toBeGreaterThan(0);
70
+ });
71
+ it("groups files into top-2-segment modules", () => {
72
+ const d = parseDiff(SAMPLE_DIFF);
73
+ expect(d.modules).toEqual(["src/auth"]);
74
+ });
75
+ it("classifies test-only diffs as 'test'", () => {
76
+ const d = parseDiff(`diff --git a/tests/unit/foo.test.ts b/tests/unit/foo.test.ts
77
+ +const x = 1;`);
78
+ expect(d.shape).toBe("test");
79
+ });
80
+ it("classifies docs-only diffs as 'docs'", () => {
81
+ const d = parseDiff(`diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
82
+ +- new bullet`);
83
+ expect(d.shape).toBe("docs");
84
+ });
85
+ it("classifies balanced add/remove ≥ 5 lines as 'refactor'", () => {
86
+ const lines = ["diff --git a/src/x.ts b/src/x.ts"];
87
+ for (let i = 0; i < 10; i++) {
88
+ lines.push(`-old line ${i}`);
89
+ lines.push(`+new line ${i}`);
90
+ }
91
+ expect(parseDiff(lines.join("\n")).shape).toBe("refactor");
92
+ });
93
+ });
94
+ describe("suggestSubject", () => {
95
+ it("uses Conventional Commits format when repo history shows it", () => {
96
+ const diff = parseDiff(SAMPLE_DIFF);
97
+ const subject = suggestSubject(diff, ["feat(auth): add jwt", "fix(auth): typo"]);
98
+ expect(subject).toMatch(/^[a-z]+\([^)]+\):\s/);
99
+ });
100
+ it("falls back to imperative subject when repo doesn't use Conventional", () => {
101
+ const diff = parseDiff(SAMPLE_DIFF);
102
+ const subject = suggestSubject(diff, ["update stuff", "fix bug"]);
103
+ expect(subject).not.toMatch(/^[a-z]+\(/);
104
+ });
105
+ });
106
+ describe("checkScope", () => {
107
+ it("approves single-module diffs", () => {
108
+ const diff = parseDiff(SAMPLE_DIFF);
109
+ const r = checkScope(diff);
110
+ expect(r.scopeOK).toBe(true);
111
+ });
112
+ it("warns at 4+ modules", () => {
113
+ const diff = {
114
+ files: [],
115
+ modules: ["src/a", "src/b", "src/c", "src/d", "src/e"],
116
+ added: 0,
117
+ removed: 0,
118
+ shape: "feat",
119
+ };
120
+ const r = checkScope(diff);
121
+ expect(r.scopeOK).toBe(false);
122
+ expect(r.message.toLowerCase()).toMatch(/scope creep|split/);
123
+ });
124
+ });
125
+ describe("recommendReviewers — store-backed", () => {
126
+ it("returns top reviewers ordered by ownership", () => {
127
+ seed([
128
+ cmt("a1", "alice", "2024-08-01", "fix", ["src/auth/jwt.ts"]),
129
+ cmt("a2", "alice", "2024-08-02", "fix", ["src/auth/jwt.ts"]),
130
+ cmt("a3", "alice", "2024-08-03", "fix", ["src/auth/jwt.ts"]),
131
+ cmt("b1", "bob", "2024-08-04", "fix", ["src/auth/middleware.ts"]),
132
+ cmt("b2", "bob", "2024-08-05", "fix", ["src/auth/middleware.ts"]),
133
+ ]);
134
+ const reviewers = recommendReviewers(store, ["src/auth/jwt.ts", "src/auth/middleware.ts"]);
135
+ expect(reviewers).toHaveLength(2);
136
+ expect(reviewers.map((r) => r.name).sort()).toEqual(["alice", "bob"]);
137
+ });
138
+ it("returns empty when no files provided", () => {
139
+ expect(recommendReviewers(store, [])).toEqual([]);
140
+ });
141
+ });
142
+ describe("coach — end-to-end", () => {
143
+ it("returns a complete CoachAdvice payload", () => {
144
+ seed([
145
+ cmt("a1", "alice", "2024-08-01", "feat(auth): add jwt", ["src/auth/jwt.ts"]),
146
+ cmt("a2", "alice", "2024-08-02", "fix(auth): typo", ["src/auth/jwt.ts"]),
147
+ ]);
148
+ const advice = coach(store, SAMPLE_DIFF);
149
+ expect(advice.diff.files.length).toBeGreaterThan(0);
150
+ expect(advice.suggestedSubject.length).toBeGreaterThan(0);
151
+ expect(advice.scopeMessage.length).toBeGreaterThan(0);
152
+ });
153
+ it("surfaces past-regret warnings when similar fixes happened recently", () => {
154
+ seed([
155
+ cmt("a1", "alice", "2024-08-01", "refactor: simplify session middleware", ["src/auth/middleware.ts"]),
156
+ cmt("a2", "alice", "2024-08-01", "hotfix: CSRF disappeared", ["src/auth/middleware.ts"]),
157
+ ]);
158
+ const advice = coach(store, SAMPLE_DIFF);
159
+ expect(advice.warnings.length).toBeGreaterThan(0);
160
+ expect(advice.warnings[0].pattern.toLowerCase()).toMatch(/middleware|hotfix/);
161
+ });
162
+ });
163
+ //# sourceMappingURL=commit-coach.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commit-coach.test.js","sourceRoot":"","sources":["../../src/insights/commit-coach.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
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 type { MnemeStore } from "../store/sqlite.js";
22
+ import { type ParsedDiff } from "./commit-coach.js";
23
+ export interface DiffFingerprint {
24
+ /** Top-2-segment modules touched, sorted. */
25
+ modules: string[];
26
+ /** File extensions touched, sorted. */
27
+ extensions: string[];
28
+ /** "feat" / "fix" / "refactor" / etc. */
29
+ shape: ParsedDiff["shape"];
30
+ /** Total touch volume: added + removed. */
31
+ size: "tiny" | "small" | "medium" | "large";
32
+ /** Whether tests were also modified. */
33
+ hasTests: boolean;
34
+ }
35
+ export interface CrystalBallPrediction {
36
+ fingerprint: DiffFingerprint;
37
+ /** Number of similar past commits found. */
38
+ similarN: number;
39
+ /** Of those, how many had no follow-up regret. */
40
+ cleanN: number;
41
+ /** Probability of clean CI / no follow-up trouble (0..1). */
42
+ pClean: number;
43
+ /** Verdict label: clear / moderate / risky / unknown. */
44
+ verdict: "clear" | "moderate" | "risky" | "unknown";
45
+ /** Most-similar past commit (a concrete reference). */
46
+ mostSimilar?: {
47
+ hash: string;
48
+ subject: string;
49
+ date: string;
50
+ outcome: "clean" | "trouble";
51
+ };
52
+ /** A 1-line recommendation. */
53
+ recommendation: string;
54
+ }
55
+ export declare function fingerprint(diff: ParsedDiff): DiffFingerprint;
56
+ /**
57
+ * Score similarity between two fingerprints. Returns 0..1.
58
+ *
59
+ * Each dimension contributes a weight; the score is a weighted average:
60
+ * modules: 0.40 (Jaccard)
61
+ * extensions: 0.20 (Jaccard)
62
+ * shape: 0.20 (1.0 if equal, else 0.0)
63
+ * size: 0.10 (linear distance over 4 buckets)
64
+ * hasTests: 0.10 (1.0 if equal, else 0.0)
65
+ */
66
+ export declare function similarity(a: DiffFingerprint, b: DiffFingerprint): number;
67
+ /**
68
+ * Predict CI/follow-up trouble probability for the given diff.
69
+ *
70
+ * @param store - the indexed store
71
+ * @param diffText - unified diff text (e.g. `git diff --staged` output)
72
+ * @param windowDays - how long after a past commit do we still count a
73
+ * follow-up as "trouble" (default 14)
74
+ */
75
+ export declare function predict(store: MnemeStore, diffText: string, windowDays?: number): CrystalBallPrediction;
76
+ //# sourceMappingURL=crystal-ball.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crystal-ball.d.ts","sourceRoot":"","sources":["../../src/insights/crystal-ball.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG/D,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,yCAAyC;IACzC,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAC3B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,wCAAwC;IACxC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,eAAe,CAAC;IAC7B,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;IACpD,uDAAuD;IACvD,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,CAAC;IAC5F,+BAA+B;IAC/B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAiB7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,GAAG,MAAM,CAoBzE;AAID;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACrB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,SAAK,GACd,qBAAqB,CA8GvB"}