@danielmarbach/mnemonic-mcp 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +67 -4
- package/build/index.js +111 -16
- package/build/index.js.map +1 -1
- package/build/recall.d.ts +1 -0
- package/build/recall.d.ts.map +1 -1
- package/build/recall.js +31 -0
- package/build/recall.js.map +1 -1
- package/build/storage.d.ts +3 -3
- package/build/storage.d.ts.map +1 -1
- package/build/storage.js +1 -1
- package/build/storage.js.map +1 -1
- package/build/structured-content.d.ts +65 -1
- package/build/structured-content.d.ts.map +1 -1
- package/build/structured-content.js +7 -1
- package/build/structured-content.js.map +1 -1
- package/package.json +9 -2
- package/scripts/install-skills.mjs +211 -0
- package/skills/mnemonic-rpi-workflow/SKILL.md +151 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ 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.0] - 2026-04-24
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `recall` now supports `mode: "workflow"` for RPIR-oriented chain retrieval while keeping compatibility with existing note graphs.
|
|
14
|
+
- Relationship types now include `derives-from` and `follows` so workflows can express directional sequencing and derivation explicitly.
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- RPIR workflow guidance now uses `mnemonic-rpi-workflow` as the prompt name.
|
|
20
|
+
- The RPIR skill is now published under `skills/mnemonic-rpi-workflow`.
|
|
21
|
+
|
|
7
22
|
## [0.24.0] - 2026-04-24
|
|
8
23
|
|
|
9
24
|
### 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
|
-
|
|
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`, `
|
|
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.
|
|
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
|
|
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,7 +14,7 @@ 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
20
|
import { cleanMarkdown } from "./markdown.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)}
|
|
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
|
|
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
|
|
932
|
+
extras.push(`stored: ${storageLabel(vault)}`);
|
|
930
933
|
if (options.includeUpdated)
|
|
931
|
-
extras.push(`updated
|
|
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("
|
|
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 =
|
|
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,
|
|
@@ -2145,6 +2167,10 @@ server.registerTool("update", {
|
|
|
2145
2167
|
.enum(NOTE_LIFECYCLES)
|
|
2146
2168
|
.optional()
|
|
2147
2169
|
.describe("Change lifecycle. Preserve the existing value unless you're intentionally switching it."),
|
|
2170
|
+
role: z
|
|
2171
|
+
.enum(NOTE_ROLES)
|
|
2172
|
+
.optional()
|
|
2173
|
+
.describe("Change role. Preserve the existing value unless you're intentionally switching it."),
|
|
2148
2174
|
summary: z.string().optional().describe("Git commit summary only. Imperative mood, concise, and focused on why the change matters."),
|
|
2149
2175
|
alwaysLoad: z
|
|
2150
2176
|
.boolean()
|
|
@@ -2159,7 +2185,7 @@ server.registerTool("update", {
|
|
|
2159
2185
|
"When true, update can commit on a protected branch without changing project policy."),
|
|
2160
2186
|
}),
|
|
2161
2187
|
outputSchema: UpdateResultSchema,
|
|
2162
|
-
}, async ({ id, content, semanticPatch, title, tags, lifecycle, summary, alwaysLoad, cwd, allowProtectedBranch = false }) => {
|
|
2188
|
+
}, async ({ id, content, semanticPatch, title, tags, lifecycle, role, summary, alwaysLoad, cwd, allowProtectedBranch = false }) => {
|
|
2163
2189
|
await ensureBranchSynced(cwd);
|
|
2164
2190
|
const found = await vaultManager.findNote(id, cwd);
|
|
2165
2191
|
if (!found) {
|
|
@@ -2212,6 +2238,7 @@ server.registerTool("update", {
|
|
|
2212
2238
|
content: patchedContent ?? cleanedContent ?? note.content,
|
|
2213
2239
|
tags: tags ?? note.tags,
|
|
2214
2240
|
lifecycle: lifecycle ?? note.lifecycle,
|
|
2241
|
+
...(role !== undefined ? { role } : (note.role ? { role: note.role } : {})),
|
|
2215
2242
|
alwaysLoad: alwaysLoad !== undefined ? alwaysLoad : note.alwaysLoad,
|
|
2216
2243
|
updatedAt: now,
|
|
2217
2244
|
};
|
|
@@ -2259,6 +2286,8 @@ server.registerTool("update", {
|
|
|
2259
2286
|
changes.push("tags");
|
|
2260
2287
|
if (lifecycle !== undefined && lifecycle !== note.lifecycle)
|
|
2261
2288
|
changes.push("lifecycle");
|
|
2289
|
+
if (role !== undefined && role !== note.role)
|
|
2290
|
+
changes.push("role");
|
|
2262
2291
|
if (alwaysLoad !== undefined && alwaysLoad !== note.alwaysLoad)
|
|
2263
2292
|
changes.push("alwaysLoad");
|
|
2264
2293
|
const changeDesc = changes.length > 0 ? `Updated ${changes.join(", ")}` : "No changes";
|
|
@@ -2303,10 +2332,12 @@ server.registerTool("update", {
|
|
|
2303
2332
|
timestamp: now,
|
|
2304
2333
|
project: noteProjectRef(updated),
|
|
2305
2334
|
lifecycle: updated.lifecycle,
|
|
2335
|
+
role: updated.role,
|
|
2306
2336
|
persistence,
|
|
2307
2337
|
};
|
|
2308
2338
|
invalidateActiveProjectCache();
|
|
2309
|
-
|
|
2339
|
+
const fieldText = changes.length > 0 ? `\nfields modified: ${changes.join(", ")}` : "";
|
|
2340
|
+
return { content: [{ type: "text", text: `Updated memory '${id}'${fieldText}\n${formatPersistenceSummary(persistence)}` }], structuredContent };
|
|
2310
2341
|
});
|
|
2311
2342
|
// ── forget ────────────────────────────────────────────────────────────────────
|
|
2312
2343
|
server.registerTool("forget", {
|
|
@@ -2486,6 +2517,7 @@ server.registerTool("get", {
|
|
|
2486
2517
|
project: noteProjectRef(note),
|
|
2487
2518
|
tags: note.tags,
|
|
2488
2519
|
lifecycle: note.lifecycle,
|
|
2520
|
+
role: note.role,
|
|
2489
2521
|
alwaysLoad: note.alwaysLoad,
|
|
2490
2522
|
relatedTo: note.relatedTo,
|
|
2491
2523
|
createdAt: note.createdAt,
|
|
@@ -2501,7 +2533,7 @@ server.registerTool("get", {
|
|
|
2501
2533
|
const lines = [];
|
|
2502
2534
|
for (const note of found) {
|
|
2503
2535
|
lines.push(`## ${note.title} (${note.id})`);
|
|
2504
|
-
lines.push(`project: ${note.project?.name ?? "global"} | stored: ${note.vault} | lifecycle: ${note.lifecycle}`);
|
|
2536
|
+
lines.push(`project: ${note.project?.name ?? "global"} | stored: ${note.vault} | lifecycle: ${note.lifecycle}${note.role ? ` | role: ${note.role}` : ""}`);
|
|
2505
2537
|
if (note.tags.length > 0)
|
|
2506
2538
|
lines.push(`tags: ${note.tags.join(", ")}`);
|
|
2507
2539
|
lines.push("");
|
|
@@ -2647,6 +2679,7 @@ server.registerTool("list", {
|
|
|
2647
2679
|
project: noteProjectRef(note),
|
|
2648
2680
|
tags: note.tags,
|
|
2649
2681
|
lifecycle: note.lifecycle,
|
|
2682
|
+
role: note.role,
|
|
2650
2683
|
vault: storageLabel(vault),
|
|
2651
2684
|
updatedAt: note.updatedAt,
|
|
2652
2685
|
hasRelated: note.relatedTo && note.relatedTo.length > 0,
|
|
@@ -3784,6 +3817,8 @@ const RELATIONSHIP_TYPES = [
|
|
|
3784
3817
|
"explains",
|
|
3785
3818
|
"example-of",
|
|
3786
3819
|
"supersedes",
|
|
3820
|
+
"derives-from",
|
|
3821
|
+
"follows",
|
|
3787
3822
|
];
|
|
3788
3823
|
server.registerTool("relate", {
|
|
3789
3824
|
title: "Relate Memories",
|
|
@@ -3809,7 +3844,7 @@ server.registerTool("relate", {
|
|
|
3809
3844
|
inputSchema: z.object({
|
|
3810
3845
|
fromId: z.string().describe("Source memory id"),
|
|
3811
3846
|
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)"),
|
|
3847
|
+
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
3848
|
bidirectional: z.boolean().optional().default(true).describe("Add relationship in both directions (default: true)"),
|
|
3814
3849
|
cwd: projectParam,
|
|
3815
3850
|
}),
|
|
@@ -4981,7 +5016,7 @@ server.registerPrompt("mnemonic-workflow-hint", {
|
|
|
4981
5016
|
"- When unsure, prefer `recall` over `remember`.\n" +
|
|
4982
5017
|
"- For repo-related tasks, pass `cwd` so mnemonic can route project memories correctly.\n\n" +
|
|
4983
5018
|
"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`
|
|
5019
|
+
"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
5020
|
"### Working-state continuity\n\n" +
|
|
4986
5021
|
"Preserve in-progress work as temporary notes when continuation value is high. Recovery happens after project orientation.\n\n" +
|
|
4987
5022
|
"**Checkpoint note structure (temporary notes):**\n" +
|
|
@@ -5026,6 +5061,66 @@ server.registerPrompt("mnemonic-workflow-hint", {
|
|
|
5026
5061
|
},
|
|
5027
5062
|
],
|
|
5028
5063
|
}));
|
|
5064
|
+
// ── mnemonic-rpi-workflow prompt ───────────────────────────────────────────────
|
|
5065
|
+
const rpiWorkflowPrompt = async () => ({
|
|
5066
|
+
messages: [
|
|
5067
|
+
{
|
|
5068
|
+
role: "user",
|
|
5069
|
+
content: {
|
|
5070
|
+
type: "text",
|
|
5071
|
+
text: "## RPIR workflow: research → plan → implement → review\n\n" +
|
|
5072
|
+
"mnemonic is the artifact store, not the runtime. Store workflow artifacts with correct roles and lifecycle; do not build orchestration in core.\n\n" +
|
|
5073
|
+
"### Request root note\n\n" +
|
|
5074
|
+
"For each RPIR workflow, create one request root note: `role: context`, `lifecycle: temporary`, `tags: [\"workflow\", \"request\"]`. All artifacts relate to it.\n\n" +
|
|
5075
|
+
"### Stage 1 — Research\n\n" +
|
|
5076
|
+
"- Create or update request root note.\n" +
|
|
5077
|
+
"- Create research notes: `role: research`, `lifecycle: temporary`.\n" +
|
|
5078
|
+
"- Distill a short research summary when findings are scattered.\n" +
|
|
5079
|
+
"- Link research `related-to` request root.\n" +
|
|
5080
|
+
"- Before creating research notes, call `recall` to check whether existing notes already cover the topic.\n\n" +
|
|
5081
|
+
"### Stage 2 — Plan\n\n" +
|
|
5082
|
+
"- Create or update one plan note: `role: plan`, `lifecycle: temporary`.\n" +
|
|
5083
|
+
"- Link plan `related-to` request root + key research notes.\n" +
|
|
5084
|
+
"- Keep plan concise and executable.\n" +
|
|
5085
|
+
"- REQUIRES: One current plan per request. Update or supersede when plan evolves.\n" +
|
|
5086
|
+
"- Material changes (architecture, scope, ordering, validation, assumptions): update plan note first, then continue.\n" +
|
|
5087
|
+
"- Non-material changes (wording, phrasing, detail): update inline without branching.\n\n" +
|
|
5088
|
+
"### Stage 3 — Implement\n\n" +
|
|
5089
|
+
"- Create temporary apply/task notes, tagged with `apply`.\n" +
|
|
5090
|
+
"- Use `role: plan` for executable steps. Use `role: context` for observations and checkpoints.\n" +
|
|
5091
|
+
"- Link apply notes `related-to` plan.\n" +
|
|
5092
|
+
"- For non-trivial work, hand narrow context to subagent: request note, current plan or relevant slice, key research notes, narrow file/task scope.\n" +
|
|
5093
|
+
"- Subagent returns: updated apply note, optional review note, recommendation (continue / block / update plan).\n\n" +
|
|
5094
|
+
"### Stage 4 — Review\n\n" +
|
|
5095
|
+
"- Create review notes: `role: review`, `lifecycle: temporary`.\n" +
|
|
5096
|
+
"- Link review `related-to` apply or plan.\n" +
|
|
5097
|
+
"- Fix directly or mark blockers.\n" +
|
|
5098
|
+
"- If review changes the plan materially, update plan note first.\n\n" +
|
|
5099
|
+
"### Stage 5 — Consolidate\n\n" +
|
|
5100
|
+
"At workflow end:\n" +
|
|
5101
|
+
"- Create decision note for resolved approaches (`lifecycle: permanent`).\n" +
|
|
5102
|
+
"- Create summary note for outcome recaps (`lifecycle: permanent`).\n" +
|
|
5103
|
+
"- Promote reusable facts and patterns into permanent reference notes.\n" +
|
|
5104
|
+
"- Let pure scaffolding and redundant checkpoints expire as temporary notes.\n\n" +
|
|
5105
|
+
"### Relationship conventions\n\n" +
|
|
5106
|
+
"Minimal set. Link to immediate upstream artifacts only. No dense cross-linking.\n" +
|
|
5107
|
+
"- research → request root\n" +
|
|
5108
|
+
"- plan → request root + key research notes\n" +
|
|
5109
|
+
"- apply/task → plan\n" +
|
|
5110
|
+
"- review → apply or plan\n" +
|
|
5111
|
+
"- outcome → plan (optionally request root)\n\n" +
|
|
5112
|
+
"### Commit discipline\n\n" +
|
|
5113
|
+
"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" +
|
|
5114
|
+
"### Iterate?\n\n" +
|
|
5115
|
+
"Only when review or checks warrant it. Not the default.",
|
|
5116
|
+
},
|
|
5117
|
+
},
|
|
5118
|
+
],
|
|
5119
|
+
});
|
|
5120
|
+
server.registerPrompt("mnemonic-rpi-workflow", {
|
|
5121
|
+
title: "RPI Workflow: Research → Plan → Implement → Review",
|
|
5122
|
+
description: "Stage protocol and conventions for structured task workflows using mnemonic as artifact store.",
|
|
5123
|
+
}, rpiWorkflowPrompt);
|
|
5029
5124
|
// ── start ─────────────────────────────────────────────────────────────────────
|
|
5030
5125
|
await warnAboutPendingMigrationsOnStartup();
|
|
5031
5126
|
const transport = new StdioServerTransport();
|