@danielmarbach/mnemonic-mcp 0.24.0 → 0.25.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@ 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
+ ## [Unreleased]
8
+
9
+ ## [0.25.1] - 2026-04-24
10
+
11
+ ### Changed
12
+
13
+ - `semanticPatch` no longer rejects content with markdown lint issues — the patch succeeds and warnings are surfaced in the response instead, so the LLM can continue without retrying from scratch. Previously any lint issue forced a hard failure.
14
+ - Structural errors (bad selector, invalid operation) and content lint errors are now distinguished from patch lint warnings with separate guidance, so the LLM gets the right fix direction for each failure mode.
15
+
16
+ ## [0.25.0] - 2026-04-24
17
+
18
+ ### Added
19
+
20
+ - `recall` now supports `mode: "workflow"` for RPIR-oriented chain retrieval while keeping compatibility with existing note graphs.
21
+ - Relationship types now include `derives-from` and `follows` so workflows can express directional sequencing and derivation explicitly.
22
+ - The npm package now ships bundled skills and includes a `mnemonic-install-skills` command to install or update local skill directories for Claude, OpenCode, and custom targets.
23
+
24
+ ### Changed
25
+
26
+ - RPIR workflow guidance now uses `mnemonic-rpi-workflow` as the prompt name.
27
+ - The RPIR skill is now published under `skills/mnemonic-rpi-workflow`.
28
+
7
29
  ## [0.24.0] - 2026-04-24
8
30
 
9
31
  ### Added
package/README.md CHANGED
@@ -51,8 +51,13 @@ No code changes required — set `EMBED_MODEL=qwen3-embedding:0.6b` in your envi
51
51
  npm install
52
52
  npm run build
53
53
  npm test
54
+
55
+ # release-confidence gate (build + full tests + isolated dogfooding)
56
+ npm run verify:release
54
57
  ```
55
58
 
59
+ The gate fails on required dogfood checks and reports advisory findings separately in the dogfood output.
60
+
56
61
  `npm run build` already runs `typecheck`, but running it explicitly first gives a faster failure loop when iterating on the codebase.
57
62
 
58
63
  For local dogfooding, start the built MCP server with:
@@ -94,6 +99,45 @@ npm install @danielmarbach/mnemonic-mcp
94
99
  npm install @danielmarbach/mnemonic-mcp@0.2.0
95
100
  ```
96
101
 
102
+ ### Install bundled skills (Claude/OpenCode)
103
+
104
+ The npm package now includes `skills/**` plus a helper binary to install them into local skill directories.
105
+
106
+ ```bash
107
+ # If mnemonic is installed in this project:
108
+ npx mnemonic-install-skills --target all --mode copy
109
+
110
+ # One-off install without adding dependency:
111
+ npx -y -p @danielmarbach/mnemonic-mcp mnemonic-install-skills --target all --mode copy
112
+ ```
113
+
114
+ Supported targets:
115
+
116
+ - `--target claude` -> `~/.claude/skills`
117
+ - `--target opencode` -> `~/.config/opencode/skills`
118
+ - `--target all` -> both (default)
119
+ - `--target custom` -> only use `--target-dir` destinations
120
+ - `--target-dir <path>` -> add any custom client skill directory
121
+
122
+ Update flow after upgrading `@danielmarbach/mnemonic-mcp`:
123
+
124
+ ```bash
125
+ npx mnemonic-install-skills --target all --mode copy --update
126
+ ```
127
+
128
+ If you prefer automatic propagation without copy refreshes, use symlink mode:
129
+
130
+ ```bash
131
+ npx mnemonic-install-skills --target all --mode symlink --update
132
+ ```
133
+
134
+ After install, load and use the skill by name:
135
+
136
+ - Skill name: `mnemonic-rpi-workflow`
137
+ - Prompt counterpart: `mnemonic-rpi-workflow`
138
+
139
+ In clients that support explicit skill loading (for example Claude Code or OpenCode), load `mnemonic-rpi-workflow` before running multi-step RPIR workflows.
140
+
97
141
  ### Homebrew
98
142
 
99
143
  The formula lives in this repository. Tap it with an explicit URL so no separate repository is needed:
@@ -312,7 +356,11 @@ Project identity derives from the **git remote URL**, normalized to a stable slu
312
356
 
313
357
  **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.
314
358
 
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.
359
+ Recall modes:
360
+
361
+ - `mode: "default"` (default): semantic recall with optional lexical reranking and bounded relationship previews.
362
+ - `mode: "temporal"`: enrich top matches with compact git-backed history (no raw diffs by default).
363
+ - `mode: "workflow"`: prioritize RPIR-style chain reconstruction while remaining compatible with legacy `related-to` links.
316
364
 
317
365
  **What temporal mode shows:**
318
366
 
@@ -341,11 +389,21 @@ Each note carries a `lifecycle`:
341
389
 
342
390
  ### Roles and lifecycle
343
391
 
344
- 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`.
392
+ 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`, `context`, `reference`, `research`, `review`. Valid importance values: `high`, `normal`, `low`.
345
393
 
346
394
  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.
347
395
 
348
- 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`.
396
+ mnemonic works without roles. Inferred roles stay internal-only, prioritization is language-independent by default, and lifecycle remains the separate durability axis. When `lifecycle` is omitted, `remember` applies soft defaults based on role: `research`, `plan`, and `review` default to `temporary`; `decision`, `summary`, and `reference` default to `permanent`. Explicit `lifecycle` always overrides the role-based default.
397
+
398
+ ### RPIR workflow conventions
399
+
400
+ For structured workflows, use the RPIR stages: research -> plan -> implement -> review (iterate only when needed).
401
+
402
+ - Create one request root note per workflow: `role: context`, `lifecycle: temporary`, `tags: ["workflow", "request"]`.
403
+ - Keep one current plan note per request (`role: plan`) and update or supersede as the plan evolves.
404
+ - For apply/task notes, do not add a new role: use `role: plan` for executable steps and `role: context` for execution observations; tag both with `apply`.
405
+ - Keep relationships sparse and immediate-upstream only: research -> request, plan -> request/research, apply -> plan, review -> apply/plan, outcome -> plan (optionally request).
406
+ - Consolidate at workflow end: promote durable outcomes into permanent decision/summary/reference notes; let temporary scaffolding expire.
349
407
 
350
408
  ### Note format
351
409
 
@@ -444,6 +502,7 @@ Imported notes are written to the main vault with `lifecycle: permanent` and `sc
444
502
 
445
503
  | Prompt | Description |
446
504
  |--------|-------------|
505
+ | `mnemonic-rpi-workflow` | Optional. Returns RPIR stage protocol and conventions: request root note pattern, stage checklists, apply/task split, sparse relationships, subagent handoff contract, and commit discipline. |
447
506
  | `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`. It also reinforces summary-first orientation via `project_memory_summary`, temporary-note recovery only after orientation, and that roles are optional prioritization hints while lifecycle stays separate. |
448
507
 
449
508
  ## Tools
@@ -463,7 +522,7 @@ Imported notes are written to the main vault with `lifecycle: permanent` and `sc
463
522
  | `memory_graph` | Show compact adjacency list of relationships |
464
523
  | `move_memory` | Move note between vaults without changing id |
465
524
  | `project_memory_summary` | Session-start entrypoint: themed notes, anchors, and orientation for fast project orientation |
466
- | `recall` | Semantic search with optional project boost and opt-in temporal history |
525
+ | `recall` | Semantic search with optional project boost, temporal history mode, and workflow chain mode |
467
526
  | `recent_memories` | Show most recently updated notes for scope |
468
527
  | `remember` | Write note + embedding; `cwd` sets context, `scope` picks storage, `lifecycle` picks temporary vs permanent |
469
528
  | `relate` | Create typed relationship between notes (bidirectional) |
@@ -504,6 +563,10 @@ relatedTo:
504
563
  | `explains` | `fromId` explains `toId` |
505
564
  | `example-of` | `fromId` is a concrete example of `toId` |
506
565
  | `supersedes` | `fromId` is the newer version of `toId` |
566
+ | `derives-from` | `fromId` is derived from `toId` |
567
+ | `follows` | `fromId` follows `toId` in sequence |
568
+
569
+ `workflow` recall mode prefers directional and typed relationships first, then falls back to `related-to` for long-term compatibility with older vaults.
507
570
 
508
571
  `relate` is bidirectional by default. `forget` automatically removes any edges pointing at the deleted note.
509
572
 
package/build/index.js CHANGED
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  import { randomUUID } from "crypto";
6
6
  import path from "path";
7
7
  import { promises as fs } from "fs";
8
- import { NOTE_LIFECYCLES } from "./storage.js";
8
+ import { NOTE_LIFECYCLES, NOTE_ROLES } from "./storage.js";
9
9
  import { embed, cosineSimilarity, embedModel } from "./embeddings.js";
10
10
  import { buildTemporalHistoryEntry, computeConfidence, getNoteProvenance } from "./provenance.js";
11
11
  import { enrichTemporalHistory } from "./temporal-interpretation.js";
@@ -14,10 +14,10 @@ import { invalidateActiveProjectCache, getOrBuildVaultEmbeddings, getOrBuildVaul
14
14
  import { performance } from "perf_hooks";
15
15
  import { filterRelationships, mergeRelationshipsFromNotes, normalizeMergePlanSourceIds, resolveEffectiveConsolidationMode, } from "./consolidate.js";
16
16
  import { suggestAutoRelationships } from "./auto-relate.js";
17
- import { computeRecallMetadataBoost, computeHybridScore, selectRecallResults, applyLexicalReranking, applyCanonicalExplanationPromotion, } from "./recall.js";
17
+ import { computeRecallMetadataBoost, computeHybridScore, selectRecallResults, selectWorkflowResults, applyLexicalReranking, applyCanonicalExplanationPromotion, } from "./recall.js";
18
18
  import { shouldTriggerLexicalRescue, rankDocumentsByTfIdf, LEXICAL_RESCUE_CANDIDATE_LIMIT, LEXICAL_RESCUE_THRESHOLD, LEXICAL_RESCUE_RESULT_LIMIT, } from "./lexical.js";
19
19
  import { getRelationshipPreview } from "./relationships.js";
20
- import { cleanMarkdown } from "./markdown.js";
20
+ import { MarkdownLintError, cleanMarkdown } from "./markdown.js";
21
21
  import { applySemanticPatches } from "./semantic-patch.js";
22
22
  import { MnemonicConfigStore, readVaultSchemaVersion } from "./config.js";
23
23
  import { CONSOLIDATION_MODES, PROTECTED_BRANCH_BEHAVIORS, PROJECT_POLICY_SCOPES, WRITE_SCOPES, isProtectedBranch, resolveProtectedBranchBehavior, resolveProtectedBranchPatterns, resolveConsolidationMode, resolveWriteScope, } from "./project-memory-policy.js";
@@ -407,12 +407,13 @@ function describeLifecycle(lifecycle) {
407
407
  function formatNote(note, score, showRawRelated = true) {
408
408
  const scoreStr = score !== undefined ? ` | similarity: ${score.toFixed(3)}` : "";
409
409
  const projectStr = note.project ? ` | project: ${note.projectName ?? note.project}` : " | global";
410
+ const roleStr = note.role ? ` | **role: ${note.role}**` : "";
410
411
  const relStr = showRawRelated && note.relatedTo && note.relatedTo.length > 0
411
412
  ? `\n**related:** ${note.relatedTo.map((r) => `\`${r.id}\` (${r.type})`).join(", ")}`
412
413
  : "";
413
414
  return (`## ${note.title}\n` +
414
415
  `**id:** \`${note.id}\`${projectStr}${scoreStr}\n` +
415
- `**tags:** ${note.tags.join(", ") || "none"} | **${describeLifecycle(note.lifecycle)}** | **updated:** ${note.updatedAt}${relStr}\n\n` +
416
+ `**tags:** ${note.tags.join(", ") || "none"} | **${describeLifecycle(note.lifecycle)}**${roleStr} | **updated:** ${note.updatedAt}${relStr}\n\n` +
416
417
  note.content);
417
418
  }
418
419
  function formatTemporalHistory(history) {
@@ -924,11 +925,13 @@ function formatListEntry(entry, options = {}) {
924
925
  const extras = [];
925
926
  if (note.tags.length > 0)
926
927
  extras.push(note.tags.join(", "));
927
- extras.push(`lifecycle=${note.lifecycle}`);
928
+ extras.push(`lifecycle: ${note.lifecycle}`);
929
+ if (note.role)
930
+ extras.push(`role: ${note.role}`);
928
931
  if (options.includeStorage)
929
- extras.push(`stored=${storageLabel(vault)}`);
932
+ extras.push(`stored: ${storageLabel(vault)}`);
930
933
  if (options.includeUpdated)
931
- extras.push(`updated=${note.updatedAt}`);
934
+ extras.push(`updated: ${note.updatedAt}`);
932
935
  const lines = [`- **${note.title}** \`${note.id}\` ${proj}${extras.length > 0 ? ` — ${extras.join(" | ")}` : ""}`];
933
936
  if (options.includeRelations && note.relatedTo && note.relatedTo.length > 0) {
934
937
  lines.push(` related: ${note.relatedTo.map((rel) => `${rel.id} (${rel.type})`).join(", ")}`);
@@ -1029,6 +1032,14 @@ function addVaultChange(vaultChanges, vault, file) {
1029
1032
  vaultChanges.set(vault, files);
1030
1033
  }
1031
1034
  }
1035
+ const ROLE_LIFECYCLE_DEFAULTS = {
1036
+ research: "temporary",
1037
+ plan: "temporary",
1038
+ review: "temporary",
1039
+ decision: "permanent",
1040
+ summary: "permanent",
1041
+ reference: "permanent",
1042
+ };
1032
1043
  // ── MCP Server ────────────────────────────────────────────────────────────────
1033
1044
  const server = new McpServer({
1034
1045
  name: "mnemonic",
@@ -1426,7 +1437,13 @@ server.registerTool("remember", {
1426
1437
  .enum(NOTE_LIFECYCLES)
1427
1438
  .optional()
1428
1439
  .describe("Memory lifetime. Use `temporary` for short-lived working context such as active investigations or transient status. " +
1429
- "Use `permanent` for durable knowledge such as decisions, fixes, patterns, and preferences."),
1440
+ "Use `permanent` for durable knowledge such as decisions, fixes, patterns, and preferences. " +
1441
+ "When omitted, defaults based on role: research/plan/review → temporary, decision/summary/reference → permanent."),
1442
+ role: z
1443
+ .enum(NOTE_ROLES)
1444
+ .optional()
1445
+ .describe("Optional prioritization hint for the note. Inferred automatically when omitted. " +
1446
+ "Set explicitly for workflow artifacts like research or review notes."),
1430
1447
  summary: z.string().optional().describe("Git commit summary only. Imperative mood, concise, and focused on why the change matters."),
1431
1448
  alwaysLoad: z
1432
1449
  .boolean()
@@ -1454,7 +1471,7 @@ server.registerTool("remember", {
1454
1471
  .describe("Optional agent hint indicating that `recall` or `list` was already used to check for an existing memory on this topic."),
1455
1472
  }),
1456
1473
  outputSchema: RememberResultSchema,
1457
- }, async ({ title, content, tags, lifecycle, summary, alwaysLoad, cwd, scope, allowProtectedBranch = false }) => {
1474
+ }, async ({ title, content, tags, lifecycle, role, summary, alwaysLoad, cwd, scope, allowProtectedBranch = false }) => {
1458
1475
  await ensureBranchSynced(cwd);
1459
1476
  const project = await resolveProject(cwd);
1460
1477
  const cleanedContent = await cleanMarkdown(content);
@@ -1483,7 +1500,8 @@ server.registerTool("remember", {
1483
1500
  const now = new Date().toISOString();
1484
1501
  const note = {
1485
1502
  id, title, content: cleanedContent, tags,
1486
- lifecycle: lifecycle ?? "permanent",
1503
+ lifecycle: lifecycle ?? (role ? ROLE_LIFECYCLE_DEFAULTS[role] : undefined) ?? "permanent",
1504
+ ...(role ? { role } : {}),
1487
1505
  alwaysLoad: alwaysLoad ?? false,
1488
1506
  project: project?.id,
1489
1507
  projectName: project?.name,
@@ -1841,6 +1859,7 @@ server.registerTool("recall", {
1841
1859
  title: "Recall",
1842
1860
  description: "Semantic search over stored memories using embeddings.\n\n" +
1843
1861
  "Supports opt-in temporal mode (`mode: \"temporal\"`) to enrich top semantic matches with compact git-backed history.\n\n" +
1862
+ "Supports workflow mode (`mode: \"workflow\"`) to prioritize RPIR-style chain reconstruction while retaining compatibility with legacy relationships.\n\n" +
1844
1863
  "Use this when:\n" +
1845
1864
  "- You know the topic but not the exact memory id\n" +
1846
1865
  "- You are starting a session and want relevant prior context\n" +
@@ -1867,7 +1886,7 @@ server.registerTool("recall", {
1867
1886
  cwd: projectParam,
1868
1887
  limit: z.number().int().min(1).max(20).optional().default(DEFAULT_RECALL_LIMIT),
1869
1888
  minSimilarity: z.number().min(0).max(1).optional().default(DEFAULT_MIN_SIMILARITY),
1870
- mode: z.enum(["default", "temporal"]).optional().default("default").describe("Temporal history is opt-in. Use `temporal` to enrich top semantic matches with compact git-backed history."),
1889
+ mode: z.enum(["default", "temporal", "workflow"]).optional().default("default").describe("Use `temporal` for compact git-backed history, or `workflow` for RPIR-oriented chain reconstruction."),
1871
1890
  verbose: z.boolean().optional().default(false).describe("Only meaningful with `mode: \"temporal\"`. Adds richer stats-based history context without returning raw diffs."),
1872
1891
  tags: z.array(z.string()).optional().describe("Filter results to notes with all of these tags."),
1873
1892
  scope: z
@@ -1996,7 +2015,9 @@ server.registerTool("recall", {
1996
2015
  promoted.push(...rescueCandidates);
1997
2016
  promoted = applyCanonicalExplanationPromotion(promoted);
1998
2017
  }
1999
- const top = selectRecallResults(promoted, limit, scope);
2018
+ const top = mode === "workflow"
2019
+ ? selectWorkflowResults(promoted, limit, scope)
2020
+ : selectRecallResults(promoted, limit, scope);
2000
2021
  if (top.length === 0) {
2001
2022
  const structuredContent = { action: "recalled", query, scope: scope || "all", results: [] };
2002
2023
  return { content: [{ type: "text", text: "No memories found matching that query." }], structuredContent };
@@ -2057,6 +2078,7 @@ server.registerTool("recall", {
2057
2078
  vault: storageLabel(vault),
2058
2079
  tags: note.tags,
2059
2080
  lifecycle: note.lifecycle,
2081
+ role: note.role,
2060
2082
  updatedAt: note.updatedAt,
2061
2083
  provenance,
2062
2084
  confidence,
@@ -2137,7 +2159,8 @@ server.registerTool("update", {
2137
2159
  }))
2138
2160
  .optional()
2139
2161
  .describe("Use for targeted edits when you know the structure. More token-efficient than passing full content. " +
2140
- "Mutually exclusive with content."),
2162
+ "Mutually exclusive with content. " +
2163
+ "If this fails, fix the issue in your patch values and retry — do NOT fall back to full content rewrite."),
2141
2164
  content: z.string().optional().describe("Full note body replacement. Use only for complete rewrites or when the note is small. Mutually exclusive with semanticPatch."),
2142
2165
  title: z.string().optional().describe("Specific, retrieval-friendly title. Prefer the concrete topic or decision, not a vague label."),
2143
2166
  tags: z.array(z.string()).optional().describe("Optional tags for later filtering. Use a small number of stable, meaningful tags."),
@@ -2145,6 +2168,10 @@ server.registerTool("update", {
2145
2168
  .enum(NOTE_LIFECYCLES)
2146
2169
  .optional()
2147
2170
  .describe("Change lifecycle. Preserve the existing value unless you're intentionally switching it."),
2171
+ role: z
2172
+ .enum(NOTE_ROLES)
2173
+ .optional()
2174
+ .describe("Change role. Preserve the existing value unless you're intentionally switching it."),
2148
2175
  summary: z.string().optional().describe("Git commit summary only. Imperative mood, concise, and focused on why the change matters."),
2149
2176
  alwaysLoad: z
2150
2177
  .boolean()
@@ -2159,7 +2186,7 @@ server.registerTool("update", {
2159
2186
  "When true, update can commit on a protected branch without changing project policy."),
2160
2187
  }),
2161
2188
  outputSchema: UpdateResultSchema,
2162
- }, async ({ id, content, semanticPatch, title, tags, lifecycle, summary, alwaysLoad, cwd, allowProtectedBranch = false }) => {
2189
+ }, async ({ id, content, semanticPatch, title, tags, lifecycle, role, summary, alwaysLoad, cwd, allowProtectedBranch = false }) => {
2163
2190
  await ensureBranchSynced(cwd);
2164
2191
  const found = await vaultManager.findNote(id, cwd);
2165
2192
  if (!found) {
@@ -2196,11 +2223,18 @@ server.registerTool("update", {
2196
2223
  }
2197
2224
  const now = new Date().toISOString();
2198
2225
  let patchedContent;
2226
+ let lintWarnings;
2199
2227
  if (semanticPatch && semanticPatch.length > 0) {
2200
2228
  try {
2201
- patchedContent = await applySemanticPatches(note.content, semanticPatch);
2229
+ const result = await applySemanticPatches(note.content, semanticPatch);
2230
+ patchedContent = result.content;
2231
+ lintWarnings = result.lintWarnings;
2202
2232
  }
2203
2233
  catch (err) {
2234
+ if (err instanceof MarkdownLintError) {
2235
+ const message = `Semantic patch produced content with markdown lint issues. Fix the lint issues in your patch values and retry — do NOT fall back to full content rewrite.\n\n${err.message}`;
2236
+ return { content: [{ type: "text", text: message }], isError: true };
2237
+ }
2204
2238
  const message = err instanceof Error ? err.message : String(err);
2205
2239
  return { content: [{ type: "text", text: `Semantic patch failed: ${message}` }], isError: true };
2206
2240
  }
@@ -2212,6 +2246,7 @@ server.registerTool("update", {
2212
2246
  content: patchedContent ?? cleanedContent ?? note.content,
2213
2247
  tags: tags ?? note.tags,
2214
2248
  lifecycle: lifecycle ?? note.lifecycle,
2249
+ ...(role !== undefined ? { role } : (note.role ? { role: note.role } : {})),
2215
2250
  alwaysLoad: alwaysLoad !== undefined ? alwaysLoad : note.alwaysLoad,
2216
2251
  updatedAt: now,
2217
2252
  };
@@ -2259,6 +2294,8 @@ server.registerTool("update", {
2259
2294
  changes.push("tags");
2260
2295
  if (lifecycle !== undefined && lifecycle !== note.lifecycle)
2261
2296
  changes.push("lifecycle");
2297
+ if (role !== undefined && role !== note.role)
2298
+ changes.push("role");
2262
2299
  if (alwaysLoad !== undefined && alwaysLoad !== note.alwaysLoad)
2263
2300
  changes.push("alwaysLoad");
2264
2301
  const changeDesc = changes.length > 0 ? `Updated ${changes.join(", ")}` : "No changes";
@@ -2303,10 +2340,16 @@ server.registerTool("update", {
2303
2340
  timestamp: now,
2304
2341
  project: noteProjectRef(updated),
2305
2342
  lifecycle: updated.lifecycle,
2343
+ role: updated.role,
2344
+ lintWarnings: lintWarnings && lintWarnings.length > 0 ? lintWarnings : undefined,
2306
2345
  persistence,
2307
2346
  };
2308
2347
  invalidateActiveProjectCache();
2309
- return { content: [{ type: "text", text: `Updated memory '${id}'\n${formatPersistenceSummary(persistence)}` }], structuredContent };
2348
+ const fieldText = changes.length > 0 ? `\nfields modified: ${changes.join(", ")}` : "";
2349
+ const warningsText = lintWarnings && lintWarnings.length > 0
2350
+ ? `\nmarkdown lint warnings (not auto-fixable):\n- ${lintWarnings.join("\n- ")}`
2351
+ : "";
2352
+ return { content: [{ type: "text", text: `Updated memory '${id}'${fieldText}${warningsText}\n${formatPersistenceSummary(persistence)}` }], structuredContent };
2310
2353
  });
2311
2354
  // ── forget ────────────────────────────────────────────────────────────────────
2312
2355
  server.registerTool("forget", {
@@ -2486,6 +2529,7 @@ server.registerTool("get", {
2486
2529
  project: noteProjectRef(note),
2487
2530
  tags: note.tags,
2488
2531
  lifecycle: note.lifecycle,
2532
+ role: note.role,
2489
2533
  alwaysLoad: note.alwaysLoad,
2490
2534
  relatedTo: note.relatedTo,
2491
2535
  createdAt: note.createdAt,
@@ -2501,7 +2545,7 @@ server.registerTool("get", {
2501
2545
  const lines = [];
2502
2546
  for (const note of found) {
2503
2547
  lines.push(`## ${note.title} (${note.id})`);
2504
- lines.push(`project: ${note.project?.name ?? "global"} | stored: ${note.vault} | lifecycle: ${note.lifecycle}`);
2548
+ lines.push(`project: ${note.project?.name ?? "global"} | stored: ${note.vault} | lifecycle: ${note.lifecycle}${note.role ? ` | role: ${note.role}` : ""}`);
2505
2549
  if (note.tags.length > 0)
2506
2550
  lines.push(`tags: ${note.tags.join(", ")}`);
2507
2551
  lines.push("");
@@ -2647,6 +2691,7 @@ server.registerTool("list", {
2647
2691
  project: noteProjectRef(note),
2648
2692
  tags: note.tags,
2649
2693
  lifecycle: note.lifecycle,
2694
+ role: note.role,
2650
2695
  vault: storageLabel(vault),
2651
2696
  updatedAt: note.updatedAt,
2652
2697
  hasRelated: note.relatedTo && note.relatedTo.length > 0,
@@ -3784,6 +3829,8 @@ const RELATIONSHIP_TYPES = [
3784
3829
  "explains",
3785
3830
  "example-of",
3786
3831
  "supersedes",
3832
+ "derives-from",
3833
+ "follows",
3787
3834
  ];
3788
3835
  server.registerTool("relate", {
3789
3836
  title: "Relate Memories",
@@ -3809,7 +3856,7 @@ server.registerTool("relate", {
3809
3856
  inputSchema: z.object({
3810
3857
  fromId: z.string().describe("Source memory id"),
3811
3858
  toId: z.string().describe("Target memory id"),
3812
- 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)"),
3859
+ 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), 'derives-from' (derived artifact), 'follows' (sequence order)"),
3813
3860
  bidirectional: z.boolean().optional().default(true).describe("Add relationship in both directions (default: true)"),
3814
3861
  cwd: projectParam,
3815
3862
  }),
@@ -4981,7 +5028,7 @@ server.registerPrompt("mnemonic-workflow-hint", {
4981
5028
  "- When unsure, prefer `recall` over `remember`.\n" +
4982
5029
  "- For repo-related tasks, pass `cwd` so mnemonic can route project memories correctly.\n\n" +
4983
5030
  "Workflow: `recall`/`list` -> `get` -> `update` or `remember` -> `relate`/`consolidate`/`move_memory`. Use `discover_tags` only when tag choice is ambiguous.\n\n" +
4984
- "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" +
5031
+ "Roles are optional prioritization hints, not schema. Lifecycle still governs durability. When `lifecycle` is omitted, `remember` applies soft defaults based on role: `research`, `plan`, and `review` default to `temporary`; `decision`, `summary`, and `reference` default to `permanent`. Explicit `lifecycle` always overrides the role-based default. Inferred roles are internal hints only. Prioritization is language-independent by default.\n\n" +
4985
5032
  "### Working-state continuity\n\n" +
4986
5033
  "Preserve in-progress work as temporary notes when continuation value is high. Recovery happens after project orientation.\n\n" +
4987
5034
  "**Checkpoint note structure (temporary notes):**\n" +
@@ -5026,6 +5073,66 @@ server.registerPrompt("mnemonic-workflow-hint", {
5026
5073
  },
5027
5074
  ],
5028
5075
  }));
5076
+ // ── mnemonic-rpi-workflow prompt ───────────────────────────────────────────────
5077
+ const rpiWorkflowPrompt = async () => ({
5078
+ messages: [
5079
+ {
5080
+ role: "user",
5081
+ content: {
5082
+ type: "text",
5083
+ text: "## RPIR workflow: research → plan → implement → review\n\n" +
5084
+ "mnemonic is the artifact store, not the runtime. Store workflow artifacts with correct roles and lifecycle; do not build orchestration in core.\n\n" +
5085
+ "### Request root note\n\n" +
5086
+ "For each RPIR workflow, create one request root note: `role: context`, `lifecycle: temporary`, `tags: [\"workflow\", \"request\"]`. All artifacts relate to it.\n\n" +
5087
+ "### Stage 1 — Research\n\n" +
5088
+ "- Create or update request root note.\n" +
5089
+ "- Create research notes: `role: research`, `lifecycle: temporary`.\n" +
5090
+ "- Distill a short research summary when findings are scattered.\n" +
5091
+ "- Link research `related-to` request root.\n" +
5092
+ "- Before creating research notes, call `recall` to check whether existing notes already cover the topic.\n\n" +
5093
+ "### Stage 2 — Plan\n\n" +
5094
+ "- Create or update one plan note: `role: plan`, `lifecycle: temporary`.\n" +
5095
+ "- Link plan `related-to` request root + key research notes.\n" +
5096
+ "- Keep plan concise and executable.\n" +
5097
+ "- REQUIRES: One current plan per request. Update or supersede when plan evolves.\n" +
5098
+ "- Material changes (architecture, scope, ordering, validation, assumptions): update plan note first, then continue.\n" +
5099
+ "- Non-material changes (wording, phrasing, detail): update inline without branching.\n\n" +
5100
+ "### Stage 3 — Implement\n\n" +
5101
+ "- Create temporary apply/task notes, tagged with `apply`.\n" +
5102
+ "- Use `role: plan` for executable steps. Use `role: context` for observations and checkpoints.\n" +
5103
+ "- Link apply notes `related-to` plan.\n" +
5104
+ "- For non-trivial work, hand narrow context to subagent: request note, current plan or relevant slice, key research notes, narrow file/task scope.\n" +
5105
+ "- Subagent returns: updated apply note, optional review note, recommendation (continue / block / update plan).\n\n" +
5106
+ "### Stage 4 — Review\n\n" +
5107
+ "- Create review notes: `role: review`, `lifecycle: temporary`.\n" +
5108
+ "- Link review `related-to` apply or plan.\n" +
5109
+ "- Fix directly or mark blockers.\n" +
5110
+ "- If review changes the plan materially, update plan note first.\n\n" +
5111
+ "### Stage 5 — Consolidate\n\n" +
5112
+ "At workflow end:\n" +
5113
+ "- Create decision note for resolved approaches (`lifecycle: permanent`).\n" +
5114
+ "- Create summary note for outcome recaps (`lifecycle: permanent`).\n" +
5115
+ "- Promote reusable facts and patterns into permanent reference notes.\n" +
5116
+ "- Let pure scaffolding and redundant checkpoints expire as temporary notes.\n\n" +
5117
+ "### Relationship conventions\n\n" +
5118
+ "Minimal set. Link to immediate upstream artifacts only. No dense cross-linking.\n" +
5119
+ "- research → request root\n" +
5120
+ "- plan → request root + key research notes\n" +
5121
+ "- apply/task → plan\n" +
5122
+ "- review → apply or plan\n" +
5123
+ "- outcome → plan (optionally request root)\n\n" +
5124
+ "### Commit discipline\n\n" +
5125
+ "Three classes: memory (research/plan/review artifacts), work (code/test/docs), memory (consolidation/promotion). When plan changes materially: update notes, commit memory, then continue work.\n\n" +
5126
+ "### Iterate?\n\n" +
5127
+ "Only when review or checks warrant it. Not the default.",
5128
+ },
5129
+ },
5130
+ ],
5131
+ });
5132
+ server.registerPrompt("mnemonic-rpi-workflow", {
5133
+ title: "RPI Workflow: Research → Plan → Implement → Review",
5134
+ description: "Stage protocol and conventions for structured task workflows using mnemonic as artifact store.",
5135
+ }, rpiWorkflowPrompt);
5029
5136
  // ── start ─────────────────────────────────────────────────────────────────────
5030
5137
  await warnAboutPendingMigrationsOnStartup();
5031
5138
  const transport = new StdioServerTransport();