opencode-claude-memory 1.7.7 → 1.7.8

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.
Files changed (69) hide show
  1. package/README.md +104 -214
  2. package/dist/agents.d.ts +12 -0
  3. package/dist/agents.js +69 -0
  4. package/dist/config.d.ts +112 -0
  5. package/dist/config.js +79 -0
  6. package/dist/extraction/ExtractionCoordinator.d.ts +62 -0
  7. package/dist/extraction/ExtractionCoordinator.js +386 -0
  8. package/dist/extraction/autodream.d.ts +33 -0
  9. package/dist/extraction/autodream.js +85 -0
  10. package/dist/extraction/forkSession.d.ts +37 -0
  11. package/dist/extraction/forkSession.js +133 -0
  12. package/dist/extraction/lock.d.ts +29 -0
  13. package/dist/extraction/lock.js +191 -0
  14. package/dist/extraction/prompts.d.ts +5 -0
  15. package/dist/extraction/prompts.js +78 -0
  16. package/dist/extraction/state.d.ts +38 -0
  17. package/dist/extraction/state.js +159 -0
  18. package/dist/hooks/ignore.d.ts +6 -0
  19. package/dist/hooks/ignore.js +53 -0
  20. package/dist/hooks/messages.d.ts +14 -0
  21. package/dist/hooks/messages.js +96 -0
  22. package/dist/index.d.ts +7 -1
  23. package/dist/index.js +50 -665
  24. package/dist/prompt/sections.d.ts +7 -0
  25. package/dist/{prompt.js → prompt/sections.js} +16 -72
  26. package/dist/prompt/systemPrompt.d.ts +7 -0
  27. package/dist/prompt/systemPrompt.js +64 -0
  28. package/dist/recall/RecallCoordinator.d.ts +36 -0
  29. package/dist/recall/RecallCoordinator.js +140 -0
  30. package/dist/recall/format.d.ts +17 -0
  31. package/dist/{recall.js → recall/format.js} +13 -36
  32. package/dist/{recallSelector.d.ts → recall/selector.d.ts} +26 -16
  33. package/dist/recall/selector.js +92 -0
  34. package/dist/sdk.d.ts +12 -0
  35. package/dist/sdk.js +8 -0
  36. package/dist/store/MemoryStore.d.ts +41 -0
  37. package/dist/store/MemoryStore.js +105 -0
  38. package/dist/store/frontmatter.d.ts +25 -0
  39. package/dist/store/frontmatter.js +61 -0
  40. package/dist/store/indexFile.d.ts +15 -0
  41. package/dist/store/indexFile.js +153 -0
  42. package/dist/{paths.d.ts → store/paths.d.ts} +7 -6
  43. package/dist/store/paths.js +187 -0
  44. package/dist/store/scan.d.ts +29 -0
  45. package/dist/store/scan.js +112 -0
  46. package/dist/tools.d.ts +9 -0
  47. package/dist/tools.js +142 -0
  48. package/dist/util/exclusiveFile.d.ts +11 -0
  49. package/dist/util/exclusiveFile.js +108 -0
  50. package/dist/util/legacyShellHook.d.ts +3 -0
  51. package/dist/util/legacyShellHook.js +23 -0
  52. package/dist/util/log.d.ts +6 -0
  53. package/dist/util/log.js +29 -0
  54. package/dist/util/ownedSessions.d.ts +7 -0
  55. package/dist/util/ownedSessions.js +39 -0
  56. package/dist/util/timeout.d.ts +5 -0
  57. package/dist/util/timeout.js +39 -0
  58. package/package.json +15 -8
  59. package/bin/opencode-memory +0 -1436
  60. package/dist/memory.d.ts +0 -31
  61. package/dist/memory.js +0 -224
  62. package/dist/memoryScan.d.ts +0 -20
  63. package/dist/memoryScan.js +0 -106
  64. package/dist/nativeExtraction.d.ts +0 -9
  65. package/dist/nativeExtraction.js +0 -80
  66. package/dist/paths.js +0 -141
  67. package/dist/prompt.d.ts +0 -4
  68. package/dist/recall.d.ts +0 -12
  69. package/dist/recallSelector.js +0 -154
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  # 🧠 Claude Code-compatible memory for OpenCode
4
4
 
5
- **Persistent, local-first shared memory for OpenCode and Claude Code — zero config and no migration required.**
5
+ **Persistent, local-first shared memory for OpenCode and Claude Code — one plugin, zero migration.**
6
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.
7
+ This OpenCode plugin lets OpenCode read and write Claude Code-compatible Markdown memory files, so both CLIs share the same project context.
8
8
 
9
9
  Claude Code writes memory → OpenCode reads it. OpenCode writes memory → Claude Code reads it.
10
10
 
@@ -12,7 +12,7 @@ Claude Code writes memory → OpenCode reads it. OpenCode writes memory → Clau
12
12
  [![npm downloads](https://img.shields.io/npm/dm/opencode-claude-memory.svg?style=flat-square)](https://www.npmjs.com/package/opencode-claude-memory)
13
13
  [![License](https://img.shields.io/npm/l/opencode-claude-memory.svg?style=flat-square)](https://github.com/kuitos/opencode-claude-memory/blob/main/LICENSE)
14
14
 
15
- [Quick Start](#-quick-start) • [Why this exists](#-why-this-exists) • [What makes this different](#-what-makes-this-different) • [How it works](#-how-it-works) • [Who this is for](#-who-this-is-for) • [FAQ](#-faq)
15
+ [Quick Start](#-quick-start) • [How it works](#-how-it-works) • [Configuration](#-configuration) • [Compatibility](#-compatibility-with-claude-code) • [Migrating from v1](#-migrating-from-v1) • [FAQ](#-faq)
16
16
 
17
17
  </div>
18
18
 
@@ -20,229 +20,95 @@ Claude Code writes memory → OpenCode reads it. OpenCode writes memory → Clau
20
20
 
21
21
  ## ✨ At a glance
22
22
 
23
- - **OpenCode plugin for Claude Code memory**
24
- Adds persistent memory tools, system prompt injection, and post-session extraction to OpenCode.
25
- - **Claude Code-compatible memory**
26
- Uses Claude Code’s existing memory paths, file format, and taxonomy.
27
- - **Zero config**
28
- Install + enable plugin, then keep using `opencode` as usual.
29
- - **Local-first, no migration**
30
- Memory stays as local Markdown files in the same directory Claude Code already uses.
31
- - **Auto-dream consolidation**
32
- Periodically runs a background memory consolidation pass (Claude-style auto-dream gating).
23
+ - **Memory tools** — `memory_save` / `memory_delete` / `memory_list` / `memory_search` / `memory_read`, plus the Claude Code memory instructions injected into every system prompt.
24
+ - **LLM recall** — before each turn a hidden agent picks the memories relevant to the query; they appear in the *first* LLM call, including single-step questions.
25
+ - **Automatic extraction** — after a session goes idle, a sandboxed fork reviews only the *new* part of the conversation and saves what is worth keeping. Sessions closed before the fork ran are caught up at the next start.
26
+ - **Auto-dream** — periodic consolidation (merge / prune / rewrite) gated on time and session count, like Claude Code.
27
+ - **Claude Code-compatible** — same directory, same file format, same taxonomy, same worktree handling. `MEMORY.md` is edited line by line so hand-organised indexes stay intact.
28
+ - **Cross-platform, no shell hook** — everything runs inside the OpenCode process through the plugin SDK. No `python3`, no `jq`, no wrapper.
33
29
 
34
30
  ## 🚀 Quick Start
35
31
 
36
- ### Prerequisites
37
-
38
- - `opencode`
39
- - `python3` available in `PATH`
40
-
41
- `python3` is a runtime dependency for the wrapper's scoped session detection and fork cleanup logic.
42
- If it is missing or not executable, post-session maintenance becomes less reliable: session targeting can fall back to less precise heuristics, and fork cleanup is skipped for safety.
43
-
44
- Common install commands:
45
-
46
- ```bash
47
- # macOS (Homebrew)
48
- brew install python
49
-
50
- # Ubuntu / Debian
51
- sudo apt-get update && sudo apt-get install -y python3
52
-
53
- # Fedora
54
- sudo dnf install -y python3
55
-
56
- # Arch Linux
57
- sudo pacman -S python
58
- ```
59
-
60
- ### 1. Install
61
-
62
- ```bash
63
- npm install -g opencode-claude-memory
64
- opencode-memory install # one-time: installs shell hook
65
- ```
66
-
67
- This installs:
68
- - The **plugin** — memory tools + system prompt injection
69
- - The `opencode-memory` **CLI** — wraps opencode with automatic memory extraction + auto-dream consolidation
70
- - A **shell hook** — defines an `opencode()` function in your `.zshrc`/`.bashrc` that delegates to `opencode-memory`
71
-
72
- If `python3` is not installed yet, install it first using the commands above before enabling the shell hook.
73
-
74
- ### 2. Configure
32
+ Requires OpenCode **≥ 1.18**.
75
33
 
76
34
  ```jsonc
77
- // opencode.json
35
+ // opencode.json (project) or ~/.config/opencode/opencode.json (global)
78
36
  {
79
37
  "plugin": ["opencode-claude-memory"]
80
38
  }
81
39
  ```
82
40
 
83
- ### 3. Use
84
-
85
- ```bash
86
- opencode
87
- ```
88
-
89
- That’s it. Memory extraction runs in the background after each session, and auto-dream consolidation is checked with time/session gates.
90
-
91
- To uninstall:
92
-
93
- ```bash
94
- opencode-memory uninstall # removes shell hook from .zshrc/.bashrc
95
- npm uninstall -g opencode-claude-memory
96
- ```
97
-
98
- To print the wrapper package version:
99
-
100
- ```bash
101
- opencode-memory self -v
102
- ```
103
-
104
- This removes the shell hook, the CLI, and the plugin. Your saved memories in `~/.claude/projects/` are **not** deleted.
105
-
106
- ## 💡 Why this exists
107
-
108
- If you use both Claude Code and OpenCode on the same repository, memory often ends up in separate silos.
109
-
110
- This project solves that by making OpenCode read and write memory in Claude Code’s existing structure, so your context carries over naturally between both tools.
111
-
112
- ## 🧩 What makes this different
113
-
114
- Most memory plugins introduce a new storage model or migration step.
115
-
116
- This one is a **compatibility layer**, not a new memory system:
117
-
118
- - same memory directory conventions as Claude Code
119
- - same Markdown + frontmatter format
120
- - same memory taxonomy (`user`, `feedback`, `project`, `reference`)
121
- - same project/worktree resolution behavior
122
-
123
- The outcome: **shared context across Claude Code and OpenCode without maintaining two memory systems.**
41
+ That's it. Start `opencode` and use it as usual. Memories live in `~/.claude/projects/<project>/memory/` (or under `$CLAUDE_CONFIG_DIR`), exactly where Claude Code keeps them.
124
42
 
125
43
  ## ⚙️ How it works
126
44
 
127
45
  ```mermaid
128
46
  graph LR
129
- A[You run opencode] --> B[Shell hook calls opencode-memory]
130
- B --> C[opencode-memory finds real binary]
131
- C --> D[Runs opencode normally]
132
- D --> E[You exit]
133
- E --> F[Extract memories if needed]
134
- F --> G[Evaluate auto-dream gate]
135
- G --> H[Consolidate memories if gate passes]
136
- H --> I[Memories saved to ~/.claude/projects/]
47
+ U[User turn] --> R[Hidden recall agent<br/>selects relevant memories]
48
+ R --> S[System prompt: instructions + MEMORY.md + recalled memories]
49
+ S --> A[Main agent answers<br/>memory_* tools available]
50
+ A --> I[session.idle]
51
+ I --> E[Extraction fork<br/>new messages only]
52
+ E --> M[(~/.claude/projects/&lt;project&gt;/memory/)]
53
+ E --> D{Auto-dream gate}
54
+ D -->|24h & 5 sessions| C[Consolidation fork]
55
+ C --> M
137
56
  ```
138
57
 
139
- The shell hook defines an `opencode()` function that delegates to `opencode-memory`:
140
-
141
- 1. Shell function intercepts `opencode` command (higher priority than PATH)
142
- 2. `opencode-memory` finds the real `opencode` binary in PATH
143
- 3. Runs it with all your arguments
144
- 4. After you exit, it checks whether the session already wrote memory files
145
- 5. If needed, it forks the session with a memory extraction prompt
146
- 6. It evaluates the auto-dream gate (default: at least 24h since last consolidation and 5 touched sessions)
147
- 7. If the gate passes, it runs a background consolidation pass to merge/prune memories
148
- 8. Maintenance runs **in the background** unless `OPENCODE_MEMORY_FOREGROUND=1`
149
- 9. Terminal maintenance logs are shown in foreground mode by default, or can be forced on/off with `OPENCODE_MEMORY_TERMINAL_LOG=1|0`
150
-
151
- ### Runtime dependencies
152
-
153
- The wrapper expects `python3` to be available at runtime.
154
-
155
- It is used for:
156
-
157
- - scoped session selection from `opencode session list`
158
- - parsing `opencode export` output to resolve session directories
159
- - safely identifying and cleaning up forked extraction / auto-dream sessions
160
-
161
- Without `python3`, the plugin tools still load, but wrapper maintenance is degraded and fork cleanup is intentionally skipped to avoid deleting the wrong session.
162
-
163
- ### Compatibility details
164
-
165
- The implementation ports core logic from Claude Code for path hashing, git-root/worktree handling, memory format, and memory prompting behavior, so both tools can operate on the same files safely.
58
+ 1. **Recall** — `experimental.chat.messages.transform` starts a selector prefetch for each new user turn (a hidden child session running `opencode-memory-recall`). `experimental.chat.system.transform` waits for it up to `recall.waitMs` (default 1.5 s) and injects the selected memories. Memories already in the conversation are not re-injected; after compaction they can surface again.
59
+ 2. **Extraction** — every `session.idle` is debounced (`extract.debounceMs`). The plugin fetches the session's messages, slices them after the per-session watermark, and — only if there is a new user message — runs `opencode-memory-extract` in a child session restricted to `memory_save` / `memory_list` / `memory_read`. On success the watermark advances; if the main agent already saved memory in that stretch the fork is skipped.
60
+ 3. **Catch-up** — on start-up the plugin lists the project's sessions and extracts the ones updated after their watermark (at most `extract.catchUpLimit`). This covers "answer, then quit immediately".
61
+ 4. **Auto-dream** — after each extracted session the gate is evaluated (`autodream.minHours` since the last pass **and** `autodream.minSessions` extracted since). When it passes, `opencode-memory-dream` runs with all five memory tools. A lock file prevents two OpenCode processes from consolidating at once.
62
+ 5. **Ignore memory** — "ignore memory" in a user message switches memory off for the rest of the session (no index, no recall); "use memory again" switches it back on.
166
63
 
167
- Key modules ported from Claude Code's `src/memdir/`:
168
-
169
- | Module | Source | Purpose |
170
- |---|---|---|
171
- | `memoryScan.ts` | `memoryScan.ts` | Recursive directory scan + frontmatter header parsing |
172
- | `recall.ts` + `recallSelector.ts` | `findRelevantMemories.ts` | LLM-selected memory recall + selected memory formatting |
173
- | `prompt.ts` | `memoryTypes.ts` + `memdir.ts` | System prompt sections, type taxonomy, truncation |
174
- | `memory.ts` | `memdir.ts` | `truncateEntrypoint()` aligned with `truncateEntrypointContent()` |
175
-
176
- ## 👥 Who this is for
177
-
178
- - You use **both Claude Code and OpenCode**.
179
- - You want **one shared memory context** across both tools.
180
- - You prefer **file-based, local-first memory** you can inspect in Git/worktrees.
181
- - You don’t want migration overhead or lock-in.
182
-
183
- ## ❓ FAQ
184
-
185
- ### Is this a new memory system?
186
-
187
- No. It is a compatibility layer that lets OpenCode use Claude Code-compatible memory layout and conventions.
188
-
189
- ### Do I need to migrate existing memory?
190
-
191
- No migration required. If you already have Claude Code memory files, OpenCode can work with them directly.
192
-
193
- ### Where is data stored?
194
-
195
- In local files under Claude-style project memory directories (for example, under `~/.claude/projects/<project>/memory/`).
196
-
197
- ### Why file-based memory?
198
-
199
- File-based memory is transparent, local-first, easy to inspect/diff/back up, and works naturally with existing developer workflows.
200
-
201
- ### Can I disable auto extraction?
202
-
203
- Yes. Set `OPENCODE_MEMORY_EXTRACT=0`.
204
-
205
- ### Can I disable auto-dream?
206
-
207
- Yes. Set `OPENCODE_MEMORY_AUTODREAM=0`. You can also tune gates with:
208
- - `OPENCODE_MEMORY_AUTODREAM_MIN_HOURS`
209
- - `OPENCODE_MEMORY_AUTODREAM_MIN_SESSIONS`
64
+ State that is private to the plugin (watermarks, auto-dream gate, lock) lives in `<CLAUDE_CONFIG_DIR>/opencode-memory/<project>/`, never inside the Claude Code project directory.
210
65
 
211
66
  ## 🔧 Configuration
212
67
 
213
- ### Environment variables
68
+ All behaviour is configured through OpenCode's own configuration. There are no `OPENCODE_MEMORY_*` environment variables.
214
69
 
215
- - `OPENCODE_MEMORY_EXTRACT` (default `1`): set `0` to disable automatic memory extraction
216
- - `OPENCODE_MEMORY_FOREGROUND` (default `0`): set `1` to run maintenance in foreground
217
- - `OPENCODE_MEMORY_TERMINAL_LOG` (default `foreground-only`): set `1` to force terminal logs on, `0` to force them off
218
- - `OPENCODE_MEMORY_MODEL`: override model used for extraction
219
- - `OPENCODE_MEMORY_AGENT`: override agent used for extraction
220
- - `OPENCODE_MEMORY_EXTRACT_TIMEOUT_MS` (default `120000`): positive integer timeout for a native extraction request in milliseconds
221
- - `OPENCODE_MEMORY_EXTRACT_MAX_STEPS` (default `30`): max agentic steps for a native extraction fork before OpenCode forces a text-only reply; set `0` to remove the cap (the timeout is then the only backstop)
222
- - `OPENCODE_MEMORY_RECALL_MODEL`: override model used for LLM memory recall selection
223
- - `OPENCODE_MEMORY_RECALL_AGENT` (default `opencode-memory-recall`): override agent used for LLM memory recall selection
224
- - `OPENCODE_MEMORY_AUTODREAM` (default `1`): set `0` to disable auto-dream consolidation
225
- - `OPENCODE_MEMORY_AUTODREAM_MIN_HOURS` (default `24`): min hours between consolidation runs
226
- - `OPENCODE_MEMORY_AUTODREAM_MIN_SESSIONS` (default `5`): min touched sessions since last consolidation
227
- - `OPENCODE_MEMORY_AUTODREAM_MODEL`: override model used for auto-dream
228
- - `OPENCODE_MEMORY_AUTODREAM_AGENT`: override agent used for auto-dream
70
+ ```jsonc
71
+ // opencode.json
72
+ {
73
+ "plugin": [
74
+ ["opencode-claude-memory", {
75
+ "extract": { "enabled": true, "timeoutMs": 120000, "debounceMs": 10000, "maxConversationChars": 60000, "catchUpLimit": 5 },
76
+ "autodream": { "enabled": true, "minHours": 24, "minSessions": 5, "timeoutMs": 300000 },
77
+ "recall": { "enabled": true, "waitMs": 1500, "timeoutMs": 30000, "maxMemories": 5 }
78
+ }]
79
+ ],
80
+ "agent": {
81
+ "opencode-memory-extract": { "model": "anthropic/claude-haiku-4-5", "steps": 20 },
82
+ "opencode-memory-recall": { "model": "anthropic/claude-haiku-4-5" },
83
+ "opencode-memory-dream": { "model": "anthropic/claude-sonnet-5" }
84
+ }
85
+ }
86
+ ```
229
87
 
230
- ### Logs
88
+ - Every option above is optional; the values shown are the defaults. Unknown keys are rejected when the plugin loads.
89
+ - When the same plugin is listed in both the global and the project `opencode.json`, OpenCode keeps the **last** declaration (project wins); options are not merged across files.
90
+ - The three agents are registered hidden with a memory-only tool sandbox. Override any field (`model`, `steps`, `temperature`, …) under `agent.<name>`; the plugin fills in the rest.
91
+ - `CLAUDE_CONFIG_DIR` is honoured exactly like Claude Code does, and is the only environment variable the plugin reads.
231
92
 
232
- Logs are written to `$TMPDIR/opencode-memory-logs/`:
233
- - `extract-*.log`: automatic memory extraction
234
- - `dream-*.log`: auto-dream consolidation
93
+ Logs go to the OpenCode service log (`opencode` log directory, service `opencode-claude-memory`).
235
94
 
236
- By default, terminal log lines are only printed when maintenance runs in foreground (`OPENCODE_MEMORY_FOREGROUND=1`). Background runs stay quiet unless you explicitly set `OPENCODE_MEMORY_TERMINAL_LOG=1`.
95
+ ## 🤝 Compatibility with Claude Code
237
96
 
238
- ### Concurrency safety
97
+ | Aspect | Claude Code | This plugin |
98
+ |---|---|---|
99
+ | Memory directory | `~/.claude/projects/<sanitized canonical git root>/memory/` | identical (`sanitizePath`, worktree → main repo resolution ported byte for byte) |
100
+ | File format | Markdown + `name` / `description` / `type` frontmatter | identical; frontmatter parsed only within the first 30 lines, as in Claude Code |
101
+ | Taxonomy | `user`, `feedback`, `project`, `reference` | identical |
102
+ | `MEMORY.md` | one-line pointers, hand-organisable | read with the same truncation rules; written with minimal line-level edits |
103
+ | Sub-directories | `team/x.md` etc. | scanned, recalled and addressable from every tool |
104
+ | System prompt | memory instructions + index + recalled memories | ported sections (`memoryTypes.ts`, `memdir.ts`) |
105
+ | Recall | LLM side query | LLM side query in a hidden child session (`findRelevantMemories.ts` port) |
106
+ | Extraction / auto-dream | after session, gated | after `session.idle` + start-up catch-up, gated the same way |
239
107
 
240
- Lock files prevent concurrent extraction/consolidation runs per project root. Stale locks are cleaned up automatically.
108
+ Memory files written by either tool need no conversion in either direction.
241
109
 
242
110
  ## 📝 Memory format
243
111
 
244
- Each memory is a Markdown file with YAML frontmatter:
245
-
246
112
  ```markdown
247
113
  ---
248
114
  name: User prefers terse responses
@@ -256,32 +122,56 @@ Skip post-action summaries. User reads diffs directly.
256
122
  **How to apply:** Don't summarize changes at the end of responses.
257
123
  ```
258
124
 
259
- Supported memory types:
260
- - `user`
261
- - `feedback`
262
- - `project`
263
- - `reference`
125
+ ## 🔁 Migrating from v1
264
126
 
265
- ## 🔧 Tools reference
127
+ v2 removes the shell wrapper, the `opencode-memory` CLI and every `OPENCODE_MEMORY_*` environment variable. Memory files are untouched and need no conversion.
266
128
 
267
- - `memory_save`: save/update a memory
268
- - `memory_delete`: delete a memory by filename
269
- - `memory_list`: list memory metadata
270
- - `memory_search`: search by keyword
271
- - `memory_read`: read full memory content
129
+ ```bash
130
+ # 1. remove the v1 shell hook, then the v1 package (v2 no longer needs a global install)
131
+ opencode-memory uninstall # or delete the ">>> opencode-memory auto-initialization >>>" block from your rc file
132
+ npm uninstall -g opencode-claude-memory
272
133
 
273
- ## 🧪 Development
134
+ # 2. drop OPENCODE_MEMORY_* from your shell configuration
135
+ grep -n OPENCODE_MEMORY ~/.zshrc ~/.bashrc ~/.zshenv ~/.profile 2>/dev/null
136
+ ```
274
137
 
275
- ```bash
276
- # Run tests
277
- bun test
138
+ ```jsonc
139
+ // 3. pin the major in opencode.json — OpenCode caches npm plugins per specifier,
140
+ // so a bare "opencode-claude-memory" keeps serving the v1 it installed earlier
141
+ {
142
+ "plugin": ["opencode-claude-memory@2"]
143
+ }
144
+ ```
278
145
 
279
- # Build published artifacts
280
- bun run build
146
+ Everything the environment variables used to control now lives under `extract`, `autodream`, `recall` and `agent.opencode-memory-*` in `opencode.json` — see [Configuration](#-configuration). The v1 documentation, including the full list of environment variables, stays available in the [v1 README](https://github.com/kuitos/opencode-claude-memory/blob/v1.7.7/README.md).
281
147
 
282
- # Release: push to main triggers semantic-release → npm publish
148
+ ## ❓ FAQ
149
+
150
+ **Is this a new memory system?** No. It is a compatibility layer around Claude Code's memory layout and conventions.
151
+
152
+ **Do I need to migrate existing memory?** No. Existing Claude Code memory files are used as they are.
153
+
154
+ **Where is data stored?** `~/.claude/projects/<project>/memory/` (or `$CLAUDE_CONFIG_DIR/projects/...`). Plugin state lives in `$CLAUDE_CONFIG_DIR/opencode-memory/<project>/`.
155
+
156
+ **Can I disable extraction, auto-dream or recall?** Yes — `extract.enabled`, `autodream.enabled`, `recall.enabled` in the plugin options.
157
+
158
+ **Why did my first answer take a moment longer?** The system prompt waits up to `recall.waitMs` for the selector. Set it to `0` to never wait (recalled memories then appear from the second LLM call of a turn onwards).
159
+
160
+ **Does the extraction fork see my whole conversation?** Only the messages after the last extraction, capped at `extract.maxConversationChars` (newest first). The fork can only call memory tools.
161
+
162
+ ## 🧪 Development
163
+
164
+ ```bash
165
+ bun install
166
+ bun test # unit, integration and eval tests
167
+ bun run evals # readable task-eval report
168
+ bun run lint # biome
169
+ bun run typecheck
170
+ bun run build # emits dist/
283
171
  ```
284
172
 
173
+ Releases are cut by semantic-release on push to `main`.
174
+
285
175
  ## 📄 License
286
176
 
287
177
  [MIT](LICENSE) © [kuitos](https://github.com/kuitos)
@@ -0,0 +1,12 @@
1
+ import type { MemoryAgents } from "./config.js";
2
+ import type { AgentConfig, PluginConfig } from "./sdk.js";
3
+ export declare const MEMORY_TOOL_NAMES: readonly ["memory_save", "memory_delete", "memory_list", "memory_search", "memory_read"];
4
+ export declare function buildAgentDefaults(agents: MemoryAgents): Record<string, AgentConfig>;
5
+ export declare function mergeAgentConfig(defaults: AgentConfig, user: AgentConfig | undefined): AgentConfig;
6
+ export declare class AgentRegistry {
7
+ private readonly defaults;
8
+ private readonly merged;
9
+ constructor(agents: MemoryAgents);
10
+ register(config: PluginConfig): void;
11
+ toolsFor(name: string): Record<string, boolean> | undefined;
12
+ }
package/dist/agents.js ADDED
@@ -0,0 +1,69 @@
1
+ import { AUTODREAM_PROMPT, EXTRACT_PROMPT } from "./extraction/prompts.js";
2
+ import { SELECT_MEMORIES_SYSTEM_PROMPT } from "./recall/selector.js";
3
+ export const MEMORY_TOOL_NAMES = [
4
+ "memory_save",
5
+ "memory_delete",
6
+ "memory_list",
7
+ "memory_search",
8
+ "memory_read",
9
+ ];
10
+ const ALL_MEMORY_TOOLS = Object.fromEntries(MEMORY_TOOL_NAMES.map((name) => [name, true]));
11
+ export function buildAgentDefaults(agents) {
12
+ return {
13
+ [agents.recall]: {
14
+ mode: "all",
15
+ hidden: true,
16
+ temperature: 0,
17
+ prompt: SELECT_MEMORIES_SYSTEM_PROMPT,
18
+ tools: { "*": false },
19
+ },
20
+ [agents.extract]: {
21
+ mode: "all",
22
+ hidden: true,
23
+ prompt: EXTRACT_PROMPT,
24
+ // A legitimate extraction is a handful of memory_save calls; the step cap terminates a model
25
+ // that keeps re-saving the same files instead of letting it spin until the timeout (#35).
26
+ steps: 30,
27
+ tools: { "*": false, memory_save: true, memory_list: true, memory_read: true },
28
+ },
29
+ [agents.dream]: {
30
+ mode: "all",
31
+ hidden: true,
32
+ prompt: AUTODREAM_PROMPT,
33
+ steps: 60,
34
+ tools: { "*": false, ...ALL_MEMORY_TOOLS },
35
+ },
36
+ };
37
+ }
38
+ export function mergeAgentConfig(defaults, user) {
39
+ if (!user)
40
+ return { ...defaults };
41
+ const merged = { ...defaults, ...user };
42
+ // `maxSteps` is the deprecated spelling of `steps`; a user who only set the old name must not be
43
+ // overridden by the default `steps`.
44
+ if (user.steps === undefined && user.maxSteps !== undefined)
45
+ delete merged.steps;
46
+ return merged;
47
+ }
48
+ export class AgentRegistry {
49
+ defaults;
50
+ merged;
51
+ constructor(agents) {
52
+ this.defaults = buildAgentDefaults(agents);
53
+ this.merged = { ...this.defaults };
54
+ }
55
+ // `config` hook: merge defaults under the user's own entries and remember the outcome so forks can
56
+ // pass the effective tool sandbox explicitly (defence in depth, see forkSession.ts).
57
+ register(config) {
58
+ config.agent ??= {};
59
+ const agent = config.agent;
60
+ for (const [name, defaults] of Object.entries(this.defaults)) {
61
+ const merged = mergeAgentConfig(defaults, agent[name]);
62
+ agent[name] = merged;
63
+ this.merged[name] = merged;
64
+ }
65
+ }
66
+ toolsFor(name) {
67
+ return this.merged[name]?.tools;
68
+ }
69
+ }
@@ -0,0 +1,112 @@
1
+ import { z } from "zod";
2
+ export declare const MemoryOptionsSchema: z.ZodObject<{
3
+ extract: z.ZodDefault<z.ZodObject<{
4
+ enabled: z.ZodDefault<z.ZodBoolean>;
5
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
6
+ debounceMs: z.ZodDefault<z.ZodNumber>;
7
+ maxConversationChars: z.ZodDefault<z.ZodNumber>;
8
+ catchUpLimit: z.ZodDefault<z.ZodNumber>;
9
+ }, "strict", z.ZodTypeAny, {
10
+ enabled: boolean;
11
+ timeoutMs: number;
12
+ debounceMs: number;
13
+ maxConversationChars: number;
14
+ catchUpLimit: number;
15
+ }, {
16
+ enabled?: boolean | undefined;
17
+ timeoutMs?: number | undefined;
18
+ debounceMs?: number | undefined;
19
+ maxConversationChars?: number | undefined;
20
+ catchUpLimit?: number | undefined;
21
+ }>>;
22
+ autodream: z.ZodDefault<z.ZodObject<{
23
+ enabled: z.ZodDefault<z.ZodBoolean>;
24
+ minHours: z.ZodDefault<z.ZodNumber>;
25
+ minSessions: z.ZodDefault<z.ZodNumber>;
26
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
27
+ }, "strict", z.ZodTypeAny, {
28
+ enabled: boolean;
29
+ timeoutMs: number;
30
+ minHours: number;
31
+ minSessions: number;
32
+ }, {
33
+ enabled?: boolean | undefined;
34
+ timeoutMs?: number | undefined;
35
+ minHours?: number | undefined;
36
+ minSessions?: number | undefined;
37
+ }>>;
38
+ recall: z.ZodDefault<z.ZodObject<{
39
+ enabled: z.ZodDefault<z.ZodBoolean>;
40
+ waitMs: z.ZodDefault<z.ZodNumber>;
41
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
42
+ maxMemories: z.ZodDefault<z.ZodNumber>;
43
+ }, "strict", z.ZodTypeAny, {
44
+ enabled: boolean;
45
+ timeoutMs: number;
46
+ waitMs: number;
47
+ maxMemories: number;
48
+ }, {
49
+ enabled?: boolean | undefined;
50
+ timeoutMs?: number | undefined;
51
+ waitMs?: number | undefined;
52
+ maxMemories?: number | undefined;
53
+ }>>;
54
+ }, "strict", z.ZodTypeAny, {
55
+ extract: {
56
+ enabled: boolean;
57
+ timeoutMs: number;
58
+ debounceMs: number;
59
+ maxConversationChars: number;
60
+ catchUpLimit: number;
61
+ };
62
+ autodream: {
63
+ enabled: boolean;
64
+ timeoutMs: number;
65
+ minHours: number;
66
+ minSessions: number;
67
+ };
68
+ recall: {
69
+ enabled: boolean;
70
+ timeoutMs: number;
71
+ waitMs: number;
72
+ maxMemories: number;
73
+ };
74
+ }, {
75
+ extract?: {
76
+ enabled?: boolean | undefined;
77
+ timeoutMs?: number | undefined;
78
+ debounceMs?: number | undefined;
79
+ maxConversationChars?: number | undefined;
80
+ catchUpLimit?: number | undefined;
81
+ } | undefined;
82
+ autodream?: {
83
+ enabled?: boolean | undefined;
84
+ timeoutMs?: number | undefined;
85
+ minHours?: number | undefined;
86
+ minSessions?: number | undefined;
87
+ } | undefined;
88
+ recall?: {
89
+ enabled?: boolean | undefined;
90
+ timeoutMs?: number | undefined;
91
+ waitMs?: number | undefined;
92
+ maxMemories?: number | undefined;
93
+ } | undefined;
94
+ }>;
95
+ export type MemoryOptions = z.infer<typeof MemoryOptionsSchema>;
96
+ export declare const MEMORY_AGENTS: {
97
+ readonly extract: "opencode-memory-extract";
98
+ readonly recall: "opencode-memory-recall";
99
+ readonly dream: "opencode-memory-dream";
100
+ };
101
+ export type MemoryAgents = typeof MEMORY_AGENTS;
102
+ export type MemoryConfig = MemoryOptions & {
103
+ claudeConfigDir: string;
104
+ homeDir: string;
105
+ agents: MemoryAgents;
106
+ };
107
+ export declare class MemoryConfigError extends Error {
108
+ constructor(message: string);
109
+ }
110
+ export declare function resolveClaudeConfigDir(env?: NodeJS.ProcessEnv): string;
111
+ export declare function parseMemoryOptions(options: unknown): MemoryOptions;
112
+ export declare function parseConfig(options: unknown, env?: NodeJS.ProcessEnv, homeDir?: string): MemoryConfig;
package/dist/config.js ADDED
@@ -0,0 +1,79 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { z } from "zod";
4
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
5
+ const timeoutMs = (fallback) => z.number().int().positive().max(MAX_TIMER_DELAY_MS).default(fallback);
6
+ // Plugin options come from `opencode.json`: `"plugin": [["opencode-claude-memory", { ... }]]`.
7
+ // The schema is strict at every level so a misspelled key fails at plugin load instead of silently
8
+ // falling back to defaults.
9
+ export const MemoryOptionsSchema = z
10
+ .object({
11
+ extract: z
12
+ .object({
13
+ enabled: z.boolean().default(true),
14
+ timeoutMs: timeoutMs(120_000),
15
+ debounceMs: z.number().int().nonnegative().max(MAX_TIMER_DELAY_MS).default(10_000),
16
+ maxConversationChars: z.number().int().positive().default(60_000),
17
+ catchUpLimit: z.number().int().nonnegative().default(5),
18
+ })
19
+ .strict()
20
+ .default({}),
21
+ autodream: z
22
+ .object({
23
+ enabled: z.boolean().default(true),
24
+ minHours: z.number().positive().default(24),
25
+ minSessions: z.number().int().positive().default(5),
26
+ timeoutMs: timeoutMs(300_000),
27
+ })
28
+ .strict()
29
+ .default({}),
30
+ recall: z
31
+ .object({
32
+ enabled: z.boolean().default(true),
33
+ waitMs: z.number().int().nonnegative().max(MAX_TIMER_DELAY_MS).default(1_500),
34
+ timeoutMs: timeoutMs(30_000),
35
+ maxMemories: z.number().int().positive().max(20).default(5),
36
+ })
37
+ .strict()
38
+ .default({}),
39
+ })
40
+ .strict();
41
+ // Agent names are fixed. Users customise the agents themselves (`agent.opencode-memory-extract.model`
42
+ // in opencode.json) instead of pointing the plugin at a differently named agent.
43
+ export const MEMORY_AGENTS = {
44
+ extract: "opencode-memory-extract",
45
+ recall: "opencode-memory-recall",
46
+ dream: "opencode-memory-dream",
47
+ };
48
+ export class MemoryConfigError extends Error {
49
+ constructor(message) {
50
+ super(message);
51
+ this.name = "MemoryConfigError";
52
+ }
53
+ }
54
+ function formatIssue(issue) {
55
+ const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
56
+ return `${path}: ${issue.message}`;
57
+ }
58
+ // `CLAUDE_CONFIG_DIR` is the only environment variable the plugin reads: it is shared with Claude Code
59
+ // and decides where both tools keep memory. Everything else is plugin options.
60
+ export function resolveClaudeConfigDir(env = process.env) {
61
+ const raw = env.CLAUDE_CONFIG_DIR?.trim();
62
+ return (raw ? raw : join(homedir(), ".claude")).normalize("NFC");
63
+ }
64
+ export function parseMemoryOptions(options) {
65
+ const result = MemoryOptionsSchema.safeParse(options ?? {});
66
+ if (!result.success) {
67
+ const issues = result.error.issues.map(formatIssue).join("; ");
68
+ throw new MemoryConfigError(`opencode-claude-memory: invalid plugin options (${issues})`);
69
+ }
70
+ return result.data;
71
+ }
72
+ export function parseConfig(options, env = process.env, homeDir = homedir()) {
73
+ return {
74
+ ...parseMemoryOptions(options),
75
+ claudeConfigDir: resolveClaudeConfigDir(env),
76
+ homeDir,
77
+ agents: MEMORY_AGENTS,
78
+ };
79
+ }