@danielmarbach/mnemonic-mcp 0.4.0 → 0.5.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/build/index.js CHANGED
@@ -304,9 +304,10 @@ function makeId(title) {
304
304
  const projectParam = z
305
305
  .string()
306
306
  .optional()
307
- .describe("The working directory of the project (absolute path). " +
308
- "Pass the cwd of the file/project being worked on. " +
309
- "Omit for global memories not tied to any project.");
307
+ .describe("Absolute path to the project working directory (get from `detect_project`). " +
308
+ "Sets project context for routing and search boosting. " +
309
+ "Pass this even when storing with scope='global' it controls project association, not storage location. " +
310
+ "Omit only for truly cross-project or personal memories.");
310
311
  async function resolveProject(cwd) {
311
312
  if (!cwd)
312
313
  return undefined;
@@ -727,9 +728,19 @@ const server = new McpServer({
727
728
  // ── detect_project ────────────────────────────────────────────────────────────
728
729
  server.registerTool("detect_project", {
729
730
  title: "Detect Project",
730
- description: "Identify which project a working directory belongs to. " +
731
- "Returns the stable project id and name. " +
732
- "Call this to know what project context to pass to other tools.",
731
+ description: "Resolve a working directory to a stable project identity derived from its git remote URL.\n\n" +
732
+ "Use this when:\n" +
733
+ "- Starting a session in a project call this first to get the `cwd` value needed by all other project-scoped tools\n" +
734
+ "- You need to confirm which project a directory belongs to\n\n" +
735
+ "Do not use this when:\n" +
736
+ "- You already know the project context from an earlier call in this session\n\n" +
737
+ "Returns: project id, name, source (git-remote/folder), and any identity override. " +
738
+ "Follow up with `project_memory_summary` or `recall` to orient on what is already known.",
739
+ annotations: {
740
+ readOnlyHint: true,
741
+ idempotentHint: true,
742
+ openWorldHint: false,
743
+ },
733
744
  outputSchema: ProjectIdentityResultSchema,
734
745
  inputSchema: z.object({
735
746
  cwd: z.string().describe("Absolute path to the working directory"),
@@ -768,7 +779,18 @@ server.registerTool("detect_project", {
768
779
  // ── get_project_identity ───────────────────────────────────────────────────────
769
780
  server.registerTool("get_project_identity", {
770
781
  title: "Get Project Identity",
771
- description: "Show the effective project identity for a working directory, including any configured remote override.",
782
+ description: "Show the effective project identity for a working directory, including any configured remote override.\n\n" +
783
+ "Use this when:\n" +
784
+ "- You need to check whether a fork is using `upstream` vs `origin` for identity\n" +
785
+ "- Debugging project-scoping issues (notes not appearing for the expected project)\n\n" +
786
+ "Do not use this when:\n" +
787
+ "- You just need the project id — use `detect_project` instead\n\n" +
788
+ "Returns: effective project id/name, default project, and any identity override. Read-only.",
789
+ annotations: {
790
+ readOnlyHint: true,
791
+ idempotentHint: true,
792
+ openWorldHint: false,
793
+ },
772
794
  inputSchema: z.object({
773
795
  cwd: z.string().describe("Absolute path to the project working directory"),
774
796
  }),
@@ -804,7 +826,19 @@ server.registerTool("get_project_identity", {
804
826
  // ── set_project_identity ───────────────────────────────────────────────────────
805
827
  server.registerTool("set_project_identity", {
806
828
  title: "Set Project Identity",
807
- description: "Override which git remote defines project identity for a repo. Useful for forks that should follow `upstream` instead of `origin`.",
829
+ description: "Override which git remote defines project identity for a repo.\n\n" +
830
+ "Use this when:\n" +
831
+ "- Working in a fork and notes should associate with the upstream project, not the fork's origin\n\n" +
832
+ "Do not use this when:\n" +
833
+ "- The default `origin` remote already points to the canonical repo\n\n" +
834
+ "Side effects: writes to main vault config.json, git commits and pushes. " +
835
+ "Changes how all future memory operations resolve this project's identity.",
836
+ annotations: {
837
+ readOnlyHint: false,
838
+ destructiveHint: false,
839
+ idempotentHint: true,
840
+ openWorldHint: false,
841
+ },
808
842
  inputSchema: z.object({
809
843
  cwd: z.string().describe("Absolute path to the project working directory"),
810
844
  remoteName: z.string().min(1).describe("Git remote name to use as the canonical project identity, such as `upstream`")
@@ -876,7 +910,16 @@ server.registerTool("set_project_identity", {
876
910
  // ── list_migrations ───────────────────────────────────────────────────────────
877
911
  server.registerTool("list_migrations", {
878
912
  title: "List Migrations",
879
- description: "List available migrations and show which ones are pending for the current schema version",
913
+ description: "List available schema migrations and show which ones are pending for each vault.\n\n" +
914
+ "Use this when:\n" +
915
+ "- Checking if vaults need migration after a mnemonic upgrade\n" +
916
+ "- Before running `execute_migration` to see what would change\n\n" +
917
+ "Returns: vault schema versions, pending migration count, and available migration descriptions. Read-only.",
918
+ annotations: {
919
+ readOnlyHint: true,
920
+ idempotentHint: true,
921
+ openWorldHint: false,
922
+ },
880
923
  inputSchema: z.object({}),
881
924
  outputSchema: MigrationListResultSchema,
882
925
  }, async () => {
@@ -919,12 +962,24 @@ server.registerTool("list_migrations", {
919
962
  // ── execute_migration ─────────────────────────────────────────────────────────
920
963
  server.registerTool("execute_migration", {
921
964
  title: "Execute Migration",
922
- description: "Execute a named migration on vault notes",
965
+ description: "Execute a named schema migration on vault notes.\n\n" +
966
+ "Use this when:\n" +
967
+ "- `list_migrations` shows pending migrations that need to be applied\n\n" +
968
+ "Do not use this when:\n" +
969
+ "- You haven't run `list_migrations` first to check what's pending\n\n" +
970
+ "Always run with `dryRun: true` first to preview changes, then re-run with `dryRun: false` to apply. " +
971
+ "Side effects: modifies note files, git commits and pushes per vault.",
972
+ annotations: {
973
+ readOnlyHint: false,
974
+ destructiveHint: false,
975
+ idempotentHint: true,
976
+ openWorldHint: false,
977
+ },
923
978
  inputSchema: z.object({
924
- migrationName: z.string().describe("Name of the migration to execute"),
925
- dryRun: z.boolean().default(true).describe("If true, show what would change without actually modifying notes"),
979
+ migrationName: z.string().describe("Name of the migration to execute (get names from `list_migrations`)"),
980
+ dryRun: z.boolean().default(true).describe("If true, show what would change without actually modifying notes. Always try dry-run first."),
926
981
  backup: z.boolean().default(true).describe("If true, warn about backing up before real migration"),
927
- cwd: projectParam.optional().describe("Optional: limit to project vault for given working directory"),
982
+ cwd: projectParam.optional().describe("Optional: limit migration to a specific project vault"),
928
983
  }),
929
984
  outputSchema: MigrationExecuteResultSchema,
930
985
  }, async ({ migrationName, dryRun, backup, cwd }) => {
@@ -992,23 +1047,40 @@ server.registerTool("execute_migration", {
992
1047
  // ── remember ──────────────────────────────────────────────────────────────────
993
1048
  server.registerTool("remember", {
994
1049
  title: "Remember",
995
- description: "Store a new memory. `cwd` sets project context. `scope` picks whether the note " +
996
- "is stored in the shared project vault or the private main vault. When omitted, " +
997
- "the project's default policy is used before falling back to legacy behavior.",
1050
+ description: "Store a new memory as a markdown note with embeddings for semantic search.\n\n" +
1051
+ "Use this when:\n" +
1052
+ "- A decision, preference, bug fix, or useful context should survive beyond this session\n" +
1053
+ "- You learn something the user had to explain that isn't obvious from the codebase\n\n" +
1054
+ "Do not use this when:\n" +
1055
+ "- A note on this topic already exists — use `recall` to check first, then `update` instead\n" +
1056
+ "- Multiple related notes have accumulated — use `consolidate` to merge them\n\n" +
1057
+ "After storing: consider whether this note relates to any note you recalled earlier in this session. " +
1058
+ "If so, call `relate` to link them while you still have context.\n\n" +
1059
+ "Side effects: writes note + embedding files, git commits. Returns persistence status.",
1060
+ annotations: {
1061
+ readOnlyHint: false,
1062
+ destructiveHint: false,
1063
+ idempotentHint: false,
1064
+ openWorldHint: true,
1065
+ },
998
1066
  inputSchema: z.object({
999
- title: z.string().describe("Short descriptive title"),
1000
- content: z.string().describe("The content to remember (markdown supported; write summary-first with the key fact or decision near the top)"),
1001
- tags: z.array(z.string()).optional().default([]).describe("Optional tags"),
1067
+ title: z.string().describe("Short, searchable title — e.g. 'JWT RS256 migration rationale', not 'auth stuff'"),
1068
+ content: z.string().describe("Markdown content. Write summary-first: put the main fact, decision, or outcome in the opening sentences, " +
1069
+ "then follow with supporting detail. Embeddings weight early content more heavily."),
1070
+ tags: z.array(z.string()).optional().default([]).describe("Searchable tags — use terms you'd search for later"),
1002
1071
  lifecycle: z
1003
1072
  .enum(NOTE_LIFECYCLES)
1004
1073
  .optional()
1005
- .describe("Whether the note is temporary working-state scaffolding or durable permanent knowledge"),
1006
- summary: z.string().optional().describe("Brief summary for git commit message (like a good commit message, describing the change). Not stored in the note."),
1074
+ .describe("Choose 'temporary' for working-state notes (plans, WIP, investigation state) that lose value once work completes. " +
1075
+ "Choose 'permanent' for durable knowledge (decisions, constraints, lessons, bug causes). Default: permanent."),
1076
+ summary: z.string().optional().describe("Git commit message summary (not stored in note). Imperative mood, 50-72 chars, explain 'why' not 'what'. " +
1077
+ "Example: 'Add JWT RS256 migration decision for distributed auth'"),
1007
1078
  cwd: projectParam,
1008
1079
  scope: z
1009
1080
  .enum(WRITE_SCOPES)
1010
1081
  .optional()
1011
- .describe("Where to store the memory: project vault or private global vault"),
1082
+ .describe("Where to store: 'project' = shared project vault (.mnemonic/), " +
1083
+ "'global' = private main vault. When omitted, uses the project's saved policy or defaults to 'project'."),
1012
1084
  }),
1013
1085
  outputSchema: RememberResultSchema,
1014
1086
  }, async ({ title, content, tags, lifecycle, summary, cwd, scope }) => {
@@ -1086,12 +1158,23 @@ server.registerTool("remember", {
1086
1158
  // ── set_project_memory_policy ─────────────────────────────────────────────────
1087
1159
  server.registerTool("set_project_memory_policy", {
1088
1160
  title: "Set Project Memory Policy",
1089
- description: "Choose the default write scope and consolidation mode for a project. " +
1090
- "This lets agents avoid asking where to store memories and how to handle consolidation.",
1161
+ description: "Set the default write scope and consolidation mode for a project.\n\n" +
1162
+ "Use this when:\n" +
1163
+ "- The user wants all memories for a project to go to a specific vault by default\n" +
1164
+ "- The current 'ask' behavior is inconvenient and should be automated\n\n" +
1165
+ "Do not use this when:\n" +
1166
+ "- You just need to store a single memory with a specific scope — pass `scope` to `remember` instead\n\n" +
1167
+ "Side effects: writes to main vault config.json, git commits and pushes.",
1168
+ annotations: {
1169
+ readOnlyHint: false,
1170
+ destructiveHint: false,
1171
+ idempotentHint: true,
1172
+ openWorldHint: false,
1173
+ },
1091
1174
  inputSchema: z.object({
1092
1175
  cwd: z.string().describe("Absolute path to the project working directory"),
1093
- defaultScope: z.enum(PROJECT_POLICY_SCOPES).describe("Default storage location for project-related memories"),
1094
- consolidationMode: z.enum(CONSOLIDATION_MODES).optional().describe("Default consolidation mode: 'supersedes' preserves history (default), 'delete' removes sources"),
1176
+ defaultScope: z.enum(PROJECT_POLICY_SCOPES).describe("Default storage: 'project' = shared .mnemonic/ vault, 'global' = private main vault, 'ask' = prompt each time"),
1177
+ consolidationMode: z.enum(CONSOLIDATION_MODES).optional().describe("Default consolidation mode: 'supersedes' preserves history (default), 'delete' removes sources immediately"),
1095
1178
  }),
1096
1179
  outputSchema: PolicyResultSchema,
1097
1180
  }, async ({ cwd, defaultScope, consolidationMode }) => {
@@ -1133,7 +1216,16 @@ server.registerTool("set_project_memory_policy", {
1133
1216
  // ── get_project_memory_policy ─────────────────────────────────────────────────
1134
1217
  server.registerTool("get_project_memory_policy", {
1135
1218
  title: "Get Project Memory Policy",
1136
- description: "Show the current default write scope for a project, if one exists.",
1219
+ description: "Show the saved default write scope and consolidation mode for a project.\n\n" +
1220
+ "Use this when:\n" +
1221
+ "- Checking what the current storage default is before changing it\n" +
1222
+ "- Debugging why memories are landing in an unexpected vault\n\n" +
1223
+ "Returns: saved policy or 'no policy set' with default behavior explanation. Read-only.",
1224
+ annotations: {
1225
+ readOnlyHint: true,
1226
+ idempotentHint: true,
1227
+ openWorldHint: false,
1228
+ },
1137
1229
  inputSchema: z.object({
1138
1230
  cwd: z.string().describe("Absolute path to the project working directory"),
1139
1231
  }),
@@ -1175,24 +1267,37 @@ server.registerTool("get_project_memory_policy", {
1175
1267
  // ── recall ────────────────────────────────────────────────────────────────────
1176
1268
  server.registerTool("recall", {
1177
1269
  title: "Recall",
1178
- description: "Semantic search over memories. " +
1179
- "When `cwd` is provided, searches both the project vault (.mnemonic/) and the " +
1180
- "main vault — project memories are boosted by +0.15 and shown first. " +
1270
+ description: "Semantic search over stored memories using embeddings.\n\n" +
1271
+ "Use this when:\n" +
1272
+ "- Starting a session search broadly (e.g. 'project overview architecture decisions') to orient on prior context\n" +
1273
+ "- Before calling `remember` — check if a note on this topic already exists (use `update` instead if so)\n" +
1274
+ "- The user mentions something unfamiliar — you may already have context stored\n" +
1275
+ "- You need to find a specific decision, bug fix, or piece of tribal knowledge\n\n" +
1276
+ "Do not use this when:\n" +
1277
+ "- You know the exact note id — use `get` instead\n" +
1278
+ "- You want a structural overview — use `project_memory_summary` instead\n\n" +
1279
+ "When `cwd` is provided, searches both project vault and main vault with project notes boosted +0.15. " +
1181
1280
  "Without `cwd`, searches only the main vault. " +
1182
- "Missing or stale embeddings (e.g. from a git pull or direct editor edit) are backfilled on demand before searching.",
1281
+ "Stale embeddings are backfilled on demand before searching.\n\n" +
1282
+ "After results: check the `related:` line on each result — call `get` with those ids to pull in linked context.",
1283
+ annotations: {
1284
+ readOnlyHint: true,
1285
+ idempotentHint: true,
1286
+ openWorldHint: true,
1287
+ },
1183
1288
  inputSchema: z.object({
1184
- query: z.string().describe("What to search for"),
1289
+ query: z.string().describe("Natural language search query — describe what you're looking for"),
1185
1290
  cwd: projectParam,
1186
1291
  limit: z.number().int().min(1).max(20).optional().default(DEFAULT_RECALL_LIMIT),
1187
1292
  minSimilarity: z.number().min(0).max(1).optional().default(DEFAULT_MIN_SIMILARITY),
1188
- tags: z.array(z.string()).optional().describe("Optional tag filter"),
1293
+ tags: z.array(z.string()).optional().describe("Filter results to notes with all of these tags"),
1189
1294
  scope: z
1190
1295
  .enum(["project", "global", "all"])
1191
1296
  .optional()
1192
1297
  .default("all")
1193
- .describe("'project' = only project memories, " +
1298
+ .describe("'project' = only this project's memories, " +
1194
1299
  "'global' = only unscoped memories, " +
1195
- "'all' = project-boosted then global (default)"),
1300
+ "'all' = both, with project notes boosted (default)"),
1196
1301
  }),
1197
1302
  outputSchema: RecallResultSchema,
1198
1303
  }, async ({ query, cwd, limit, minSimilarity, tags, scope }) => {
@@ -1279,17 +1384,32 @@ server.registerTool("recall", {
1279
1384
  // ── update ────────────────────────────────────────────────────────────────────
1280
1385
  server.registerTool("update", {
1281
1386
  title: "Update Memory",
1282
- description: "Update the content, title, or tags of an existing memory by id. `cwd` helps locate project notes but does not change project metadata.",
1387
+ description: "Update the content, title, tags, or lifecycle of an existing memory by id.\n\n" +
1388
+ "Use this when:\n" +
1389
+ "- A stored memory is outdated — a decision was revisited, a dependency upgraded, a pattern changed\n" +
1390
+ "- You `recall` something and notice it's stale or partially wrong\n" +
1391
+ "- You want to add detail to an existing note rather than creating a duplicate\n\n" +
1392
+ "Do not use this when:\n" +
1393
+ "- No note exists on this topic yet — use `remember` instead\n" +
1394
+ "- Multiple notes need merging — use `consolidate` instead\n\n" +
1395
+ "Side effects: re-embeds the note, git commits. Only provided fields are changed; omitted fields keep their current values. " +
1396
+ "Project metadata is not changed by this tool.",
1397
+ annotations: {
1398
+ readOnlyHint: false,
1399
+ destructiveHint: false,
1400
+ idempotentHint: false,
1401
+ openWorldHint: true,
1402
+ },
1283
1403
  inputSchema: z.object({
1284
- id: z.string().describe("Memory id to update"),
1285
- content: z.string().optional(),
1286
- title: z.string().optional(),
1287
- tags: z.array(z.string()).optional(),
1404
+ id: z.string().describe("Memory id to update (get this from `recall` or `list`)"),
1405
+ content: z.string().optional().describe("New content (replaces existing). Write summary-first."),
1406
+ title: z.string().optional().describe("New title"),
1407
+ tags: z.array(z.string()).optional().describe("New tags (replaces existing tag list)"),
1288
1408
  lifecycle: z
1289
1409
  .enum(NOTE_LIFECYCLES)
1290
1410
  .optional()
1291
- .describe("Set to temporary for working-state notes or permanent for durable knowledge"),
1292
- summary: z.string().optional().describe("Brief summary of what changed and why (for git commit message). Not stored in the note."),
1411
+ .describe("Change lifecycle. Preserve the existing value unless you're intentionally switching it."),
1412
+ summary: z.string().optional().describe("Git commit message summary explaining what changed and why. Not stored in the note."),
1293
1413
  cwd: projectParam,
1294
1414
  }),
1295
1415
  outputSchema: UpdateResultSchema,
@@ -1365,9 +1485,23 @@ server.registerTool("update", {
1365
1485
  // ── forget ────────────────────────────────────────────────────────────────────
1366
1486
  server.registerTool("forget", {
1367
1487
  title: "Forget",
1368
- description: "Delete a memory by id. Pass `cwd` when targeting project memories from a fresh project-scoped server.",
1488
+ description: "Permanently delete a memory and its embedding. Cleans up dangling relationship references in other notes.\n\n" +
1489
+ "Use this when:\n" +
1490
+ "- A memory is fully superseded and keeping it would cause confusion\n" +
1491
+ "- The content is factually wrong and should not appear in search results\n\n" +
1492
+ "Do not use this when:\n" +
1493
+ "- The memory is just outdated — use `update` to correct it instead\n" +
1494
+ "- The memory has historical value — use `relate` with type 'supersedes' to mark it replaced\n" +
1495
+ "- You want to merge notes — use `consolidate` instead\n\n" +
1496
+ "Side effects: deletes note + embedding files, removes relationship references from other notes, git commits per vault.",
1497
+ annotations: {
1498
+ readOnlyHint: false,
1499
+ destructiveHint: true,
1500
+ idempotentHint: true,
1501
+ openWorldHint: false,
1502
+ },
1369
1503
  inputSchema: z.object({
1370
- id: z.string().describe("Memory id to delete"),
1504
+ id: z.string().describe("Memory id to permanently delete"),
1371
1505
  cwd: projectParam,
1372
1506
  }),
1373
1507
  outputSchema: ForgetResultSchema,
@@ -1408,8 +1542,19 @@ server.registerTool("forget", {
1408
1542
  // ── get ───────────────────────────────────────────────────────────────────────
1409
1543
  server.registerTool("get", {
1410
1544
  title: "Get Memory",
1411
- description: "Fetch one or more notes by exact id. Returns full note content, metadata, and relationships. " +
1412
- "Pass `cwd` to search the project vault when looking up project notes.",
1545
+ description: "Fetch one or more notes by exact id. Returns full content, metadata, tags, lifecycle, and relationships.\n\n" +
1546
+ "Use this when:\n" +
1547
+ "- You know the exact note id (from `recall` results, `related:` links, or `list` output)\n" +
1548
+ "- You need full content that `recall` or `list` only summarized\n\n" +
1549
+ "Do not use this when:\n" +
1550
+ "- You're searching by topic — use `recall` instead\n" +
1551
+ "- You only need location metadata — use `where_is_memory` instead\n\n" +
1552
+ "Read-only. Supports batch fetching of multiple ids in one call.",
1553
+ annotations: {
1554
+ readOnlyHint: true,
1555
+ idempotentHint: true,
1556
+ openWorldHint: false,
1557
+ },
1413
1558
  inputSchema: z.object({
1414
1559
  ids: z.array(z.string()).min(1).describe("One or more memory ids to fetch"),
1415
1560
  cwd: projectParam,
@@ -1463,9 +1608,18 @@ server.registerTool("get", {
1463
1608
  // ── where_is_memory ───────────────────────────────────────────────────────────
1464
1609
  server.registerTool("where_is_memory", {
1465
1610
  title: "Where Is Memory",
1466
- description: "Show a memory's project association and actual storage location (main vault or project vault). " +
1467
- "Lightweight alternative to `get` when you only need location metadata, not content. " +
1468
- "Pass `cwd` to include the project vault when searching.",
1611
+ description: "Show a memory's project association and storage location without fetching full content.\n\n" +
1612
+ "Use this when:\n" +
1613
+ "- Checking whether a note lives in the project vault or main vault before a `move_memory`\n" +
1614
+ "- Debugging storage routing — a note landed in the wrong vault\n\n" +
1615
+ "Do not use this when:\n" +
1616
+ "- You need the note's content — use `get` instead\n\n" +
1617
+ "Read-only. Returns: title, project association, vault, last updated, relationship count.",
1618
+ annotations: {
1619
+ readOnlyHint: true,
1620
+ idempotentHint: true,
1621
+ openWorldHint: false,
1622
+ },
1469
1623
  inputSchema: z.object({
1470
1624
  id: z.string().describe("Memory id to locate"),
1471
1625
  cwd: projectParam,
@@ -1503,7 +1657,22 @@ server.registerTool("where_is_memory", {
1503
1657
  // ── list ──────────────────────────────────────────────────────────────────────
1504
1658
  server.registerTool("list", {
1505
1659
  title: "List Memories",
1506
- description: "List stored memories. Pass `cwd` to include the project vault, or omit for main vault only.",
1660
+ description: "List stored memories with filtering by scope, vault, and tags.\n\n" +
1661
+ "Use this when:\n" +
1662
+ "- Browsing all memories for a project or globally\n" +
1663
+ "- Filtering by tags to find related notes\n" +
1664
+ "- Checking what exists before deciding whether to `remember` or `update`\n\n" +
1665
+ "Do not use this when:\n" +
1666
+ "- Searching by topic — use `recall` for semantic search\n" +
1667
+ "- You want a high-level project overview — use `project_memory_summary` instead\n" +
1668
+ "- You want recent changes — use `recent_memories` instead\n\n" +
1669
+ "Read-only. Returns id, title, tags, lifecycle for each note. " +
1670
+ "Use optional flags to include previews, storage location, timestamps, or relationships.",
1671
+ annotations: {
1672
+ readOnlyHint: true,
1673
+ idempotentHint: true,
1674
+ openWorldHint: false,
1675
+ },
1507
1676
  inputSchema: z.object({
1508
1677
  cwd: projectParam,
1509
1678
  scope: z
@@ -1515,12 +1684,12 @@ server.registerTool("list", {
1515
1684
  .enum(["project-vault", "main-vault", "any"])
1516
1685
  .optional()
1517
1686
  .default("any")
1518
- .describe("Filter by actual storage location instead of project association"),
1519
- tags: z.array(z.string()).optional().describe("Optional tag filter"),
1520
- includeRelations: z.boolean().optional().default(false).describe("Include related memory ids/types"),
1521
- includePreview: z.boolean().optional().default(false).describe("Include a short content preview for each memory"),
1522
- includeStorage: z.boolean().optional().default(false).describe("Include whether the memory lives in the project vault or main vault"),
1523
- includeUpdated: z.boolean().optional().default(false).describe("Include the last updated timestamp for each memory"),
1687
+ .describe("Filter by actual storage location (vault) instead of project association"),
1688
+ tags: z.array(z.string()).optional().describe("Filter to notes matching all of these tags"),
1689
+ includeRelations: z.boolean().optional().default(false).describe("Include related memory ids and relationship types"),
1690
+ includePreview: z.boolean().optional().default(false).describe("Include a short content preview for each note"),
1691
+ includeStorage: z.boolean().optional().default(false).describe("Show which vault each note is stored in"),
1692
+ includeUpdated: z.boolean().optional().default(false).describe("Include last-updated timestamp for each note"),
1524
1693
  }),
1525
1694
  outputSchema: ListResultSchema,
1526
1695
  }, async ({ cwd, scope, storedIn, tags, includeRelations, includePreview, includeStorage, includeUpdated }) => {
@@ -1569,7 +1738,20 @@ server.registerTool("list", {
1569
1738
  // ── recent_memories ───────────────────────────────────────────────────────────
1570
1739
  server.registerTool("recent_memories", {
1571
1740
  title: "Recent Memories",
1572
- description: "Show the most recently updated memories for the current project or global vault.",
1741
+ description: "Show the most recently updated memories, sorted by last modification time.\n\n" +
1742
+ "Use this when:\n" +
1743
+ "- Orienting at session start to see what was last worked on\n" +
1744
+ "- Catching stale notes that may need updating\n" +
1745
+ "- Reviewing what changed since last session\n\n" +
1746
+ "Do not use this when:\n" +
1747
+ "- Searching by topic — use `recall` instead\n" +
1748
+ "- You want a thematic overview — use `project_memory_summary` instead\n\n" +
1749
+ "Read-only. Includes content previews and storage location by default.",
1750
+ annotations: {
1751
+ readOnlyHint: true,
1752
+ idempotentHint: true,
1753
+ openWorldHint: false,
1754
+ },
1573
1755
  inputSchema: z.object({
1574
1756
  cwd: projectParam,
1575
1757
  scope: z.enum(["project", "global", "all"]).optional().default("all"),
@@ -1621,7 +1803,20 @@ server.registerTool("recent_memories", {
1621
1803
  // ── memory_graph ──────────────────────────────────────────────────────────────
1622
1804
  server.registerTool("memory_graph", {
1623
1805
  title: "Memory Graph",
1624
- description: "Show memory relationships for the current project or selected scope as a compact adjacency list.",
1806
+ description: "Show memory relationships as a compact adjacency list.\n\n" +
1807
+ "Use this when:\n" +
1808
+ "- Spotting dense clusters of related notes — these are consolidation candidates\n" +
1809
+ "- Understanding how notes connect before a `consolidate` operation\n" +
1810
+ "- Visualizing the knowledge structure for a project\n\n" +
1811
+ "Do not use this when:\n" +
1812
+ "- You need note content — use `get` instead\n" +
1813
+ "- You want a thematic overview — use `project_memory_summary` instead\n\n" +
1814
+ "Read-only. Only shows notes that have at least one relationship.",
1815
+ annotations: {
1816
+ readOnlyHint: true,
1817
+ idempotentHint: true,
1818
+ openWorldHint: false,
1819
+ },
1625
1820
  inputSchema: z.object({
1626
1821
  cwd: projectParam,
1627
1822
  scope: z.enum(["project", "global", "all"]).optional().default("all"),
@@ -1682,7 +1877,19 @@ server.registerTool("memory_graph", {
1682
1877
  // ── project_memory_summary ────────────────────────────────────────────────────
1683
1878
  server.registerTool("project_memory_summary", {
1684
1879
  title: "Project Memory Summary",
1685
- description: "Summarize what mnemonic currently knows about a project, including policy, themes, recent changes, and storage layout.",
1880
+ description: "Get a rich overview of everything mnemonic knows about a project: policy, themes, note counts, storage layout, and recent changes.\n\n" +
1881
+ "Use this when:\n" +
1882
+ "- Starting a session — call this after `detect_project` to orient on prior context before doing work\n" +
1883
+ "- Checking how many notes exist and how they're distributed across vaults\n\n" +
1884
+ "Do not use this when:\n" +
1885
+ "- Searching for a specific topic — use `recall` instead\n" +
1886
+ "- You want just the recent notes — use `recent_memories` instead\n\n" +
1887
+ "Read-only. Groups notes by theme (decisions, architecture, tooling, bugs, etc.) with examples.",
1888
+ annotations: {
1889
+ readOnlyHint: true,
1890
+ idempotentHint: true,
1891
+ openWorldHint: false,
1892
+ },
1686
1893
  inputSchema: z.object({
1687
1894
  cwd: z.string().describe("Absolute path to the project working directory"),
1688
1895
  maxPerTheme: z.number().int().min(1).max(5).optional().default(3),
@@ -1763,14 +1970,24 @@ server.registerTool("project_memory_summary", {
1763
1970
  // ── sync ──────────────────────────────────────────────────────────────────────
1764
1971
  server.registerTool("sync", {
1765
1972
  title: "Sync",
1766
- description: "Bring the vault to a fully operational state: git sync when a remote exists, " +
1767
- "plus embedding backfill always. Use `force=true` to rebuild all embeddings. " +
1768
- "Always syncs the main vault. " +
1769
- "When `cwd` is provided, also syncs the project vault (.mnemonic/) " +
1770
- "so you pull in notes added by collaborators.",
1973
+ description: "Pull remote changes, push local commits, and backfill missing embeddings.\n\n" +
1974
+ "Use this when:\n" +
1975
+ "- Starting a session and you want the latest notes from collaborators\n" +
1976
+ "- Embedding model changed use `force: true` to rebuild all embeddings\n" +
1977
+ "- Notes were added by direct file edit or git pull and need embedding backfill\n\n" +
1978
+ "Do not use this when:\n" +
1979
+ "- You just called `remember` or `update` — those tools handle their own embedding and commit\n\n" +
1980
+ "Always syncs the main vault. When `cwd` is provided, also syncs the project vault (.mnemonic/). " +
1981
+ "Side effects: git fetch/pull/push, Ollama embedding API calls.",
1982
+ annotations: {
1983
+ readOnlyHint: false,
1984
+ destructiveHint: false,
1985
+ idempotentHint: true,
1986
+ openWorldHint: true,
1987
+ },
1771
1988
  inputSchema: z.object({
1772
1989
  cwd: projectParam,
1773
- force: z.boolean().optional().default(false).describe("Rebuild all embeddings even if current model already has them"),
1990
+ force: z.boolean().optional().default(false).describe("Rebuild all embeddings even if the current model already generated them"),
1774
1991
  }),
1775
1992
  outputSchema: SyncResultSchema,
1776
1993
  }, async ({ cwd, force }) => {
@@ -1833,10 +2050,25 @@ server.registerTool("sync", {
1833
2050
  // ── move_memory ───────────────────────────────────────────────────────────────
1834
2051
  server.registerTool("move_memory", {
1835
2052
  title: "Move Memory",
1836
- description: "Move a memory between the main vault and the current project's vault without changing its id or project metadata.",
2053
+ description: "Move a memory between the main vault and the project vault without changing its id.\n\n" +
2054
+ "Use this when:\n" +
2055
+ "- A note was stored in the wrong vault and needs to be relocated\n" +
2056
+ "- A private note (main vault) should become shared (project vault) or vice versa\n\n" +
2057
+ "Do not use this when:\n" +
2058
+ "- You want to change the note's content — use `update` instead\n" +
2059
+ "- You want to delete the note — use `forget` instead\n\n" +
2060
+ "When moving to project vault, project metadata is rewritten from `cwd`. " +
2061
+ "When moving to main vault, existing project association is preserved. " +
2062
+ "Side effects: writes to target vault, deletes from source vault, git commits per vault.",
2063
+ annotations: {
2064
+ readOnlyHint: false,
2065
+ destructiveHint: false,
2066
+ idempotentHint: true,
2067
+ openWorldHint: false,
2068
+ },
1837
2069
  inputSchema: z.object({
1838
2070
  id: z.string().describe("Memory id to move"),
1839
- target: z.enum(["main-vault", "project-vault"]).describe("Destination storage location"),
2071
+ target: z.enum(["main-vault", "project-vault"]).describe("Destination: 'main-vault' for private storage, 'project-vault' for shared project storage"),
1840
2072
  cwd: projectParam,
1841
2073
  }),
1842
2074
  outputSchema: MoveResultSchema,
@@ -1926,15 +2158,30 @@ const RELATIONSHIP_TYPES = [
1926
2158
  ];
1927
2159
  server.registerTool("relate", {
1928
2160
  title: "Relate Memories",
1929
- description: "Create a typed relationship between two memories. " +
1930
- "By default adds the relationship in both directions. " +
1931
- "Notes may be in different vaults each vault gets its own commit. " +
1932
- "Pass `cwd` to include the current project vault when resolving ids.",
2161
+ description: "Create a typed, bidirectional relationship between two memories.\n\n" +
2162
+ "Use this when:\n" +
2163
+ "- You just stored a note that connects to something you recalled earlier in this session\n" +
2164
+ "- Two notes cover the same topic, one explains the other, or one replaces the other\n\n" +
2165
+ "Do not use this when:\n" +
2166
+ "- The connection is weak or forced — don't over-link, one or two meaningful edges per note is better\n" +
2167
+ "- You want to remove a relationship — use `unrelate` instead\n\n" +
2168
+ "Relationship types:\n" +
2169
+ "- 'related-to': same topic or area\n" +
2170
+ "- 'explains': clarifies why the other note's decision was made\n" +
2171
+ "- 'example-of': concrete instance of a general pattern\n" +
2172
+ "- 'supersedes': replaces a previous decision or approach\n\n" +
2173
+ "Side effects: modifies both notes' frontmatter, git commits per vault. Notes may be in different vaults.",
2174
+ annotations: {
2175
+ readOnlyHint: false,
2176
+ destructiveHint: false,
2177
+ idempotentHint: true,
2178
+ openWorldHint: false,
2179
+ },
1933
2180
  inputSchema: z.object({
1934
- fromId: z.string().describe("The source memory id"),
1935
- toId: z.string().describe("The target memory id"),
1936
- type: z.enum(RELATIONSHIP_TYPES).default("related-to"),
1937
- bidirectional: z.boolean().optional().default(true),
2181
+ fromId: z.string().describe("Source memory id"),
2182
+ toId: z.string().describe("Target memory id"),
2183
+ type: z.enum(RELATIONSHIP_TYPES).default("related-to").describe("Relationship type: 'related-to' (same topic), 'explains' (clarifies why), 'example-of' (instance of pattern), 'supersedes' (replaces)"),
2184
+ bidirectional: z.boolean().optional().default(true).describe("Add relationship in both directions (default: true)"),
1938
2185
  cwd: projectParam,
1939
2186
  }),
1940
2187
  outputSchema: RelateResultSchema,
@@ -2007,11 +2254,22 @@ server.registerTool("relate", {
2007
2254
  // ── unrelate ──────────────────────────────────────────────────────────────────
2008
2255
  server.registerTool("unrelate", {
2009
2256
  title: "Remove Relationship",
2010
- description: "Remove the relationship between two memories. Pass `cwd` to include the current project vault.",
2257
+ description: "Remove an existing relationship between two memories.\n\n" +
2258
+ "Use this when:\n" +
2259
+ "- A relationship was created incorrectly or is no longer relevant\n\n" +
2260
+ "Do not use this when:\n" +
2261
+ "- You want to delete the note entirely — use `forget` instead\n\n" +
2262
+ "Side effects: modifies both notes' frontmatter (when bidirectional), git commits per vault.",
2263
+ annotations: {
2264
+ readOnlyHint: false,
2265
+ destructiveHint: true,
2266
+ idempotentHint: true,
2267
+ openWorldHint: false,
2268
+ },
2011
2269
  inputSchema: z.object({
2012
- fromId: z.string().describe("The source memory id"),
2013
- toId: z.string().describe("The target memory id"),
2014
- bidirectional: z.boolean().optional().default(true),
2270
+ fromId: z.string().describe("Source memory id"),
2271
+ toId: z.string().describe("Target memory id"),
2272
+ bidirectional: z.boolean().optional().default(true).describe("Remove relationship in both directions (default: true)"),
2015
2273
  cwd: projectParam,
2016
2274
  }),
2017
2275
  outputSchema: RelateResultSchema,
@@ -2074,9 +2332,28 @@ server.registerTool("unrelate", {
2074
2332
  // ── consolidate ───────────────────────────────────────────────────────────────
2075
2333
  server.registerTool("consolidate", {
2076
2334
  title: "Consolidate Memories",
2077
- description: "Analyze memories for consolidation opportunities or execute merges. " +
2078
- "Strategies that modify data (execute-merge, prune-superseded) require confirmation. " +
2079
- "Cross-vault: gathers notes from both main and project vaults for the detected project.",
2335
+ description: "Analyze memories for duplicates and clusters, or merge multiple notes into one.\n\n" +
2336
+ "Use this when:\n" +
2337
+ "- 3+ notes on the same topic have accumulated from incremental captures\n" +
2338
+ "- A feature or bug arc is complete and related notes can be synthesized\n" +
2339
+ "- `memory_graph` shows a dense cluster of tightly-related nodes\n\n" +
2340
+ "Do not use this when:\n" +
2341
+ "- Only one note needs updating — use `update` instead\n" +
2342
+ "- Notes are on different topics — don't force a merge\n\n" +
2343
+ "Workflow: start with 'dry-run' or 'suggest-merges' to see recommendations, then 'execute-merge' with a mergePlan. " +
2344
+ "Optionally follow up with 'prune-superseded' to clean up old notes.\n\n" +
2345
+ "Modes: 'supersedes' preserves source notes with a supersedes relationship (default). " +
2346
+ "'delete' removes source notes immediately. " +
2347
+ "When all sources are temporary, prefer 'delete' so scaffolding is cleaned up.\n\n" +
2348
+ "Read-only strategies: detect-duplicates, find-clusters, suggest-merges, dry-run. " +
2349
+ "Mutating strategies: execute-merge, prune-superseded. " +
2350
+ "Gathers notes from both main and project vaults.",
2351
+ annotations: {
2352
+ readOnlyHint: false,
2353
+ destructiveHint: true,
2354
+ idempotentHint: false,
2355
+ openWorldHint: true,
2356
+ },
2080
2357
  inputSchema: z.object({
2081
2358
  cwd: projectParam,
2082
2359
  strategy: z
@@ -2088,29 +2365,31 @@ server.registerTool("consolidate", {
2088
2365
  "prune-superseded",
2089
2366
  "dry-run",
2090
2367
  ])
2091
- .describe("Analysis or action to perform"),
2368
+ .describe("What to do: 'dry-run' = full analysis without changes, 'detect-duplicates' = find similar pairs, " +
2369
+ "'find-clusters' = group by theme and relationships, 'suggest-merges' = actionable merge recommendations, " +
2370
+ "'execute-merge' = perform a merge (requires mergePlan), 'prune-superseded' = delete notes marked as superseded"),
2092
2371
  mode: z
2093
2372
  .enum(CONSOLIDATION_MODES)
2094
2373
  .optional()
2095
- .describe("Override the project's default consolidation mode (supersedes or delete)"),
2374
+ .describe("Override the project's default: 'supersedes' preserves history, 'delete' removes sources immediately"),
2096
2375
  threshold: z
2097
2376
  .number()
2098
2377
  .min(0)
2099
2378
  .max(1)
2100
2379
  .optional()
2101
2380
  .default(0.85)
2102
- .describe("Similarity threshold for detecting duplicates"),
2381
+ .describe("Cosine similarity threshold for duplicate detection (0.85 default)"),
2103
2382
  mergePlan: z
2104
2383
  .object({
2105
- sourceIds: z.array(z.string()).min(2).describe("Notes to merge into a single consolidated note"),
2384
+ sourceIds: z.array(z.string()).min(2).describe("Ids of notes to merge into one consolidated note"),
2106
2385
  targetTitle: z.string().describe("Title for the consolidated note"),
2107
- content: z.string().optional().describe("Custom body for the consolidated note. When provided, replaces the auto-merged source content. Use this to distil only durable knowledge instead of dumping all source content verbatim."),
2108
- description: z.string().optional().describe("Optional context explaining the consolidation (stored in note)"),
2109
- summary: z.string().optional().describe("Brief summary of merge rationale (for git commit message only)"),
2386
+ content: z.string().optional().describe("Custom body for the consolidated note distill durable knowledge rather than dumping all source content verbatim"),
2387
+ description: z.string().optional().describe("Context explaining the consolidation rationale (stored in the note)"),
2388
+ summary: z.string().optional().describe("Git commit message summary (not stored in note)"),
2110
2389
  tags: z.array(z.string()).optional().describe("Tags for the consolidated note (defaults to union of source tags)"),
2111
2390
  })
2112
2391
  .optional()
2113
- .describe("Required for execute-merge strategy"),
2392
+ .describe("Required for 'execute-merge' strategy. Get sourceIds from 'suggest-merges' output."),
2114
2393
  }),
2115
2394
  outputSchema: ConsolidateResultSchema,
2116
2395
  }, async ({ cwd, strategy, mode, threshold, mergePlan }) => {