@danielmarbach/mnemonic-mcp 0.18.0 → 0.19.1

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 (40) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +21 -1
  3. package/build/index.js +98 -53
  4. package/build/index.js.map +1 -1
  5. package/build/project-introspection.d.ts +5 -2
  6. package/build/project-introspection.d.ts.map +1 -1
  7. package/build/project-introspection.js +33 -4
  8. package/build/project-introspection.js.map +1 -1
  9. package/build/projections.d.ts +4 -1
  10. package/build/projections.d.ts.map +1 -1
  11. package/build/projections.js +14 -2
  12. package/build/projections.js.map +1 -1
  13. package/build/provenance.d.ts +1 -0
  14. package/build/provenance.d.ts.map +1 -1
  15. package/build/provenance.js.map +1 -1
  16. package/build/recall.d.ts +2 -0
  17. package/build/recall.d.ts.map +1 -1
  18. package/build/recall.js +23 -0
  19. package/build/recall.js.map +1 -1
  20. package/build/relationships.d.ts +3 -2
  21. package/build/relationships.d.ts.map +1 -1
  22. package/build/relationships.js +46 -5
  23. package/build/relationships.js.map +1 -1
  24. package/build/role-suggestions.d.ts +20 -0
  25. package/build/role-suggestions.d.ts.map +1 -0
  26. package/build/role-suggestions.js +125 -0
  27. package/build/role-suggestions.js.map +1 -0
  28. package/build/storage.d.ts +7 -0
  29. package/build/storage.d.ts.map +1 -1
  30. package/build/storage.js +29 -0
  31. package/build/storage.js.map +1 -1
  32. package/build/structured-content.d.ts +15 -0
  33. package/build/structured-content.d.ts.map +1 -1
  34. package/build/structured-content.js +3 -0
  35. package/build/structured-content.js.map +1 -1
  36. package/build/temporal-interpretation.d.ts +40 -0
  37. package/build/temporal-interpretation.d.ts.map +1 -0
  38. package/build/temporal-interpretation.js +192 -0
  39. package/build/temporal-interpretation.js.map +1 -0
  40. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ 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.19.1] - 2026-03-28
8
+
9
+ ### Fixed
10
+
11
+ - Thin dynamic theme buckets (fewer than 2 notes) are now collapsed into "other" in `project_memory_summary`, reducing theme noise without affecting fixed themes.
12
+ - `suggestedNext` in `project_memory_summary` now prefers theme-diverse anchors, avoiding suggestions that repeat the primary entry's theme.
13
+ - `changeDescription` for `unknown`-category history entries now uses stats to produce "Substantially updated the note." or "Minor update to the note." instead of always "Updated the note."
14
+ - `summarizeHistory` now produces more informative summaries for `unknown`-dominant multi-commit notes, using total additions and substantial-update count as signals.
15
+ - `isProjectionStale` now skips re-embedding when `updatedAt` differs but projected content is unchanged, avoiding unnecessary re-embeds for relationship-only note changes.
16
+
17
+ ## [0.19.0] - 2026-03-28
18
+
19
+ ### Added
20
+
21
+ - Role and importance inference (`src/role-suggestions.ts`): notes are automatically assigned a suggested `role` and `importance` from structural signals; inference is language-independent and never overwrites explicit frontmatter.
22
+ - `alwaysLoad: true` in note frontmatter marks a note as an explicit session anchor with highest recall and relationship-expansion priority.
23
+ - `recall` and relationship expansion now factor in effective role and importance, so summary and decision notes surface higher without manual tagging.
24
+ - Temporal recall history entries now include a `changeDescription` and notes include a `historySummary` capturing the overall evolution pattern; classification uses structural signals only and degrades gracefully when stats are unavailable.
25
+
26
+ ### Changed
27
+
28
+ - The workflow hint and user-facing docs now state that roles are optional prioritization hints, inferred roles stay internal-only, lifecycle remains the durability control, and default prioritization is language-independent.
29
+
7
30
  ## [0.18.0] - 2026-03-27
8
31
 
9
32
  ### Added
package/README.md CHANGED
@@ -310,6 +310,16 @@ Project identity derives from the **git remote URL**, normalized to a stable slu
310
310
 
311
311
  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.
312
312
 
313
+ **What temporal mode shows:**
314
+
315
+ - **Per-change descriptions** (`changeDescription`): human-readable summaries like "Expanded the note with additional detail" or "Minor refinement to existing content."
316
+ - **Note-level history summaries** (`historySummary`): overall patterns like "The core decision remained stable while rationale and examples expanded." or "The note was connected to related work through incremental updates."
317
+ - **Semantic change categories**: create, refine, expand, clarify, connect, restructure, reverse, unknown
318
+
319
+ **How it works:**
320
+
321
+ mnemonic interprets change semantically using structural and statistical signals (size ratios, heading changes, section movements) rather than language-dependent analysis. Raw diffs are intentionally NOT part of default temporal output—you get interpretive summaries that explain what kind of change happened, not patch noise.
322
+
313
323
  Use `verbose: true` together with temporal mode when you want richer change stats such as additions, deletions, files changed, and change classification. Those stats describe the whole commit that touched the note, not a raw diff excerpt, so recall stays bounded and does not return full diffs.
314
324
 
315
325
  The `scope` parameter on `recall` narrows results:
@@ -325,6 +335,14 @@ Each note carries a `lifecycle`:
325
335
  - `"permanent"` *(default)* — durable knowledge for future sessions
326
336
  - `"temporary"` — working-state scaffolding (plans, WIP checkpoints) that can be cleaned up once consolidated
327
337
 
338
+ ### Roles and lifecycle
339
+
340
+ Roles are optional prioritization hints, not required schema. mnemonic infers a `role` and `importance` from structural signals (heading count, bullet density, inbound references, relationship types) — inference is language-independent and never overwrites explicit frontmatter. Valid roles: `summary`, `decision`, `plan`, `log`, `reference`. Valid importance values: `high`, `normal`.
341
+
342
+ Set `alwaysLoad: true` in a note's frontmatter to mark it as an explicit session anchor; it receives the highest recall and relationship-expansion priority regardless of inferred role.
343
+
344
+ mnemonic works without roles. Inferred roles stay internal-only, prioritization is language-independent by default, and lifecycle remains the separate durability axis. A note with `role: plan` can still be either `temporary` or `permanent`.
345
+
328
346
  ### Note format
329
347
 
330
348
  Notes are standard markdown with YAML frontmatter:
@@ -422,7 +440,7 @@ Imported notes are written to the main vault with `lifecycle: permanent` and `sc
422
440
 
423
441
  | Prompt | Description |
424
442
  |--------|-------------|
425
- | `mnemonic-workflow-hint` | Optional. Returns an imperative decision protocol for weaker and stronger models: use `recall` or `list` first, inspect with `get`, update existing memories, remember only when nothing matches, then organize with `relate`, `consolidate`, or `move_memory`. Not auto-injected request it on demand. |
443
+ | `mnemonic-workflow-hint` | Optional. Returns a compact decision protocol: use `recall` or `list` first, inspect with `get`, update existing memories, remember only when nothing matches, then organize with `relate`, `consolidate`, or `move_memory`. Also reminds models that roles are optional prioritization hints, inferred roles are internal-only, prioritization is language-independent by default, and lifecycle stays separate. |
426
444
 
427
445
  ## Tools
428
446
 
@@ -556,6 +574,8 @@ mnemonic and Beads address complementary concerns. mnemonic is a **knowledge gra
556
574
 
557
575
  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.
558
576
 
577
+ Roles, when present, are separate from lifecycle: they help prioritization and retrieval, not retention policy. mnemonic still works without roles, and any inferred role metadata remains an internal hint rather than part of the user-facing note contract.
578
+
559
579
  ## Contributing
560
580
 
561
581
  See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development setup, dogfooding workflow, testing requirements, and pull request guidelines.
package/build/index.js CHANGED
@@ -8,16 +8,18 @@ import { promises as fs } from "fs";
8
8
  import { NOTE_LIFECYCLES } from "./storage.js";
9
9
  import { embed, cosineSimilarity, embedModel } from "./embeddings.js";
10
10
  import { buildTemporalHistoryEntry, computeConfidence, getNoteProvenance } from "./provenance.js";
11
+ import { enrichTemporalHistory } from "./temporal-interpretation.js";
11
12
  import { getOrBuildProjection } from "./projections.js";
12
13
  import { invalidateActiveProjectCache, getOrBuildVaultEmbeddings, getOrBuildVaultNoteList, getSessionCachedNote, } from "./cache.js";
13
14
  import { performance } from "perf_hooks";
14
15
  import { filterRelationships, mergeRelationshipsFromNotes, normalizeMergePlanSourceIds, resolveEffectiveConsolidationMode, } from "./consolidate.js";
15
- import { selectRecallResults } from "./recall.js";
16
+ import { computeRecallMetadataBoost, selectRecallResults } from "./recall.js";
16
17
  import { getRelationshipPreview } from "./relationships.js";
17
18
  import { cleanMarkdown } from "./markdown.js";
18
19
  import { MnemonicConfigStore, readVaultSchemaVersion } from "./config.js";
19
20
  import { CONSOLIDATION_MODES, PROTECTED_BRANCH_BEHAVIORS, PROJECT_POLICY_SCOPES, WRITE_SCOPES, isProtectedBranch, resolveProtectedBranchBehavior, resolveProtectedBranchPatterns, resolveConsolidationMode, resolveWriteScope, } from "./project-memory-policy.js";
20
- import { classifyTheme, classifyThemeWithGraduation, computeThemesWithGraduation, summarizePreview, titleCaseTheme, withinThemeScore, anchorScore, buildThemeCache, computeConnectionDiversity, } from "./project-introspection.js";
21
+ import { classifyTheme, classifyThemeWithGraduation, computeThemesWithGraduation, summarizePreview, titleCaseTheme, withinThemeScore, anchorScore, computeConnectionDiversity, } from "./project-introspection.js";
22
+ import { getEffectiveMetadata } from "./role-suggestions.js";
21
23
  import { detectProject, getCurrentGitBranch, resolveProjectIdentity } from "./project.js";
22
24
  import { VaultManager } from "./vault.js";
23
25
  import { checkBranchChange } from "./branch-tracker.js";
@@ -405,7 +407,8 @@ function formatTemporalHistory(history) {
405
407
  const lines = ["**history:**"];
406
408
  for (const entry of history) {
407
409
  const summary = entry.summary ? ` — ${entry.summary}` : "";
408
- lines.push(`- \`${entry.commitHash.slice(0, 7)}\` ${entry.timestamp} ${entry.message}${summary}`);
410
+ const changeDesc = entry.changeDescription ? ` (${entry.changeDescription})` : "";
411
+ lines.push(`- \`${entry.commitHash.slice(0, 7)}\` ${entry.timestamp} — ${entry.message}${summary}${changeDesc}`);
409
412
  }
410
413
  return lines.join("\n");
411
414
  }
@@ -1817,7 +1820,8 @@ server.registerTool("recall", {
1817
1820
  if (isProjectNote)
1818
1821
  continue;
1819
1822
  }
1820
- const boost = isCurrentProject ? 0.15 : 0;
1823
+ const metadataBoost = computeRecallMetadataBoost(getEffectiveMetadata(note));
1824
+ const boost = (isCurrentProject ? 0.15 : 0) + metadataBoost;
1821
1825
  scored.push({ id: rec.id, score: rawScore, boosted: rawScore + boost, vault, isCurrentProject: Boolean(isCurrentProject) });
1822
1826
  }
1823
1827
  }
@@ -1842,13 +1846,17 @@ server.registerTool("recall", {
1842
1846
  const provenance = await getNoteProvenance(vault.git, filePath);
1843
1847
  const confidence = computeConfidence(note.lifecycle, note.updatedAt, centrality);
1844
1848
  let history;
1849
+ let historySummary;
1845
1850
  if (mode === "temporal") {
1846
1851
  if (index < TEMPORAL_HISTORY_NOTE_LIMIT) {
1847
1852
  const commits = await vault.git.getFileHistory(filePath, TEMPORAL_HISTORY_COMMIT_LIMIT);
1848
- history = await Promise.all(commits.map(async (commit) => {
1853
+ const rawHistory = await Promise.all(commits.map(async (commit) => {
1849
1854
  const stats = await vault.git.getCommitStats(filePath, commit.hash);
1850
1855
  return buildTemporalHistoryEntry(commit, stats, verbose);
1851
1856
  }));
1857
+ const enriched = enrichTemporalHistory(rawHistory);
1858
+ history = enriched.interpretedHistory;
1859
+ historySummary = enriched.historySummary;
1852
1860
  }
1853
1861
  }
1854
1862
  // Add relationship preview for top N results (fail-soft)
@@ -1878,6 +1886,7 @@ server.registerTool("recall", {
1878
1886
  provenance,
1879
1887
  confidence,
1880
1888
  history,
1889
+ historySummary,
1881
1890
  relationships,
1882
1891
  });
1883
1892
  }
@@ -2839,12 +2848,38 @@ server.registerTool("project_memory_summary", {
2839
2848
  return { content: [{ type: "text", text: `No memories found for project ${project.name}.` }], structuredContent };
2840
2849
  }
2841
2850
  const policyLine = await formatProjectPolicyLine(project.id);
2842
- // Build theme cache for connection diversity scoring (project-scoped only)
2843
- // This uses simple classifyTheme for consistent diversity calculations
2844
- const themeCache = buildThemeCache(projectEntries.map(e => e.note));
2851
+ const projectNoteIds = new Set(projectEntries.map(e => e.note.id));
2845
2852
  // Compute promoted themes from keywords (graduation system)
2846
2853
  const graduationResult = computeThemesWithGraduation(projectEntries.map(e => e.note));
2847
2854
  const promotedThemes = new Set(graduationResult.promotedThemes);
2855
+ const themeCache = graduationResult.themeAssignments;
2856
+ const inboundReferences = new Map();
2857
+ const linkedByPermanentNotes = new Map();
2858
+ for (const entry of projectEntries) {
2859
+ for (const rel of entry.note.relatedTo ?? []) {
2860
+ if (!projectNoteIds.has(rel.id)) {
2861
+ continue;
2862
+ }
2863
+ inboundReferences.set(rel.id, (inboundReferences.get(rel.id) ?? 0) + 1);
2864
+ if (entry.note.lifecycle === "permanent") {
2865
+ linkedByPermanentNotes.set(rel.id, (linkedByPermanentNotes.get(rel.id) ?? 0) + 1);
2866
+ }
2867
+ }
2868
+ }
2869
+ const effectiveMetadataById = new Map(projectEntries.map((entry) => {
2870
+ const inbound = inboundReferences.get(entry.note.id) ?? 0;
2871
+ const visibleOutbound = (entry.note.relatedTo ?? []).filter((rel) => projectNoteIds.has(rel.id)).length;
2872
+ const metadata = getEffectiveMetadata(entry.note, {
2873
+ inboundReferences: inbound,
2874
+ linkedByPermanentNotes: linkedByPermanentNotes.get(entry.note.id) ?? 0,
2875
+ anchorCandidate: entry.note.lifecycle === "permanent" && (visibleOutbound > 0 || inbound > 0),
2876
+ });
2877
+ return [entry.note.id, {
2878
+ metadata,
2879
+ inbound,
2880
+ visibleOutbound,
2881
+ }];
2882
+ }));
2848
2883
  // Categorize by theme with graduation (project-scoped only)
2849
2884
  const themed = new Map();
2850
2885
  for (const entry of projectEntries) {
@@ -2857,6 +2892,17 @@ server.registerTool("project_memory_summary", {
2857
2892
  const fixedThemes = ["overview", "decisions", "tooling", "bugs", "architecture", "quality"];
2858
2893
  const dynamicThemes = graduationResult.promotedThemes.filter(t => !fixedThemes.includes(t));
2859
2894
  const themeOrder = [...fixedThemes, ...dynamicThemes.sort(), "other"];
2895
+ // Collapse thin dynamic-theme buckets (< 2 notes) into "other" to reduce noise.
2896
+ // Fixed themes are kept even when small; only keyword-graduated themes are collapsed.
2897
+ const fixedThemeSet = new Set(fixedThemes);
2898
+ for (const [theme, bucket] of Array.from(themed.entries())) {
2899
+ if (!fixedThemeSet.has(theme) && theme !== "other" && bucket.length < 2) {
2900
+ const otherBucket = themed.get("other") ?? [];
2901
+ otherBucket.push(...bucket);
2902
+ themed.set("other", otherBucket);
2903
+ themed.delete(theme);
2904
+ }
2905
+ }
2860
2906
  // Calculate notes distribution (project-scoped only)
2861
2907
  const projectVaultCount = projectEntries.filter(e => e.vault.isProject).length;
2862
2908
  const mainVaultProjectEntries = projectEntries.filter(e => !e.vault.isProject);
@@ -2877,7 +2923,7 @@ server.registerTool("project_memory_summary", {
2877
2923
  if (!bucket || bucket.length === 0)
2878
2924
  continue;
2879
2925
  // Sort by within-theme score
2880
- const sorted = [...bucket].sort((a, b) => withinThemeScore(b.note) - withinThemeScore(a.note));
2926
+ const sorted = [...bucket].sort((a, b) => withinThemeScore(b.note, effectiveMetadataById.get(b.note.id)?.metadata) - withinThemeScore(a.note, effectiveMetadataById.get(a.note.id)?.metadata));
2881
2927
  const top = sorted.slice(0, maxPerTheme);
2882
2928
  sections.push(`\n${titleCaseTheme(theme)}:`);
2883
2929
  sections.push(...top.map(e => `- ${e.note.title} (\`${e.note.id}\`)`));
@@ -2897,49 +2943,32 @@ server.registerTool("project_memory_summary", {
2897
2943
  sections.push(`\nRecent activity (start here):`);
2898
2944
  sections.push(...recent.map(e => `- ${e.note.updatedAt} — ${e.note.title}`));
2899
2945
  // Anchor notes with diversity constraint (project-scoped only)
2900
- // Separate tagged anchors (can have no relationships) from scored anchors (need relationships)
2901
- const taggedAnchorEntries = projectEntries.filter(e => e.note.lifecycle === "permanent" &&
2902
- e.note.tags.some(t => t.toLowerCase() === "anchor" || t.toLowerCase() === "alwaysload"));
2903
2946
  const scoredAnchorCandidates = projectEntries
2904
- .filter(e => e.note.lifecycle === "permanent" && (e.note.relatedTo?.length ?? 0) > 0)
2905
- .map(e => ({
2906
- entry: e,
2907
- score: anchorScore(e.note, themeCache),
2908
- theme: classifyTheme(e.note),
2909
- }))
2910
- .filter(x => x.score > -Infinity)
2911
- .sort((a, b) => b.score - a.score);
2912
- // Score tagged anchors too, so they can compete for primaryEntry
2913
- const scoredTaggedAnchors = taggedAnchorEntries
2914
- .map(e => ({
2915
- entry: e,
2916
- score: anchorScore(e.note, themeCache),
2917
- theme: classifyTheme(e.note),
2918
- }))
2919
- .sort((a, b) => b.score - a.score);
2947
+ .map(e => {
2948
+ const baselineContext = effectiveMetadataById.get(e.note.id);
2949
+ const metadata = baselineContext?.metadata;
2950
+ return {
2951
+ entry: e,
2952
+ metadata,
2953
+ score: anchorScore(e.note, themeCache, metadata),
2954
+ theme: themeCache.get(e.note.id) ?? "other",
2955
+ alwaysLoad: metadata?.alwaysLoad === true,
2956
+ explicitOrientationRole: metadata?.roleSource === "explicit" &&
2957
+ (metadata.role === "summary" || metadata.role === "decision"),
2958
+ hasVisibleGraphParticipation: (baselineContext?.visibleOutbound ?? 0) > 0 || (baselineContext?.inbound ?? 0) > 0,
2959
+ };
2960
+ })
2961
+ .filter(candidate => candidate.score > -Infinity)
2962
+ .filter(candidate => candidate.alwaysLoad || candidate.explicitOrientationRole || candidate.hasVisibleGraphParticipation)
2963
+ .sort((a, b) => b.score - a.score || a.entry.note.title.localeCompare(b.entry.note.title));
2920
2964
  // Enforce max 2 per theme for scored anchors
2921
2965
  const anchorThemeCounts = new Map();
2922
2966
  const anchors = [];
2923
2967
  const anchorIds = new Set();
2924
- // Add tagged anchors first (capped at 10 total across all themes), scored by anchorScore
2925
- for (const candidate of scoredTaggedAnchors.slice(0, 10)) {
2926
- if (anchors.length >= 10)
2927
- break;
2928
- anchors.push({
2929
- id: candidate.entry.note.id,
2930
- title: candidate.entry.note.title,
2931
- centrality: candidate.entry.note.relatedTo?.length ?? 0,
2932
- connectionDiversity: computeConnectionDiversity(candidate.entry.note, themeCache),
2933
- updatedAt: candidate.entry.note.updatedAt,
2934
- });
2935
- anchorIds.add(candidate.entry.note.id);
2936
- }
2937
2968
  // Add scored anchors with theme diversity constraint
2938
2969
  for (const candidate of scoredAnchorCandidates) {
2939
2970
  if (anchors.length >= 10)
2940
2971
  break;
2941
- if (anchorIds.has(candidate.entry.note.id))
2942
- continue;
2943
2972
  const theme = candidate.theme;
2944
2973
  const themeCount = anchorThemeCounts.get(theme) ?? 0;
2945
2974
  if (themeCount >= 2)
@@ -3039,8 +3068,29 @@ server.registerTool("project_memory_summary", {
3039
3068
  };
3040
3069
  const primaryEnriched = primaryAnchor ? await enrichOrientationNote(primaryAnchor) : {};
3041
3070
  const primaryRelationships = primaryAnchor ? await enrichOrientationNoteWithRelationships(primaryAnchor) : {};
3042
- const suggestedEnriched = await Promise.all(anchors.slice(1, 4).map(enrichOrientationNote));
3043
- const suggestedRelationships = await Promise.all(anchors.slice(1, 4).map(enrichOrientationNoteWithRelationships));
3071
+ // Select theme-diverse suggestedNext: avoid repeating the primary anchor's theme.
3072
+ // Backfills without constraint if not enough theme-distinct candidates exist.
3073
+ const primaryTheme = primaryAnchor ? (themeCache.get(primaryAnchor.id) ?? "other") : "other";
3074
+ const usedSuggestedThemes = new Set([primaryTheme]);
3075
+ const suggestedCandidates = [];
3076
+ for (const anchor of anchors.slice(1)) {
3077
+ if (suggestedCandidates.length >= 3)
3078
+ break;
3079
+ const anchorTheme = themeCache.get(anchor.id) ?? "other";
3080
+ if (!usedSuggestedThemes.has(anchorTheme)) {
3081
+ suggestedCandidates.push(anchor);
3082
+ usedSuggestedThemes.add(anchorTheme);
3083
+ }
3084
+ }
3085
+ for (const anchor of anchors.slice(1)) {
3086
+ if (suggestedCandidates.length >= 3)
3087
+ break;
3088
+ if (!suggestedCandidates.includes(anchor)) {
3089
+ suggestedCandidates.push(anchor);
3090
+ }
3091
+ }
3092
+ const suggestedEnriched = await Promise.all(suggestedCandidates.map(enrichOrientationNote));
3093
+ const suggestedRelationships = await Promise.all(suggestedCandidates.map(enrichOrientationNoteWithRelationships));
3044
3094
  // Enrich fallback primaryEntry when no anchors exist
3045
3095
  let fallbackEnriched = {};
3046
3096
  let fallbackRelationships = {};
@@ -3075,7 +3125,7 @@ server.registerTool("project_memory_summary", {
3075
3125
  ...fallbackEnriched,
3076
3126
  ...fallbackRelationships,
3077
3127
  },
3078
- suggestedNext: anchors.slice(1, 4).map((a, i) => ({
3128
+ suggestedNext: suggestedCandidates.map((a, i) => ({
3079
3129
  id: a.id,
3080
3130
  title: a.title,
3081
3131
  rationale: `Centrality ${a.centrality}, connects ${a.connectionDiversity} themes`,
@@ -4597,18 +4647,13 @@ server.registerPrompt("mnemonic-workflow-hint", {
4597
4647
  type: "text",
4598
4648
  text: "## Mnemonic MCP workflow hints\n\n" +
4599
4649
  "Avoid duplicate memories. Prefer inspecting and updating existing memories before creating new ones.\n\n" +
4600
- "### Hard rules\n\n" +
4601
4650
  "- REQUIRES: Before `remember`, call `recall` or `list` first.\n" +
4602
4651
  "- If `recall` or `list` returns a plausible match, call `get` before deciding whether to `update` or `remember`.\n" +
4603
4652
  "- If an existing memory already covers the topic, use `update`, not `remember`.\n" +
4604
4653
  "- When unsure, prefer `recall` over `remember`.\n" +
4605
4654
  "- For repo-related tasks, pass `cwd` so mnemonic can route project memories correctly.\n\n" +
4606
- "### Decision protocol\n\n" +
4607
- "1. Need to store, refine, or connect knowledge about a topic? Start with `recall`, or use `list` when you need deterministic browsing by scope, storage, or tags.\n" +
4608
- "2. If `recall` or `list` returns matching ids, use `get` to inspect the best match.\n" +
4609
- "3. If one memory should be refined, call `update`.\n" +
4610
- "4. If no memory covers the topic and tag choice is ambiguous, call `discover_tags` with note context, then call `remember`.\n" +
4611
- "5. After storing or updating, use `relate` for strong connections, `consolidate` for overlap, and `move_memory` for wrong storage location.\n\n" +
4655
+ "Workflow: `recall`/`list` -> `get` -> `update` or `remember` -> `relate`/`consolidate`/`move_memory`. Use `discover_tags` only when tag choice is ambiguous.\n\n" +
4656
+ "Roles are optional prioritization hints, not schema. Lifecycle still governs durability. `role: plan` does not imply `temporary`. Inferred roles are internal hints only. Prioritization is language-independent by default.\n\n" +
4612
4657
  "### Anti-patterns\n\n" +
4613
4658
  "- Bad: call `remember` immediately because the user said 'remember'.\n" +
4614
4659
  "- Good: `recall` or `list` first, then `get`, then `update` or `remember`.\n" +