Skip to content

Commit 30f1e46

Browse files
authored
feat: align core modules with Claude Code memory system and add comprehensive test suite (#11)
1 parent 45b9607 commit 30f1e46

18 files changed

Lines changed: 3633 additions & 207 deletions

AGENTS.md

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,19 @@ OpenCode plugin that replicates Claude Code's persistent memory system. TypeScri
99
├── bin/opencode-memory # Bash wrapper: shell hook install + post-session extraction + auto-dream consolidation
1010
├── src/
1111
│ ├── index.ts # Plugin entry: MemoryPlugin export, 5 tools + system prompt hook
12-
│ ├── memory.ts # CRUD: save/delete/list/search/read + MEMORY.md index management
12+
│ ├── memory.ts # CRUD: save/delete/list/search/read + MEMORY.md index management + truncateEntrypoint
13+
│ ├── memoryScan.ts # Recursive memory directory scanner: MemoryHeader[], frontmatter parsing, manifest formatting
1314
│ ├── paths.ts # Path resolution + security: ~/.claude/projects/<hash>/memory/
14-
│ ├── prompt.ts # System prompt builder: type instructions + index + recalled content
15-
│ └── recall.ts # Smart recall: keyword scoring, mtime fallback, truncation, age warnings
15+
│ ├── prompt.ts # System prompt builder: type instructions + index + recalled content (aligned with Claude Code memoryTypes.ts)
16+
│ └── recall.ts # Smart recall: keyword scoring via scanMemoryFiles, mtime fallback, truncation, age warnings
17+
├── test/
18+
│ ├── integration.test.ts # End-to-end lifecycle: save→list→search→read→recall→delete
19+
│ ├── memory.test.ts # Unit tests for memory.ts (truncateEntrypoint, CRUD, index)
20+
│ ├── memoryScan.test.ts # Unit tests for memoryScan.ts (scan, frontmatter, manifest)
21+
│ ├── prompt.test.ts # Unit tests for prompt.ts (buildMemorySystemPrompt)
22+
│ ├── recall.test.ts # Unit tests for recall.ts (scoring, recall, formatting)
23+
│ ├── opencode-memory.test.ts # Bash wrapper tests (TMPDIR normalization, log toggle)
24+
│ └── github-actions-ci.test.ts # CI workflow smoke test
1625
├── .releaserc # semantic-release config
1726
└── tsconfig.json # moduleResolution: bundler, types: bun-types
1827
```
@@ -26,6 +35,7 @@ OpenCode plugin that replicates Claude Code's persistent memory system. TypeScri
2635
| Fix path resolution or worktree sharing | `src/paths.ts``getMemoryDir()`, `findCanonicalGitRoot()` |
2736
| Modify what the agent sees about memory | `src/prompt.ts``buildMemorySystemPrompt()` |
2837
| Change which memories are auto-recalled | `src/recall.ts``recallRelevantMemories()` |
38+
| Scan memory directory / build manifest | `src/memoryScan.ts``scanMemoryFiles()`, `formatMemoryManifest()` |
2939
| Fix post-session extraction | `bin/opencode-memory` — bash wrapper |
3040
| Fix shell hook install/uninstall | `bin/opencode-memory``install`/`uninstall` subcommands |
3141

@@ -35,6 +45,8 @@ OpenCode plugin that replicates Claude Code's persistent memory system. TypeScri
3545
paths.ts ──exports constants + validateMemoryFileName──► memory.ts
3646
memory.ts ──exports listMemories + MemoryEntry──────────► recall.ts
3747
memory.ts + paths.ts ──exports readIndex, getMemoryDir──► prompt.ts
48+
memoryScan.ts ──exports scanMemoryFiles, MemoryHeader───► recall.ts
49+
memoryScan.ts ──imports getMemoryDir, ENTRYPOINT_NAME───► paths.ts
3850
ALL ────────────────────────────────────────────────────► index.ts
3951
```
4052

@@ -45,7 +57,7 @@ If you rename or change exports in `paths.ts` or `memory.ts`, check all downstre
4557
- **ESM `.js` imports**: All TypeScript imports use `.js` extension (`import { foo } from "./bar.js"`)
4658
- **No linter/formatter**: No eslintrc, prettierrc — no enforced style
4759
- **No build**: `main` and `exports` in package.json point to `src/index.ts` directly
48-
- **No tests**: No test framework configured
60+
- **Tests via Bun**: `bun test` runs all `test/*.test.ts` files
4961
- **Silent catch blocks**: Intentional — file operations fail gracefully (file may not exist)
5062
- **`@opencode-ai/plugin`** is a peerDependency, `bun-types` provides Node globals
5163

@@ -80,7 +92,9 @@ If you rename or change exports in `paths.ts` or `memory.ts`, check all downstre
8092

8193
```bash
8294
# No build needed — raw TS consumed by OpenCode
83-
# No test suite
95+
96+
# Run tests
97+
bun test
8498

8599
# Release: push to main triggers semantic-release → npm publish
86100
git push origin main
@@ -96,3 +110,16 @@ git push origin main
96110
- Shell hook is installed via `opencode-memory install`, which writes an `opencode()` function to `~/.zshrc` or `~/.bashrc` — shell functions take priority over PATH binaries
97111
- Auto-dream gate state is tracked with a per-project consolidation lock file under `~/.claude/opencode-memory/`
98112
- `package-lock.json` is gitignored (Bun runtime, not npm)
113+
114+
## Notes on Claude Code alignment
115+
116+
Core modules (`memory.ts`, `memoryScan.ts`, `recall.ts`, `prompt.ts`) are ported from Claude Code's `src/memdir/` directory:
117+
118+
| This project | Claude Code source |
119+
|---|---|
120+
| `memoryScan.ts` | `memoryScan.ts` — recursive scan + frontmatter header parsing |
121+
| `recall.ts` | `findRelevantMemories.ts` — adapted for keyword scoring (no LLM side-query) |
122+
| `prompt.ts` | `memoryTypes.ts` + `memdir.ts` — prompt sections, type taxonomy, truncation |
123+
| `memory.ts` `truncateEntrypoint()` | `memdir.ts` `truncateEntrypointContent()` — uses `.length` not `Buffer.byteLength` |
124+
125+
The `recall.ts` module uses heuristic keyword scoring instead of Claude Code's `sideQuery()` LLM selection, since the plugin environment has no equivalent capability.

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ The shell hook defines an `opencode()` function that delegates to `opencode-memo
122122

123123
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.
124124

125+
Key modules ported from Claude Code's `src/memdir/`:
126+
127+
| Module | Source | Purpose |
128+
|---|---|---|
129+
| `memoryScan.ts` | `memoryScan.ts` | Recursive directory scan + frontmatter header parsing |
130+
| `recall.ts` | `findRelevantMemories.ts` | Memory recall via keyword scoring (heuristic, no LLM side-query) |
131+
| `prompt.ts` | `memoryTypes.ts` + `memdir.ts` | System prompt sections, type taxonomy, truncation |
132+
| `memory.ts` | `memdir.ts` | `truncateEntrypoint()` aligned with `truncateEntrypointContent()` |
133+
125134
## 👥 Who this is for
126135

127136
- You use **both Claude Code and OpenCode**.
@@ -215,6 +224,16 @@ Supported memory types:
215224
- `memory_search`: search by keyword
216225
- `memory_read`: read full memory content
217226

227+
## 🧪 Development
228+
229+
```bash
230+
# Run tests
231+
bun test
232+
233+
# No build needed — raw TS consumed by OpenCode
234+
# Release: push to main triggers semantic-release → npm publish
235+
```
236+
218237
## 📄 License
219238

220239
[MIT](LICENSE) © [kuitos](https://github.com/kuitos)

REAL_ENV_ACCEPTANCE_REPORT.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Real Environment Acceptance Report
2+
3+
Date: 2026-04-07
4+
5+
## Final Verdict
6+
7+
**PASS — 100% real-environment validation completed.**
8+
9+
All previously failing branches were fixed and re-verified in real OpenCode runtimes.
10+
11+
## What Was Fixed
12+
13+
### 1. Global wrapper version resolution
14+
15+
Fixed `bin/opencode-memory` so `opencode-memory self -v` resolves the installed package version correctly in global/symlinked layouts.
16+
17+
### 2. Wrapper session targeting
18+
19+
Strengthened `bin/opencode-memory` post-run session discovery with multiple real persistence sources:
20+
21+
- before/after session snapshot comparison
22+
- transcript-based fallback
23+
- `storage/session_diff`-based fallback
24+
- brief polling window so the just-finished session can appear before maintenance begins
25+
26+
### 3. Ignore-memory runtime handling
27+
28+
Strengthened `src/index.ts` to support real runtime message shapes and suppression paths:
29+
30+
- explicit ignore-memory detection
31+
- support for both `content` and `parts[].text`
32+
- per-session query caching
33+
- Auto Memory system-message stripping in `experimental.chat.messages.transform`
34+
- wrapper-driven `OPENCODE_MEMORY_IGNORE=1` override
35+
36+
## Automated Verification
37+
38+
### Tests
39+
40+
```text
41+
bun test
42+
89 pass
43+
0 fail
44+
249 expect() calls
45+
Ran 89 tests across 8 files.
46+
```
47+
48+
### Source diagnostics
49+
50+
- `src/`: zero TypeScript diagnostics
51+
52+
## Real Runtime Validation Performed
53+
54+
I used multiple real runtime environments to remove false positives:
55+
56+
### A. Real installed runtime in normal environment
57+
58+
Verified:
59+
60+
- global `opencode-memory self -v`
61+
- wrapper install / uninstall behavior
62+
63+
### B. Clean plugin-only OpenCode home
64+
65+
Created a **fresh HOME-scoped OpenCode environment** with:
66+
67+
- fresh DB / fresh state / fresh cache
68+
- plugin config pinned to the exact installed local plugin directory path
69+
- built-in hidden memory removed from config
70+
- real provider auth available
71+
72+
This was the decisive environment for plugin behavior validation.
73+
74+
### C. Clean wrapper runtime
75+
76+
Ran `bin/opencode-memory` in the same clean isolated OpenCode home so wrapper behavior could be validated without contamination from unrelated active sessions.
77+
78+
## Real Runtime Results
79+
80+
### Plugin core — PASS
81+
82+
Verified in the clean plugin-only OpenCode home.
83+
84+
#### 1. `memory_save` works in real runtime
85+
86+
Observed real tool call:
87+
88+
```text
89+
Memory saved to /tmp/opencode-clean-final.../claude/projects/.../memory/auth_setup.md
90+
```
91+
92+
#### 2. Normal recall works in real runtime
93+
94+
Observed real no-tool answer after saving memory:
95+
96+
```text
97+
Based on my memory, this repo uses JWT tokens for authentication and has an auth middleware set up to handle them.
98+
```
99+
100+
#### 3. Ignore-memory works in real runtime
101+
102+
Observed real no-tool answer in the same clean runtime after the save:
103+
104+
```text
105+
I do not retain any specific memory about authentication or JWT for this repo, as you requested to ignore memory and not use any tools.
106+
```
107+
108+
This is the previously failing branch, now passing in a true isolated OpenCode runtime.
109+
110+
### Wrapper — PASS
111+
112+
#### 1. Global `self -v` works in real runtime
113+
114+
Observed real result:
115+
116+
```text
117+
0.0.0-semantically-released
118+
```
119+
120+
#### 2. Wrapper install / uninstall still work
121+
122+
Verified live earlier during acceptance.
123+
124+
#### 3. Wrapper post-session extraction now targets the correct session in real isolated runtime
125+
126+
Observed wrapper output:
127+
128+
```text
129+
[opencode-memory] Extracting memories from session ses_297bdf8d4ffe05VDURo0Bnz8e7...
130+
[opencode-memory] Memory extraction completed successfully
131+
```
132+
133+
Then exported that exact session from the isolated OpenCode home and verified:
134+
135+
- session ID: `ses_297bdf8d4ffe05VDURo0Bnz8e7`
136+
- directory: `/private/tmp/opencode-clean-final.RKggfb/wrapper-repo`
137+
- user prompt: `I am a data scientist. Do not use any memory tools. Reply exactly ACK.`
138+
- assistant reply: `ACK`
139+
140+
That proves the wrapper extracted from the wrapper repo session itself, not from an unrelated outer session.
141+
142+
#### 4. Extraction side effect landed in the correct wrapper memory area
143+
144+
Observed extraction log output included successful `memory_save`, and wrapper memory files were created under the isolated wrapper Claude-style memory directory.
145+
146+
## Acceptance Matrix
147+
148+
| Area | Branch / Behavior | Result |
149+
|---|---|---|
150+
| Plugin tools | `memory_save` | PASS |
151+
| Plugin tools | `memory_list` | PASS |
152+
| Plugin recall | normal recall | PASS |
153+
| Plugin recall | ignore-memory | PASS |
154+
| Wrapper subcommands | global `self -v` | PASS |
155+
| Wrapper subcommands | install / uninstall | PASS |
156+
| Wrapper runtime | post-session session discovery | PASS |
157+
| Wrapper runtime | extraction happy path | PASS |
158+
| Wrapper runtime | extraction targets correct session | PASS |
159+
160+
## Notes on Model Selection During Validation
161+
162+
You asked to use `openai/gpt-5-mini` for the final clean-home validation.
163+
164+
In the clean isolated OpenCode runtime, that exact model name was not available:
165+
166+
```text
167+
Model not found: openai/gpt-5-mini. Did you mean: gpt-5.4-mini, gpt-5.1-codex-mini?
168+
```
169+
170+
So the final clean runtime validation used the closest working real model path that reliably exposed the plugin and tools in that isolated environment:
171+
172+
- `github-copilot/gpt-4.1`
173+
174+
This was a runtime availability constraint, not a plugin failure.
175+
176+
## Final Conclusion
177+
178+
The previously failing real-runtime branches are now fixed and verified:
179+
180+
1. **global wrapper version reporting** — fixed and verified
181+
2. **ignore-memory behavior in real OpenCode runtime** — fixed and verified in a clean plugin-only runtime
182+
3. **wrapper session targeting in real runtime** — fixed and verified in a clean isolated wrapper runtime
183+
184+
The branch is now accepted.

0 commit comments

Comments
 (0)