Claude Code plugin that makes the code-memory backend ambient
inside Claude Code — same spirit as the
OpenCode plugin, adapted to Claude Code's hook
model.
It auto-learns by re-indexing files the agent writes / edits, nudges
the agent toward the index before it grep/reads the filesystem, and
records the session as an episode on stop. The plugin sits next to the
existing code-memory MCP server, which stays available for the agent
to call manually (codememory_retrieve, codememory_record,
codememory_reingest, codememory_callers, …).
| Hook | Behavior |
|---|---|
SessionStart |
Background code-memory ingest <cwd> (git delta) so the index reflects out-of-band edits (vim, IDE, git pull, git checkout) since the last session. |
UserPromptSubmit |
Detects durable user assertions (preference / decision / rejection / ownership / location patterns) and injects a nudge reminding the agent to call codememory_assert_claim. Resets the per-turn gate flag (see PreToolUse). |
PreToolUse (Read/Bash/Grep/Glob) |
First-tool gate: if no explicit codememory_* MCP tool has fired this turn, emits a one-shot soft nudge as additionalContext reminding the agent to query the index before scanning the filesystem. Never blocks; the queued tool still runs. Goes silent once any explicit codememory_* MCP tool fires, and once per turn either way. |
PostToolUse (Write/Edit/MultiEdit) |
(a) Fires code-memory reingest <path>. (b) Schedules a debounced code-memory resolve to re-point cross-file CALLS edges. |
PostToolUse (mcp__code-memory__codememory_*) |
Marks the gate flag as satisfied so the rest of the turn stays silent. |
Stop |
Records the session as an episode via code-memory record with the first user message + git diff as the patch (best-effort). |
All backend calls are best-effort. If code-memory is not on PATH,
every hook degrades to a benign no-op — your Claude Code session is
never blocked.
-
The
code-memoryCLI on PATH:pipx install git+https://github.com/fmflurry/code-memory # or uv tool install git+https://github.com/fmflurry/code-memoryIf you only have
uvx, shim it (see "Custom binary" below). -
Running infra: FalkorDB + Qdrant + Ollama with
bge-m3. See the main README. -
The repo must have been ingested at least once:
code-memory ingest /path/to/repo
-
Node.js 18+ on PATH (Claude Code already requires it).
# user scope (default) — pick this for everyday use
./plugins/claude-code/install.sh
# project scope (committed to .mcp.json + scoped to this dir)
./plugins/claude-code/install.sh --scope project
# skip MCP server registration
./plugins/claude-code/install.sh --no-mcpThe installer registers this repo as a local Claude Code marketplace
(via claude plugin marketplace add <repo>) using the manifest at
.claude-plugin/marketplace.json, then installs the plugin via
claude plugin install code-memory@code-memory. The hooks defined in
hooks/hooks.json only fire once Claude Code has the plugin registered
in ~/.claude/plugins/installed_plugins.json — a bare symlink into
~/.claude/plugins/ is not enough.
Restart Claude Code after installing so the new hooks take effect.
The plugin handles the automatic path; the MCP server still exposes
the manual tools so the agent can call them when it judges retrieval /
recording useful on its own. Add to ~/.claude.json:
Restart Claude Code.
Updating the plugin or MCP server? Claude Code caches the MCP server's tool list per session — after pulling new code, fully restart Claude Code (not just
/clear) so new tools likecodememory_assert_claimbecome visible to the agent. Confirm by running/mcpand verifying the expected tool count.
The plugin reads two environment variables:
| Variable | Default | Purpose |
|---|---|---|
CODE_MEMORY_BIN |
code-memory |
Override the binary name / absolute path. |
CODE_MEMORY_PROJECT |
(none) | Forwarded as --project <slug> to every CLI call. Useful for monorepos with non-standard slugs. |
Everything else lives as inline constants in the script files:
| Constant | File | Default | Purpose |
|---|---|---|---|
RESOLVER_DEBOUNCE_MS |
scripts/resolver-debounce.js |
1.5 s | Quiet period after the last write before the resolver re-runs. |
Hook timeouts (SessionStart/PostToolUse/...) |
hooks/hooks.json |
5–12 s | Maximum wall-clock per hook invocation. |
If code-memory isn't on PATH (for example, you only have uvx), shim
it:
# put this in ~/.local/bin/code-memory and chmod +x
exec uvx --from git+https://github.com/fmflurry/code-memory code-memory "$@"Or point the plugin at the wrapper directly:
CODE_MEMORY_BIN=/full/path/to/wrapper claude # launching Claude Code with it set/code-memory <retrieve|record|reingest|resolve|ingest> [args...]
forwards to the local CLI. Use it for the cases the hooks don't cover
automatically (force a custom-verdict record, ad-hoc query, etc.).
skills/code-memory/SKILL.md documents the tools and when the agent
should call retrieve / record / reingest / graph tools manually. Claude
Code surfaces it automatically when the plugin is installed.
OpenCode plugins are Bun-loaded TypeScript modules that hold in-memory state across hooks. Claude Code spawns a fresh shell process per hook, so this plugin:
- Uses plain Node.js (no build step, no runtime deps).
- Persists session state on disk under
$XDG_CACHE_HOME/code-memory/claude-plugin/(or~/.cache/...). - Reads/writes that state on every hook entry (
loadSession/saveSession). - Debounces the cross-file resolver via a marker file + a detached
worker process (
resolver-debounce.js) instead of a JSsetTimeout.
Hook ↔ event mapping:
| OpenCode hook | Claude Code hook |
|---|---|
First chat.message of a session |
SessionStart |
chat.message (claim-intent detection) |
UserPromptSubmit |
tool.execute.after for write/edit/patch |
PostToolUse matched on `Write |
event session.idle |
Stop |
- A missing or broken backend never crashes the session — every hook exits cleanly with no output if the CLI is missing.
- A re-ingest after
Write/Edit/MultiEditis fire-and-forget; the agent's turn returns immediately. - Session episodes are written on
Stop, with the captured first user message andgit diffas the patch. - The resolver runs at most once per ~1.5 s burst of writes regardless of how many files were touched.
The product proposition collapses if the agent answers from a stale graph. Code-memory has two completely different states to keep current:
- Per-file state — symbol definitions, imports, and call expressions for a single source file. Cheap to rebuild from one file.
- Cross-file state — the resolved CALLS edges that point a caller at the real defined Symbol node (instead of a placeholder). Touching one file can invalidate edges in many others, so a single-file re-ingest is not enough on its own.
Both must move forward together, and edits can come from places the
plugin can't see (vim, IDE saves, git pull, git stash pop). Below
is the full matrix of failure modes and what the plugin does about
each.
| Failure mode | Mitigation |
|---|---|
| Agent rewrites a file → that file's symbols are stale. | PostToolUse fires code-memory reingest <path> — tree-sitter re-parses, the file's nodes + edges are dropped and re-inserted, and its Qdrant chunks are replaced. |
| Agent rewrites a file → callers in other files now point at deleted/renamed symbols. | After every write, the plugin schedules a debounced code-memory resolve via resolver-debounce.js. The resolver scans the whole graph and re-points placeholder name::X CALLS edges to the real Symbol nodes. |
| Agent does a 20-file refactor in 2 seconds → resolver would run 20 times back-to-back. | Resolver scheduling is debounced by a marker file + RESOLVER_DEBOUNCE_MS (1.5 s). A new write resets the timer; only the worker that wakes up to a stable marker actually fires resolve. |
File changes outside Claude Code between sessions (vim, IDE, git pull, git checkout). |
SessionStart fires a one-shot background code-memory ingest <cwd>. The ingest is git-aware and only re-walks files whose hash moved — and it re-runs the resolver. |
| Backend (FalkorDB / Qdrant / Ollama) is down. | All CLI calls are guarded by per-command timeouts. Failure is silently no-op'd; the agent's turn is never blocked. |
code-memory CLI is missing on PATH. |
createMemoryClient detects this once per hook invocation and short-circuits every method. The plugin stays loaded but inert. |
| Agent never explicitly records what it did. | Stop fires code-memory record with the first user message + cumulative git diff as the patch (verdict = "idle"). Future sessions can recall the episode even without manual record. |
- File deletions via the
Writetool.reingeston a missing path skips cleanly, but the previous File node + DEFINES edges + chunks linger until the next git-aware delta ingest evicts them. - Renames look like delete + create to the plugin. The graph keeps the old node until the next delta ingest.
- Bare external module imports (
@scope/pkg,rxjs). The resolver does not chase npm dependencies, so callers into external packages stay unresolvable. This is an architectural choice, not a freshness bug. - Pure reads. If the agent only opens files, no hook fires. That's correct: nothing changed.
Think of the plugin as three concentric refresh loops, each cheaper and more frequent than the next:
session start every write every user prompt
│ │ │
▼ ▼ ▼
delta-ingest reingest + debounced retrieve
+ resolver resolver + pack (no I/O if
(catches OOB invalidation dedup'd within
edits) (cross-file edge 60 s)
accuracy)
If any layer fails, the layer above eventually catches up. The system is designed so that stale data is a temporary state, not a steady state.
The plugin is plain Node.js (no build step, no node_modules). To
sanity-check a hook locally:
echo '{"prompt":"How does getBearerToken work?","cwd":"'"$PWD"'","session_id":"test"}' \
| node plugins/claude-code/scripts/on-user-prompt.jsState is stored under $XDG_CACHE_HOME/code-memory/claude-plugin/ (or
~/.cache/code-memory/claude-plugin/ if XDG is unset). Delete it to
reset between local tests:
rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}/code-memory/claude-plugin"MIT — see LICENSE.
{ "mcpServers": { "code-memory": { "type": "stdio", "command": "uvx", "args": [ "--from", "git+https://github.com/fmflurry/code-memory", "code-memory-mcp" ], "env": { "CODE_MEMORY_PROJECT": "auto" } } } }