Conventions for agents working on the @mathew-cf/opencode-memory codebase.
| Command | Purpose |
|---|---|
bun test |
Run all tests (bun:test) |
bun run typecheck |
TypeScript check (tsc --noEmit) |
bun run build |
Bundle to dist/ + emit .d.ts |
The optional native knowledge-base integration test needs a cached embedding
model: OPENCODE_MEMORY_TEST_RAG_INTEGRATION=1 bun test test/knowledge.test.ts.
Run all three before committing:
bun run typecheck && bun test && bun run buildsrc/
index.ts # OpenCode v1 adapter — wires tools, hooks, config
v2.ts # OpenCode v2 plugin entry
cli.ts # `opencode-memory` bin: init / status / help
config.ts # applyConfig() — skills.paths, agent prompts, permissions
constants.ts # CATEGORIES, DEFAULT_MEMORY_SUBDIR, STOP_WORDS
lib/
paths.ts # resolveHome, resolveMemoryDir, normPath, ragIndexDir
knowledge-config.ts # named knowledge bases from the user's config.toml
frontmatter.ts # parseFrontmatter, bumpAccessFields, todayISO
search-terms.ts # parseSearchTerms, countTermMatches, scoreCandidate
rag.ts # ensureRag, ragSearch, spawnRagIndex, downloadModel
db.ts # resolveDbPath, sqlStr, querySqlite
tool-definition.ts # v2-oriented tool definition helper
tools/
memory.ts # search / list / save / access / setup
knowledge.ts # read-only search / read / list over named rag.toml scopes
session.ts # search / read / list (reads opencode.db)
hooks/
guard.ts # tool-call tracking, nudges, compaction context
test/
helpers.ts # withMemoryDir, writeMemoryFile, makeTempDir
frontmatter.test.ts # YAML parser + access-field mutation
search-terms.test.ts # Tokenization + scoring
paths.test.ts # Cross-platform home + memory dir resolution
memory.test.ts # Integration tests against temp memory dirs
session.test.ts # Integration tests against a temp SQLite db
guard.test.ts # Hook state machine + compaction output
config.test.ts # applyConfig() additive behaviour
cli.test.ts # CLI dispatcher + skill-symlink install
skills/
opencode-memory/
SKILL.md # The bundled skill, auto-registered at plugin load
scripts/
sync-version.ts # Sync package.json version into README.md
This package ships three surfaces against the same shared core:
| Surface | Entry point | Audience |
|---|---|---|
| OpenCode v1 | src/index.ts |
V1 adapter for tools, hooks, and config |
| OpenCode v2 | src/v2.ts |
Native tool, hook, and skill registration |
opencode-memory |
src/cli.ts |
Humans (init, status) + post-install |
Both plugins share src/tools/memory.ts, src/tools/session.ts, and src/lib/*. Tool definitions use the v2 input and result shape; index.ts adapts them for v1.
Exports a default v1 plugin object. Its server function returns:
tool— custom tools keyed withmemory_,knowledge_, andsession_prefixes. Knowledge bases are read-only and configured separately from the memory corpus.config— callsapplyConfig()to register the bundled skill directory, add edit/external_directory permissions for~/opencode-memory/**, and prepend the memory-awareness appendix to the built-in subagent promptstool.execute.after—guard.toolAfter, tracks memory/session tool usage, fires nudgesexperimental.session.compacting—guard.compacting, injects preserve-through-compaction context
Every tool has two layers:
runXxx(input)— pure TypeScript function, notool()wrapper. Takes plain arguments, reads env lazily, returns a string. Covered directly by integration tests.defineTool({ description, input, execute })— a v2-oriented definition that callsrunXxxand returns{ content }. The v1 entry converts its input shape and result.
This lets tests cover the real behaviour without constructing a fake OpenCode context.
The lib layer never reads config statically. Both resolveMemoryDir() and resolveDbPath() read process.env fresh on each call. That's how tests inject temp directories (withMemoryDir sets OPENCODE_MEMORY_DIR) and temp databases (session.test.ts sets OPENCODE_DB in beforeAll).
The package supplies the native rag CLI for both live keyword search and semantic search. If the binary is unavailable, search returns an empty result rather than throwing to the tool caller; memory_setup reports the installation problem. Pi session search falls back to scanning its session files when keyword prefiltering fails.
- Runtime: Bun. No Node-only APIs in src or test.
- Imports:
node:prefix for Node builtins (node:path,node:fs/promises,node:os). - Tool definitions: use
defineTool()withzod/v4input schemas. Keep descriptions actionable — they're the LLM's only spec. Only the v1 adapter imports@opencode-ai/plugin. - Error handling: tool execute paths catch exceptions and return strings. Never throw to the caller; the agent reads whatever you return.
- Types: keep shared types in the file that owns the logic (e.g.
SessionStateinhooks/guard.ts). Only hoist to a top-leveltypes.tswhen two unrelated modules need the same shape. - Pure helpers live in
src/lib/. If a helper uses the filesystem or the shell, it belongs in the tool that calls it. - No organization- or environment-specific references. This is a public plugin — examples should be generic (no internal hostnames, team names, proprietary tools, or ticket IDs).
test/helpers.tsprovideswithMemoryDir(cb)which creates a fresh temp dir, points$OPENCODE_MEMORY_DIRat it, runs the callback, then cleans up and restores the env.- Pure-logic tests (
frontmatter.test.ts,search-terms.test.ts,paths.test.ts,guard.test.ts,config.test.ts) take <100ms in aggregate — they don't hit the filesystem at all. memory.test.tsandsession.test.tsare integration tests.session.test.tsbuilds a temp SQLite DB with the minimum schema we actually touch;memory.test.tssearches real files through the native rag keyword command.- Ranking assertions are relative, not absolute — e.g.
expect(aIdx).toBeLessThan(bIdx). Pinning exact scores makes the ranker impossible to tune.
- Add a file (or new exports) under
src/tools/. - Write the pure
runXxx(input)function first. It should be callable from a test with plain arguments and return a string. - Wrap it with
defineTool({ description, input, execute }). Keep the description opinionated — tell the agent when to call it and what to look for in the output. - Register it in
src/v2.tsand adapt it insrc/index.tsunder the appropriate namespace (memory_xxxorsession_xxx). - Write tests for both the pure helpers and the integration path.
- If the tool needs a new permission or a new agent prompt, update
src/config.tsandtest/config.test.ts.
Versions are synced into README.md automatically via scripts/sync-version.ts:
npm version patch # or minor / major — triggers the version npm script
git push --follow-tags
npm publish --access public