opencode-claude-memory 1.7.0 β†’ 1.7.2

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/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  # 🧠 Claude Code-compatible memory for OpenCode
4
4
 
5
- **Make OpenCode and Claude Code share the same memory β€” zero config, local-first, and no migration required.**
5
+ **Persistent, local-first shared memory for OpenCode and Claude Code β€” zero config and no migration required.**
6
+
7
+ This OpenCode memory plugin lets OpenCode read and write Claude Code-compatible Markdown memory files, so both CLIs share the same project context.
6
8
 
7
9
  Claude Code writes memory β†’ OpenCode reads it. OpenCode writes memory β†’ Claude Code reads it.
8
10
 
@@ -18,6 +20,8 @@ Claude Code writes memory β†’ OpenCode reads it. OpenCode writes memory β†’ Clau
18
20
 
19
21
  ## ✨ At a glance
20
22
 
23
+ - **OpenCode plugin for Claude Code memory**
24
+ Adds persistent memory tools, system prompt injection, and post-session extraction to OpenCode.
21
25
  - **Claude Code-compatible memory**
22
26
  Uses Claude Code’s existing memory paths, file format, and taxonomy.
23
27
  - **Zero config**
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
+ import { parse, resolve } from "path";
2
3
  import { buildMemorySystemPrompt } from "./prompt.js";
3
4
  import { formatRecalledMemories, recallSelectedMemories } from "./recall.js";
4
- import { assertSupportedRecallSelectorClient, selectRelevantMemoryFilenames } from "./recallSelector.js";
5
+ import { isSupportedRecallSelectorClient, selectRelevantMemoryFilenames } from "./recallSelector.js";
5
6
  import { scanMemoryFiles } from "./memoryScan.js";
6
7
  import { saveMemory, deleteMemory, listMemories, searchMemories, readMemory, MEMORY_TYPES, } from "./memory.js";
7
8
  import { getMemoryDir } from "./paths.js";
@@ -117,6 +118,15 @@ function getRecallModel() {
117
118
  modelID: raw.slice(slashIdx + 1),
118
119
  };
119
120
  }
121
+ function isRootPath(path) {
122
+ const resolved = resolve(path);
123
+ return resolved === parse(resolved).root;
124
+ }
125
+ function resolveMemoryRoot(worktree, directory) {
126
+ if (isRootPath(worktree) && !isRootPath(directory))
127
+ return directory;
128
+ return worktree;
129
+ }
120
130
  function isUsefulRecallQuery(query) {
121
131
  const trimmed = query?.trim();
122
132
  if (!trimmed)
@@ -134,7 +144,8 @@ function alreadySurfacedKey(header) {
134
144
  function startRecallPrefetch(input) {
135
145
  if (!input.client || !isUsefulRecallQuery(input.query))
136
146
  return undefined;
137
- assertSupportedRecallSelectorClient(input.client);
147
+ if (!isSupportedRecallSelectorClient(input.client))
148
+ return undefined;
138
149
  const memoryDir = getMemoryDir(input.worktree);
139
150
  const headers = scanMemoryFiles(memoryDir).filter((header) => !input.alreadySurfaced.has(alreadySurfacedKey(header)));
140
151
  if (headers.length === 0)
@@ -225,7 +236,8 @@ function getCallID(ctx) {
225
236
  }
226
237
  export const MemoryPlugin = async ({ worktree, directory, client }) => {
227
238
  directory ??= worktree;
228
- getMemoryDir(worktree);
239
+ const memoryRoot = resolveMemoryRoot(worktree, directory);
240
+ getMemoryDir(memoryRoot);
229
241
  return {
230
242
  config: async (config) => {
231
243
  const agentName = getRecallAgent();
@@ -285,7 +297,7 @@ export const MemoryPlugin = async ({ worktree, directory, client }) => {
285
297
  : startRecallPrefetch({
286
298
  client: client,
287
299
  directory,
288
- worktree,
300
+ worktree: memoryRoot,
289
301
  parentSessionID: sessionID,
290
302
  turnID,
291
303
  query,
@@ -321,7 +333,7 @@ export const MemoryPlugin = async ({ worktree, directory, client }) => {
321
333
  const ignoreMemoryContext = process.env.OPENCODE_MEMORY_IGNORE === "1" || shouldIgnoreMemoryContext(query);
322
334
  const recalled = ignoreMemoryContext ? [] : consumeRecallPrefetch(ctx);
323
335
  const recalledSection = formatRecalledMemories(recalled);
324
- const memoryPrompt = buildMemorySystemPrompt(worktree, recalledSection, {
336
+ const memoryPrompt = buildMemorySystemPrompt(memoryRoot, recalledSection, {
325
337
  includeIndex: !ignoreMemoryContext,
326
338
  });
327
339
  output.system.push(memoryPrompt);
@@ -350,7 +362,7 @@ export const MemoryPlugin = async ({ worktree, directory, client }) => {
350
362
  .describe("Memory content. For feedback/project types, structure as: rule/fact, then **Why:** and **How to apply:** lines"),
351
363
  },
352
364
  async execute(args, _ctx) {
353
- const filePath = saveMemory(worktree, args.file_name, args.name, args.description, args.type, args.content);
365
+ const filePath = saveMemory(memoryRoot, args.file_name, args.name, args.description, args.type, args.content);
354
366
  return `Memory saved to ${filePath}`;
355
367
  },
356
368
  }),
@@ -360,7 +372,7 @@ export const MemoryPlugin = async ({ worktree, directory, client }) => {
360
372
  file_name: tool.schema.string().describe("File name of the memory to delete (with or without .md extension)"),
361
373
  },
362
374
  async execute(args, _ctx) {
363
- const deleted = deleteMemory(worktree, args.file_name);
375
+ const deleted = deleteMemory(memoryRoot, args.file_name);
364
376
  return deleted ? `Memory "${args.file_name}" deleted.` : `Memory "${args.file_name}" not found.`;
365
377
  },
366
378
  }),
@@ -370,7 +382,7 @@ export const MemoryPlugin = async ({ worktree, directory, client }) => {
370
382
  "or when you need to recall what's been stored.",
371
383
  args: {},
372
384
  async execute(_args, ctx) {
373
- const entries = listMemories(worktree);
385
+ const entries = listMemories(memoryRoot);
374
386
  const callID = getCallID(ctx);
375
387
  if (callID)
376
388
  memoryListCountByCallID.set(callID, entries.length);
@@ -388,7 +400,7 @@ export const MemoryPlugin = async ({ worktree, directory, client }) => {
388
400
  query: tool.schema.string().describe("Search query β€” searches across name, description, and content"),
389
401
  },
390
402
  async execute(args, ctx) {
391
- const results = searchMemories(worktree, args.query);
403
+ const results = searchMemories(memoryRoot, args.query);
392
404
  const callID = getCallID(ctx);
393
405
  if (callID)
394
406
  memorySearchCountByCallID.set(callID, results.length);
@@ -405,7 +417,7 @@ export const MemoryPlugin = async ({ worktree, directory, client }) => {
405
417
  file_name: tool.schema.string().describe("File name of the memory to read (with or without .md extension)"),
406
418
  },
407
419
  async execute(args, _ctx) {
408
- const entry = readMemory(worktree, args.file_name);
420
+ const entry = readMemory(memoryRoot, args.file_name);
409
421
  if (!entry) {
410
422
  return `Memory "${args.file_name}" not found.`;
411
423
  }
@@ -1,6 +1,6 @@
1
1
  import { type MemoryHeader } from "./memoryScan.js";
2
2
  export declare const SELECT_MEMORIES_SYSTEM_PROMPT = "You are selecting memories that will be useful to OpenCode as it processes a user's query. You will be given the user's query and a list of available memory files with their filenames and descriptions.\n\nReturn a list of filenames for the memories that will clearly be useful to OpenCode as it processes the user's query (up to 5). Only include memories that you are certain will be helpful based on their name and description.\n- If you are unsure if a memory will be useful in processing the user's query, then do not include it in your list. Be selective and discerning.\n- If there are no memories in the list that would clearly be useful, feel free to return an empty list.\n- If a list of recently-used tools is provided, do not select memories that are usage reference or API documentation for those tools (OpenCode is already exercising them). DO still select memories containing warnings, gotchas, or known issues about those tools \u2014 active use is exactly when those matter.\n";
3
- export declare const UNSUPPORTED_RECALL_SELECTOR_CLIENT_MESSAGE = "opencode-claude-memory LLM recall requires an OpenCode SDK with structured output session.prompt support. Please upgrade OpenCode/@opencode-ai/plugin.";
3
+ export declare const UNSUPPORTED_RECALL_SELECTOR_CLIENT_MESSAGE = "opencode-claude-memory LLM recall requires an OpenCode SDK session client with create/prompt/delete support.";
4
4
  export type SessionClient = {
5
5
  session?: {
6
6
  create?: (...args: unknown[]) => Promise<unknown>;
@@ -17,7 +17,7 @@ const SELECT_MEMORIES_FORMAT = {
17
17
  additionalProperties: false,
18
18
  },
19
19
  };
20
- export const UNSUPPORTED_RECALL_SELECTOR_CLIENT_MESSAGE = "opencode-claude-memory LLM recall requires an OpenCode SDK with structured output session.prompt support. Please upgrade OpenCode/@opencode-ai/plugin.";
20
+ export const UNSUPPORTED_RECALL_SELECTOR_CLIENT_MESSAGE = "opencode-claude-memory LLM recall requires an OpenCode SDK session client with create/prompt/delete support.";
21
21
  function unwrapData(response) {
22
22
  if (!response || typeof response !== "object")
23
23
  return response;
@@ -71,12 +71,9 @@ function extractSelectedMemories(response) {
71
71
  }
72
72
  export function isSupportedRecallSelectorClient(client) {
73
73
  const session = client?.session;
74
- return Boolean(session?.create &&
75
- session?.prompt &&
76
- session?.delete &&
77
- session.create.length >= 2 &&
78
- session.prompt.length >= 2 &&
79
- session.delete.length >= 2);
74
+ return Boolean(typeof session?.create === "function" &&
75
+ typeof session.prompt === "function" &&
76
+ typeof session.delete === "function");
80
77
  }
81
78
  export function assertSupportedRecallSelectorClient(client) {
82
79
  if (!isSupportedRecallSelectorClient(client)) {
@@ -87,9 +84,11 @@ async function createSelectorSession(client, directory, parentSessionID) {
87
84
  if (!client.session?.create)
88
85
  return undefined;
89
86
  const response = await client.session.create({
90
- directory,
91
- parentID: parentSessionID,
92
- title: "opencode-memory recall selector",
87
+ body: {
88
+ parentID: parentSessionID,
89
+ title: "opencode-memory recall selector",
90
+ },
91
+ query: { directory },
93
92
  });
94
93
  return extractSessionID(response);
95
94
  }
@@ -104,13 +103,20 @@ async function promptSelectorSession(client, sessionID, directory, agent, model,
104
103
  format: SELECT_MEMORIES_FORMAT,
105
104
  parts: [{ type: "text", text: content }],
106
105
  };
107
- return client.session.prompt({ sessionID, directory, ...body });
106
+ return client.session.prompt({
107
+ path: { id: sessionID },
108
+ query: { directory },
109
+ body,
110
+ });
108
111
  }
109
112
  async function deleteSelectorSession(client, sessionID, directory) {
110
113
  if (!client.session?.delete)
111
114
  return;
112
115
  try {
113
- await client.session.delete({ sessionID, directory });
116
+ await client.session.delete({
117
+ path: { id: sessionID },
118
+ query: { directory },
119
+ });
114
120
  }
115
121
  catch {
116
122
  // Best-effort cleanup. A failed selector deletion should not affect recall.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "opencode-claude-memory",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "type": "module",
5
- "description": "Claude Code-compatible memory compatibility layer for OpenCode β€” zero config, local-first, no migration",
5
+ "description": "OpenCode plugin for Claude Code memory: persistent, local-first shared memory with Claude Code-compatible Markdown files, auto extraction, and auto-dream",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
@@ -26,13 +26,29 @@
26
26
  "type": "git",
27
27
  "url": "git+https://github.com/kuitos/opencode-claude-memory.git"
28
28
  },
29
+ "bugs": {
30
+ "url": "https://github.com/kuitos/opencode-claude-memory/issues"
31
+ },
29
32
  "homepage": "https://github.com/kuitos/opencode-claude-memory#readme",
30
33
  "keywords": [
31
34
  "opencode",
35
+ "opencode-plugin",
36
+ "opencode-ai",
37
+ "claude",
38
+ "claude-code",
39
+ "claude-code-memory",
40
+ "claude-memory",
32
41
  "plugin",
33
42
  "memory",
34
- "persistent",
35
- "cross-session",
43
+ "persistent-memory",
44
+ "agent-memory",
45
+ "ai-memory",
46
+ "cross-session-memory",
47
+ "shared-memory",
48
+ "local-first",
49
+ "markdown-memory",
50
+ "coding-agent",
51
+ "developer-tools",
36
52
  "claude-code-compatible"
37
53
  ],
38
54
  "license": "MIT",