@mneme-ai/core 0.11.0 → 0.12.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 (45) hide show
  1. package/dist/insights/chronicle.d.ts +41 -0
  2. package/dist/insights/chronicle.d.ts.map +1 -0
  3. package/dist/insights/chronicle.js +169 -0
  4. package/dist/insights/chronicle.js.map +1 -0
  5. package/dist/insights/chronicle.test.d.ts +2 -0
  6. package/dist/insights/chronicle.test.d.ts.map +1 -0
  7. package/dist/insights/chronicle.test.js +92 -0
  8. package/dist/insights/chronicle.test.js.map +1 -0
  9. package/dist/insights/constellation.d.ts +63 -0
  10. package/dist/insights/constellation.d.ts.map +1 -0
  11. package/dist/insights/constellation.js +147 -0
  12. package/dist/insights/constellation.js.map +1 -0
  13. package/dist/insights/constellation.test.d.ts +2 -0
  14. package/dist/insights/constellation.test.d.ts.map +1 -0
  15. package/dist/insights/constellation.test.js +105 -0
  16. package/dist/insights/constellation.test.js.map +1 -0
  17. package/dist/insights/dna.d.ts +100 -0
  18. package/dist/insights/dna.d.ts.map +1 -0
  19. package/dist/insights/dna.js +264 -0
  20. package/dist/insights/dna.js.map +1 -0
  21. package/dist/insights/dna.test.d.ts +2 -0
  22. package/dist/insights/dna.test.d.ts.map +1 -0
  23. package/dist/insights/dna.test.js +142 -0
  24. package/dist/insights/dna.test.js.map +1 -0
  25. package/dist/insights/drift.d.ts +43 -0
  26. package/dist/insights/drift.d.ts.map +1 -0
  27. package/dist/insights/drift.js +170 -0
  28. package/dist/insights/drift.js.map +1 -0
  29. package/dist/insights/drift.test.d.ts +2 -0
  30. package/dist/insights/drift.test.d.ts.map +1 -0
  31. package/dist/insights/drift.test.js +126 -0
  32. package/dist/insights/drift.test.js.map +1 -0
  33. package/dist/insights/index.d.ts +5 -0
  34. package/dist/insights/index.d.ts.map +1 -1
  35. package/dist/insights/index.js +5 -0
  36. package/dist/insights/index.js.map +1 -1
  37. package/dist/insights/oracle.d.ts +59 -0
  38. package/dist/insights/oracle.d.ts.map +1 -0
  39. package/dist/insights/oracle.js +0 -0
  40. package/dist/insights/oracle.js.map +1 -0
  41. package/dist/insights/oracle.test.d.ts +2 -0
  42. package/dist/insights/oracle.test.d.ts.map +1 -0
  43. package/dist/insights/oracle.test.js +133 -0
  44. package/dist/insights/oracle.test.js.map +1 -0
  45. package/package.json +1 -1
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { extractDna, compareDna } from "./dna.js";
3
+ function mk(p) {
4
+ return {
5
+ hash: p.hash,
6
+ shortHash: p.hash.slice(0, 7),
7
+ authorName: p.author ?? "Alice",
8
+ authorEmail: p.email ?? "alice@example.com",
9
+ authorDate: p.date,
10
+ committerDate: p.date,
11
+ subject: p.subject,
12
+ body: p.body ?? "",
13
+ files: p.files ?? ["src/x.ts"],
14
+ parents: [],
15
+ prNumber: p.pr,
16
+ };
17
+ }
18
+ describe("extractDna", () => {
19
+ it("returns empty strand when no commits match author", () => {
20
+ const dna = extractDna([], "alice@example.com");
21
+ expect(dna.commitCount).toBe(0);
22
+ expect(dna.hash).toBe("0000000");
23
+ });
24
+ it("extracts commitCount and date range correctly", () => {
25
+ const commits = [
26
+ mk({ hash: "a1", date: "2024-01-15T09:00:00Z", subject: "init" }),
27
+ mk({ hash: "a2", date: "2024-06-22T14:00:00Z", subject: "polish" }),
28
+ ];
29
+ const dna = extractDna(commits, "alice@example.com");
30
+ expect(dna.commitCount).toBe(2);
31
+ expect(dna.fromDate).toBe("2024-01-15");
32
+ expect(dna.toDate).toBe("2024-06-22");
33
+ });
34
+ it("filters by author email or name", () => {
35
+ const commits = [
36
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "alice work", author: "Alice", email: "alice@x.com" }),
37
+ mk({ hash: "b1", date: "2024-01-02T09:00:00Z", subject: "bob work", author: "Bob", email: "bob@x.com" }),
38
+ ];
39
+ const aliceDna = extractDna(commits, "alice@x.com");
40
+ expect(aliceDna.commitCount).toBe(1);
41
+ const bobDna = extractDna(commits, "Bob");
42
+ expect(bobDna.commitCount).toBe(1);
43
+ });
44
+ it("conventionalRatio detects conventional-commit prefixes", () => {
45
+ const commits = [
46
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "feat(core): add caching" }),
47
+ mk({ hash: "a2", date: "2024-01-02T09:00:00Z", subject: "fix: handle null body" }),
48
+ mk({ hash: "a3", date: "2024-01-03T09:00:00Z", subject: "random subject" }),
49
+ ];
50
+ const dna = extractDna(commits, "alice@example.com");
51
+ expect(dna.style.conventionalRatio).toBeCloseTo(2 / 3, 2);
52
+ });
53
+ it("imperativeRatio rises when subjects start with imperatives", () => {
54
+ const commits = [
55
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "Add caching" }),
56
+ mk({ hash: "a2", date: "2024-01-02T09:00:00Z", subject: "fix typo" }),
57
+ mk({ hash: "a3", date: "2024-01-03T09:00:00Z", subject: "Update docs" }),
58
+ ];
59
+ const dna = extractDna(commits, "alice@example.com");
60
+ expect(dna.message.imperativeRatio).toBeGreaterThan(0.6);
61
+ });
62
+ it("bodyRatio counts only commits with non-empty body", () => {
63
+ const commits = [
64
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "A", body: "explains why" }),
65
+ mk({ hash: "a2", date: "2024-01-02T09:00:00Z", subject: "B" }),
66
+ ];
67
+ const dna = extractDna(commits, "alice@example.com");
68
+ expect(dna.message.bodyRatio).toBeCloseTo(0.5, 2);
69
+ });
70
+ it("topVerbs lists most-used leading verbs", () => {
71
+ const commits = [
72
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "add x" }),
73
+ mk({ hash: "a2", date: "2024-01-02T09:00:00Z", subject: "add y" }),
74
+ mk({ hash: "a3", date: "2024-01-03T09:00:00Z", subject: "fix z" }),
75
+ ];
76
+ const dna = extractDna(commits, "alice@example.com");
77
+ expect(dna.message.topVerbs[0]?.verb).toBe("add");
78
+ expect(dna.message.topVerbs[0]?.count).toBe(2);
79
+ });
80
+ it("hours histogram counts each commit by UTC hour", () => {
81
+ const commits = [
82
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "x" }),
83
+ mk({ hash: "a2", date: "2024-01-02T09:30:00Z", subject: "x" }),
84
+ mk({ hash: "a3", date: "2024-01-03T15:00:00Z", subject: "x" }),
85
+ ];
86
+ const dna = extractDna(commits, "alice@example.com");
87
+ expect(dna.hours.byHour[9]).toBe(2);
88
+ expect(dna.hours.byHour[15]).toBe(1);
89
+ });
90
+ it("weekendRatio reflects Sat/Sun commits", () => {
91
+ // 2024-06-15 is Saturday, 2024-06-17 is Monday
92
+ const commits = [
93
+ mk({ hash: "a1", date: "2024-06-15T09:00:00Z", subject: "weekend work" }),
94
+ mk({ hash: "a2", date: "2024-06-17T09:00:00Z", subject: "weekday work" }),
95
+ ];
96
+ const dna = extractDna(commits, "alice@example.com");
97
+ expect(dna.hours.weekendRatio).toBe(0.5);
98
+ });
99
+ it("topDirs reflects most-touched root directories", () => {
100
+ const commits = [
101
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "x", files: ["src/auth/x.ts", "src/auth/y.ts"] }),
102
+ mk({ hash: "a2", date: "2024-01-02T09:00:00Z", subject: "x", files: ["src/payments/z.ts"] }),
103
+ ];
104
+ const dna = extractDna(commits, "alice@example.com");
105
+ const topDir = dna.files.topDirs[0]?.dir;
106
+ expect(topDir).toMatch(/^src\//);
107
+ });
108
+ it("hash is deterministic for the same input", () => {
109
+ const commits = [
110
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "init" }),
111
+ ];
112
+ const a = extractDna(commits, "alice@example.com");
113
+ const b = extractDna(commits, "alice@example.com");
114
+ expect(a.hash).toBe(b.hash);
115
+ expect(a.hash.length).toBe(7);
116
+ });
117
+ it("compareDna returns similarity 1.0 for two identical strands", () => {
118
+ const commits = [
119
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "feat: add caching" }),
120
+ mk({ hash: "a2", date: "2024-01-02T09:00:00Z", subject: "fix: handle null" }),
121
+ ];
122
+ const a = extractDna(commits, "alice@example.com");
123
+ const b = extractDna(commits, "alice@example.com");
124
+ const cmp = compareDna(a, b);
125
+ expect(cmp.similarity).toBeGreaterThan(0.95);
126
+ });
127
+ it("compareDna returns lower similarity for divergent styles", () => {
128
+ const aliceCommits = [
129
+ mk({ hash: "a1", date: "2024-01-01T09:00:00Z", subject: "feat: add x", author: "Alice", email: "a@x.com", files: ["src/auth/a.ts"] }),
130
+ mk({ hash: "a2", date: "2024-01-02T09:00:00Z", subject: "feat: add y", author: "Alice", email: "a@x.com", files: ["src/auth/b.ts"] }),
131
+ ];
132
+ const bobCommits = [
133
+ mk({ hash: "b1", date: "2024-01-01T22:00:00Z", subject: "did some stuff", author: "Bob", email: "b@x.com", files: ["legacy/c.py"] }),
134
+ mk({ hash: "b2", date: "2024-01-02T23:00:00Z", subject: "more random work", author: "Bob", email: "b@x.com", files: ["legacy/d.py"] }),
135
+ ];
136
+ const a = extractDna(aliceCommits, "a@x.com");
137
+ const b = extractDna(bobCommits, "b@x.com");
138
+ const cmp = compareDna(a, b);
139
+ expect(cmp.similarity).toBeLessThan(0.7);
140
+ });
141
+ });
142
+ //# sourceMappingURL=dna.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dna.test.js","sourceRoot":"","sources":["../../src/insights/dna.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * `mneme drift` — visualize topical drift in a repo's commits over time.
3
+ *
4
+ * Buckets commits into time windows (default: quarter), classifies each
5
+ * commit by intent (feature / refactor / firefight / polish / docs), then
6
+ * emits per-bucket distributions you can plot or render as a sparkline.
7
+ *
8
+ * Why novel: shipped products do not exist for this. Academic papers
9
+ * (arxiv 2110.00697, etc.) cluster commits semantically but stop at the
10
+ * paper. Mneme's drift command brings it to the CLI.
11
+ */
12
+ import type { Commit } from "../types.js";
13
+ export type DriftKind = "feature" | "refactor" | "firefight" | "polish" | "docs" | "other";
14
+ export interface DriftBucket {
15
+ /** Window label e.g. "2024-Q1" or "2024-08". */
16
+ label: string;
17
+ fromDate: string;
18
+ toDate: string;
19
+ /** Total commits in window. */
20
+ total: number;
21
+ /** Counts by drift kind. */
22
+ byKind: Record<DriftKind, number>;
23
+ /** Dominant kind for this window (highest count). */
24
+ dominant: DriftKind;
25
+ }
26
+ export interface DriftReport {
27
+ /** Granularity used. */
28
+ granularity: "quarter" | "month";
29
+ buckets: DriftBucket[];
30
+ /** Insights derived from the trajectory. */
31
+ insights: DriftInsight[];
32
+ }
33
+ export interface DriftInsight {
34
+ kind: "burnout" | "recovery" | "rewrite-cluster" | "polish-streak" | "shift";
35
+ fromBucket: string;
36
+ toBucket: string;
37
+ description: string;
38
+ }
39
+ export declare function classifyCommit(c: Commit): DriftKind;
40
+ export declare function buildDrift(commits: Commit[], opts?: {
41
+ granularity?: "quarter" | "month";
42
+ }): DriftReport;
43
+ //# sourceMappingURL=drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift.d.ts","sourceRoot":"","sources":["../../src/insights/drift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3F,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAClC,qDAAqD;IACrD,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,wBAAwB;IACxB,WAAW,EAAE,SAAS,GAAG,OAAO,CAAC;IACjC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,4CAA4C;IAC5C,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,iBAAiB,GAAG,eAAe,GAAG,OAAO,CAAC;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,CAQnD;AAED,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,SAAS,GAAG,OAAO,CAAA;CAAO,GAC/C,WAAW,CAoDb"}
@@ -0,0 +1,170 @@
1
+ const FEATURE_RE = /\b(feat|feature|add|implement|introduce|support|expose)\b/i;
2
+ const REFACTOR_RE = /\b(refactor|rewrite|migrate|switch(?:ed)? to|extract|consolidate|simplify)\b/i;
3
+ const FIRE_RE = /\b(fix|hotfix|revert|rollback|broken|crash|regression|emergency|urgent|critical)\b/i;
4
+ const POLISH_RE = /\b(typo|format|lint|rename|comment|whitespace|cleanup|chore|polish|tighten)\b/i;
5
+ const DOCS_RE = /\b(docs?|readme|comment|documentation)\b/i;
6
+ const KINDS = ["feature", "refactor", "firefight", "polish", "docs", "other"];
7
+ export function classifyCommit(c) {
8
+ const text = `${c.subject}\n${c.body || ""}`;
9
+ if (FIRE_RE.test(text))
10
+ return "firefight";
11
+ if (REFACTOR_RE.test(text))
12
+ return "refactor";
13
+ if (FEATURE_RE.test(text))
14
+ return "feature";
15
+ if (DOCS_RE.test(text))
16
+ return "docs";
17
+ if (POLISH_RE.test(text))
18
+ return "polish";
19
+ return "other";
20
+ }
21
+ export function buildDrift(commits, opts = {}) {
22
+ const granularity = opts.granularity ?? "quarter";
23
+ const sorted = [...commits].sort((a, b) => a.authorDate.localeCompare(b.authorDate));
24
+ if (sorted.length === 0) {
25
+ return { granularity, buckets: [], insights: [] };
26
+ }
27
+ const buckets = new Map();
28
+ for (const c of sorted) {
29
+ const label = labelOf(c.authorDate, granularity);
30
+ const range = rangeOf(label, granularity);
31
+ let b = buckets.get(label);
32
+ if (!b) {
33
+ b = {
34
+ label,
35
+ fromDate: range.from,
36
+ toDate: range.to,
37
+ total: 0,
38
+ byKind: { feature: 0, refactor: 0, firefight: 0, polish: 0, docs: 0, other: 0 },
39
+ dominant: "other",
40
+ };
41
+ buckets.set(label, b);
42
+ }
43
+ const k = classifyCommit(c);
44
+ b.byKind[k] += 1;
45
+ b.total += 1;
46
+ }
47
+ // resolve dominant
48
+ for (const b of buckets.values()) {
49
+ let best = "other";
50
+ let bestN = -1;
51
+ for (const k of KINDS) {
52
+ const n = b.byKind[k];
53
+ if (n > bestN) {
54
+ bestN = n;
55
+ best = k;
56
+ }
57
+ }
58
+ b.dominant = best;
59
+ }
60
+ const ordered = [...buckets.values()].sort((a, b) => a.fromDate.localeCompare(b.fromDate));
61
+ return {
62
+ granularity,
63
+ buckets: ordered,
64
+ insights: detectInsights(ordered),
65
+ };
66
+ }
67
+ function detectInsights(buckets) {
68
+ const out = [];
69
+ if (buckets.length < 2)
70
+ return out;
71
+ // Burnout: firefight ratio rises ≥ 30 percentage points across 2+ adjacent buckets.
72
+ for (let i = 1; i < buckets.length; i++) {
73
+ const prev = buckets[i - 1];
74
+ const cur = buckets[i];
75
+ const prevFire = ratio(prev.byKind.firefight, prev.total);
76
+ const curFire = ratio(cur.byKind.firefight, cur.total);
77
+ if (curFire - prevFire >= 0.3 && cur.total >= 5) {
78
+ out.push({
79
+ kind: "burnout",
80
+ fromBucket: prev.label,
81
+ toBucket: cur.label,
82
+ description: `firefight ratio jumped from ${pct(prevFire)} to ${pct(curFire)} — possible incident streak or burnout signal.`,
83
+ });
84
+ }
85
+ if (prevFire >= 0.4 && curFire < prevFire - 0.2) {
86
+ out.push({
87
+ kind: "recovery",
88
+ fromBucket: prev.label,
89
+ toBucket: cur.label,
90
+ description: `firefight ratio fell from ${pct(prevFire)} to ${pct(curFire)} — repo stabilizing.`,
91
+ });
92
+ }
93
+ }
94
+ // Rewrite cluster: 2+ adjacent buckets where dominant = refactor
95
+ for (let i = 0; i < buckets.length - 1; i++) {
96
+ if (buckets[i].dominant === "refactor" && buckets[i + 1].dominant === "refactor") {
97
+ out.push({
98
+ kind: "rewrite-cluster",
99
+ fromBucket: buckets[i].label,
100
+ toBucket: buckets[i + 1].label,
101
+ description: `consecutive periods dominated by refactors — major restructuring effort.`,
102
+ });
103
+ break;
104
+ }
105
+ }
106
+ // Polish streak: 3+ adjacent buckets dominated by polish or docs
107
+ let polishRun = 0;
108
+ let polishStart = "";
109
+ for (const b of buckets) {
110
+ if (b.dominant === "polish" || b.dominant === "docs") {
111
+ if (polishRun === 0)
112
+ polishStart = b.label;
113
+ polishRun++;
114
+ if (polishRun >= 3) {
115
+ out.push({
116
+ kind: "polish-streak",
117
+ fromBucket: polishStart,
118
+ toBucket: b.label,
119
+ description: `${polishRun} consecutive periods of polish/docs — mature, stable phase.`,
120
+ });
121
+ break;
122
+ }
123
+ }
124
+ else {
125
+ polishRun = 0;
126
+ }
127
+ }
128
+ return out;
129
+ }
130
+ function ratio(num, denom) {
131
+ return denom === 0 ? 0 : num / denom;
132
+ }
133
+ function pct(r) {
134
+ return `${Math.round(r * 100)}%`;
135
+ }
136
+ function labelOf(iso, gran) {
137
+ const d = new Date(iso);
138
+ if (Number.isNaN(d.getTime()))
139
+ return "unknown";
140
+ const y = d.getUTCFullYear();
141
+ const m = d.getUTCMonth(); // 0..11
142
+ if (gran === "month") {
143
+ return `${y}-${String(m + 1).padStart(2, "0")}`;
144
+ }
145
+ const q = Math.floor(m / 3) + 1;
146
+ return `${y}-Q${q}`;
147
+ }
148
+ function rangeOf(label, gran) {
149
+ if (gran === "month") {
150
+ const [y, m] = label.split("-");
151
+ const ynum = Number(y);
152
+ const mnum = Number(m);
153
+ const last = new Date(Date.UTC(ynum, mnum, 0));
154
+ return {
155
+ from: `${y}-${m}-01`,
156
+ to: `${y}-${m}-${String(last.getUTCDate()).padStart(2, "0")}`,
157
+ };
158
+ }
159
+ // quarter
160
+ const [y, qstr] = label.split("-Q");
161
+ const q = Number(qstr);
162
+ const startMonth = (q - 1) * 3 + 1;
163
+ const endMonth = startMonth + 2;
164
+ const last = new Date(Date.UTC(Number(y), endMonth, 0));
165
+ return {
166
+ from: `${y}-${String(startMonth).padStart(2, "0")}-01`,
167
+ to: `${y}-${String(endMonth).padStart(2, "0")}-${String(last.getUTCDate()).padStart(2, "0")}`,
168
+ };
169
+ }
170
+ //# sourceMappingURL=drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift.js","sourceRoot":"","sources":["../../src/insights/drift.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=drift.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift.test.d.ts","sourceRoot":"","sources":["../../src/insights/drift.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,126 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { buildDrift, classifyCommit } from "./drift.js";
3
+ function mk(p) {
4
+ return {
5
+ hash: p.hash,
6
+ shortHash: p.hash.slice(0, 7),
7
+ authorName: "Test",
8
+ authorEmail: "t@e.com",
9
+ authorDate: p.date,
10
+ committerDate: p.date,
11
+ subject: p.subject,
12
+ body: p.body ?? "",
13
+ files: [],
14
+ parents: [],
15
+ };
16
+ }
17
+ describe("classifyCommit", () => {
18
+ it("classifies firefight from fix/hotfix/revert keywords", () => {
19
+ expect(classifyCommit(mk({ hash: "a", date: "2024-01-01", subject: "fix bug" }))).toBe("firefight");
20
+ expect(classifyCommit(mk({ hash: "b", date: "2024-01-01", subject: "hotfix critical" }))).toBe("firefight");
21
+ expect(classifyCommit(mk({ hash: "c", date: "2024-01-01", subject: "revert bad change" }))).toBe("firefight");
22
+ });
23
+ it("classifies refactor from rewrite/migrate keywords", () => {
24
+ expect(classifyCommit(mk({ hash: "a", date: "2024-01-01", subject: "refactor handler" }))).toBe("refactor");
25
+ expect(classifyCommit(mk({ hash: "b", date: "2024-01-01", subject: "rewrite parser" }))).toBe("refactor");
26
+ expect(classifyCommit(mk({ hash: "c", date: "2024-01-01", subject: "migrate to v3" }))).toBe("refactor");
27
+ });
28
+ it("classifies feature from feat/add/implement keywords", () => {
29
+ expect(classifyCommit(mk({ hash: "a", date: "2024-01-01", subject: "feat: add caching" }))).toBe("feature");
30
+ expect(classifyCommit(mk({ hash: "b", date: "2024-01-01", subject: "add new endpoint" }))).toBe("feature");
31
+ expect(classifyCommit(mk({ hash: "c", date: "2024-01-01", subject: "implement OAuth" }))).toBe("feature");
32
+ });
33
+ it("classifies polish from chore/typo/format keywords", () => {
34
+ expect(classifyCommit(mk({ hash: "a", date: "2024-01-01", subject: "chore: reformat" }))).toBe("polish");
35
+ expect(classifyCommit(mk({ hash: "b", date: "2024-01-01", subject: "fix typo in README" }))).toBe("firefight");
36
+ });
37
+ it("falls back to 'other' for ambiguous subjects", () => {
38
+ expect(classifyCommit(mk({ hash: "a", date: "2024-01-01", subject: "merge branch" }))).toBe("other");
39
+ });
40
+ });
41
+ describe("buildDrift", () => {
42
+ it("returns empty report for empty input", () => {
43
+ const r = buildDrift([]);
44
+ expect(r.buckets).toHaveLength(0);
45
+ expect(r.insights).toHaveLength(0);
46
+ });
47
+ it("groups commits into quarter buckets by default", () => {
48
+ const commits = [
49
+ mk({ hash: "a1", date: "2024-01-15", subject: "feat: x" }),
50
+ mk({ hash: "a2", date: "2024-04-15", subject: "feat: y" }),
51
+ mk({ hash: "a3", date: "2024-08-15", subject: "feat: z" }),
52
+ ];
53
+ const r = buildDrift(commits);
54
+ expect(r.granularity).toBe("quarter");
55
+ expect(r.buckets.length).toBe(3);
56
+ expect(r.buckets[0].label).toBe("2024-Q1");
57
+ expect(r.buckets[1].label).toBe("2024-Q2");
58
+ expect(r.buckets[2].label).toBe("2024-Q3");
59
+ });
60
+ it("supports month granularity", () => {
61
+ const commits = [
62
+ mk({ hash: "a1", date: "2024-01-15", subject: "x" }),
63
+ mk({ hash: "a2", date: "2024-02-15", subject: "y" }),
64
+ ];
65
+ const r = buildDrift(commits, { granularity: "month" });
66
+ expect(r.granularity).toBe("month");
67
+ expect(r.buckets[0].label).toBe("2024-01");
68
+ expect(r.buckets[1].label).toBe("2024-02");
69
+ });
70
+ it("dominant kind is the highest-count category in a bucket", () => {
71
+ const commits = [
72
+ mk({ hash: "a1", date: "2024-01-01", subject: "fix bug 1" }),
73
+ mk({ hash: "a2", date: "2024-01-02", subject: "fix bug 2" }),
74
+ mk({ hash: "a3", date: "2024-01-03", subject: "feat: add x" }),
75
+ ];
76
+ const r = buildDrift(commits);
77
+ expect(r.buckets[0].dominant).toBe("firefight");
78
+ });
79
+ it("detects burnout insight when firefight ratio jumps", () => {
80
+ const commits = [];
81
+ // Q1: 5 features
82
+ for (let i = 0; i < 5; i++) {
83
+ commits.push(mk({ hash: `f${i}`, date: `2024-01-${(i + 1).toString().padStart(2, "0")}`, subject: `feat: x${i}` }));
84
+ }
85
+ // Q2: 5 fires
86
+ for (let i = 0; i < 5; i++) {
87
+ commits.push(mk({ hash: `b${i}`, date: `2024-04-${(i + 1).toString().padStart(2, "0")}`, subject: `fix bug ${i}` }));
88
+ }
89
+ const r = buildDrift(commits);
90
+ expect(r.insights.some((i) => i.kind === "burnout")).toBe(true);
91
+ });
92
+ it("detects recovery insight when firefight ratio falls", () => {
93
+ const commits = [];
94
+ for (let i = 0; i < 5; i++) {
95
+ commits.push(mk({ hash: `b${i}`, date: `2024-01-${(i + 1).toString().padStart(2, "0")}`, subject: `fix bug ${i}` }));
96
+ }
97
+ for (let i = 0; i < 5; i++) {
98
+ commits.push(mk({ hash: `f${i}`, date: `2024-04-${(i + 1).toString().padStart(2, "0")}`, subject: `feat: nice thing ${i}` }));
99
+ }
100
+ const r = buildDrift(commits);
101
+ expect(r.insights.some((i) => i.kind === "recovery")).toBe(true);
102
+ });
103
+ it("detects rewrite cluster across consecutive quarters", () => {
104
+ const commits = [];
105
+ for (let i = 0; i < 4; i++) {
106
+ commits.push(mk({ hash: `r${i}`, date: `2024-01-${(i + 1).toString().padStart(2, "0")}`, subject: `refactor module ${i}` }));
107
+ }
108
+ for (let i = 0; i < 4; i++) {
109
+ commits.push(mk({ hash: `s${i}`, date: `2024-04-${(i + 1).toString().padStart(2, "0")}`, subject: `rewrite component ${i}` }));
110
+ }
111
+ const r = buildDrift(commits);
112
+ expect(r.insights.some((i) => i.kind === "rewrite-cluster")).toBe(true);
113
+ });
114
+ it("orders buckets chronologically by fromDate", () => {
115
+ const commits = [
116
+ mk({ hash: "a3", date: "2025-01-15", subject: "x" }),
117
+ mk({ hash: "a1", date: "2024-01-15", subject: "x" }),
118
+ mk({ hash: "a2", date: "2024-04-15", subject: "x" }),
119
+ ];
120
+ const r = buildDrift(commits);
121
+ for (let i = 1; i < r.buckets.length; i++) {
122
+ expect(r.buckets[i - 1].fromDate.localeCompare(r.buckets[i].fromDate)).toBeLessThan(0);
123
+ }
124
+ });
125
+ });
126
+ //# sourceMappingURL=drift.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift.test.js","sourceRoot":"","sources":["../../src/insights/drift.test.ts"],"names":[],"mappings":"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"}
@@ -21,4 +21,9 @@ export * from "./crystal-ball.js";
21
21
  export * from "./time-machine.js";
22
22
  export * from "./premortem.js";
23
23
  export * from "./ghost.js";
24
+ export * from "./dna.js";
25
+ export * from "./drift.js";
26
+ export * from "./chronicle.js";
27
+ export * from "./oracle.js";
28
+ export * from "./constellation.js";
24
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC"}
@@ -21,4 +21,9 @@ export * from "./crystal-ball.js";
21
21
  export * from "./time-machine.js";
22
22
  export * from "./premortem.js";
23
23
  export * from "./ghost.js";
24
+ export * from "./dna.js";
25
+ export * from "./drift.js";
26
+ export * from "./chronicle.js";
27
+ export * from "./oracle.js";
28
+ export * from "./constellation.js";
24
29
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * `mneme oracle` — predict near-future co-edits and surface likely
3
+ * collisions where two authors will both touch the same file.
4
+ *
5
+ * Builds an author-file affinity matrix from recent history (default:
6
+ * last 90 days), then projects the per-author probability of touching
7
+ * each file in the upcoming window. When two authors both have high
8
+ * probability for the same file, that's a *predicted collision* —
9
+ * worth flagging for a quick sync.
10
+ *
11
+ * Pure data extraction. No external services. Mneme owns the WHO and
12
+ * the WHAT; this combines them.
13
+ */
14
+ import type { Commit } from "../types.js";
15
+ export interface AuthorFileAffinity {
16
+ author: string;
17
+ filePath: string;
18
+ /** How many times the author touched the file in the window. */
19
+ touches: number;
20
+ /** Recency-weighted score 0..1 (more recent = higher). */
21
+ score: number;
22
+ }
23
+ export interface FilePrediction {
24
+ filePath: string;
25
+ /** Top likely editors with probabilities (sorted). */
26
+ candidates: Array<{
27
+ author: string;
28
+ probability: number;
29
+ }>;
30
+ }
31
+ export interface Collision {
32
+ filePath: string;
33
+ authorA: string;
34
+ authorB: string;
35
+ /** Joint probability — geometric mean of the two individual probabilities. */
36
+ jointProbability: number;
37
+ /** How recently they both touched it together (days ago, or -1 if never). */
38
+ daysSinceLastJointTouch: number;
39
+ }
40
+ export interface OracleReport {
41
+ /** Total commits considered. */
42
+ windowCommits: number;
43
+ /** Top per-file predictions. */
44
+ predictions: FilePrediction[];
45
+ /** Predicted collisions sorted by jointProbability desc. */
46
+ collisions: Collision[];
47
+ }
48
+ /**
49
+ * Build an Oracle report. Caller passes the full commit history; we
50
+ * keep only the recent window.
51
+ */
52
+ export declare function buildOracle(commits: Commit[], opts?: {
53
+ nowMs?: number;
54
+ windowDays?: number;
55
+ topPredictionsPerFile?: number;
56
+ minTouchesForFile?: number;
57
+ collisionFloor?: number;
58
+ }): OracleReport;
59
+ //# sourceMappingURL=oracle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oracle.d.ts","sourceRoot":"","sources":["../../src/insights/oracle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,UAAU,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,4DAA4D;IAC5D,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE;IACJ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACpB,GACL,YAAY,CA2Fd"}
Binary file