@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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/server/store.py +3 -1
- package/skills/brain/SKILL.md +3 -2
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;qBAQa;iBAEI;EACf;EACA;;qBAGW,QAAQ,OAAO;
|
|
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
|
-
|
|
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
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
|
-
|
|
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}")
|
package/skills/brain/SKILL.md
CHANGED
|
@@ -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
|
|
19
|
-
|
|
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
|
|