@mneme-ai/core 0.8.3 → 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 @@
1
+ {"version":3,"file":"suggest.js","sourceRoot":"","sources":["../../src/insights/suggest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=suggest.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest.test.d.ts","sourceRoot":"","sources":["../../src/insights/suggest.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { suggestFollowUps, extractTopicWord } from "./suggest.js";
3
+ const cmt = (hash, subject, author = "alice", files = []) => ({
4
+ hash,
5
+ shortHash: hash.slice(0, 7),
6
+ authorName: author,
7
+ authorEmail: `${author}@example.com`,
8
+ authorDate: "2024-08-12T00:00:00Z",
9
+ committerDate: "2024-08-12T00:00:00Z",
10
+ subject,
11
+ body: "",
12
+ parents: [],
13
+ files,
14
+ });
15
+ const result = (hash, score, files = []) => ({
16
+ commit: cmt(hash, "subj", "alice", files),
17
+ score,
18
+ matchedChunks: [],
19
+ });
20
+ describe("extractTopicWord", () => {
21
+ it("returns camelCase identifier when present", () => {
22
+ expect(extractTopicWord("why does parseAmount throw?")).toBe("parseAmount");
23
+ });
24
+ it("returns path-like token when present", () => {
25
+ expect(extractTopicWord("what about src/payment changes?")).toBe("src/payment");
26
+ });
27
+ it("returns the longest meaningful word otherwise", () => {
28
+ expect(extractTopicWord("why is authentication broken")).toBe("authentication");
29
+ });
30
+ it("filters stop-words and code-noise words", () => {
31
+ // 'the', 'how', 'does', 'use' filtered → longest remaining is "function"
32
+ expect(extractTopicWord("how does the function use this")).toBe("function");
33
+ });
34
+ it("returns undefined when nothing concrete remains", () => {
35
+ expect(extractTopicWord("why")).toBeUndefined();
36
+ expect(extractTopicWord("the and or")).toBeUndefined();
37
+ });
38
+ });
39
+ describe("suggestFollowUps", () => {
40
+ it("returns no suggestions when results are empty", () => {
41
+ expect(suggestFollowUps("anything", [])).toEqual([]);
42
+ });
43
+ it("suggests `mneme why <file>` when top result has files", () => {
44
+ const out = suggestFollowUps("why does X exist?", [result("a", 0.05, ["src/payment.ts"])]);
45
+ expect(out.find((s) => s.command.startsWith("mneme why"))).toBeDefined();
46
+ });
47
+ it("suggests `mneme story <topic>` when topic word is extractable", () => {
48
+ const out = suggestFollowUps("how does authentication work?", [result("a", 0.05)]);
49
+ expect(out.find((s) => s.command.startsWith("mneme story"))).toBeDefined();
50
+ });
51
+ it("suggests `mneme who-knows <topic>` with the topic", () => {
52
+ const out = suggestFollowUps("authentication question", [result("a", 0.05)]);
53
+ expect(out.find((s) => s.command.startsWith("mneme who-knows"))).toBeDefined();
54
+ });
55
+ it("suggests `mneme blast <commit>` when there are 2+ results", () => {
56
+ const out = suggestFollowUps("auth", [result("abc1234", 0.05), result("def5678", 0.04)]);
57
+ expect(out.find((s) => s.command.includes("blast"))).toBeDefined();
58
+ });
59
+ it("caps suggestions at 3", () => {
60
+ const out = suggestFollowUps("authentication question", [result("abc1234", 0.05, ["src/auth.ts"]), result("def5678", 0.04)]);
61
+ expect(out.length).toBeLessThanOrEqual(3);
62
+ });
63
+ it("every suggestion has a non-empty command and reason", () => {
64
+ const out = suggestFollowUps("stripe webhook bigint", [result("a", 0.05, ["src/webhook.ts"]), result("b", 0.04)]);
65
+ for (const s of out) {
66
+ expect(s.command.length).toBeGreaterThan(5);
67
+ expect(s.reason.length).toBeGreaterThan(10);
68
+ }
69
+ });
70
+ });
71
+ //# sourceMappingURL=suggest.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest.test.js","sourceRoot":"","sources":["../../src/insights/suggest.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `who-knows` — surface the people most likely to answer a question about
3
+ * a topic, ranked by their git activity on commits/files matching that topic.
4
+ *
5
+ * Why this exists: every team has tribal knowledge. "Ask alice about Stripe"
6
+ * is institutional memory. Mneme already indexes commits — making the
7
+ * institutional memory queryable is a small step from the engine the project
8
+ * already has.
9
+ *
10
+ * Scoring is intentionally simple and explainable. We avoid any "rank by
11
+ * cosine similarity of author embedding" trickery: this is a counting
12
+ * problem, and the user should be able to verify by running `git log
13
+ * --author=alice -- <files>`.
14
+ */
15
+ import type { MnemeStore } from "../store/sqlite.js";
16
+ export interface ExpertCandidate {
17
+ /** Author name as it appears in git. */
18
+ name: string;
19
+ email: string;
20
+ /** Number of commits authored that match the topic. */
21
+ commitCount: number;
22
+ /** Last commit author-date in ISO; lets the caller flag stale experts. */
23
+ lastTouch: string;
24
+ /** Distinct files touched (capped at 1000 for memory hygiene). */
25
+ filesTouched: number;
26
+ /** Score: log(commits + 1) × recency bonus. Higher = more authoritative. */
27
+ score: number;
28
+ /** Tier label derived from score + recency. */
29
+ tier: "definitive" | "active" | "stale" | "occasional";
30
+ }
31
+ export interface WhoKnowsOptions {
32
+ topic: string;
33
+ /** Cap on candidates returned (default 5). */
34
+ topN?: number;
35
+ /** ISO date — ignore commits older than this for the recency bonus. */
36
+ since?: string;
37
+ /** Today, in ISO format. Lets tests inject a deterministic clock. */
38
+ now?: Date;
39
+ }
40
+ /**
41
+ * Find people likely to know about `topic`. Searches commit messages,
42
+ * subjects, bodies, and PR titles via FTS, then aggregates by author.
43
+ */
44
+ export declare function whoKnows(store: MnemeStore, opts: WhoKnowsOptions): ExpertCandidate[];
45
+ /** log-based commit weight × recency multiplier. Pure function, easy to test. */
46
+ export declare function scoreCandidate(commitCount: number, lastTouchIso: string, now: Date): number;
47
+ export declare function tierOf(commitCount: number, lastTouchIso: string, now: Date): ExpertCandidate["tier"];
48
+ //# sourceMappingURL=who-knows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"who-knows.d.ts","sourceRoot":"","sources":["../../src/insights/who-knows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,IAAI,EAAE,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAY,CAAC;CACxD;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAKD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,eAAe,GAAG,eAAe,EAAE,CA6DpF;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,CAQ3F;AAED,wBAAgB,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,eAAe,CAAC,MAAM,CAAC,CAOpG"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `who-knows` — surface the people most likely to answer a question about
3
+ * a topic, ranked by their git activity on commits/files matching that topic.
4
+ *
5
+ * Why this exists: every team has tribal knowledge. "Ask alice about Stripe"
6
+ * is institutional memory. Mneme already indexes commits — making the
7
+ * institutional memory queryable is a small step from the engine the project
8
+ * already has.
9
+ *
10
+ * Scoring is intentionally simple and explainable. We avoid any "rank by
11
+ * cosine similarity of author embedding" trickery: this is a counting
12
+ * problem, and the user should be able to verify by running `git log
13
+ * --author=alice -- <files>`.
14
+ */
15
+ const STALE_DAYS = 180;
16
+ const ACTIVE_DAYS = 90;
17
+ /**
18
+ * Find people likely to know about `topic`. Searches commit messages,
19
+ * subjects, bodies, and PR titles via FTS, then aggregates by author.
20
+ */
21
+ export function whoKnows(store, opts) {
22
+ const topN = opts.topN ?? 5;
23
+ const now = opts.now ?? new Date();
24
+ // Pull commits whose chunks match the topic via FTS.
25
+ const ftsHits = store.ftsSearch(opts.topic, 200);
26
+ if (ftsHits.length === 0)
27
+ return [];
28
+ // Aggregate by commit hash (one chunk per commit can appear multiple times).
29
+ const commitHashes = new Set(ftsHits.map((h) => h.commitHash));
30
+ const buckets = new Map();
31
+ for (const hash of commitHashes) {
32
+ const c = store.getCommit(hash);
33
+ if (!c)
34
+ continue;
35
+ if (opts.since && c.authorDate < opts.since)
36
+ continue;
37
+ const key = `${c.authorName}|${c.authorEmail}`;
38
+ if (!buckets.has(key)) {
39
+ buckets.set(key, {
40
+ name: c.authorName,
41
+ email: c.authorEmail,
42
+ commitCount: 0,
43
+ lastTouch: c.authorDate,
44
+ files: new Set(),
45
+ });
46
+ }
47
+ const b = buckets.get(key);
48
+ b.commitCount += 1;
49
+ if (c.authorDate > b.lastTouch)
50
+ b.lastTouch = c.authorDate;
51
+ for (const f of (c.files ?? []).slice(0, 50))
52
+ b.files.add(f);
53
+ if (b.files.size > 1000) {
54
+ // Hygiene cap — extremely active authors on huge repos.
55
+ const arr = [...b.files];
56
+ b.files = new Set(arr.slice(0, 1000));
57
+ }
58
+ }
59
+ const candidates = [];
60
+ for (const b of buckets.values()) {
61
+ const score = scoreCandidate(b.commitCount, b.lastTouch, now);
62
+ candidates.push({
63
+ name: b.name,
64
+ email: b.email,
65
+ commitCount: b.commitCount,
66
+ lastTouch: b.lastTouch,
67
+ filesTouched: b.files.size,
68
+ score,
69
+ tier: tierOf(b.commitCount, b.lastTouch, now),
70
+ });
71
+ }
72
+ candidates.sort((a, b) => b.score - a.score);
73
+ return candidates.slice(0, topN);
74
+ }
75
+ /** log-based commit weight × recency multiplier. Pure function, easy to test. */
76
+ export function scoreCandidate(commitCount, lastTouchIso, now) {
77
+ const lastTouch = new Date(lastTouchIso);
78
+ const daysAgo = Math.max(0, (now.getTime() - lastTouch.getTime()) / (1000 * 60 * 60 * 24));
79
+ // Recency bonus: 1.0 within 30 days, decays linearly to 0.3 at 365 days, then floor 0.3.
80
+ const recency = daysAgo < 30 ? 1.0 : Math.max(0.3, 1.0 - (daysAgo - 30) / 480);
81
+ // Volume: log scale to avoid one mega-contributor dominating.
82
+ const volume = Math.log2(commitCount + 1);
83
+ return volume * recency;
84
+ }
85
+ export function tierOf(commitCount, lastTouchIso, now) {
86
+ const lastTouch = new Date(lastTouchIso);
87
+ const daysAgo = (now.getTime() - lastTouch.getTime()) / (1000 * 60 * 60 * 24);
88
+ if (commitCount >= 10 && daysAgo <= ACTIVE_DAYS)
89
+ return "definitive";
90
+ if (commitCount >= 3 && daysAgo <= ACTIVE_DAYS)
91
+ return "active";
92
+ if (daysAgo > STALE_DAYS)
93
+ return "stale";
94
+ return "occasional";
95
+ }
96
+ //# sourceMappingURL=who-knows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"who-knows.js","sourceRoot":"","sources":["../../src/insights/who-knows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=who-knows.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"who-knows.test.d.ts","sourceRoot":"","sources":["../../src/insights/who-knows.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { scoreCandidate, tierOf } from "./who-knows.js";
3
+ const today = new Date("2026-05-04T00:00:00Z");
4
+ const daysAgo = (n) => new Date(today.getTime() - n * 86_400_000).toISOString();
5
+ describe("scoreCandidate — pure scoring function", () => {
6
+ it("returns 0 for zero commits", () => {
7
+ expect(scoreCandidate(0, daysAgo(0), today)).toBe(0);
8
+ });
9
+ it("recent commits score higher than old commits with the same count", () => {
10
+ const recent = scoreCandidate(10, daysAgo(5), today);
11
+ const old = scoreCandidate(10, daysAgo(300), today);
12
+ expect(recent).toBeGreaterThan(old);
13
+ });
14
+ it("more commits score higher than fewer with the same recency", () => {
15
+ const more = scoreCandidate(50, daysAgo(10), today);
16
+ const fewer = scoreCandidate(5, daysAgo(10), today);
17
+ expect(more).toBeGreaterThan(fewer);
18
+ });
19
+ it("uses log-scale volume — 100 commits is not 100× more than 1 commit", () => {
20
+ const one = scoreCandidate(1, daysAgo(0), today);
21
+ const hundred = scoreCandidate(100, daysAgo(0), today);
22
+ // log2(101) ≈ 6.66, log2(2) = 1 → hundred / one ≈ 6.66, NOT 100.
23
+ expect(hundred / one).toBeLessThan(10);
24
+ });
25
+ it("recency floors at 0.3 even for very old commits", () => {
26
+ const ancient = scoreCandidate(10, daysAgo(2000), today);
27
+ const oneYear = scoreCandidate(10, daysAgo(365), today);
28
+ // Both should be at or near the floor.
29
+ expect(ancient).toBeGreaterThan(0);
30
+ expect(oneYear).toBeGreaterThan(0);
31
+ });
32
+ });
33
+ describe("tierOf — readable expert tier", () => {
34
+ it('"definitive" for many recent commits', () => {
35
+ expect(tierOf(20, daysAgo(10), today)).toBe("definitive");
36
+ });
37
+ it('"active" for moderate recent commits', () => {
38
+ expect(tierOf(5, daysAgo(20), today)).toBe("active");
39
+ });
40
+ it('"stale" for any volume but very old', () => {
41
+ expect(tierOf(100, daysAgo(300), today)).toBe("stale");
42
+ });
43
+ it('"occasional" — middle ground (≥1 commit, < ACTIVE_DAYS old, but few)', () => {
44
+ expect(tierOf(1, daysAgo(40), today)).toBe("occasional");
45
+ });
46
+ });
47
+ //# sourceMappingURL=who-knows.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"who-knows.test.js","sourceRoot":"","sources":["../../src/insights/who-knows.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
1
  export * from "./search.js";
2
2
  export * from "./rerank.js";
3
+ export * from "./intent.js";
4
+ export * from "./synthesize.js";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retrieve/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retrieve/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
@@ -1,3 +1,5 @@
1
1
  export * from "./search.js";
2
2
  export * from "./rerank.js";
3
+ export * from "./intent.js";
4
+ export * from "./synthesize.js";
3
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retrieve/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retrieve/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Query intent classifier.
3
+ *
4
+ * Mneme is designed for SPECIFIC questions ("why does X exist?", "when did Y
5
+ * change?"). It's not a code-review oracle and not a general-purpose chatbot.
6
+ * When users ask vague questions ("how to improve my code", "what's wrong"),
7
+ * the right move is to redirect them BEFORE retrieval — pretending to answer
8
+ * with low-confidence results is dishonest.
9
+ *
10
+ * Classification is regex-based on purpose:
11
+ * - Deterministic, runs in microseconds, no LLM needed
12
+ * - User-overridable: flags exist for "trust me, just retrieve"
13
+ * - Falsifiable: every signal is a documented heuristic, not a black box
14
+ */
15
+ export type Intent = "specific" | "lookup" | "temporal" | "vague";
16
+ export interface IntentResult {
17
+ intent: Intent;
18
+ /** Why this classification was chosen — for transparency / debugging. */
19
+ reason: string;
20
+ /**
21
+ * If intent === "vague", a redirect message to show the user instead of
22
+ * running retrieval. Empty for other intents.
23
+ */
24
+ redirect?: string;
25
+ }
26
+ /**
27
+ * Classify a query's intent. Order matters: lookup and temporal patterns are
28
+ * checked BEFORE vague patterns, so "who wrote how-to-do-x.md" is classified
29
+ * as a lookup, not a vague query.
30
+ */
31
+ export declare function classifyIntent(query: string): IntentResult;
32
+ //# sourceMappingURL=intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../src/retrieve/intent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,MAAM,GACd,UAAU,GACV,QAAQ,GACR,UAAU,GACV,OAAO,CAAC;AAEZ,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAoDD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAwC1D"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Query intent classifier.
3
+ *
4
+ * Mneme is designed for SPECIFIC questions ("why does X exist?", "when did Y
5
+ * change?"). It's not a code-review oracle and not a general-purpose chatbot.
6
+ * When users ask vague questions ("how to improve my code", "what's wrong"),
7
+ * the right move is to redirect them BEFORE retrieval — pretending to answer
8
+ * with low-confidence results is dishonest.
9
+ *
10
+ * Classification is regex-based on purpose:
11
+ * - Deterministic, runs in microseconds, no LLM needed
12
+ * - User-overridable: flags exist for "trust me, just retrieve"
13
+ * - Falsifiable: every signal is a documented heuristic, not a black box
14
+ */
15
+ /** Trigger phrases for vague queries. Listed by frequency in real misuse. */
16
+ const VAGUE_PATTERNS = [
17
+ { re: /\bhow\s+to\b/i, reason: '"how to" — Mneme indexes history, not best-practices' },
18
+ { re: /\b(best|good)\s+(practice|practices|way)s?\b/i, reason: 'asks for best practice — not a history question' },
19
+ { re: /\b(improve|optimize|refactor|review)\s+(my|the|this)\s+code\b/i, reason: 'general code-improvement request' },
20
+ { re: /\bshould\s+i\b/i, reason: '"should I" — opinion, not memory' },
21
+ { re: /\bcan\s+you\s+(write|generate|create)\b/i, reason: 'asks Mneme to generate code — out of scope' },
22
+ { re: /^(help|hello|hi|test)\s*$/i, reason: 'greeting / smoke-test, not a real question' },
23
+ { re: /\bwhat\s+is\s+(the\s+)?(meaning|point)\s+of\s+(life|this)\b/i, reason: 'philosophical' },
24
+ ];
25
+ /** Lookup: question is about a specific person, hash, or PR number. */
26
+ const LOOKUP_PATTERNS = [
27
+ { re: /\bwho\s+(wrote|made|created|authored|added)\b/i, reason: 'who-question — author lookup' },
28
+ { re: /\bwhat\s+did\s+\S+\s+(do|change|commit|write)\b/i, reason: 'asks what a person did — author lookup' },
29
+ { re: /\bPR\s*#?\d+\b/i, reason: 'specific PR reference' },
30
+ { re: /\b[a-f0-9]{7,40}\b/, reason: 'commit hash reference' },
31
+ ];
32
+ /** Temporal: question asks about time, dates, or change-over-time. */
33
+ const TEMPORAL_PATTERNS = [
34
+ { re: /\bwhen\s+(did|was|will)\b/i, reason: 'when-question — timeline query' },
35
+ { re: /\b(last|recent|latest|previous|first|earliest)\s+(commit|change|update|time)\b/i, reason: 'temporal modifier' },
36
+ { re: /\b(yesterday|today|last\s+(week|month|year|quarter))\b/i, reason: 'relative time reference' },
37
+ { re: /\b(20\d{2})-(0[1-9]|1[0-2])\b/, reason: 'absolute date reference (YYYY-MM)' },
38
+ ];
39
+ /** Specific: WHY/WHAT/HOW questions about a concrete thing. */
40
+ const SPECIFIC_PATTERNS = [
41
+ { re: /\bwhy\s+(does|did|do|is|are|was|were)\b/i, reason: 'why-question with concrete subject' },
42
+ { re: /\bwhat\s+(does|did|do|is|are)\b/i, reason: 'what-question (concrete)' },
43
+ { re: /\bhow\s+(does|did|do|is|are)\b/i, reason: 'how-question (about behavior, not advice)' },
44
+ ];
45
+ /** Words that, if PRESENT, suggest concreteness even for "how to" patterns. */
46
+ const CONCRETE_HINTS = /(\.[a-z]+\b|::|\bsrc\/|\b[A-Z][a-zA-Z]{2,}[A-Z][a-z]|\bfunction\s+\w+|\bclass\s+\w+|\bmodule\s+\w+|\bPR\s*#?\d+)/;
47
+ const VAGUE_REDIRECT_TEMPLATE = [
48
+ 'I work best with specific questions about your repo\'s history.',
49
+ 'Try one of these instead:',
50
+ '',
51
+ ' • "why does <function or pattern> exist?"',
52
+ ' • "when did we change <module>?"',
53
+ ' • "who wrote <file>?"',
54
+ ' • "what does <commit hash> do?"',
55
+ '',
56
+ 'For general code improvement advice, use a chat assistant — Mneme is the',
57
+ 'memory layer that feeds those tools, not a substitute for them.',
58
+ ].join('\n');
59
+ /**
60
+ * Classify a query's intent. Order matters: lookup and temporal patterns are
61
+ * checked BEFORE vague patterns, so "who wrote how-to-do-x.md" is classified
62
+ * as a lookup, not a vague query.
63
+ */
64
+ export function classifyIntent(query) {
65
+ const q = query.trim();
66
+ if (!q) {
67
+ return { intent: "vague", reason: "empty query", redirect: VAGUE_REDIRECT_TEMPLATE };
68
+ }
69
+ // 1. Lookup patterns win — questions about a specific person/hash/PR are
70
+ // always specific enough to retrieve.
71
+ for (const p of LOOKUP_PATTERNS) {
72
+ if (p.re.test(q))
73
+ return { intent: "lookup", reason: p.reason };
74
+ }
75
+ // 2. Temporal patterns — when-questions and date references.
76
+ for (const p of TEMPORAL_PATTERNS) {
77
+ if (p.re.test(q))
78
+ return { intent: "temporal", reason: p.reason };
79
+ }
80
+ // 3. Vague patterns — but only fire if the query has NO concrete hint.
81
+ for (const p of VAGUE_PATTERNS) {
82
+ if (p.re.test(q) && !CONCRETE_HINTS.test(q)) {
83
+ return { intent: "vague", reason: p.reason, redirect: VAGUE_REDIRECT_TEMPLATE };
84
+ }
85
+ }
86
+ // 4. Specific question patterns.
87
+ for (const p of SPECIFIC_PATTERNS) {
88
+ if (p.re.test(q))
89
+ return { intent: "specific", reason: p.reason };
90
+ }
91
+ // 5. Default: short queries (< 4 words) treated as keyword search → specific;
92
+ // long unstructured queries → vague.
93
+ const wordCount = q.split(/\s+/).length;
94
+ if (wordCount <= 6)
95
+ return { intent: "specific", reason: "short keyword-style query" };
96
+ if (CONCRETE_HINTS.test(q))
97
+ return { intent: "specific", reason: "contains concrete identifier (file, class, PR)" };
98
+ return {
99
+ intent: "vague",
100
+ reason: "long query with no concrete identifier or specific question pattern",
101
+ redirect: VAGUE_REDIRECT_TEMPLATE,
102
+ };
103
+ }
104
+ //# sourceMappingURL=intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.js","sourceRoot":"","sources":["../../src/retrieve/intent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=intent.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.test.d.ts","sourceRoot":"","sources":["../../src/retrieve/intent.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,106 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { classifyIntent } from "./intent.js";
3
+ describe("classifyIntent — vague queries (the regression we are fixing)", () => {
4
+ it('classifies "how to improve my code" as vague', () => {
5
+ const r = classifyIntent("how to improve my code");
6
+ expect(r.intent).toBe("vague");
7
+ expect(r.redirect).toBeDefined();
8
+ expect(r.redirect).toContain("specific");
9
+ });
10
+ it('classifies "best practice for X" as vague when X is generic', () => {
11
+ expect(classifyIntent("best practice for naming things").intent).toBe("vague");
12
+ expect(classifyIntent("what is a good way to write tests").intent).toBe("vague");
13
+ });
14
+ it('classifies "should I X" as vague (opinion request)', () => {
15
+ expect(classifyIntent("should I refactor this module").intent).toBe("vague");
16
+ });
17
+ it("classifies greetings and smoke tests as vague", () => {
18
+ expect(classifyIntent("hello").intent).toBe("vague");
19
+ expect(classifyIntent("test").intent).toBe("vague");
20
+ expect(classifyIntent("help").intent).toBe("vague");
21
+ });
22
+ it("classifies empty string as vague", () => {
23
+ expect(classifyIntent("").intent).toBe("vague");
24
+ expect(classifyIntent(" ").intent).toBe("vague");
25
+ });
26
+ it("vague results include a redirect message that mentions specific question forms", () => {
27
+ const r = classifyIntent("how to write good code");
28
+ expect(r.redirect).toMatch(/why does/);
29
+ expect(r.redirect).toMatch(/when did/);
30
+ expect(r.redirect).toMatch(/who wrote/);
31
+ });
32
+ });
33
+ describe("classifyIntent — specific (the happy path)", () => {
34
+ it('classifies "why does X exist?" as specific', () => {
35
+ expect(classifyIntent("why does parseAmount use try/catch?").intent).toBe("specific");
36
+ });
37
+ it('classifies "what does X do?" as specific', () => {
38
+ expect(classifyIntent("what does the OrderQueue worker do?").intent).toBe("specific");
39
+ });
40
+ it('classifies "how does X work?" as specific (behavior question)', () => {
41
+ expect(classifyIntent("how does the auth middleware work?").intent).toBe("specific");
42
+ });
43
+ it("classifies short keyword queries as specific", () => {
44
+ expect(classifyIntent("stripe webhook bigint").intent).toBe("specific");
45
+ expect(classifyIntent("auth jwt").intent).toBe("specific");
46
+ });
47
+ });
48
+ describe("classifyIntent — lookup (author / hash / PR queries)", () => {
49
+ it('classifies "who wrote X" as lookup', () => {
50
+ expect(classifyIntent("who wrote the OrderQueue worker?").intent).toBe("lookup");
51
+ expect(classifyIntent("who created src/payment.ts").intent).toBe("lookup");
52
+ });
53
+ it("classifies PR-number references as lookup", () => {
54
+ expect(classifyIntent("PR #482").intent).toBe("lookup");
55
+ expect(classifyIntent("explain pr #1234").intent).toBe("lookup");
56
+ });
57
+ it("classifies commit-hash references as lookup", () => {
58
+ expect(classifyIntent("a1b2c3d4 what did this do").intent).toBe("lookup");
59
+ expect(classifyIntent("abcdef1 changes").intent).toBe("lookup");
60
+ });
61
+ });
62
+ describe("classifyIntent — temporal (timeline / date queries)", () => {
63
+ it('classifies "when did X" as temporal', () => {
64
+ expect(classifyIntent("when did we change the auth middleware?").intent).toBe("temporal");
65
+ });
66
+ it('classifies relative-time queries as temporal', () => {
67
+ expect(classifyIntent("what changed last week").intent).toBe("temporal");
68
+ expect(classifyIntent("commits from last month on payment").intent).toBe("temporal");
69
+ });
70
+ it('classifies absolute YYYY-MM dates as temporal', () => {
71
+ expect(classifyIntent("auth changes in 2024-09").intent).toBe("temporal");
72
+ });
73
+ it("classifies superlative-time queries as temporal", () => {
74
+ expect(classifyIntent("latest commit on stripe").intent).toBe("temporal");
75
+ expect(classifyIntent("first time we used JWT").intent).toBe("temporal");
76
+ });
77
+ });
78
+ describe("classifyIntent — concrete-hint override (vague pattern + concrete identifier)", () => {
79
+ it('"how to refactor src/payment.ts" is specific despite "how to" (has file path)', () => {
80
+ expect(classifyIntent("how to refactor src/payment.ts").intent).toBe("specific");
81
+ });
82
+ it("'best way to call PaymentService' is specific (CamelCase identifier)", () => {
83
+ expect(classifyIntent("best way to call PaymentService").intent).toBe("specific");
84
+ });
85
+ it("'how to use PR #482' is lookup (PR reference takes priority)", () => {
86
+ expect(classifyIntent("how to use PR #482").intent).toBe("lookup");
87
+ });
88
+ });
89
+ describe("classifyIntent — every result has a non-empty reason for transparency", () => {
90
+ it("returns a reason string for every intent class", () => {
91
+ const samples = [
92
+ "how to improve",
93
+ "why does X exist",
94
+ "who wrote Y",
95
+ "when did Z change",
96
+ "stripe bigint webhook",
97
+ "",
98
+ ];
99
+ for (const q of samples) {
100
+ const r = classifyIntent(q);
101
+ expect(typeof r.reason).toBe("string");
102
+ expect(r.reason.length).toBeGreaterThan(5);
103
+ }
104
+ });
105
+ });
106
+ //# sourceMappingURL=intent.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.test.js","sourceRoot":"","sources":["../../src/retrieve/intent.test.ts"],"names":[],"mappings":"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"}