@mneme-ai/core 0.8.4 → 0.9.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 (144) 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 +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -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/decisions.d.ts +38 -0
  22. package/dist/insights/decisions.d.ts.map +1 -0
  23. package/dist/insights/decisions.js +125 -0
  24. package/dist/insights/decisions.js.map +1 -0
  25. package/dist/insights/decisions.test.d.ts +2 -0
  26. package/dist/insights/decisions.test.d.ts.map +1 -0
  27. package/dist/insights/decisions.test.js +141 -0
  28. package/dist/insights/decisions.test.js.map +1 -0
  29. package/dist/insights/dream.d.ts +71 -0
  30. package/dist/insights/dream.d.ts.map +1 -0
  31. package/dist/insights/dream.js +235 -0
  32. package/dist/insights/dream.js.map +1 -0
  33. package/dist/insights/dream.test.d.ts +2 -0
  34. package/dist/insights/dream.test.d.ts.map +1 -0
  35. package/dist/insights/dream.test.js +127 -0
  36. package/dist/insights/dream.test.js.map +1 -0
  37. package/dist/insights/index.d.ts +16 -0
  38. package/dist/insights/index.d.ts.map +1 -0
  39. package/dist/insights/index.js +16 -0
  40. package/dist/insights/index.js.map +1 -0
  41. package/dist/insights/obsidian.d.ts +42 -0
  42. package/dist/insights/obsidian.d.ts.map +1 -0
  43. package/dist/insights/obsidian.js +263 -0
  44. package/dist/insights/obsidian.js.map +1 -0
  45. package/dist/insights/obsidian.test.d.ts +2 -0
  46. package/dist/insights/obsidian.test.d.ts.map +1 -0
  47. package/dist/insights/obsidian.test.js +241 -0
  48. package/dist/insights/obsidian.test.js.map +1 -0
  49. package/dist/insights/stack-trace.d.ts +40 -0
  50. package/dist/insights/stack-trace.d.ts.map +1 -0
  51. package/dist/insights/stack-trace.js +127 -0
  52. package/dist/insights/stack-trace.js.map +1 -0
  53. package/dist/insights/stack-trace.test.d.ts +2 -0
  54. package/dist/insights/stack-trace.test.d.ts.map +1 -0
  55. package/dist/insights/stack-trace.test.js +103 -0
  56. package/dist/insights/stack-trace.test.js.map +1 -0
  57. package/dist/insights/story.d.ts +34 -0
  58. package/dist/insights/story.d.ts.map +1 -0
  59. package/dist/insights/story.js +100 -0
  60. package/dist/insights/story.js.map +1 -0
  61. package/dist/insights/story.test.d.ts +2 -0
  62. package/dist/insights/story.test.d.ts.map +1 -0
  63. package/dist/insights/story.test.js +99 -0
  64. package/dist/insights/story.test.js.map +1 -0
  65. package/dist/insights/suggest.d.ts +29 -0
  66. package/dist/insights/suggest.d.ts.map +1 -0
  67. package/dist/insights/suggest.js +93 -0
  68. package/dist/insights/suggest.js.map +1 -0
  69. package/dist/insights/suggest.test.d.ts +2 -0
  70. package/dist/insights/suggest.test.d.ts.map +1 -0
  71. package/dist/insights/suggest.test.js +71 -0
  72. package/dist/insights/suggest.test.js.map +1 -0
  73. package/dist/insights/who-knows.d.ts +48 -0
  74. package/dist/insights/who-knows.d.ts.map +1 -0
  75. package/dist/insights/who-knows.js +96 -0
  76. package/dist/insights/who-knows.js.map +1 -0
  77. package/dist/insights/who-knows.test.d.ts +2 -0
  78. package/dist/insights/who-knows.test.d.ts.map +1 -0
  79. package/dist/insights/who-knows.test.js +47 -0
  80. package/dist/insights/who-knows.test.js.map +1 -0
  81. package/dist/retrieve/index.d.ts +2 -0
  82. package/dist/retrieve/index.d.ts.map +1 -1
  83. package/dist/retrieve/index.js +2 -0
  84. package/dist/retrieve/index.js.map +1 -1
  85. package/dist/retrieve/intent.d.ts +32 -0
  86. package/dist/retrieve/intent.d.ts.map +1 -0
  87. package/dist/retrieve/intent.js +104 -0
  88. package/dist/retrieve/intent.js.map +1 -0
  89. package/dist/retrieve/intent.test.d.ts +2 -0
  90. package/dist/retrieve/intent.test.d.ts.map +1 -0
  91. package/dist/retrieve/intent.test.js +106 -0
  92. package/dist/retrieve/intent.test.js.map +1 -0
  93. package/dist/retrieve/search.d.ts +30 -0
  94. package/dist/retrieve/search.d.ts.map +1 -1
  95. package/dist/retrieve/search.js +48 -0
  96. package/dist/retrieve/search.js.map +1 -1
  97. package/dist/retrieve/search.test.js +84 -1
  98. package/dist/retrieve/search.test.js.map +1 -1
  99. package/dist/retrieve/synthesize.d.ts +57 -0
  100. package/dist/retrieve/synthesize.d.ts.map +1 -0
  101. package/dist/retrieve/synthesize.js +160 -0
  102. package/dist/retrieve/synthesize.js.map +1 -0
  103. package/dist/retrieve/synthesize.test.d.ts +2 -0
  104. package/dist/retrieve/synthesize.test.d.ts.map +1 -0
  105. package/dist/retrieve/synthesize.test.js +116 -0
  106. package/dist/retrieve/synthesize.test.js.map +1 -0
  107. package/dist/store/schema.d.ts +2 -2
  108. package/dist/store/schema.d.ts.map +1 -1
  109. package/dist/store/schema.js +55 -1
  110. package/dist/store/schema.js.map +1 -1
  111. package/dist/store/sqlite.test.js +1 -1
  112. package/dist/util/index.d.ts +2 -0
  113. package/dist/util/index.d.ts.map +1 -1
  114. package/dist/util/index.js +2 -1
  115. package/dist/util/index.js.map +1 -1
  116. package/dist/util/redact.d.ts +58 -0
  117. package/dist/util/redact.d.ts.map +1 -0
  118. package/dist/util/redact.js +129 -0
  119. package/dist/util/redact.js.map +1 -0
  120. package/dist/util/redact.test.d.ts +2 -0
  121. package/dist/util/redact.test.d.ts.map +1 -0
  122. package/dist/util/redact.test.js +148 -0
  123. package/dist/util/redact.test.js.map +1 -0
  124. package/dist/wisdom/calibrator.d.ts +43 -0
  125. package/dist/wisdom/calibrator.d.ts.map +1 -0
  126. package/dist/wisdom/calibrator.js +120 -0
  127. package/dist/wisdom/calibrator.js.map +1 -0
  128. package/dist/wisdom/feedback.d.ts +45 -0
  129. package/dist/wisdom/feedback.d.ts.map +1 -0
  130. package/dist/wisdom/feedback.js +116 -0
  131. package/dist/wisdom/feedback.js.map +1 -0
  132. package/dist/wisdom/index.d.ts +15 -0
  133. package/dist/wisdom/index.d.ts.map +1 -0
  134. package/dist/wisdom/index.js +15 -0
  135. package/dist/wisdom/index.js.map +1 -0
  136. package/dist/wisdom/types.d.ts +67 -0
  137. package/dist/wisdom/types.d.ts.map +1 -0
  138. package/dist/wisdom/types.js +20 -0
  139. package/dist/wisdom/types.js.map +1 -0
  140. package/dist/wisdom/wisdom.test.d.ts +2 -0
  141. package/dist/wisdom/wisdom.test.d.ts.map +1 -0
  142. package/dist/wisdom/wisdom.test.js +144 -0
  143. package/dist/wisdom/wisdom.test.js.map +1 -0
  144. package/package.json +1 -1
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `stack-trace` — given an error / stack trace, find the commits that
3
+ * touched each frame and any past incidents at the same locations.
4
+ *
5
+ * Why this exists: when a prod bug fires, the most useful question is
6
+ * "have we seen this here before?" Mneme already indexes git + incidents.
7
+ * Parsing a stack trace and querying both is a small composition of pieces
8
+ * we already have.
9
+ *
10
+ * Supported formats (frame parser):
11
+ * - JS/TS: " at functionName (path/to/file.ts:42:15)"
12
+ * - Python: " File "path/to/file.py", line 42, in functionName"
13
+ * - Go: "goroutine 1 [running]: ... path/to/file.go:42 +0x..."
14
+ * - Java: " at com.example.Foo.bar(Foo.java:42)"
15
+ *
16
+ * Pure parsing — no I/O. Composing with retrieval lives in the CLI command.
17
+ */
18
+ const PATTERNS = [
19
+ // JS/TS — V8 / Node.js / browser
20
+ // " at functionName (path/to/file.ts:42:15)"
21
+ // " at path/to/file.ts:42:15"
22
+ {
23
+ language: "js",
24
+ re: /\bat\s+(?:(?<fn>[\w$.<>[\]\s]+?)\s+\()?(?<file>[^():\n]+\.[a-zA-Z]+):(?<line>\d+)(?::\d+)?\)?/g,
25
+ map: (m) => {
26
+ const file = m.groups?.file?.trim();
27
+ if (!file)
28
+ return null;
29
+ return {
30
+ file,
31
+ line: Number(m.groups?.line ?? 0),
32
+ function: m.groups?.fn?.trim() || undefined,
33
+ language: "js",
34
+ };
35
+ },
36
+ },
37
+ // Python — CPython tracebacks
38
+ // ' File "path/to/file.py", line 42, in functionName'
39
+ {
40
+ language: "python",
41
+ re: /File\s+"(?<file>[^"]+\.py[i]?)",\s+line\s+(?<line>\d+)(?:,\s+in\s+(?<fn>\w+))?/g,
42
+ map: (m) => ({
43
+ file: m.groups.file,
44
+ line: Number(m.groups.line),
45
+ function: m.groups?.fn || undefined,
46
+ language: "python",
47
+ }),
48
+ },
49
+ // Go — runtime panic format
50
+ // " /home/x/main.go:42 +0x..."
51
+ // "main.foo(0x0, 0x0)"
52
+ // " /home/x/foo.go:42"
53
+ {
54
+ language: "go",
55
+ re: /(?<file>[^\s():]+\.go):(?<line>\d+)/g,
56
+ map: (m) => ({
57
+ file: m.groups.file,
58
+ line: Number(m.groups.line),
59
+ language: "go",
60
+ }),
61
+ },
62
+ // Java
63
+ // " at com.example.Foo.bar(Foo.java:42)"
64
+ {
65
+ language: "java",
66
+ re: /\bat\s+(?<fn>[\w.$]+)\((?<file>[^()]+\.java):(?<line>\d+)\)/g,
67
+ map: (m) => ({
68
+ file: m.groups.file,
69
+ line: Number(m.groups.line),
70
+ function: m.groups?.fn || undefined,
71
+ language: "java",
72
+ }),
73
+ },
74
+ ];
75
+ /**
76
+ * Parse a multi-line stack trace into ordered frames. Frames are deduped
77
+ * when the same (file, line) appears consecutively. Languages that share
78
+ * patterns (e.g. .ts files might also match the python file regex if
79
+ * weirdly formatted) are disambiguated by extension first.
80
+ */
81
+ export function parseStackTrace(text) {
82
+ if (!text || !text.trim())
83
+ return [];
84
+ const seen = new Set();
85
+ const frames = [];
86
+ for (const p of PATTERNS) {
87
+ const re = new RegExp(p.re.source, p.re.flags);
88
+ let m;
89
+ while ((m = re.exec(text)) !== null) {
90
+ const frame = p.map(m);
91
+ if (!frame)
92
+ continue;
93
+ // Skip language-mismatch (e.g. js regex matching a Java line).
94
+ const ext = frame.file.split(".").pop()?.toLowerCase();
95
+ if (frame.language === "js" && ext && !["js", "ts", "jsx", "tsx", "mjs", "cjs"].includes(ext))
96
+ continue;
97
+ const key = `${frame.file}:${frame.line}`;
98
+ if (seen.has(key))
99
+ continue;
100
+ seen.add(key);
101
+ frames.push(frame);
102
+ }
103
+ }
104
+ // Sort by appearance order in original text (so most-recent-frame-first
105
+ // for languages where the trace is bottom-up like Python).
106
+ // Detection: count file paths — the first frame in the input is what we keep first.
107
+ return frames.sort((a, b) => text.indexOf(a.file) - text.indexOf(b.file));
108
+ }
109
+ /**
110
+ * Heuristic: detect whether the trace appears to be from a particular
111
+ * language. Useful when displaying frame analysis ("Python traceback").
112
+ */
113
+ export function detectLanguage(text) {
114
+ const t = text.toLowerCase();
115
+ if (t.includes("traceback") || t.includes('file "') || t.includes(".py"))
116
+ return t.includes(".py") ? "python" : "unknown";
117
+ if (t.includes("goroutine") || /\.go:\d+/.test(t))
118
+ return "go";
119
+ if (/\.java:\d+/.test(t))
120
+ return "java";
121
+ if (/\b(typeerror|referenceerror|syntaxerror|rangeerror)\b/.test(t))
122
+ return "js";
123
+ if (/\.[jt]sx?:\d+/.test(t))
124
+ return "js";
125
+ return "unknown";
126
+ }
127
+ //# sourceMappingURL=stack-trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-trace.js","sourceRoot":"","sources":["../../src/insights/stack-trace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stack-trace.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-trace.test.d.ts","sourceRoot":"","sources":["../../src/insights/stack-trace.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseStackTrace, detectLanguage } from "./stack-trace.js";
3
+ describe("parseStackTrace — JavaScript/TypeScript", () => {
4
+ it("parses V8-style frames with function name", () => {
5
+ const trace = `TypeError: Cannot read property 'amount' of undefined
6
+ at parseAmount (src/payment.ts:42:15)
7
+ at processCharge (src/payment.ts:78:9)
8
+ at Object.<anonymous> (src/index.ts:5:1)`;
9
+ const frames = parseStackTrace(trace);
10
+ expect(frames).toHaveLength(3);
11
+ expect(frames[0]).toMatchObject({ file: "src/payment.ts", line: 42, function: "parseAmount", language: "js" });
12
+ expect(frames[1].function).toBe("processCharge");
13
+ });
14
+ it("parses anonymous frames (no function name)", () => {
15
+ const trace = `Error: kaboom
16
+ at /app/dist/main.js:1:42`;
17
+ const frames = parseStackTrace(trace);
18
+ expect(frames[0]?.file).toBe("/app/dist/main.js");
19
+ expect(frames[0]?.function).toBeUndefined();
20
+ });
21
+ });
22
+ describe("parseStackTrace — Python", () => {
23
+ it("parses CPython tracebacks", () => {
24
+ const trace = `Traceback (most recent call last):
25
+ File "/app/main.py", line 42, in process_charge
26
+ amount = parse_amount(payload["amount"])
27
+ File "/app/payment.py", line 17, in parse_amount
28
+ return Decimal(value)
29
+ ValueError: not a number`;
30
+ const frames = parseStackTrace(trace);
31
+ expect(frames.length).toBeGreaterThanOrEqual(2);
32
+ const main = frames.find((f) => f.file.endsWith("main.py"));
33
+ expect(main).toMatchObject({ line: 42, function: "process_charge", language: "python" });
34
+ });
35
+ });
36
+ describe("parseStackTrace — Go", () => {
37
+ it("parses Go panic file:line references", () => {
38
+ const trace = `goroutine 1 [running]:
39
+ main.foo(0x0)
40
+ /home/dev/app/main.go:42 +0x12
41
+ runtime.main()
42
+ /usr/local/go/src/runtime/proc.go:250`;
43
+ const frames = parseStackTrace(trace);
44
+ expect(frames.length).toBeGreaterThanOrEqual(2);
45
+ expect(frames.find((f) => f.file === "/home/dev/app/main.go")?.line).toBe(42);
46
+ });
47
+ });
48
+ describe("parseStackTrace — Java", () => {
49
+ it("parses Java stack frames with class.method(File.java:line)", () => {
50
+ const trace = `Exception in thread "main" java.lang.NullPointerException
51
+ at com.example.Foo.bar(Foo.java:42)
52
+ at com.example.App.main(App.java:7)`;
53
+ const frames = parseStackTrace(trace);
54
+ expect(frames).toHaveLength(2);
55
+ expect(frames[0]).toMatchObject({ file: "Foo.java", line: 42, language: "java" });
56
+ expect(frames[0].function).toContain("Foo.bar");
57
+ });
58
+ });
59
+ describe("parseStackTrace — edge cases", () => {
60
+ it("returns empty array for empty input", () => {
61
+ expect(parseStackTrace("")).toEqual([]);
62
+ expect(parseStackTrace(" \n \n")).toEqual([]);
63
+ });
64
+ it("dedupes consecutive identical (file, line) frames", () => {
65
+ const trace = `at a (foo.ts:42:1)
66
+ at a (foo.ts:42:1)
67
+ at a (foo.ts:42:1)`;
68
+ expect(parseStackTrace(trace)).toHaveLength(1);
69
+ });
70
+ it("filters out non-matching language extensions", () => {
71
+ // ".java" file should not match the JS regex even if the format looks similar.
72
+ const trace = `at com.x.Foo.bar(Foo.java:42)`;
73
+ const frames = parseStackTrace(trace);
74
+ expect(frames.every((f) => f.language === "java")).toBe(true);
75
+ });
76
+ it("handles a mixed trace (multiple languages in one input)", () => {
77
+ const trace = `TypeError: kaboom
78
+ at handler (src/api.ts:42:1)
79
+ And in the worker:
80
+ File "/app/worker.py", line 7, in run_job`;
81
+ const frames = parseStackTrace(trace);
82
+ expect(frames.find((f) => f.language === "js")).toBeDefined();
83
+ expect(frames.find((f) => f.language === "python")).toBeDefined();
84
+ });
85
+ });
86
+ describe("detectLanguage — heuristic top-level detection", () => {
87
+ it("python (Traceback header)", () => {
88
+ expect(detectLanguage("Traceback (most recent call last):\n File \"x.py\", line 1, in f")).toBe("python");
89
+ });
90
+ it("go (goroutine header)", () => {
91
+ expect(detectLanguage("goroutine 1 [running]:\n/x/main.go:1 +0x")).toBe("go");
92
+ });
93
+ it("java (.java:N pattern)", () => {
94
+ expect(detectLanguage("at com.x.Foo.bar(Foo.java:42)")).toBe("java");
95
+ });
96
+ it("js (TypeError keyword)", () => {
97
+ expect(detectLanguage("TypeError: Cannot read property 'x' of undefined")).toBe("js");
98
+ });
99
+ it("unknown when no marker", () => {
100
+ expect(detectLanguage("just some random text")).toBe("unknown");
101
+ });
102
+ });
103
+ //# sourceMappingURL=stack-trace.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-trace.test.js","sourceRoot":"","sources":["../../src/insights/stack-trace.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `story <topic>` — narrative generation. Take a topic, find every commit
3
+ * that touched it, group into "acts" (initial / refactor / incidents /
4
+ * stable), and emit a structured timeline. The LLM (when available) can
5
+ * polish the act labels and write a short prose summary.
6
+ *
7
+ * Pure data extraction in this file. The CLI command renders + optionally
8
+ * pipes through an LLM for prose.
9
+ */
10
+ import type { Commit } from "../types.js";
11
+ export interface StoryAct {
12
+ /** Stable id used for the section header. */
13
+ id: "initial" | "refactor" | "incident" | "evolution" | "stable";
14
+ /** Human-readable title — "Act I — The Beginning" etc. */
15
+ title: string;
16
+ /** Commits in chronological order (oldest first). */
17
+ commits: Commit[];
18
+ /** Date range of the act. */
19
+ fromDate: string;
20
+ toDate: string;
21
+ }
22
+ export interface Story {
23
+ topic: string;
24
+ acts: StoryAct[];
25
+ totalCommits: number;
26
+ spanDays: number;
27
+ }
28
+ /**
29
+ * Build a Story from a list of commits matching the topic. Commits should
30
+ * already be filtered by relevance (e.g. via FTS) and sorted chronologically
31
+ * by the caller.
32
+ */
33
+ export declare function buildStory(topic: string, commits: Commit[]): Story;
34
+ //# sourceMappingURL=story.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/insights/story.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,QAAQ;IACvB,6CAA6C;IAC7C,EAAE,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;IACjE,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAqElE"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * `story <topic>` — narrative generation. Take a topic, find every commit
3
+ * that touched it, group into "acts" (initial / refactor / incidents /
4
+ * stable), and emit a structured timeline. The LLM (when available) can
5
+ * polish the act labels and write a short prose summary.
6
+ *
7
+ * Pure data extraction in this file. The CLI command renders + optionally
8
+ * pipes through an LLM for prose.
9
+ */
10
+ const REFACTOR_KEYWORDS = /\b(refactor|rewrite|migrate|switch(?:ed)?|replace[ds]?|deprecate)\b/i;
11
+ const INCIDENT_KEYWORDS = /\b(incident|outage|hotfix|revert|rollback|broken|critical|emergency|p[01])\b/i;
12
+ /**
13
+ * Build a Story from a list of commits matching the topic. Commits should
14
+ * already be filtered by relevance (e.g. via FTS) and sorted chronologically
15
+ * by the caller.
16
+ */
17
+ export function buildStory(topic, commits) {
18
+ const sorted = [...commits].sort((a, b) => a.authorDate.localeCompare(b.authorDate));
19
+ if (sorted.length === 0) {
20
+ return { topic, acts: [], totalCommits: 0, spanDays: 0 };
21
+ }
22
+ const acts = [];
23
+ // Act I — Initial: the very first commit on the topic.
24
+ const initial = sorted[0];
25
+ acts.push({
26
+ id: "initial",
27
+ title: "Act I — The Beginning",
28
+ commits: [initial],
29
+ fromDate: initial.authorDate.slice(0, 10),
30
+ toDate: initial.authorDate.slice(0, 10),
31
+ });
32
+ const remaining = sorted.slice(1);
33
+ let currentFlavor = null;
34
+ let currentBucket = [];
35
+ const flush = () => {
36
+ if (currentBucket.length === 0 || !currentFlavor)
37
+ return;
38
+ acts.push({
39
+ id: currentFlavor,
40
+ title: titleFor(currentFlavor, acts.length),
41
+ commits: [...currentBucket],
42
+ fromDate: currentBucket[0].authorDate.slice(0, 10),
43
+ toDate: currentBucket[currentBucket.length - 1].authorDate.slice(0, 10),
44
+ });
45
+ currentBucket = [];
46
+ currentFlavor = null;
47
+ };
48
+ for (const c of remaining) {
49
+ const flavor = detectFlavor(c);
50
+ if (flavor !== currentFlavor)
51
+ flush();
52
+ currentFlavor = flavor;
53
+ currentBucket.push(c);
54
+ }
55
+ flush();
56
+ // Final act — if the most recent commit is older than 90 days, consider
57
+ // the topic "stable" (no recent change).
58
+ const last = sorted[sorted.length - 1];
59
+ const ageDays = (Date.now() - new Date(last.authorDate).getTime()) / 86_400_000;
60
+ if (ageDays > 90 && acts[acts.length - 1].id !== "stable") {
61
+ acts.push({
62
+ id: "stable",
63
+ title: `${acts.length === 0 ? "Act I" : romanFor(acts.length + 1)} — The Stable State`,
64
+ commits: [last],
65
+ fromDate: last.authorDate.slice(0, 10),
66
+ toDate: new Date().toISOString().slice(0, 10),
67
+ });
68
+ }
69
+ const span = Math.round((new Date(last.authorDate).getTime() - new Date(initial.authorDate).getTime()) / 86_400_000);
70
+ return {
71
+ topic,
72
+ acts,
73
+ totalCommits: sorted.length,
74
+ spanDays: span,
75
+ };
76
+ }
77
+ function detectFlavor(c) {
78
+ const text = `${c.subject}\n${c.body || ""}`;
79
+ if (INCIDENT_KEYWORDS.test(text))
80
+ return "incident";
81
+ if (REFACTOR_KEYWORDS.test(text))
82
+ return "refactor";
83
+ return "evolution";
84
+ }
85
+ function titleFor(flavor, actIndex) {
86
+ const roman = romanFor(actIndex + 1);
87
+ switch (flavor) {
88
+ case "refactor":
89
+ return `${roman} — The Refactor`;
90
+ case "incident":
91
+ return `${roman} — Incidents Strike`;
92
+ case "evolution":
93
+ return `${roman} — Steady Evolution`;
94
+ }
95
+ }
96
+ function romanFor(n) {
97
+ const roman = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"];
98
+ return n <= 10 ? `Act ${roman[n - 1]}` : `Act ${n}`;
99
+ }
100
+ //# sourceMappingURL=story.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"story.js","sourceRoot":"","sources":["../../src/insights/story.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=story.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"story.test.d.ts","sourceRoot":"","sources":["../../src/insights/story.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { buildStory } from "./story.js";
3
+ const cmt = (hash, date, subject, body = "") => ({
4
+ hash,
5
+ shortHash: hash.slice(0, 7),
6
+ authorName: "alice",
7
+ authorEmail: "alice@example.com",
8
+ authorDate: `${date}T00:00:00Z`,
9
+ committerDate: `${date}T00:00:00Z`,
10
+ subject,
11
+ body,
12
+ parents: [],
13
+ files: [],
14
+ });
15
+ describe("buildStory — basic structure", () => {
16
+ it("returns empty story for empty commit list", () => {
17
+ const s = buildStory("auth", []);
18
+ expect(s.acts).toEqual([]);
19
+ expect(s.totalCommits).toBe(0);
20
+ expect(s.spanDays).toBe(0);
21
+ });
22
+ it("first commit always opens Act I", () => {
23
+ const s = buildStory("auth", [cmt("a1", "2024-01-01", "feat: add passport.js")]);
24
+ expect(s.acts[0].id).toBe("initial");
25
+ expect(s.acts[0].title).toContain("Beginning");
26
+ });
27
+ it("totalCommits and spanDays reflect input", () => {
28
+ const s = buildStory("auth", [
29
+ cmt("a1", "2024-01-01", "feat: passport"),
30
+ cmt("a2", "2024-04-01", "refactor: replace passport"),
31
+ ]);
32
+ expect(s.totalCommits).toBe(2);
33
+ expect(s.spanDays).toBeGreaterThanOrEqual(89); // about 90 days between Jan 1 and Apr 1
34
+ });
35
+ });
36
+ describe("buildStory — act detection", () => {
37
+ it("groups consecutive refactor-style commits into one Refactor act", () => {
38
+ const s = buildStory("auth", [
39
+ cmt("a1", "2024-01-01", "feat: passport"),
40
+ cmt("a2", "2024-04-01", "refactor: replace passport with custom"),
41
+ cmt("a3", "2024-04-08", "refactor: switch to JWT signing"),
42
+ cmt("a4", "2024-04-15", "refactor: migrate session middleware"),
43
+ ]);
44
+ const refactorActs = s.acts.filter((a) => a.id === "refactor");
45
+ expect(refactorActs).toHaveLength(1);
46
+ expect(refactorActs[0].commits).toHaveLength(3);
47
+ });
48
+ it("flags 'hotfix' and 'incident' commits as incident acts", () => {
49
+ const s = buildStory("auth", [
50
+ cmt("a1", "2024-01-01", "feat: passport"),
51
+ cmt("a2", "2024-02-01", "hotfix: CSRF bypass after refactor", ""),
52
+ cmt("a3", "2024-02-02", "revert: PR #42 caused outage"),
53
+ ]);
54
+ expect(s.acts.find((a) => a.id === "incident")).toBeDefined();
55
+ });
56
+ it("commits without keywords flagged as evolution", () => {
57
+ const s = buildStory("auth", [
58
+ cmt("a1", "2024-01-01", "feat: passport"),
59
+ cmt("a2", "2024-01-15", "feat: add /me endpoint"),
60
+ cmt("a3", "2024-02-01", "feat: add /logout endpoint"),
61
+ ]);
62
+ expect(s.acts.find((a) => a.id === "evolution")).toBeDefined();
63
+ });
64
+ it("transitions across flavors close+open separate acts", () => {
65
+ const s = buildStory("auth", [
66
+ cmt("a1", "2024-01-01", "feat: passport"), // initial
67
+ cmt("a2", "2024-01-10", "feat: add session cookies"), // evolution
68
+ cmt("a3", "2024-02-01", "refactor: replace passport"), // refactor
69
+ cmt("a4", "2024-02-15", "feat: add audit logs"), // evolution
70
+ cmt("a5", "2024-03-01", "hotfix: critical XSS"), // incident
71
+ ]);
72
+ const ids = s.acts.map((a) => a.id);
73
+ expect(ids[0]).toBe("initial");
74
+ expect(ids).toContain("refactor");
75
+ expect(ids).toContain("evolution");
76
+ expect(ids).toContain("incident");
77
+ });
78
+ it("appends a Stable State act when the latest commit is > 90 days old", () => {
79
+ const oneYearAgo = new Date(Date.now() - 400 * 86_400_000).toISOString().slice(0, 10);
80
+ const s = buildStory("auth", [cmt("a1", oneYearAgo, "feat: passport")]);
81
+ const stable = s.acts.find((a) => a.id === "stable");
82
+ expect(stable).toBeDefined();
83
+ });
84
+ });
85
+ describe("buildStory — date metadata", () => {
86
+ it("each act records fromDate and toDate", () => {
87
+ const s = buildStory("auth", [
88
+ cmt("a1", "2024-01-01", "feat: passport"),
89
+ cmt("a2", "2024-04-01", "refactor: replace passport"),
90
+ cmt("a3", "2024-04-15", "refactor: switch to JWT"),
91
+ ]);
92
+ for (const act of s.acts) {
93
+ expect(act.fromDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
94
+ expect(act.toDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
95
+ expect(act.fromDate <= act.toDate).toBe(true);
96
+ }
97
+ });
98
+ });
99
+ //# sourceMappingURL=story.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"story.test.js","sourceRoot":"","sources":["../../src/insights/story.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Smart suggestions — given a question and its results, propose 3 follow-up
3
+ * commands the user might want to run next.
4
+ *
5
+ * Pure function. Heuristic, not LLM. Deterministic so tests can verify it.
6
+ *
7
+ * Design principle: every suggestion must lead to an actionable command
8
+ * the user can copy-paste. Vague hints ("explore more", "look around") are
9
+ * useless; concrete commands compound.
10
+ */
11
+ import type { SearchResult } from "../types.js";
12
+ export interface Suggestion {
13
+ /** The CLI command, ready to copy-paste. */
14
+ command: string;
15
+ /** One-line "why this is interesting". */
16
+ reason: string;
17
+ }
18
+ /**
19
+ * Build follow-up suggestions for an `ask` query.
20
+ * Returns 0..3 suggestions, ordered by likely usefulness.
21
+ */
22
+ export declare function suggestFollowUps(question: string, results: SearchResult[]): Suggestion[];
23
+ /**
24
+ * Extract a "topic word" from a question — the most concrete noun, suitable
25
+ * for plugging into `story <topic>` or `who-knows <topic>`. Falsy when nothing
26
+ * concrete is present.
27
+ */
28
+ export declare function extractTopicWord(question: string): string | undefined;
29
+ //# sourceMappingURL=suggest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest.d.ts","sourceRoot":"","sources":["../../src/insights/suggest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,UAAU,EAAE,CAiDxF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA0BrE"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Smart suggestions — given a question and its results, propose 3 follow-up
3
+ * commands the user might want to run next.
4
+ *
5
+ * Pure function. Heuristic, not LLM. Deterministic so tests can verify it.
6
+ *
7
+ * Design principle: every suggestion must lead to an actionable command
8
+ * the user can copy-paste. Vague hints ("explore more", "look around") are
9
+ * useless; concrete commands compound.
10
+ */
11
+ /**
12
+ * Build follow-up suggestions for an `ask` query.
13
+ * Returns 0..3 suggestions, ordered by likely usefulness.
14
+ */
15
+ export function suggestFollowUps(question, results) {
16
+ const out = [];
17
+ if (results.length === 0)
18
+ return out;
19
+ const top = results[0];
20
+ const topAuthor = top.commit.authorName;
21
+ const topFile = (top.commit.files ?? [])[0];
22
+ // 1. If the top commit has files, suggest `mneme why <file>:<line>` for the
23
+ // most-changed file. Anchor question into a specific location.
24
+ if (topFile) {
25
+ out.push({
26
+ command: `mneme why ${topFile}`,
27
+ reason: `Walk the blame + history of the file most changed in the top commit`,
28
+ });
29
+ }
30
+ // 2. Story command — narrate the topic across acts.
31
+ const topicWord = extractTopicWord(question);
32
+ if (topicWord) {
33
+ out.push({
34
+ command: `mneme story ${topicWord}`,
35
+ reason: `See how "${topicWord}" evolved across acts (initial / refactor / incidents)`,
36
+ });
37
+ }
38
+ // 3. Who-knows — surface the human expert.
39
+ if (topicWord && topAuthor) {
40
+ out.push({
41
+ command: `mneme who-knows ${topicWord}`,
42
+ reason: `Find people most likely to know about "${topicWord}" (top hit so far: ${topAuthor})`,
43
+ });
44
+ }
45
+ else if (topAuthor) {
46
+ out.push({
47
+ command: `mneme who-knows ${topAuthor.split(/\s+/)[0]?.toLowerCase()}`,
48
+ reason: `See where ${topAuthor} has been most active recently`,
49
+ });
50
+ }
51
+ // 4. Blast radius if the top commit looks recent.
52
+ if (results.length >= 2 && top.commit.shortHash) {
53
+ out.push({
54
+ command: `mneme blast ${top.commit.shortHash}`,
55
+ reason: `Predict incidents likely to follow shipping the top commit (base-rate verdict)`,
56
+ });
57
+ }
58
+ return out.slice(0, 3);
59
+ }
60
+ /**
61
+ * Extract a "topic word" from a question — the most concrete noun, suitable
62
+ * for plugging into `story <topic>` or `who-knows <topic>`. Falsy when nothing
63
+ * concrete is present.
64
+ */
65
+ export function extractTopicWord(question) {
66
+ // 1. Look for camelCase / PascalCase identifiers (case-sensitive on the original).
67
+ const camel = question.match(/[a-z]+[A-Z][a-zA-Z]+/);
68
+ if (camel)
69
+ return camel[0];
70
+ // 2. Look for path-like tokens (lowercased — paths are usually lowercase anyway).
71
+ const q = question.toLowerCase();
72
+ const path = q.match(/[a-z]+\/[a-z]+/);
73
+ if (path)
74
+ return path[0];
75
+ // 3. Strip stop-words and punctuation and pick the longest remaining token.
76
+ const STOP = new Set([
77
+ "the", "a", "an", "is", "are", "was", "were", "do", "does", "did",
78
+ "why", "when", "what", "who", "how", "where", "this", "that",
79
+ "to", "from", "on", "in", "of", "for", "with", "and", "or", "but",
80
+ "code", "file", "method", "module", "any",
81
+ "use", "uses", "used", "using",
82
+ ]);
83
+ const words = q
84
+ .replace(/[?.,!;:'"()[\]{}]/g, " ")
85
+ .split(/\s+/)
86
+ .filter((w) => w.length >= 4 && !STOP.has(w));
87
+ if (words.length === 0)
88
+ return undefined;
89
+ // Prefer longest distinct-looking word.
90
+ words.sort((a, b) => b.length - a.length);
91
+ return words[0];
92
+ }
93
+ //# sourceMappingURL=suggest.js.map