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 +5 -1
- package/dist/index.js +22 -10
- package/dist/recallSelector.d.ts +1 -1
- package/dist/recallSelector.js +18 -12
- package/package.json +20 -4
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# π§ Claude Code-compatible memory for OpenCode
|
|
4
4
|
|
|
5
|
-
**
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
420
|
+
const entry = readMemory(memoryRoot, args.file_name);
|
|
409
421
|
if (!entry) {
|
|
410
422
|
return `Memory "${args.file_name}" not found.`;
|
|
411
423
|
}
|
package/dist/recallSelector.d.ts
CHANGED
|
@@ -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
|
|
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>;
|
package/dist/recallSelector.js
CHANGED
|
@@ -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
|
|
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
|
|
76
|
-
session
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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({
|
|
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({
|
|
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.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Claude Code
|
|
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
|
-
"
|
|
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",
|