opencode-memoir 2.1.0 → 2.1.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAkJxE,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAoJxE,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -119,11 +119,19 @@ var MemoirOpenCode = async (_input, rawOptions) => {
119
119
  if (storePath) {
120
120
  mcpCommand.push("--store", storePath);
121
121
  }
122
+ const mcpServer = {
123
+ type: "local",
124
+ command: mcpCommand,
125
+ environment: storePath ? { MEMOIR_STORE: storePath } : void 0
126
+ };
122
127
  const hooks = {
123
128
  name: "memoir",
124
129
  config: async (config) => {
125
130
  debugLog("config hook: mcpCommand =", JSON.stringify(mcpCommand));
126
131
  debugLog("config hook: mcpCommand length =", mcpCommand.length);
132
+ const mcp = config.mcp ?? {};
133
+ mcp.memoir = mcpServer;
134
+ config.mcp = mcp;
127
135
  config.command = config.command ?? {};
128
136
  config.command["memoir:onboard"] = {
129
137
  description: "Populate or refresh Memoir onboarding for this project",
@@ -142,15 +150,7 @@ Memory rules:
142
150
 
143
151
  Then call memoir_memoir_remember with replace=true for durable onboarding facts. Use namespace codebase:onboard in git repositories and project:onboard outside git.`
144
152
  };
145
- const mcp = config.mcp ?? {};
146
- mcp.memoir = {
147
- type: "local",
148
- command: mcpCommand,
149
- environment: storePath ? { MEMOIR_STORE: storePath } : void 0,
150
- enabled: true
151
- };
152
- config.mcp = mcp;
153
- debugLog("config hook: registered mcp.memoir.command =", JSON.stringify(mcp.memoir?.command));
153
+ debugLog("config hook: mcpCommand =", JSON.stringify(mcpCommand));
154
154
  },
155
155
  "shell.env": async (_input2, output) => {
156
156
  try {
@@ -212,6 +212,7 @@ Then call memoir_memoir_remember with replace=true for durable onboarding facts.
212
212
  pruneAll();
213
213
  }
214
214
  };
215
+ hooks.mcp = { memoir: mcpServer };
215
216
  return hooks;
216
217
  };
217
218
  var plugin = {
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/debug.ts", "../src/memory-saver.ts", "../src/store.ts", "../src/index.ts"],
4
- "sourcesContent": ["/**\n * Debug logger writing to stderr. Prefixes with [memoir] for easy filtering.\n * Only writes when MEMOIR_DEBUG=1 is set.\n */\nexport function debugLog(...args: unknown[]): void {\n if (process.env.MEMOIR_DEBUG !== \"1\") return;\n const ts = new Date().toISOString();\n process.stderr.write(`[memoir ${ts}] ${args.map((a) => String(a)).join(\" \")}\\n`);\n}\n", "/** Per-session message count for periodic reminders. */\nexport const sessionMsgCount = new Map<string, number>();\n\nconst REMINDER_INTERVAL = Math.max(0, parseInt(process.env.MEMOIR_REMINDER_INTERVAL ?? \"5\", 10));\n\nexport function incrementMsgCount(sessionID: string): number {\n const count = (sessionMsgCount.get(sessionID) ?? 0) + 1;\n sessionMsgCount.set(sessionID, count);\n return count;\n}\n\nexport function shouldRemind(count: number): boolean {\n return REMINDER_INTERVAL > 0 && count > 1 && count % REMINDER_INTERVAL === 0;\n}\n\nexport function pruneAll(): void {\n sessionMsgCount.clear();\n}\n", "import { execFile, execFileSync } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { debugLog } from \"./debug.js\";\n\nconst execFileAsync = promisify(execFile);\n\nfunction errorMessage(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/** Resolve memoir store path.\n * 1. Plugin `store` option override\n * 2. MEMOIR_STORE env var\n * 3. Git root \u2192 `~/.memoir/<slug>` (all worktrees share one store)\n * 4. Resolved cwd \u2192 `~/.memoir/<slug>` for non-git dirs\n */\nexport function deriveStorePath(cwd = process.cwd()): string {\n if (pluginStoreOverride) return pluginStoreOverride;\n const envStore = process.env.MEMOIR_STORE;\n if (envStore) return envStore;\n\n let projectDir: string;\n try {\n const gitRoot = execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n encoding: \"utf8\",\n timeout: 3_000,\n }).trim();\n projectDir = gitRoot;\n } catch {\n projectDir = resolve(cwd);\n }\n\n const slug = projectDir.replace(/[/.]/g, \"-\");\n return join(homedir(), \".memoir\", slug);\n}\n\nexport let pluginStoreOverride: string | undefined;\n\nexport function setPluginStoreOverride(store: string | undefined): void {\n pluginStoreOverride = store;\n}\n\n/** Get current git branch (empty string if not in a git repo). */\nexport function currentGitBranch(cwd = process.cwd()): string {\n try {\n return execFileSync(\"git\", [\"branch\", \"--show-current\"], {\n cwd,\n encoding: \"utf8\",\n timeout: 3_000,\n }).trim();\n } catch {\n return \"\";\n }\n}\n\nconst branchCache = new Map<string, string>();\n\nexport function getCachedBranch(sessionID: string): string {\n return branchCache.get(sessionID) ?? \"\";\n}\n\nexport function setCachedBranch(sessionID: string, branch: string): void {\n branchCache.set(sessionID, branch);\n}\n\n/** Call the memoir CLI directly. */\nexport async function callMemoir(args: string[], store: string): Promise<string | null> {\n try {\n const { stdout } = (await execFileAsync(\"memoir\", [\"--store\", store, ...args], {\n encoding: \"utf8\",\n timeout: 15_000,\n maxBuffer: 1_024 * 1_024,\n })) as { stdout: string };\n return stdout.trim();\n } catch (e) {\n debugLog(\"callMemoir failed:\", errorMessage(e));\n return null;\n }\n}\n\nexport async function autoMatchMemoirBranch(store: string, sessionID: string): Promise<void> {\n const codeBranch = currentGitBranch();\n if (!codeBranch) return;\n\n const cached = getCachedBranch(sessionID);\n if (cached === codeBranch) return;\n\n // Check if branch exists in memoir\n const branchResult = await callMemoir([\"--json\", \"branch\"], store);\n if (branchResult === null) return;\n\n try {\n const data = JSON.parse(branchResult);\n const branches: string[] = data?.branches ?? [];\n if (!branches.includes(codeBranch)) {\n await callMemoir([\"branch\", codeBranch], store);\n }\n await callMemoir([\"checkout\", codeBranch], store);\n setCachedBranch(sessionID, codeBranch);\n } catch (e) {\n debugLog(\"autoMatchMemoirBranch: parsing failed:\", errorMessage(e));\n }\n}\n\nexport function pruneBranchCache(): void {\n branchCache.clear();\n}\n", "import type { Config, Plugin, PluginModule } from \"@opencode-ai/plugin\";\nimport { debugLog } from \"./debug.js\";\nimport { incrementMsgCount, pruneAll, sessionMsgCount, shouldRemind } from \"./memory-saver.js\";\nimport {\n autoMatchMemoirBranch,\n callMemoir,\n deriveStorePath,\n pruneBranchCache,\n setPluginStoreOverride,\n} from \"./store.js\";\n\nconst sessionsWithStartupHint = new Set<string>();\n\nfunction errorMessage(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\nconst MemoirOpenCode: Plugin = async (_input, rawOptions) => {\n const opts = (rawOptions ?? {}) as { store?: string };\n if (opts.store) setPluginStoreOverride(opts.store);\n\n const storePath = deriveStorePath();\n\n const mcpCommand = [\"memoir-mcp\"];\n if (storePath) {\n mcpCommand.push(\"--store\", storePath);\n }\n\n const hooks: Record<string, unknown> = {\n name: \"memoir\",\n\n config: async (config: Config): Promise<void> => {\n debugLog(\"config hook: mcpCommand =\", JSON.stringify(mcpCommand));\n debugLog(\"config hook: mcpCommand length =\", mcpCommand.length);\n\n config.command = config.command ?? {};\n config.command[\"memoir:onboard\"] = {\n description: \"Populate or refresh Memoir onboarding for this project\",\n template: `Populate or refresh Memoir onboarding for the current project.\n\nWorkflow:\n- First obtain a project file tree to understand structure.\n- Start studying from project documentation.\n- Continue only based on what the tree and documentation show.\n\nMemory rules:\n- Record only verified facts from files/docs/code or explicit user statements.\n- Do not write inferred user thoughts, intentions, preferences, or opinions.\n- Do not use preferences.* paths unless the user explicitly stated a preference.\n- If a fact is your interpretation, do not save it; report it as uncertain instead.\n\nThen call memoir_memoir_remember with replace=true for durable onboarding facts. Use namespace codebase:onboard in git repositories and project:onboard outside git.`,\n };\n\n // Register memoir MCP server\n const mcp = config.mcp ?? {};\n mcp.memoir = {\n type: \"local\" as const,\n command: mcpCommand,\n environment: storePath ? { MEMOIR_STORE: storePath } : undefined,\n enabled: true,\n };\n config.mcp = mcp;\n\n debugLog(\"config hook: registered mcp.memoir.command =\", JSON.stringify(mcp.memoir?.command));\n },\n\n \"shell.env\": async (\n _input: unknown,\n output: { env: Record<string, string> },\n ): Promise<void> => {\n try {\n if (storePath) {\n output.env.MEMOIR_STORE = storePath;\n }\n } catch (e) {\n debugLog(\"shell.env: failed:\", errorMessage(e));\n }\n },\n\n \"chat.message\": async (\n input: { sessionID?: string },\n _output: { parts?: Array<{ type: string; text: string }> },\n ): Promise<void> => {\n try {\n const sid = input.sessionID ?? \"default\";\n incrementMsgCount(sid);\n\n await autoMatchMemoirBranch(storePath, sid);\n } catch (e) {\n debugLog(\"chat.message: failed:\", errorMessage(e));\n }\n },\n\n \"experimental.chat.system.transform\": async (\n input: { sessionID?: string },\n output: { system?: string[] },\n ): Promise<void> => {\n try {\n const sid = input.sessionID ?? \"default\";\n\n if (input.sessionID && !sessionsWithStartupHint.has(sid)) {\n sessionsWithStartupHint.add(sid);\n output.system?.unshift(\n '[memoir] You have Memoir MCP tools (memoir_memoir_recall, memoir_memoir_remember, memoir_memoir_get, memoir_memoir_summarize, memoir_memoir_status, memoir_memoir_branches, memoir_memoir_checkout).\\nSAVE (memoir_memoir_remember) after: task completion, user stating preferences/constraints, discovering project facts. RECALL (memoir_memoir_recall \\u2192 memoir_memoir_get) at session start for prior context, when user asks about prior work.\\nNamespaces: \"default\" (user context), \"codebase:onboard\" (project facts in git repos). Taxonomy paths: preferences.*, project.*, codebase.*, decisions.*, learning.*.',\n );\n }\n\n const count = sessionMsgCount.get(sid) ?? 0;\n if (shouldRemind(count)) {\n output.system?.push(\n \"\\n[memoir] Reminder: Use memoir_memoir_remember to save context, memoir_memoir_recall to retrieve prior context.\",\n );\n }\n } catch (e) {\n debugLog(\"system.transform: failed:\", errorMessage(e));\n }\n },\n\n dispose: async (): Promise<void> => {\n try {\n if (process.env.MEMOIR_AUTO_SAVE === \"1\") {\n for (const [sid, count] of sessionMsgCount) {\n await callMemoir(\n [\n \"remember\",\n `session.${sid}`,\n \"--message\",\n `Session ended \u2014 ${count} user messages exchanged`,\n ],\n storePath,\n );\n }\n }\n } catch (e) {\n debugLog(\"dispose: save failed:\", errorMessage(e));\n }\n sessionsWithStartupHint.clear();\n pruneBranchCache();\n pruneAll();\n },\n };\n\n return hooks;\n};\n\nconst plugin: PluginModule = {\n id: \"opencode-memoir\",\n server: MemoirOpenCode,\n};\n\nexport default plugin;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Debug logger writing to stderr. Prefixes with [memoir] for easy filtering.\n * Only writes when MEMOIR_DEBUG=1 is set.\n */\nexport function debugLog(...args: unknown[]): void {\n if (process.env.MEMOIR_DEBUG !== \"1\") return;\n const ts = new Date().toISOString();\n process.stderr.write(`[memoir ${ts}] ${args.map((a) => String(a)).join(\" \")}\\n`);\n}\n", "/** Per-session message count for periodic reminders. */\nexport const sessionMsgCount = new Map<string, number>();\n\nconst REMINDER_INTERVAL = Math.max(0, parseInt(process.env.MEMOIR_REMINDER_INTERVAL ?? \"5\", 10));\n\nexport function incrementMsgCount(sessionID: string): number {\n const count = (sessionMsgCount.get(sessionID) ?? 0) + 1;\n sessionMsgCount.set(sessionID, count);\n return count;\n}\n\nexport function shouldRemind(count: number): boolean {\n return REMINDER_INTERVAL > 0 && count > 1 && count % REMINDER_INTERVAL === 0;\n}\n\nexport function pruneAll(): void {\n sessionMsgCount.clear();\n}\n", "import { execFile, execFileSync } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { debugLog } from \"./debug.js\";\n\nconst execFileAsync = promisify(execFile);\n\nfunction errorMessage(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/** Resolve memoir store path.\n * 1. Plugin `store` option override\n * 2. MEMOIR_STORE env var\n * 3. Git root \u2192 `~/.memoir/<slug>` (all worktrees share one store)\n * 4. Resolved cwd \u2192 `~/.memoir/<slug>` for non-git dirs\n */\nexport function deriveStorePath(cwd = process.cwd()): string {\n if (pluginStoreOverride) return pluginStoreOverride;\n const envStore = process.env.MEMOIR_STORE;\n if (envStore) return envStore;\n\n let projectDir: string;\n try {\n const gitRoot = execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n encoding: \"utf8\",\n timeout: 3_000,\n }).trim();\n projectDir = gitRoot;\n } catch {\n projectDir = resolve(cwd);\n }\n\n const slug = projectDir.replace(/[/.]/g, \"-\");\n return join(homedir(), \".memoir\", slug);\n}\n\nexport let pluginStoreOverride: string | undefined;\n\nexport function setPluginStoreOverride(store: string | undefined): void {\n pluginStoreOverride = store;\n}\n\n/** Get current git branch (empty string if not in a git repo). */\nexport function currentGitBranch(cwd = process.cwd()): string {\n try {\n return execFileSync(\"git\", [\"branch\", \"--show-current\"], {\n cwd,\n encoding: \"utf8\",\n timeout: 3_000,\n }).trim();\n } catch {\n return \"\";\n }\n}\n\nconst branchCache = new Map<string, string>();\n\nexport function getCachedBranch(sessionID: string): string {\n return branchCache.get(sessionID) ?? \"\";\n}\n\nexport function setCachedBranch(sessionID: string, branch: string): void {\n branchCache.set(sessionID, branch);\n}\n\n/** Call the memoir CLI directly. */\nexport async function callMemoir(args: string[], store: string): Promise<string | null> {\n try {\n const { stdout } = (await execFileAsync(\"memoir\", [\"--store\", store, ...args], {\n encoding: \"utf8\",\n timeout: 15_000,\n maxBuffer: 1_024 * 1_024,\n })) as { stdout: string };\n return stdout.trim();\n } catch (e) {\n debugLog(\"callMemoir failed:\", errorMessage(e));\n return null;\n }\n}\n\nexport async function autoMatchMemoirBranch(store: string, sessionID: string): Promise<void> {\n const codeBranch = currentGitBranch();\n if (!codeBranch) return;\n\n const cached = getCachedBranch(sessionID);\n if (cached === codeBranch) return;\n\n // Check if branch exists in memoir\n const branchResult = await callMemoir([\"--json\", \"branch\"], store);\n if (branchResult === null) return;\n\n try {\n const data = JSON.parse(branchResult);\n const branches: string[] = data?.branches ?? [];\n if (!branches.includes(codeBranch)) {\n await callMemoir([\"branch\", codeBranch], store);\n }\n await callMemoir([\"checkout\", codeBranch], store);\n setCachedBranch(sessionID, codeBranch);\n } catch (e) {\n debugLog(\"autoMatchMemoirBranch: parsing failed:\", errorMessage(e));\n }\n}\n\nexport function pruneBranchCache(): void {\n branchCache.clear();\n}\n", "import type { Config, Plugin, PluginModule } from \"@opencode-ai/plugin\";\nimport { debugLog } from \"./debug.js\";\nimport { incrementMsgCount, pruneAll, sessionMsgCount, shouldRemind } from \"./memory-saver.js\";\nimport {\n autoMatchMemoirBranch,\n callMemoir,\n deriveStorePath,\n pruneBranchCache,\n setPluginStoreOverride,\n} from \"./store.js\";\n\nconst sessionsWithStartupHint = new Set<string>();\n\nfunction errorMessage(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\nconst MemoirOpenCode: Plugin = async (_input, rawOptions) => {\n const opts = (rawOptions ?? {}) as { store?: string };\n if (opts.store) setPluginStoreOverride(opts.store);\n\n const storePath = deriveStorePath();\n\n const mcpCommand = [\"memoir-mcp\"];\n if (storePath) {\n mcpCommand.push(\"--store\", storePath);\n }\n\n const mcpServer = {\n type: \"local\" as const,\n command: mcpCommand,\n environment: storePath ? { MEMOIR_STORE: storePath } : undefined,\n };\n\n const hooks: Record<string, unknown> = {\n name: \"memoir\",\n\n config: async (config: Config): Promise<void> => {\n debugLog(\"config hook: mcpCommand =\", JSON.stringify(mcpCommand));\n debugLog(\"config hook: mcpCommand length =\", mcpCommand.length);\n\n const mcp = config.mcp ?? {};\n mcp.memoir = mcpServer;\n config.mcp = mcp;\n\n config.command = config.command ?? {};\n config.command[\"memoir:onboard\"] = {\n description: \"Populate or refresh Memoir onboarding for this project\",\n template: `Populate or refresh Memoir onboarding for the current project.\n\nWorkflow:\n- First obtain a project file tree to understand structure.\n- Start studying from project documentation.\n- Continue only based on what the tree and documentation show.\n\nMemory rules:\n- Record only verified facts from files/docs/code or explicit user statements.\n- Do not write inferred user thoughts, intentions, preferences, or opinions.\n- Do not use preferences.* paths unless the user explicitly stated a preference.\n- If a fact is your interpretation, do not save it; report it as uncertain instead.\n\nThen call memoir_memoir_remember with replace=true for durable onboarding facts. Use namespace codebase:onboard in git repositories and project:onboard outside git.`,\n };\n\n debugLog(\"config hook: mcpCommand =\", JSON.stringify(mcpCommand));\n },\n\n \"shell.env\": async (\n _input: unknown,\n output: { env: Record<string, string> },\n ): Promise<void> => {\n try {\n if (storePath) {\n output.env.MEMOIR_STORE = storePath;\n }\n } catch (e) {\n debugLog(\"shell.env: failed:\", errorMessage(e));\n }\n },\n\n \"chat.message\": async (\n input: { sessionID?: string },\n _output: { parts?: Array<{ type: string; text: string }> },\n ): Promise<void> => {\n try {\n const sid = input.sessionID ?? \"default\";\n incrementMsgCount(sid);\n\n await autoMatchMemoirBranch(storePath, sid);\n } catch (e) {\n debugLog(\"chat.message: failed:\", errorMessage(e));\n }\n },\n\n \"experimental.chat.system.transform\": async (\n input: { sessionID?: string },\n output: { system?: string[] },\n ): Promise<void> => {\n try {\n const sid = input.sessionID ?? \"default\";\n\n if (input.sessionID && !sessionsWithStartupHint.has(sid)) {\n sessionsWithStartupHint.add(sid);\n output.system?.unshift(\n '[memoir] You have Memoir MCP tools (memoir_memoir_recall, memoir_memoir_remember, memoir_memoir_get, memoir_memoir_summarize, memoir_memoir_status, memoir_memoir_branches, memoir_memoir_checkout).\\nSAVE (memoir_memoir_remember) after: task completion, user stating preferences/constraints, discovering project facts. RECALL (memoir_memoir_recall \\u2192 memoir_memoir_get) at session start for prior context, when user asks about prior work.\\nNamespaces: \"default\" (user context), \"codebase:onboard\" (project facts in git repos). Taxonomy paths: preferences.*, project.*, codebase.*, decisions.*, learning.*.',\n );\n }\n\n const count = sessionMsgCount.get(sid) ?? 0;\n if (shouldRemind(count)) {\n output.system?.push(\n \"\\n[memoir] Reminder: Use memoir_memoir_remember to save context, memoir_memoir_recall to retrieve prior context.\",\n );\n }\n } catch (e) {\n debugLog(\"system.transform: failed:\", errorMessage(e));\n }\n },\n\n dispose: async (): Promise<void> => {\n try {\n if (process.env.MEMOIR_AUTO_SAVE === \"1\") {\n for (const [sid, count] of sessionMsgCount) {\n await callMemoir(\n [\n \"remember\",\n `session.${sid}`,\n \"--message\",\n `Session ended \u2014 ${count} user messages exchanged`,\n ],\n storePath,\n );\n }\n }\n } catch (e) {\n debugLog(\"dispose: save failed:\", errorMessage(e));\n }\n sessionsWithStartupHint.clear();\n pruneBranchCache();\n pruneAll();\n },\n };\n\n hooks.mcp = { memoir: mcpServer };\n\n return hooks;\n};\n\nconst plugin: PluginModule = {\n id: \"opencode-memoir\",\n server: MemoirOpenCode,\n};\n\nexport default plugin;\n"],
5
+ "mappings": "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",
6
6
  "names": ["errorMessage", "_input"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-memoir",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Memoir memory plugin for OpenCode — Git-versioned, taxonomy-structured AI memory.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",