@danielmarbach/mnemonic-mcp 0.21.0 → 0.23.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ All notable changes to `mnemonic` will be documented in this file.
4
4
 
5
5
  The format is loosely based on Keep a Changelog and uses semver-style version headings.
6
6
 
7
+ ## [0.23.0] - 2026-04-17
8
+
9
+ ### Added
10
+
11
+ - Lexical rescue now ranks candidates by TF-IDF similarity, improving recall for identifier-heavy and jargon queries without affecting semantic ranking.
12
+ - Recall now boosts notes that explain key decisions and concepts when you ask "why"-style questions, using structural signals like role, connections, and format rather than keyword matching.
13
+ - `run-dogfood-packs.mjs --isolated` copies notes into a temporary workspace for reproducible validation runs without polluting the live vault.
14
+ - Rescue candidates no longer appear when `minSimilarity` is set above the default, so explicit quality filters are respected.
15
+
16
+ ### Changed
17
+
18
+ - Decision and overview notes now surface more reliably for questions like "why are embeddings gitignored" instead of being outranked by incidental mentions.
19
+ - Lexical rescue now correctly activates when no semantic results are found at all.
20
+
21
+ ## [0.22.0] - 2026-04-13
22
+
23
+ ### Added
24
+
25
+ - Session-aware relationship carry-forward: when a new memory clearly continues durable context inspected earlier in the same MCP session, `remember` and `update` can now attach bounded `related-to` links automatically without relying on repo-specific rules or English-only cues.
26
+
27
+ ### Changed
28
+
29
+ - Active session cache now preserves recently inspected note context across unrelated mutations, so follow-up writes can still reuse durable session context after intermediate note changes.
30
+ - Hybrid recall reranking now adds rare-token coverage and contiguous significant-token phrase signals on top of the existing semantic-plus-lexical scoring, improving generic rationale-style query handling without changing storage or requiring project-specific heuristics.
31
+
7
32
  ## [0.21.0] - 2026-04-05
8
33
 
9
34
  ### Added
package/README.md CHANGED
@@ -17,7 +17,7 @@ For the high-level system map, see [`ARCHITECTURE.md`](ARCHITECTURE.md). For rel
17
17
 
18
18
  ## Stability
19
19
 
20
- mnemonic is at the inception stage. The storage format (frontmatter schema, vault layout, config structure) is still stabilizing and **may change in breaking ways** between releases. Migrations are provided when possible, but treat your vault as something you can afford to rebuild or re-migrate during this period. Keep an eye on the changelog; mnemonic surfaces pending migrations at startup and `list_migrations` shows pending work per vault after each update.
20
+ The storage format is stable with migration support for any future changes. Keep an eye on the changelog; `list_migrations` shows pending work per vault after each update.
21
21
 
22
22
  **Scale:** Designed for simplicity and portability — not large-scale knowledge bases.
23
23
 
@@ -45,7 +45,7 @@ No code changes required — set `EMBED_MODEL=qwen3-embedding:0.6b` in your envi
45
45
 
46
46
  ## Setup
47
47
 
48
- ### Native (Node.js 18+)
48
+ ### Native (Node.js 20+)
49
49
 
50
50
  ```bash
51
51
  npm install
@@ -63,6 +63,8 @@ npm run mcp:local
63
63
 
64
64
  This rebuilds first, then launches `build/index.js`, so MCP clients always point at the latest source.
65
65
 
66
+ For reproducible dogfooding of recency and relationship-navigation behavior, prefer the isolated dogfood runner over the live project vault. The isolated runner copies the current `.mnemonic` notes into a temporary workspace, runs the chosen pack there, and deletes the workspace afterward.
67
+
66
68
  ### Docker
67
69
 
68
70
  ```bash
@@ -308,7 +310,7 @@ Project identity derives from the **git remote URL**, normalized to a stable slu
308
310
 
309
311
  `recall` with `cwd` searches both vaults. Project notes get a **+0.15 similarity boost** — a soft signal, not a hard filter — so global memories remain accessible while project context floats to the top.
310
312
 
311
- **Hybrid recall** enhances semantic search with lightweight lexical reranking over note projections. When semantic results are weak, a bounded lexical rescue path scans projections for additional candidates, improving exact-match and partial-query recall without changing the storage model or adding new infrastructure. Lexical scores act as tiebreakers they cannot overcome a large semantic gap but can reorder close candidates.
313
+ **Hybrid recall** enhances semantic search with lightweight lexical reranking over note projections. When semantic results are weak, a bounded lexical rescue path scans projections for additional candidates, improving exact-match and identifier-heavy recall without changing the storage model or adding new infrastructure. **Canonical explanation promotion** boosts notes that explain key decisions and concepts for "why"-style questions, using structural signals like role, connections, and format rather than keyword matching.
312
314
 
313
315
  Temporal recall is opt-in via `mode: "temporal"`. It keeps semantic selection first, then enriches only the top matches with compact git-backed history so agents can inspect how a note evolved without turning recall into raw log or diff output.
314
316
 
@@ -572,6 +574,14 @@ This keeps early ideation reusable as personal/global knowledge while moving con
572
574
 
573
575
  mnemonic and Beads address complementary concerns. mnemonic is a **knowledge graph**: it stores notes, relationships between them, and lets agents retrieve relevant context through semantic search. [Beads](https://github.com/steveyegge/beads) is a **task and dependency tracker**: it models work items and their dependencies so agents can determine what is ready to execute next. Both tools can coexist in the same workflow — mnemonic stores knowledge and reasoning while Beads manages execution.
574
576
 
577
+ **How does mnemonic differ from Memory Bank MCP?**
578
+
579
+ mnemonic and Memory Bank MCP both provide persistent memory for agents, but differ in hosting and scope. Memory Bank MCP is a **centralized service** — your memory lives in a remote MCP service and is accessed across projects through that single endpoint. mnemonic is **local-first** — your memories live as plain markdown files on your machine: project-scoped notes in `.mnemonic/` within each repo, and personal notes in a global vault under your home directory. There is no always-on server to configure or depend on; the MCP server spawns on demand per session.
580
+
581
+ **How does mnemonic differ from Basic Memory?**
582
+
583
+ Both tools are local-first and use markdown, but with different scoping models. [Basic Memory](https://github.com/basicmachines/basicmemory) maintains a **knowledge base per project** that agents can search and update, with optional cloud sync. mnemonic splits memory into **two distinct vaults**: a global personal vault (`~/mnemonic-vault/`) for cross-project knowledge, and a project-scoped vault (`.mnemonic/`) that travels with the repo and is shared via git. This lets you capture early ideas globally before a repo exists, then migrate only project-relevant notes into the shared vault once collaboration begins.
584
+
575
585
  **What are temporary notes?**
576
586
 
577
587
  mnemonic distinguishes between two lifecycle states. `temporary` notes capture evolving working-state: hypotheses, in-progress plans, experiment results, draft reasoning. `permanent` notes capture durable knowledge: decisions, root cause explanations, architectural guidance, lessons learned. As an investigation progresses, a cluster of temporary notes is typically `consolidate`d into one or more permanent notes, and the scaffolding is discarded. This two-phase lifecycle keeps exploratory thinking from polluting long-term memory while still giving agents a place to reason incrementally before committing to a conclusion.
@@ -0,0 +1,9 @@
1
+ import type { Note, Relationship } from "./storage.js";
2
+ export interface SessionAccessCandidate {
3
+ note: Note;
4
+ accessedAt: string;
5
+ accessKind: "get" | "recall" | "summary";
6
+ score?: number;
7
+ }
8
+ export declare function suggestAutoRelationships(source: Note, candidates: SessionAccessCandidate[]): Relationship[];
9
+ //# sourceMappingURL=auto-relate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-relate.d.ts","sourceRoot":"","sources":["../src/auto-relate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAkDD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,IAAI,EACZ,UAAU,EAAE,sBAAsB,EAAE,GACnC,YAAY,EAAE,CAgChB"}
@@ -0,0 +1,73 @@
1
+ import { buildProjection, extractProjectionSummary } from "./projections.js";
2
+ import { computeLexicalScore, normalizeText, tokenize } from "./lexical.js";
3
+ const MIN_TITLE_MENTION_LENGTH = 12;
4
+ const MAX_AUTO_RELATIONSHIPS = 2;
5
+ const STRONG_OVERLAP_THRESHOLD = 0.42;
6
+ function hasExplicitTitleMention(source, candidate) {
7
+ const normalizedTitle = normalizeText(candidate.title);
8
+ if (normalizedTitle.length < MIN_TITLE_MENTION_LENGTH) {
9
+ return false;
10
+ }
11
+ const haystack = normalizeText(`${source.title}\n${source.content}`);
12
+ return haystack.includes(normalizedTitle);
13
+ }
14
+ function computeSharedTagScore(source, candidate) {
15
+ if (source.tags.length === 0 || candidate.tags.length === 0) {
16
+ return 0;
17
+ }
18
+ const sourceTags = new Set(source.tags.map((tag) => normalizeText(tag)));
19
+ const candidateTags = new Set(candidate.tags.map((tag) => normalizeText(tag)));
20
+ let shared = 0;
21
+ for (const tag of sourceTags) {
22
+ if (candidateTags.has(tag)) {
23
+ shared++;
24
+ }
25
+ }
26
+ return Math.min(shared, 3) * 0.08;
27
+ }
28
+ function computeTitleTokenOverlap(source, candidate) {
29
+ const sourceTokens = new Set(tokenize(source.title));
30
+ const candidateTokens = new Set(tokenize(candidate.title));
31
+ if (sourceTokens.size === 0 || candidateTokens.size === 0) {
32
+ return 0;
33
+ }
34
+ let shared = 0;
35
+ for (const token of sourceTokens) {
36
+ if (candidateTokens.has(token)) {
37
+ shared++;
38
+ }
39
+ }
40
+ return shared / Math.max(sourceTokens.size, candidateTokens.size);
41
+ }
42
+ export function suggestAutoRelationships(source, candidates) {
43
+ const existingIds = new Set((source.relatedTo ?? []).map((rel) => rel.id));
44
+ const ranked = candidates
45
+ .filter((candidate) => candidate.note.id !== source.id)
46
+ .filter((candidate) => candidate.note.lifecycle === "permanent")
47
+ .filter((candidate) => candidate.note.project === source.project)
48
+ .filter((candidate) => !existingIds.has(candidate.note.id))
49
+ .map((candidate) => {
50
+ const explicitMention = hasExplicitTitleMention(source, candidate.note);
51
+ const sourceSummary = extractProjectionSummary(source);
52
+ const candidateProjection = buildProjection(candidate.note).projectionText;
53
+ const lexical = computeLexicalScore(sourceSummary || source.title, candidateProjection);
54
+ const titleOverlap = computeTitleTokenOverlap(source, candidate.note);
55
+ const sharedTags = computeSharedTagScore(source, candidate.note);
56
+ const recencyBoost = candidate.score ? Math.min(candidate.score, 1) * 0.05 : 0;
57
+ const score = (explicitMention ? 1 : 0) +
58
+ lexical * 0.45 +
59
+ titleOverlap * 0.2 +
60
+ sharedTags +
61
+ recencyBoost;
62
+ return { candidate, explicitMention, lexical, score };
63
+ })
64
+ .filter(({ explicitMention, lexical, score }) => explicitMention || (lexical >= STRONG_OVERLAP_THRESHOLD && score >= 0.32))
65
+ .sort((left, right) => {
66
+ if (right.score !== left.score)
67
+ return right.score - left.score;
68
+ return right.candidate.accessedAt.localeCompare(left.candidate.accessedAt);
69
+ })
70
+ .slice(0, MAX_AUTO_RELATIONSHIPS);
71
+ return ranked.map(({ candidate }) => ({ id: candidate.note.id, type: "related-to" }));
72
+ }
73
+ //# sourceMappingURL=auto-relate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-relate.js","sourceRoot":"","sources":["../src/auto-relate.ts"],"names":[],"mappings":"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"}
package/build/cache.d.ts CHANGED
@@ -6,12 +6,23 @@ interface VaultCache {
6
6
  noteList: Note[];
7
7
  embeddings: EmbeddingRecord[];
8
8
  }
9
+ interface SessionAccessRecord {
10
+ noteId: string;
11
+ vaultPath: string;
12
+ accessedAt: string;
13
+ accessKind: "get" | "recall" | "summary";
14
+ score?: number;
15
+ }
9
16
  export interface SessionProjectCache {
10
17
  projectId: string;
11
18
  /** Per-vault caches keyed by vaultPath. Built lazily per vault on first access. */
12
19
  vaultCaches: Map<string, VaultCache>;
13
20
  /** Projection cache shared across all cached vaults for this project. */
14
21
  projectionsById: Map<string, NoteProjection>;
22
+ /** Recently inspected notes in the current MCP session. */
23
+ recentAccesses: SessionAccessRecord[];
24
+ /** Snapshots of recently inspected notes that survive cache invalidation. */
25
+ recentNotesByKey: Map<string, Note>;
15
26
  /** ISO timestamp of when this cache entry was first created. */
16
27
  lastBuiltAt: string;
17
28
  }
@@ -57,10 +68,20 @@ export declare function getSessionCachedNote(projectId: string, vaultPath: strin
57
68
  * Returns `undefined` when no cache or projection exists.
58
69
  */
59
70
  export declare function getSessionCachedProjection(projectId: string, noteId: string): NoteProjection | undefined;
71
+ export declare function setSessionCachedNote(projectId: string, vaultPath: string, note: Note): void;
72
+ export declare function getRecentSessionAccessNote(projectId: string, vaultPath: string, noteId: string): Note | undefined;
60
73
  /**
61
74
  * Store a projection in the session cache.
62
75
  * No-op when no active cache exists for this project.
63
76
  */
64
77
  export declare function setSessionCachedProjection(projectId: string, noteId: string, projection: NoteProjection): void;
78
+ export declare function recordSessionNoteAccess(projectId: string, vaultPath: string, noteId: string, accessKind: "get" | "recall" | "summary", score?: number): void;
79
+ export declare function getRecentSessionNoteAccesses(projectId: string): Array<{
80
+ noteId: string;
81
+ vaultPath: string;
82
+ accessedAt: string;
83
+ accessKind: "get" | "recall" | "summary";
84
+ score?: number;
85
+ }>;
65
86
  export {};
66
87
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAIxC,UAAU,UAAU;IAClB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC7B,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjB,UAAU,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,yEAAyE;IACzE,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC7C,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;CACrB;AAqCD;;;;;GAKG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CAKnD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAIxF;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,IAAI,EAAE,GAAG,SAAS,CAAC,CA6B7B;AAED;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,eAAe,EAAE,GAAG,SAAS,CAAC,CA6BxC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI,GAAG,SAAS,CAIlB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,cAAc,GAAG,SAAS,CAI5B;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,cAAc,GACzB,IAAI,CAIN"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
package/build/cache.js CHANGED
@@ -18,6 +18,8 @@ function ensureActiveProjectCache(projectId) {
18
18
  projectId,
19
19
  vaultCaches: new Map(),
20
20
  projectionsById: new Map(),
21
+ recentAccesses: [],
22
+ recentNotesByKey: new Map(),
21
23
  lastBuiltAt: new Date().toISOString(),
22
24
  };
23
25
  sessionCaches.activeProject = fresh;
@@ -33,7 +35,14 @@ function ensureActiveProjectCache(projectId) {
33
35
  export function invalidateActiveProjectCache() {
34
36
  if (sessionCaches.activeProject) {
35
37
  debugLog("cache:invalidate", `project=${sessionCaches.activeProject.projectId}`);
36
- sessionCaches.activeProject = undefined;
38
+ sessionCaches.activeProject = {
39
+ projectId: sessionCaches.activeProject.projectId,
40
+ vaultCaches: new Map(),
41
+ projectionsById: new Map(),
42
+ recentAccesses: sessionCaches.activeProject.recentAccesses,
43
+ recentNotesByKey: sessionCaches.activeProject.recentNotesByKey,
44
+ lastBuiltAt: new Date().toISOString(),
45
+ };
37
46
  }
38
47
  }
39
48
  /**
@@ -42,9 +51,12 @@ export function invalidateActiveProjectCache() {
42
51
  */
43
52
  export function getActiveProjectCache(projectId) {
44
53
  const cache = sessionCaches.activeProject;
45
- if (cache?.projectId === projectId)
46
- return cache;
47
- return undefined;
54
+ if (!cache || cache.projectId !== projectId)
55
+ return undefined;
56
+ if (cache.vaultCaches.size === 0 && cache.projectionsById.size === 0) {
57
+ return undefined;
58
+ }
59
+ return cache;
48
60
  }
49
61
  /**
50
62
  * Get the full note list for a vault from the session cache, building it lazily if needed.
@@ -136,6 +148,30 @@ export function getSessionCachedProjection(projectId, noteId) {
136
148
  return undefined;
137
149
  return cache.projectionsById.get(noteId);
138
150
  }
151
+ export function setSessionCachedNote(projectId, vaultPath, note) {
152
+ const cache = ensureActiveProjectCache(projectId);
153
+ cache.recentNotesByKey.set(`${vaultPath}::${note.id}`, note);
154
+ const existing = cache.vaultCaches.get(vaultPath);
155
+ if (existing) {
156
+ existing.notesById.set(note.id, note);
157
+ if (!existing.noteList.some((entry) => entry.id === note.id)) {
158
+ existing.noteList.push(note);
159
+ }
160
+ return;
161
+ }
162
+ cache.vaultCaches.set(vaultPath, {
163
+ notesById: new Map([[note.id, note]]),
164
+ noteList: [note],
165
+ embeddings: [],
166
+ });
167
+ }
168
+ export function getRecentSessionAccessNote(projectId, vaultPath, noteId) {
169
+ const cache = sessionCaches.activeProject;
170
+ if (!cache || cache.projectId !== projectId) {
171
+ return undefined;
172
+ }
173
+ return cache.recentNotesByKey.get(`${vaultPath}::${noteId}`);
174
+ }
139
175
  /**
140
176
  * Store a projection in the session cache.
141
177
  * No-op when no active cache exists for this project.
@@ -146,4 +182,18 @@ export function setSessionCachedProjection(projectId, noteId, projection) {
146
182
  return;
147
183
  cache.projectionsById.set(noteId, projection);
148
184
  }
185
+ export function recordSessionNoteAccess(projectId, vaultPath, noteId, accessKind, score) {
186
+ const cache = ensureActiveProjectCache(projectId);
187
+ cache.recentAccesses = cache.recentAccesses
188
+ .filter((entry) => !(entry.noteId === noteId && entry.vaultPath === vaultPath))
189
+ .concat({ noteId, vaultPath, accessedAt: new Date().toISOString(), accessKind, score })
190
+ .slice(-25);
191
+ }
192
+ export function getRecentSessionNoteAccesses(projectId) {
193
+ const cache = sessionCaches.activeProject;
194
+ if (!cache || cache.projectId !== projectId) {
195
+ return [];
196
+ }
197
+ return [...cache.recentAccesses].reverse();
198
+ }
149
199
  //# sourceMappingURL=cache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
package/build/index.js CHANGED
@@ -10,11 +10,12 @@ import { embed, cosineSimilarity, embedModel } from "./embeddings.js";
10
10
  import { buildTemporalHistoryEntry, computeConfidence, getNoteProvenance } from "./provenance.js";
11
11
  import { enrichTemporalHistory } from "./temporal-interpretation.js";
12
12
  import { getOrBuildProjection } from "./projections.js";
13
- import { invalidateActiveProjectCache, getOrBuildVaultEmbeddings, getOrBuildVaultNoteList, getSessionCachedNote, getSessionCachedProjection, setSessionCachedProjection, } from "./cache.js";
13
+ import { invalidateActiveProjectCache, getOrBuildVaultEmbeddings, getOrBuildVaultNoteList, getRecentSessionNoteAccesses, getRecentSessionAccessNote, getSessionCachedNote, getSessionCachedProjection, recordSessionNoteAccess, setSessionCachedNote, setSessionCachedProjection, } from "./cache.js";
14
14
  import { performance } from "perf_hooks";
15
15
  import { filterRelationships, mergeRelationshipsFromNotes, normalizeMergePlanSourceIds, resolveEffectiveConsolidationMode, } from "./consolidate.js";
16
- import { computeRecallMetadataBoost, computeHybridScore, selectRecallResults, applyLexicalReranking } from "./recall.js";
17
- import { shouldTriggerLexicalRescue, computeLexicalScore, LEXICAL_RESCUE_CANDIDATE_LIMIT, LEXICAL_RESCUE_THRESHOLD, LEXICAL_RESCUE_RESULT_LIMIT, } from "./lexical.js";
16
+ import { suggestAutoRelationships } from "./auto-relate.js";
17
+ import { computeRecallMetadataBoost, computeHybridScore, selectRecallResults, applyLexicalReranking, applyCanonicalExplanationPromotion, } from "./recall.js";
18
+ import { shouldTriggerLexicalRescue, rankDocumentsByTfIdf, LEXICAL_RESCUE_CANDIDATE_LIMIT, LEXICAL_RESCUE_THRESHOLD, LEXICAL_RESCUE_RESULT_LIMIT, } from "./lexical.js";
18
19
  import { getRelationshipPreview } from "./relationships.js";
19
20
  import { cleanMarkdown } from "./markdown.js";
20
21
  import { MnemonicConfigStore, readVaultSchemaVersion } from "./config.js";
@@ -1489,6 +1490,21 @@ server.registerTool("remember", {
1489
1490
  updatedAt: now,
1490
1491
  memoryVersion: 1,
1491
1492
  };
1493
+ if (project) {
1494
+ const accessCandidates = getRecentSessionNoteAccesses(project.id)
1495
+ .map((entry) => {
1496
+ const cachedNote = getSessionCachedNote(project.id, entry.vaultPath, entry.noteId)
1497
+ ?? getRecentSessionAccessNote(project.id, entry.vaultPath, entry.noteId);
1498
+ return cachedNote
1499
+ ? { note: cachedNote, accessedAt: entry.accessedAt, accessKind: entry.accessKind, score: entry.score }
1500
+ : null;
1501
+ })
1502
+ .filter((entry) => entry !== null);
1503
+ const autoRelationships = suggestAutoRelationships(note, accessCandidates);
1504
+ if (autoRelationships.length > 0) {
1505
+ note.relatedTo = autoRelationships;
1506
+ }
1507
+ }
1492
1508
  await vault.storage.writeNote(note);
1493
1509
  let embeddingStatus = { status: "written" };
1494
1510
  try {
@@ -1741,9 +1757,25 @@ server.registerTool("get_project_memory_policy", {
1741
1757
  };
1742
1758
  });
1743
1759
  // ── Lexical rescue helper ─────────────────────────────────────────────────────
1744
- async function collectLexicalRescueCandidates(vaults, query, project, scope, tags, existingIds) {
1760
+ function buildRecallCandidateContext(note) {
1761
+ const metadata = getEffectiveMetadata(note);
1762
+ const relatedCount = note.relatedTo?.length ?? 0;
1763
+ return {
1764
+ metadata,
1765
+ metadataBoost: computeRecallMetadataBoost(metadata),
1766
+ lifecycle: note.lifecycle,
1767
+ relatedCount,
1768
+ connectionDiversity: new Set((note.relatedTo ?? []).map((rel) => rel.type)).size,
1769
+ structureScore: Math.min(0.04, [
1770
+ note.content.includes("## ") ? 0.02 : 0,
1771
+ note.content.includes("- ") || note.content.includes("1. ") ? 0.01 : 0,
1772
+ note.content.length >= 400 ? 0.01 : 0,
1773
+ ].reduce((sum, value) => sum + value, 0)),
1774
+ };
1775
+ }
1776
+ async function collectLexicalRescueCandidates(vaults, query, project, scope, tags, lifecycle, existingIds) {
1745
1777
  const existingIdSet = new Set(existingIds.map((c) => c.id));
1746
- const candidates = [];
1778
+ const rescuePool = [];
1747
1779
  for (const vault of vaults) {
1748
1780
  const notes = await vault.storage.listNotes().catch(() => []);
1749
1781
  for (const note of notes) {
@@ -1754,6 +1786,8 @@ async function collectLexicalRescueCandidates(vaults, query, project, scope, tag
1754
1786
  if (!tags.every((t) => noteTags.has(t)))
1755
1787
  continue;
1756
1788
  }
1789
+ if (lifecycle && note.lifecycle !== lifecycle)
1790
+ continue;
1757
1791
  const isProjectNote = note.project !== undefined;
1758
1792
  const isCurrentProject = project && note.project === project.id;
1759
1793
  if (scope === "project" && !isCurrentProject)
@@ -1763,24 +1797,39 @@ async function collectLexicalRescueCandidates(vaults, query, project, scope, tag
1763
1797
  const projection = await getOrBuildProjection(vault.storage, note).catch(() => undefined);
1764
1798
  if (!projection)
1765
1799
  continue;
1766
- const lexicalScore = computeLexicalScore(query, projection.projectionText);
1767
- if (lexicalScore < LEXICAL_RESCUE_THRESHOLD)
1768
- continue;
1769
- const metadataBoost = computeRecallMetadataBoost(getEffectiveMetadata(note));
1770
- const boost = (isCurrentProject ? 0.15 : 0) + metadataBoost;
1771
- candidates.push({
1800
+ rescuePool.push({
1772
1801
  id: note.id,
1773
- score: 0,
1774
- boosted: boost,
1775
1802
  vault,
1776
1803
  isCurrentProject: Boolean(isCurrentProject),
1777
- lexicalScore,
1804
+ projectionText: projection.projectionText,
1805
+ context: buildRecallCandidateContext(note),
1778
1806
  });
1779
- if (candidates.length >= LEXICAL_RESCUE_CANDIDATE_LIMIT)
1780
- break;
1781
1807
  }
1782
- if (candidates.length >= LEXICAL_RESCUE_CANDIDATE_LIMIT)
1783
- break;
1808
+ }
1809
+ const rankedRescueIds = new Map(rankDocumentsByTfIdf(query, rescuePool.map((candidate) => ({ id: candidate.id, text: candidate.projectionText })), LEXICAL_RESCUE_CANDIDATE_LIMIT).map((candidate) => [candidate.id, candidate.score]));
1810
+ const candidates = [];
1811
+ for (const candidate of rescuePool) {
1812
+ const tfIdfScore = rankedRescueIds.get(candidate.id);
1813
+ if (tfIdfScore === undefined || tfIdfScore <= 0)
1814
+ continue;
1815
+ const lexicalScore = tfIdfScore;
1816
+ if (lexicalScore < LEXICAL_RESCUE_THRESHOLD)
1817
+ continue;
1818
+ const boost = (candidate.isCurrentProject ? 0.15 : 0) + candidate.context.metadataBoost;
1819
+ candidates.push({
1820
+ id: candidate.id,
1821
+ score: lexicalScore,
1822
+ semanticScoreForPromotion: 0,
1823
+ boosted: boost,
1824
+ vault: candidate.vault,
1825
+ isCurrentProject: candidate.isCurrentProject,
1826
+ lexicalScore,
1827
+ lifecycle: candidate.context.lifecycle,
1828
+ relatedCount: candidate.context.relatedCount,
1829
+ connectionDiversity: candidate.context.connectionDiversity,
1830
+ structureScore: candidate.context.structureScore,
1831
+ metadata: candidate.context.metadata,
1832
+ });
1784
1833
  }
1785
1834
  return candidates
1786
1835
  .sort((a, b) => computeHybridScore(b) - computeHybridScore(a))
@@ -1889,9 +1938,21 @@ server.registerTool("recall", {
1889
1938
  if (isProjectNote)
1890
1939
  continue;
1891
1940
  }
1892
- const metadataBoost = computeRecallMetadataBoost(getEffectiveMetadata(note));
1893
- const boost = (isCurrentProject ? 0.15 : 0) + metadataBoost;
1894
- scored.push({ id: rec.id, score: rawScore, boosted: rawScore + boost, vault, isCurrentProject: Boolean(isCurrentProject) });
1941
+ const context = buildRecallCandidateContext(note);
1942
+ const boost = (isCurrentProject ? 0.15 : 0) + context.metadataBoost;
1943
+ scored.push({
1944
+ id: rec.id,
1945
+ score: rawScore,
1946
+ semanticScoreForPromotion: rawScore,
1947
+ boosted: rawScore + boost,
1948
+ vault,
1949
+ isCurrentProject: Boolean(isCurrentProject),
1950
+ lifecycle: context.lifecycle,
1951
+ relatedCount: context.relatedCount,
1952
+ connectionDiversity: context.connectionDiversity,
1953
+ structureScore: context.structureScore,
1954
+ metadata: context.metadata,
1955
+ });
1895
1956
  }
1896
1957
  }
1897
1958
  const projectionTexts = new Map();
@@ -1922,14 +1983,19 @@ server.registerTool("recall", {
1922
1983
  }
1923
1984
  return undefined;
1924
1985
  };
1986
+ const strongestSemanticScore = scored.reduce((max, candidate) => max === undefined ? candidate.score : Math.max(max, candidate.score), undefined);
1925
1987
  const reranked = applyLexicalReranking(scored, query, getProjectionText);
1926
- // Lexical rescue: when semantic results are weak, scan projections for additional candidates
1927
- const topScore = reranked.length > 0 ? reranked[0].score : undefined;
1928
- if (shouldTriggerLexicalRescue(topScore, reranked.length)) {
1929
- const rescueCandidates = await collectLexicalRescueCandidates(vaults, query, project ?? undefined, scope, tags, reranked);
1930
- reranked.push(...rescueCandidates);
1931
- }
1932
- const top = selectRecallResults(reranked, limit, scope);
1988
+ let promoted = applyCanonicalExplanationPromotion(reranked);
1989
+ // Lexical rescue: when semantic results are weak, scan projections for additional candidates.
1990
+ // Skip rescue when the caller set a strict minSimilarity above the default,
1991
+ // because rescue candidates lack genuine semantic backing.
1992
+ const rescueAllowed = minSimilarity <= DEFAULT_MIN_SIMILARITY;
1993
+ if (rescueAllowed && shouldTriggerLexicalRescue(strongestSemanticScore, scored.length)) {
1994
+ const rescueCandidates = await collectLexicalRescueCandidates(vaults, query, project ?? undefined, scope, tags, lifecycle, promoted);
1995
+ promoted.push(...rescueCandidates);
1996
+ promoted = applyCanonicalExplanationPromotion(promoted);
1997
+ }
1998
+ const top = selectRecallResults(promoted, limit, scope);
1933
1999
  if (top.length === 0) {
1934
2000
  const structuredContent = { action: "recalled", query, scope: scope || "all", results: [] };
1935
2001
  return { content: [{ type: "text", text: "No memories found matching that query." }], structuredContent };
@@ -1976,8 +2042,11 @@ server.registerTool("recall", {
1976
2042
  const formattedRelationships = relationships !== undefined
1977
2043
  ? `\n\n${formatRelationshipPreview(relationships)}`
1978
2044
  : "";
2045
+ const provenanceLine = provenance || confidence
2046
+ ? `\n**confidence:** ${confidence ?? "medium"}${provenance?.recentlyChanged ? " | **recently changed**" : ""}`
2047
+ : "";
1979
2048
  // Suppress raw related IDs when enriched preview is shown to avoid duplication
1980
- sections.push(`${formatNote(note, score, relationships === undefined)}${formattedHistory}${formattedRelationships}`);
2049
+ sections.push(`${formatNote(note, score, relationships === undefined)}${provenanceLine}${formattedHistory}${formattedRelationships}`);
1981
2050
  structuredResults.push({
1982
2051
  id,
1983
2052
  title: note.title,
@@ -1994,6 +2063,16 @@ server.registerTool("recall", {
1994
2063
  historySummary,
1995
2064
  relationships,
1996
2065
  });
2066
+ if (project) {
2067
+ setSessionCachedNote(project.id, vault.storage.vaultPath, note);
2068
+ recordSessionNoteAccess(project.id, vault.storage.vaultPath, id, "recall", computeHybridScore({
2069
+ id,
2070
+ score,
2071
+ boosted,
2072
+ vault,
2073
+ isCurrentProject: note.project === project.id,
2074
+ }));
2075
+ }
1997
2076
  }
1998
2077
  }
1999
2078
  const textContent = `${header}\n\n${sections.join("\n\n---\n\n")}`;
@@ -2094,6 +2173,27 @@ server.registerTool("update", {
2094
2173
  alwaysLoad: alwaysLoad !== undefined ? alwaysLoad : note.alwaysLoad,
2095
2174
  updatedAt: now,
2096
2175
  };
2176
+ if (updated.project) {
2177
+ const accessCandidates = getRecentSessionNoteAccesses(updated.project)
2178
+ .map((entry) => {
2179
+ const cachedNote = getSessionCachedNote(updated.project, entry.vaultPath, entry.noteId)
2180
+ ?? getRecentSessionAccessNote(updated.project, entry.vaultPath, entry.noteId);
2181
+ return cachedNote
2182
+ ? { note: cachedNote, accessedAt: entry.accessedAt, accessKind: entry.accessKind, score: entry.score }
2183
+ : null;
2184
+ })
2185
+ .filter((entry) => entry !== null);
2186
+ const autoRelationships = suggestAutoRelationships(updated, accessCandidates);
2187
+ if (autoRelationships.length > 0) {
2188
+ const existing = [...(updated.relatedTo ?? [])];
2189
+ for (const relationship of autoRelationships) {
2190
+ if (!existing.some((rel) => rel.id === relationship.id && rel.type === relationship.type)) {
2191
+ existing.push(relationship);
2192
+ }
2193
+ }
2194
+ updated.relatedTo = existing;
2195
+ }
2196
+ }
2097
2197
  await vault.storage.writeNote(updated);
2098
2198
  let embeddingStatus = { status: "written" };
2099
2199
  try {
@@ -2349,6 +2449,10 @@ server.registerTool("get", {
2349
2449
  vault: storageLabel(vault),
2350
2450
  relationships,
2351
2451
  });
2452
+ if (project) {
2453
+ setSessionCachedNote(project.id, vault.storage.vaultPath, note);
2454
+ recordSessionNoteAccess(project.id, vault.storage.vaultPath, note.id, "get");
2455
+ }
2352
2456
  }
2353
2457
  const lines = [];
2354
2458
  for (const note of found) {