@luisarg/memory-mcp 0.1.6 → 0.1.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;qBAQa;iBAEI;EACf;EACA;;qBAGW,QAAQ,OAAO;wBA+CZ,MAAM,KAAK,SAAS,QAAQ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;qBAQa;iBAEI;EACf;EACA;;qBAGW,QAAQ,OAAO;wBAqCZ,MAAM,KAAK,SAAS,QAAQ"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { cpSync, existsSync, mkdirSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
- import { dirname, isAbsolute, join } from "node:path";
3
+ import { dirname, isAbsolute, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import Schema from "@deepseek-ai/schemastery";
6
6
  import { readFile } from "node:fs/promises";
@@ -131,6 +131,7 @@ function resolveMemoryPath(value) {
131
131
  return isAbsolute(v) ? v : join(homedir(), v);
132
132
  }
133
133
  /** Copy the bundled dir into `target` when `key` is missing there. */
134
+ /** Copy the bundled dir into `target` when `key` is missing there (user data). */
134
135
  function ensure(target, bundled, key) {
135
136
  if (existsSync(join(target, key))) return false;
136
137
  if (!existsSync(bundled)) return false;
@@ -138,26 +139,23 @@ function ensure(target, bundled, key) {
138
139
  cpSync(bundled, target, { recursive: true });
139
140
  return true;
140
141
  }
141
- /** Copy one bundled file into `target` when missing (upgrades add files 0.1.1 → 0.1.2). */
142
- function ensureFile(target, bundled, file) {
143
- const dest = join(target, file);
144
- if (existsSync(dest)) return false;
145
- const src = join(bundled, file);
146
- if (!existsSync(src)) return false;
147
- mkdirSync(target, { recursive: true });
148
- cpSync(src, dest);
149
- return true;
150
- }
151
142
  function apply(ctx, config) {
152
143
  ctx.inject(["skills"], (ctx) => {
153
144
  ctx.skills.registerProvider(() => skillsProvider);
154
145
  });
155
146
  const serverDir = resolveUnderHome(config.serverDir, "memory-vault-server");
156
147
  const memoryPath = resolveMemoryPath(config.memoryPath);
157
- if (ensure(serverDir, join(packageRoot, "server"), "server.py")) console.log(`[memory-mcp] installed memory-vault-server -> ${serverDir}`);
148
+ const bundledServer = join(packageRoot, "server");
149
+ if (existsSync(join(bundledServer, "server.py")) && resolve(bundledServer) !== resolve(serverDir)) {
150
+ const firstBoot = !existsSync(join(serverDir, "server.py"));
151
+ mkdirSync(serverDir, { recursive: true });
152
+ cpSync(bundledServer, serverDir, {
153
+ recursive: true,
154
+ force: true
155
+ });
156
+ if (firstBoot) console.log(`[memory-mcp] installed memory-vault-server -> ${serverDir}`);
157
+ }
158
158
  if (ensure(memoryPath, join(packageRoot, "vault"), "type-registry.yaml")) console.log(`[memory-mcp] installed vault starter -> ${memoryPath}`);
159
- const bundled = join(packageRoot, "server");
160
- for (const file of ["launcher.mjs", "requirements.txt"]) if (ensureFile(serverDir, bundled, file)) console.log(`[memory-mcp] installed ${file} -> ${serverDir}`);
161
159
  if (!existsSync(join(serverDir, "server.py"))) console.warn(`[memory-mcp] memory-vault-server not found at ${serverDir} and not bundled — set DSH_MEMORY_SERVER_DIR (or run \`node scripts/bundle-assets.mjs\` in a checkout)`);
162
160
  }
163
161
  //#endregion
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["parseYaml"],"sources":["../src/skills.ts","../src/index.ts"],"sourcesContent":["/**\n * Bundled skills shipped in this package: `brain` (read the vault) and\n * `checkpoint` (capture a session into it).\n *\n * A provider rather than `ctx.skills.register()` on purpose. A registration\n * lands at the runtime rank, which outranks a user's own skill directories,\n * while BUNDLED_SKILL_RANK is the weakest rank in the local discovery table:\n * shipping at the weakest rank means a user who drops their own `brain` into\n * `~/.agents/skills` keeps winning the name. These are defaults, not a takeover.\n *\n * Each SKILL.md stays the single source of its own name, description and\n * usage guidance — the frontmatter is parsed here with the same `yaml`\n * dependency the harness's own filesystem provider uses, so the exact file that\n * ships in this package also works copied into a user skill root.\n */\nimport { readFile } from 'node:fs/promises'\nimport { fileURLToPath } from 'node:url'\nimport { parse as parseYaml } from 'yaml'\nimport {\n BUNDLED_SKILL_RANK,\n type SkillCandidate,\n type SkillDefinition,\n type SkillProvider,\n type SkillResourceBase,\n} from '@deepseek-ai/dsh-skill'\n\n/** Provider name registered on `ctx.skills`. */\nexport const SKILLS_PROVIDER = 'memory-mcp-skills'\n\n/** Shipped skill directories, relative to the package root. */\nconst SKILL_NAMES = ['brain', 'checkpoint'] as const\n\nconst SKILLS_ROOT = new URL('../skills/', import.meta.url)\nconst INVOCATION = { modelInvocable: true, userInvocable: true } as const\n\ninterface Frontmatter {\n readonly name: string\n readonly description: string\n readonly whenToUse?: string\n}\n\nfunction skillUrl(name: string): URL {\n return new URL(`${name}/SKILL.md`, SKILLS_ROOT)\n}\n\nfunction resourceBase(name: string): SkillResourceBase {\n return { kind: 'directory', path: fileURLToPath(new URL(`${name}/`, SKILLS_ROOT)) }\n}\n\n/** Split YAML frontmatter from the body, mirroring the harness filesystem provider. */\nfunction splitFrontmatter(raw: string): { data: Record<string, unknown>; body: string } {\n const match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/.exec(raw)\n if (match === null) throw new Error('SKILL.md has no YAML frontmatter')\n const parsed: unknown = parseYaml(match[1])\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new TypeError('SKILL.md frontmatter must be a YAML mapping')\n }\n return { data: parsed as Record<string, unknown>, body: raw.slice(match[0].length).trim() }\n}\n\nfunction textField(data: Record<string, unknown>, key: string): string | undefined {\n const value = data[key]\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\n/** Read one shipped skill, rejecting a file whose frontmatter disagrees with its directory. */\nasync function loadSkill(name: string): Promise<{ frontmatter: Frontmatter; body: string }> {\n const { data, body } = splitFrontmatter(await readFile(skillUrl(name), 'utf8'))\n const declared = textField(data, 'name')\n if (declared !== name) {\n throw new Error(`skills/${name}/SKILL.md declares name \"${declared ?? '(none)'}\"`)\n }\n const description = textField(data, 'description')\n if (description === undefined) throw new Error(`skills/${name}/SKILL.md has no description`)\n const whenToUse = textField(data, 'whenToUse')\n return {\n frontmatter: { name, description, ...whenToUse === undefined ? {} : { whenToUse } },\n body,\n }\n}\n\n/** Skills shipped as packaged Markdown assets. */\nexport const skillsProvider: SkillProvider = {\n name: SKILLS_PROVIDER,\n\n async list(): Promise<readonly SkillCandidate[]> {\n return await Promise.all(SKILL_NAMES.map(async (name) => {\n const { frontmatter } = await loadSkill(name)\n return {\n ...frontmatter,\n path: fileURLToPath(skillUrl(name)),\n invocation: INVOCATION,\n source: 'bundled',\n provider: SKILLS_PROVIDER,\n resourceBase: resourceBase(name),\n rank: BUNDLED_SKILL_RANK,\n locator: skillUrl(name),\n }\n }))\n },\n\n async get(candidate): Promise<SkillDefinition | undefined> {\n // A body that is no longer loadable resolves to `undefined`: the registry\n // passes that straight back to its caller, while throwing here would\n // surface a raw ENOENT as a tool error instead of \"no longer available\".\n const loaded = await loadSkill(candidate.name).catch(() => undefined)\n if (loaded === undefined) return undefined\n const { frontmatter, body } = loaded\n return {\n ...frontmatter,\n path: fileURLToPath(skillUrl(candidate.name)),\n invocation: INVOCATION,\n source: 'bundled',\n provider: SKILLS_PROVIDER,\n resourceBase: resourceBase(candidate.name),\n content: body,\n }\n },\n}\n","import { cpSync, existsSync, mkdirSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, isAbsolute, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\nimport { skillsProvider } from './skills.js'\n\nexport const name = 'memory-mcp'\n\nexport interface Config {\n memoryPath: string\n serverDir: string\n}\n\nexport const Config: Schema<Config> = Schema.object({\n memoryPath: Schema.string().default(process.env.DSH_MEMORY_PATH ?? ''),\n serverDir: Schema.string().default(process.env.DSH_MEMORY_SERVER_DIR ?? ''),\n})\n\nconst packageRoot = dirname(dirname(fileURLToPath(import.meta.url)))\n\n/** Harness home, resolved like the harness itself ($DSH_HOME, or ~/.dsh). */\nfunction dshHome(): string {\n const env = process.env.DSH_HOME?.trim()\n return env && env.length > 0 ? env : join(homedir(), '.dsh')\n}\n\n/** Absolute paths stay; empty/relative values resolve under the harness home. */\nfunction resolveUnderHome(value: string, segment: string): string {\n const v = value.trim()\n if (v.length === 0) return join(dshHome(), segment)\n return isAbsolute(v) ? v : join(dshHome(), v)\n}\n\n/** Vault root: `~/.memories` by default, independent of `$DSH_HOME`. */\nfunction resolveMemoryPath(value: string): string {\n const v = value.trim()\n if (v.length === 0) return join(homedir(), '.memories')\n return isAbsolute(v) ? v : join(homedir(), v)\n}\n\n/** Copy the bundled dir into `target` when `key` is missing there. */\nfunction ensure(target: string, bundled: string, key: string): boolean {\n if (existsSync(join(target, key))) return false\n if (!existsSync(bundled)) return false\n mkdirSync(target, { recursive: true })\n cpSync(bundled, target, { recursive: true })\n return true\n}\n\n/** Copy one bundled file into `target` when missing (upgrades add files 0.1.1 → 0.1.2). */\nfunction ensureFile(target: string, bundled: string, file: string): boolean {\n const dest = join(target, file)\n if (existsSync(dest)) return false\n const src = join(bundled, file)\n if (!existsSync(src)) return false\n mkdirSync(target, { recursive: true })\n cpSync(src, dest)\n return true\n}\n\nexport function apply(ctx: Context, config: Config) {\n // Ship the skills that drive the tools this plugin exposes, at the bundled\n // rank, so a user's own `brain`/`checkpoint` in a skill directory still wins.\n // Injected rather than declared in the plugin's `inject`: the vault bootstrap\n // and the MCP client must keep working in a deployment with no skill catalog.\n ctx.inject(['skills'], (ctx) => {\n ctx.skills.registerProvider(() => skillsProvider)\n })\n\n const serverDir = resolveUnderHome(config.serverDir, 'memory-vault-server')\n const memoryPath = resolveMemoryPath(config.memoryPath)\n\n // Self-contained install: first boot copies the bundled server and vault\n // starter under the harness home when they are missing. Env-overridden\n // paths are respected (never overwritten, never copied over).\n if (ensure(serverDir, join(packageRoot, 'server'), 'server.py')) {\n console.log(`[memory-mcp] installed memory-vault-server -> ${serverDir}`)\n }\n if (ensure(memoryPath, join(packageRoot, 'vault'), 'type-registry.yaml')) {\n console.log(`[memory-mcp] installed vault starter -> ${memoryPath}`)\n }\n // launcher.mjs runs the server via uv or the pip-venv fallback; upgrades of\n // existing installs (server.py already present) still need the new files.\n const bundled = join(packageRoot, 'server')\n for (const file of ['launcher.mjs', 'requirements.txt']) {\n if (ensureFile(serverDir, bundled, file)) {\n console.log(`[memory-mcp] installed ${file} -> ${serverDir}`)\n }\n }\n if (!existsSync(join(serverDir, 'server.py'))) {\n console.warn(\n `[memory-mcp] memory-vault-server not found at ${serverDir} and not bundled — ` +\n 'set DSH_MEMORY_SERVER_DIR (or run `node scripts/bundle-assets.mjs` in a checkout)',\n )\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["parseYaml"],"sources":["../src/skills.ts","../src/index.ts"],"sourcesContent":["/**\n * Bundled skills shipped in this package: `brain` (read the vault) and\n * `checkpoint` (capture a session into it).\n *\n * A provider rather than `ctx.skills.register()` on purpose. A registration\n * lands at the runtime rank, which outranks a user's own skill directories,\n * while BUNDLED_SKILL_RANK is the weakest rank in the local discovery table:\n * shipping at the weakest rank means a user who drops their own `brain` into\n * `~/.agents/skills` keeps winning the name. These are defaults, not a takeover.\n *\n * Each SKILL.md stays the single source of its own name, description and\n * usage guidance — the frontmatter is parsed here with the same `yaml`\n * dependency the harness's own filesystem provider uses, so the exact file that\n * ships in this package also works copied into a user skill root.\n */\nimport { readFile } from 'node:fs/promises'\nimport { fileURLToPath } from 'node:url'\nimport { parse as parseYaml } from 'yaml'\nimport {\n BUNDLED_SKILL_RANK,\n type SkillCandidate,\n type SkillDefinition,\n type SkillProvider,\n type SkillResourceBase,\n} from '@deepseek-ai/dsh-skill'\n\n/** Provider name registered on `ctx.skills`. */\nexport const SKILLS_PROVIDER = 'memory-mcp-skills'\n\n/** Shipped skill directories, relative to the package root. */\nconst SKILL_NAMES = ['brain', 'checkpoint'] as const\n\nconst SKILLS_ROOT = new URL('../skills/', import.meta.url)\nconst INVOCATION = { modelInvocable: true, userInvocable: true } as const\n\ninterface Frontmatter {\n readonly name: string\n readonly description: string\n readonly whenToUse?: string\n}\n\nfunction skillUrl(name: string): URL {\n return new URL(`${name}/SKILL.md`, SKILLS_ROOT)\n}\n\nfunction resourceBase(name: string): SkillResourceBase {\n return { kind: 'directory', path: fileURLToPath(new URL(`${name}/`, SKILLS_ROOT)) }\n}\n\n/** Split YAML frontmatter from the body, mirroring the harness filesystem provider. */\nfunction splitFrontmatter(raw: string): { data: Record<string, unknown>; body: string } {\n const match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/.exec(raw)\n if (match === null) throw new Error('SKILL.md has no YAML frontmatter')\n const parsed: unknown = parseYaml(match[1])\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new TypeError('SKILL.md frontmatter must be a YAML mapping')\n }\n return { data: parsed as Record<string, unknown>, body: raw.slice(match[0].length).trim() }\n}\n\nfunction textField(data: Record<string, unknown>, key: string): string | undefined {\n const value = data[key]\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\n/** Read one shipped skill, rejecting a file whose frontmatter disagrees with its directory. */\nasync function loadSkill(name: string): Promise<{ frontmatter: Frontmatter; body: string }> {\n const { data, body } = splitFrontmatter(await readFile(skillUrl(name), 'utf8'))\n const declared = textField(data, 'name')\n if (declared !== name) {\n throw new Error(`skills/${name}/SKILL.md declares name \"${declared ?? '(none)'}\"`)\n }\n const description = textField(data, 'description')\n if (description === undefined) throw new Error(`skills/${name}/SKILL.md has no description`)\n const whenToUse = textField(data, 'whenToUse')\n return {\n frontmatter: { name, description, ...whenToUse === undefined ? {} : { whenToUse } },\n body,\n }\n}\n\n/** Skills shipped as packaged Markdown assets. */\nexport const skillsProvider: SkillProvider = {\n name: SKILLS_PROVIDER,\n\n async list(): Promise<readonly SkillCandidate[]> {\n return await Promise.all(SKILL_NAMES.map(async (name) => {\n const { frontmatter } = await loadSkill(name)\n return {\n ...frontmatter,\n path: fileURLToPath(skillUrl(name)),\n invocation: INVOCATION,\n source: 'bundled',\n provider: SKILLS_PROVIDER,\n resourceBase: resourceBase(name),\n rank: BUNDLED_SKILL_RANK,\n locator: skillUrl(name),\n }\n }))\n },\n\n async get(candidate): Promise<SkillDefinition | undefined> {\n // A body that is no longer loadable resolves to `undefined`: the registry\n // passes that straight back to its caller, while throwing here would\n // surface a raw ENOENT as a tool error instead of \"no longer available\".\n const loaded = await loadSkill(candidate.name).catch(() => undefined)\n if (loaded === undefined) return undefined\n const { frontmatter, body } = loaded\n return {\n ...frontmatter,\n path: fileURLToPath(skillUrl(candidate.name)),\n invocation: INVOCATION,\n source: 'bundled',\n provider: SKILLS_PROVIDER,\n resourceBase: resourceBase(candidate.name),\n content: body,\n }\n },\n}\n","import { cpSync, existsSync, mkdirSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, isAbsolute, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\nimport { skillsProvider } from './skills.js'\n\nexport const name = 'memory-mcp'\n\nexport interface Config {\n memoryPath: string\n serverDir: string\n}\n\nexport const Config: Schema<Config> = Schema.object({\n memoryPath: Schema.string().default(process.env.DSH_MEMORY_PATH ?? ''),\n serverDir: Schema.string().default(process.env.DSH_MEMORY_SERVER_DIR ?? ''),\n})\n\nconst packageRoot = dirname(dirname(fileURLToPath(import.meta.url)))\n\n/** Harness home, resolved like the harness itself ($DSH_HOME, or ~/.dsh). */\nfunction dshHome(): string {\n const env = process.env.DSH_HOME?.trim()\n return env && env.length > 0 ? env : join(homedir(), '.dsh')\n}\n\n/** Absolute paths stay; empty/relative values resolve under the harness home. */\nfunction resolveUnderHome(value: string, segment: string): string {\n const v = value.trim()\n if (v.length === 0) return join(dshHome(), segment)\n return isAbsolute(v) ? v : join(dshHome(), v)\n}\n\n/** Vault root: `~/.memories` by default, independent of `$DSH_HOME`. */\nfunction resolveMemoryPath(value: string): string {\n const v = value.trim()\n if (v.length === 0) return join(homedir(), '.memories')\n return isAbsolute(v) ? v : join(homedir(), v)\n}\n\n/** Copy the bundled dir into `target` when `key` is missing there. */\n/** Copy the bundled dir into `target` when `key` is missing there (user data). */\nfunction ensure(target: string, bundled: string, key: string): boolean {\n if (existsSync(join(target, key))) return false\n if (!existsSync(bundled)) return false\n mkdirSync(target, { recursive: true })\n cpSync(bundled, target, { recursive: true })\n return true\n}\n\nexport function apply(ctx: Context, config: Config) {\n // Ship the skills that drive the tools this plugin exposes, at the bundled\n // rank, so a user's own `brain`/`checkpoint` in a skill directory still wins.\n // Injected rather than declared in the plugin's `inject`: the vault bootstrap\n // and the MCP client must keep working in a deployment with no skill catalog.\n ctx.inject(['skills'], (ctx) => {\n ctx.skills.registerProvider(() => skillsProvider)\n })\n\n const serverDir = resolveUnderHome(config.serverDir, 'memory-vault-server')\n const memoryPath = resolveMemoryPath(config.memoryPath)\n\n // Self-contained install. The vault is user data, so it is copied only when\n // missing; the server directory is our code, so it is refreshed on every boot.\n // 0.1.5 copied the server only when `server.py` was absent, which froze the\n // Python code of every existing install (and shipped fixes to nobody).\n // cpSync merges: a pip `.venv` or `__pycache__` left in the target survives.\n const bundledServer = join(packageRoot, 'server')\n // `resolve` because a checkout can legitimately point serverDir at the bundle\n // itself (tests and local dev); copying a directory onto itself throws EINVAL.\n if (existsSync(join(bundledServer, 'server.py')) && resolve(bundledServer) !== resolve(serverDir)) {\n const firstBoot = !existsSync(join(serverDir, 'server.py'))\n mkdirSync(serverDir, { recursive: true })\n cpSync(bundledServer, serverDir, { recursive: true, force: true })\n if (firstBoot) console.log(`[memory-mcp] installed memory-vault-server -> ${serverDir}`)\n }\n if (ensure(memoryPath, join(packageRoot, 'vault'), 'type-registry.yaml')) {\n console.log(`[memory-mcp] installed vault starter -> ${memoryPath}`)\n }\n if (!existsSync(join(serverDir, 'server.py'))) {\n console.warn(\n `[memory-mcp] memory-vault-server not found at ${serverDir} and not bundled — ` +\n 'set DSH_MEMORY_SERVER_DIR (or run `node scripts/bundle-assets.mjs` in a checkout)',\n )\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luisarg/memory-mcp",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "DSH MCP client for the memory vault",
6
6
  "repository": {
package/server/store.py CHANGED
@@ -175,7 +175,9 @@ def _write_okf_file(
175
175
  frontmatter_lines.append(f"project: {project}")
176
176
  if openspec_change_id:
177
177
  frontmatter_lines.append(f"openspec_change_id: {openspec_change_id}")
178
- if confidence is not None and entry_type in ("facts", "conventions"):
178
+ # Confidence is a fact-only field: `templates/fact.md` is the only OKF template
179
+ # that declares it, and `store_fact` is the only tool whose schema exposes it.
180
+ if confidence is not None and entry_type == "facts":
179
181
  frontmatter_lines.append(f"confidence: {confidence}")
180
182
  for key, value in (extra_fields or {}).items():
181
183
  frontmatter_lines.append(f"{key}: {value}")
@@ -15,8 +15,9 @@ The memory plugin exposes a Markdown knowledge base (OKF) over MCP; the vault's
15
15
  `memory.db` is a derived SQLite FTS5 index over that Markdown. Read it through the
16
16
  plugin's tools — `mcp__memory__*` when the server is named `memory` — never by hand.
17
17
 
18
- **`/brain` is read-only.** It writes nothing: capture is `/checkpoint`, and the profile
19
- layer is `/checkpoint-perfil`.
18
+ **`/brain` is read-only.** It writes nothing: capture is `/checkpoint`. The profile layer
19
+ that `/checkpoint-perfil` maintains is a user-level skill, not one this plugin ships — if
20
+ that command is not in the skill catalog, `store_profile` is the only profile writer here.
20
21
 
21
22
  ## 1. Locate the vault
22
23