claude-session-continuity-mcp
零配置的Claude Code会话连续性。通过Claude Hooks自动捕获上下文,提供24个工具用于记忆、任务、解决方案和知识图谱。支持94种以上语言的多语言语义搜索。
文档
passbaton
Session continuity for AI coding agents. Your agent picks up where it left off — never re-explain your project again. Persistent memory for Claude Code, OpenAI Codex CLI & Google Gemini CLI, sharing one local db: auto context injection, compaction handover, semantic search, and error→solution recall. Zero config, zero API cost, 100% local.
⚡ One install → context auto-loads every session · 🧩 survives compaction (0 re-explaining) · 🔒 100% local, $0 API

Renamed (v2.0.0): this project was previously
claude-session-continuity-mcp. The old name suggested it was Claude-only — it never was. Claude Code, Codex CLI, and Gemini CLI are all first-class and share one local memory. Existing installs keep working: the oldclaude-hook-*commands still ship as aliases. See Migrating from v1.
The Problem
Every new session — whether you're in Claude Code, Codex CLI, or Gemini CLI:
"This is a Next.js 15 project with App Router..."
"We decided to use Server Actions because..."
"Last time we were working on the auth system..."
"The build command is pnpm build..."
5 minutes of context-setting. Every. Single. Time.
The Solution
Fully automatic. Lifecycle hooks handle everything without manual calls — on Claude Code, OpenAI Codex CLI, and Google Gemini CLI, sharing one local memory so context carries across all three:
# Session start → Auto-loads relevant context + recent session history
# When asking → Auto-injects relevant memories/solutions
# During conversation → Tracks active files (Edit, Write)
# On compact → Structured handover context for continuity
# On exit → Extracts commits, decisions, error-fix pairs from transcript
← Auto-output on session start:
# my-app - Session Resumed
📍 **State**: Implementing signup form
## Recent Sessions
### 2026-02-28
**Work**: Completed OAuth integration with Google provider
**Commits**: feat: add OAuth callback handler; fix: redirect URI config
**Decisions**: Use Server Actions instead of API routes
### 2026-02-27
**Work**: Set up authentication foundation
**Next**: Implement signup form validation
## Directives
- 🔴 Always use Zod for form validation
- 📎 Prefer Server Components by default
## Key Memories
- 🎯 Decided on App Router, using Server Actions
- ⚠️ OAuth redirect_uri mismatch → check env file
Zero manual work. Context follows you.
Why this over other memory tools?
Most Claude memory tools rely on explicit tool calls ("remember this"), a cloud API, or a background AI worker. This one is deliberately different:
| passbaton | Typical cloud/AI-memory MCP | |
|---|---|---|
| Setup | npm i -g → hooks auto-install | Manual server + API key |
| Trigger | 5 automatic hooks (no commands) | You call a remember tool |
| Storage | 100% local SQLite | Cloud / external service |
| API cost | $0 — local embeddings | Per-token / subscription |
| Latency | < 5ms (on-device) | Network round-trip |
| Privacy | Never leaves your machine | Sent to a provider |
| Search | FTS5 + local semantic, KO/EN/JA cross-lingual | Varies |
If you want zero-config, offline, no-cost memory that just happens while you work — this is it.
Auto-injection vs. explicit search
There's also a great class of local search tools (e.g. ctx) that index your agent history so you can query it (search "failed migration"). That's complementary, not the same job:
| passbaton | Local-search tools (ctx, etc.) | |
|---|---|---|
| How you use it | Automatic — context appears on session start, no command | You (or the agent) run a search query |
| Compaction | PreCompact hook re-injects a handover → 0 context re-explained after a compact | Not its job (it's a search index) |
| Best at | Never losing your thread across sessions & compacts, hands-off | Finding a specific past decision/command on demand |
| Coverage | Claude Code + Codex CLI + Gemini CLI (where auto-injection is possible) | Often 30+ agents indexed for search |
Use search when you want to look something up. Use this when you want your context to follow you without asking.
Resume card — the repo is the source of truth (v2.6.0+)
Long-running projects usually keep their real hand-off in the repo — a HANDOFF.md whose newest
entry is on top, a plan file with a "current task" block, a stop file left by an unattended loop.
A DB summary of the last session cannot replace those, and it goes stale the moment someone else
edits them. Since 2.6.0 a project can declare those files, and SessionStart shows verbatim
excerpts of them first, each with its line range and a content hash:
// <project>/.claude/passbaton.config.json
{
"continuity": {
"sources": [
{ "path": "docs/HANDOFF.md", "lines": 30, "label": "HANDOFF (newest entry)" },
{ "path": ".ralph/fix_plan.md", "block": "지금 할 일", "label": "current task" },
{ "path": ".ralph/DEMO_STOP", "lines": 3, "label": "loop stop reason" }
]
}
}
lines— the first N lines (default 40, max 200)block— the first list item or heading containing that text, up to the next item/heading of the same or higher level (max 120 lines)- Files are re-read on every start (never cached). A source that cannot be read is shown as NOT READ, never silently dropped
- When a card is present the DB state is relabelled Last recorded state (may be stale)
- The same card is available as the read-only MCP tool
resume_card, so another agent can read it too - Boundaries — the real path (after links/junctions) must stay inside the project; regular text files only
(
.md.markdown.txt.rstor no extension, checked on the link target too); at most the first 2 MB is read; at most 8 sources. Names that usually hold secrets are never read even if declared: any path segment matching.env*,.git,.ssh,.aws,.gnupg,.npmrc,.netrc,id_*,credentials*,secret(s)*,*.pem,*.key,*.pfx… (sodocs/credentials-guide.mdis refused;docs/secret-sauce.mdis not — rename rather than weaken the rule) PASSBATON_CARD_CHARS(default 3200, 400–20000) caps the whole card, headings included- (v2.7.2+) Space a missing or short source leaves unused is handed once, evenly, to the sources that were cut by their share
(never to a source that hit its own
lines/block cap, never reviving an omitted one). What was already shown stays as the start of each excerpt; if the files change between reads or the rendered card would exceed the cap, the card stays as it was. Ashfall: 2,390 → 3,064 of 3,200 chars, +3 ms - (v2.7.3+) Run origin. A loop (e.g. Ralph) exports
PASSBATON_ROLE=loop; Stop stores it per row (sessions.run_origin). Only the exact valueloopcounts — unset or anything else isunknown(never assumed to be a conversation); old rows stayunknown. The origin is not part of duplicate suppression, so a re-delivered Stop is still one row. - (v2.7.3+) Conversation cards hide confirmed loop rows from Recent Sessions (filtered in SQL before
LIMIT 3, rows are never deleted);unknownrows stay and are tagged[origin unknown]. A loop session keeps the old behaviour. The same applies to the card's 📍 State line: if the stored state links to a confirmed loop row, the card shows the newest eligible non-loop record instead (text, time and origin from that one record), ornone— never the loop state. The line now carriesrecorded … · origin unknown; a state that links to no session row is not assumed to be a conversation. The verification Heads up is not filtered by origin — it is labelled a project-wide verification record. - (v2.7.3+) Current-task warning. Mark a source
"role": "current-task"; if that block is missing (no_block), the file is missing/unreadable, or the block is empty, the card's first line is⚠ CURRENT TASK UNKNOWN …. An explicit "none — waiting" block is a normal read (no warning); a source hidden by the budget getsℹ … (card budget — not a failure). - (v2.7.3+, off by default) Loop minimal card. With
"continuity": { "loopCard": "minimal" }andPASSBATON_ROLE=loop, sources marked"role": "current-task"or"history"show only path · range · sha1 · status ("read the source directly"); unmarked sources (stop file, decision index) keep their bodies, and Recent Sessions is left out. Sources are still read every time. Node comparison on an Ashfall copy:docs/pb154-m1-card-compare.md - (v2.7.3+) Optional sources.
"optional": trueon a source turns only "file not found" into a neutralfile absent (optional source — not an error)line (for a stop file that normally does not exist). Boundary/secret/type refusals, unreadable files, a missing block, an empty range and truncation still show as before; an absent source never counts as "read". A non-boolean value is reported as ignored and the source keeps the default behaviour. - (v2.7.3+) Summary provenance. Stop stores which branch produced the summary (
sessions.summary_source,active_context.state_source): commit message · assistant text · fallback (action line or file list) · no result. The 📍 State line and each Recent Sessions row show it from that same record (_(via commit message · …)_); old rows and rows written by other tools showvia unknown(never guessed from the text). A commit-message summary is still not proof that the work is done. - (v2.7.3+) Less noise, same meaning. A Recent row with zero observations from a complete check is shortened to
files: 0 obs in checked scope · 1/1 roots · boundary=turn_start · declared 1 (valid 1); partial checks, skipped roots, no baseline, declaration errors and "not recorded" keep the long form. TheSolutions: N recordedstatistic is left out only when a resume card with at least one read source was delivered to a non-loop session. - (v2.8+) No error-time solution injection. The PostToolUse Bash-error →
<past-solution>path is gone: the installer only ever registered that hook forEdit/Write, so no installed setup ever reached it. Recording error→fix pairs at Stop and the MCP tools (solution_record,solution_find,solution_suggest) are unchanged. - (v2.8+) Compaction recovery fixes. PreCompact no longer fails on a hooks-only database (no
project_contexttable — that table is created only by the MCP server): the handover row is stored and the "context saved" notice is shown. A table that exists but cannot be read is still reported as an error. The "Last human request" line is taken only from realusertranscript lines — API request copies (api-request-blob) and notification renders are skipped, and notifications with attributes (<system-reminder id="…">) are stripped. If no human request can be identified, the line saysnot identifiedinstead of showing leftover text. - (v2.8+) Partial state writes keep the other fields. Every writer of
active_context(Stop, PostToolUse, PreCompact, MCPsession_end, …) now updates only the columns it actually has. An omitted field is kept; an explicitnull/empty string still clears it. Writing the State also writes where it came from, and only a State write movesupdated_at. - (v2.8+) Past-work lookup stays in the current project. "Last time …" prompts search sessions, memories and solutions
filtered to the current project before sorting and
LIMIT; rows with no project are never shown, nothing is searched when the project is unknown, and memories need the same eligibility as at SessionStart (unverified ones are not injected). - (v2.8+) Injection ledger (with
hookTraceon). Each hook writes, per emitted fragment, its source, source ids, UTF-8 bytes, byte offset in the emitted body and a hash, plus one line per omitted fragment with the reason and anemittedtotal — all measured on the body actually printed; turning the trace on or off does not change that body. Source ids are the real ones: DB rows by their keys (s:12,m:4,active_context,session_route:<session>/<project>); files in a routed card as<workspace path>:<from>-<to>@<sha1 of that range>(~<last shown line>when cut), files in the SessionStart card as<path>@<sha1>. A source that could not be read is written as<path>@<outcome>without a hash; no DB id is written for a row that does not exist.
Two side-effect-free CLIs for hand-offs between agents (v2.8+)
Both are separate entry points that never open the database (the passbaton binary opens it on start) and never write
hook traces or settings.
passbaton-card — a snapshot of the resume card, for handing to another agent (e.g. a reviewer model) exactly as built.
passbaton-card --project-dir <dir> --out <card.md> [--meta <meta.json>] [--max-chars N] [--origin loop|unknown] [--no-git] [--force]
passbaton-card --verify <meta.json> [--card <card.md>]
- Writes only
--out(the card bytes) and--meta(or prints the meta to stdout). Refuses to overwrite without--forceand does not create directories — a snapshot is meant to be immutable. - Each source file is read once; the card text, the whole-file SHA-256, the selected range (and its SHA-256), truncation /
omission / budget-hidden state and
delivery: full | partial | noneall come from that same buffer. The meta also records the render version, the SHA-256 ofpassbaton.config.jsonand of the whole card. Git blob/HEAD comparison is auxiliary only — untracked and modified files are identified by their bytes. - The meta says
claims: card-file-created-only. A snapshot proves which bytes were in the card when it was made; it is not proof that anyone received or read it.--verifyre-reads the files and reports what changed since (the card file itself, a source, or the config). - A card can be complete while a source inside it is only partly shown (
delivery: partial): "the card arrived whole" is not "every source arrived whole". - Exit codes:
0ok / match ·1verify mismatch ·2usage ·3no sources declared (no card) ·4write refused or failed.
passbaton-search — direct text search over declared sources only. No index, no embeddings.
passbaton-search --project-dir <dir> [--config <file>] [--max-results N] [--no-git] [--json] <query…>
passbaton-search --verify <report.json>
passbaton-search --project-dir <dir> --gold <gold.json> [--config <file>] [--no-git]
- Searches
search.sourcesin.claude/passbaton.config.json({path}·{glob}· optionalblock= only under that heading); without it, only thecontinuity.sourcespaths...and absolute paths are refused; a sub-folder with its own passbaton config or.gitis treated as another project and skipped. Default 20 results (max 200). - Each hit: path, heading path, line range, SHA-256 of the whole file, of the range and of the excerpt (bytes actually read),
the excerpt, and git status (auxiliary).
--verifyre-reads a saved--jsonreport and flags changed or deleted sources. - Supersession is only shown from explicit evidence: rows of a declared
search.supersessiontable (| target | relation | evidence |) that name a section (§N). Rows without a section are listed underignoredEvidence. Without evidence the label is대체 관계 미확인(supersession unknown) — never "still valid" and never "wholly replaced". ⚠ Labels attach to a whole numbered section, while real evidence is often one clause of it; a clause-level replacement can therefore look like a whole-section one. Don't wire a supersession table into real search until that is fixed. - Text search only finds the words that are there. Measured on one project's decision records (8 fixed questions): 0/8 with the default sources, 2/8 with the decision replies declared — every remaining miss was a vocabulary mismatch (the answer section never uses the question's words). Paraphrased questions miss.
- Output carries
authority: none — lookup aid only …— results are pointers to read, not permission to act, and nothing is written back. - Exit codes:
0ok / match / gold passed ·1verify mismatch / gold failed ·2usage ·3no sources declared.
Routing from a workspace root (v2.7.0+)
Project detection follows the cwd, so a conversation opened at a monorepo root never got a sub-project's card.
Declare which commands select which project in the root .claude/passbaton.config.json:
{ "routing": { "commands": { "/ashfall": "apps/kenshi-fantasy", "/astra": "apps/kenshi-fantasy", "/home": "." } } }
- Only a registered command at the very start of what you type selects a project (
/ashfall …). The same text inside a sentence, a quote, code, a path mention or a harness notification does not. (Claude Code passes the typed text to the hook as-is — observed with a no-output probe hook underclaude -p,tests/fixtures/hook-input-claude-code-2026-10-23.json— so the expanded<command-name>form seen in transcripts is not accepted.) Define every routing and release command as a real command (.claude/commands/<name>.md): whether an undefined/namereaches the hook in the interactive UI was not observed - Routing runs only when the hook's
cwdis the workspace root (same real path — a sub-app with the root's name does not count; nocwdin the input means no routing) - A release command (mapped to
".") is required. Without one, routing is off as a whole - The choice is per session and survives compaction/resume of that session; a new session starts unrouted. Another routed command switches; the release command releases. If the chosen mapping disappears from the config, the next turn says once that the route was cleared
- The card is sent when the choice changes, when the card (or its header — e.g. a renamed release command) changes, and after compaction — not every turn. If the post-compaction card did not fit the budget, it is sent on the next turn
- The card says what selected it and that the conversation is still recorded under the root project — passbaton does not re-file root sessions under the sub-project
- Paths must be real directories inside the workspace (checked after links/junctions); invalid entries are ignored
- All of this goes out in the hook's single output (Codex/Gemini receive one JSON object)
- Cost (Ashfall data, 15 runs, median, Windows): 94 ms (2.6.1) → 100 ms with nothing selected, 112 ms with a selection held
Recording routed turns under the routed project — opt-in (v2.9.0+)
By default (above) every turn of a root conversation is recorded under the root, so the root's next card fills up with sub-project work. Opt in per workspace:
{ "routing": { "commands": { "/ashfall": "apps/kenshi-fantasy", "/home": "." }, "record": "target" } }
- From then on, each turn taken while a route is selected is recorded under the routed project (same DB file — only the
projectvalue changes). The root card no longer shows it; a session opened inside that project does. Turns after the release command go back to the root. Old rows are not moved or re-classified. - A routed turn's summary comes from that turn's answer only — commits, decisions and earlier answers collected from the whole transcript (possibly another project's segment) are not used for it
- Provenance:
sessions.routed_from={root, via, target, since, session, turn}. When the route no longer maps, the target name equals the root's, or two routed folders share a name, the turn stays under the root with{root, unattributed, …} - Auto-extracted memories, solutions and decisions still go to the root project (unchanged)
- Any other
recordvalue is ignored (root) and reported as invalid; the same command declared twice with different case is rejected (it used to be silently overwritten)
Check what the hooks will read, without a DB or side effects:
npx passbaton-card --routes # or: --workspace <root>
# ROUTES workspace=… record=target · ROUTE /ashfall -> apps/kenshi-fantasy project=kenshi-fantasy dir=… · ROUTES-CHECK ok
Exit 0 = fine · 1 = invalid entry, disabled declaration or a project-name clash · 3 = no declaration.
One workspace root for the server, hooks and config CLI (v2.9.0+)
The MCP server and the config CLI had their own root detectors. The server stopped at the nearest .claude/ and had no
home guard, so a sub-repo apps/<app>/ with its own .claude/ made the server open apps/<app>/.claude/sessions.db
while the hooks wrote to the monorepo DB. All three now use the hooks' resolver. The home guard also stops at the real
account profile (os.userInfo().homedir), not only HOME/USERPROFILE — with HOME redirected, runs could climb into
the real ~/.claude/sessions.db.
Card source options: ages, tally, need, health — opt-in (v2.10.0+)
All four are optional per source in continuity.sources. Without them the card bytes are the same as 2.9.
{ "continuity": { "sources": [
{ "path": "docs/PENDING", "label": "requests", "ages": true, "tally": ["waiting for reply", "needs prep"] },
{ "path": "docs/INDEX.md", "block": "Active", "label": "decision index", "need": { "rows": 1, "items": ["Active"], "chars": 600 } },
{ "path": ".runner/health.json", "label": "run health", "health": { "maxAgeMinutes": 45 } }
] } }
ages: true— each ISO-8601 time with an offset orZin the shown excerpt gets its age relative to the card timetally: [...](1–6 words, 30 chars each) — counts lines containing each word across the whole selected range, including lines cut by the budget, so "3 waiting" stays true when only one is shownneed: { rows?, items?, chars? }— what the shown excerpt must contain to count as enough:rows= table data rows (header, separator and fenced lines excluded — a header-only table is 0),items= words that must appear verbatim,chars= a minimum body share taken from other sources inside the same total budget. Withrows/itemsdeclared the header saysneed met, or⚠ INSUFFICIENT — … — read <path:lines> directlywith the omitted amount. Undeclared = no verdicthealth: { maxAgeMinutes }— the source is a run-state JSON written by your own runner or supervisor; passbaton only shows it (it does not count logs or interpret words). Shape:observed_at(required, ISO with offset),state(required, producer's word),attention,normal_stop(booleans),last_attempt,last_success(ISO),failure {count, first, last, text}. Card lines:run health: …·⚠ RUN NEEDS ATTENTION — …·stopped normally·⚠ RUN HEALTH STALE(older thanmaxAgeMinutes) ·⚠ RUN HEALTH UNKNOWN(missing, broken, clock going backwards, or contradictory)
Stop summary picks the explicit status first (v2.10.0+)
The saved summary of a turn now prefers an explicit status / next step / blocker line. A heading-only line ("Review results") or a mid-sentence fragment is never used as the state, and the row records where the summary came from.
Decision index through the card (v2.7.0+)
When work is steered by written decisions (reviews, replies from another agent), keep a short index in the repo and declare its active section as a source. The index points to the original text; passbaton does not judge whether a decision is valid.
## Active
| ID | Decision | Decided by | Source | Resume condition | Unmet condition / evidence | State (A / H) | Replaces / invalidated |
|---|---|---|---|---|---|---|---|
| PB-07 | keep card cap 3200 | delegated reviewer | reply-132.md:70-81 (correction) | a measured case where truncation blocked a resume | none yet | A closed / H none | replaces reply-132.md:60-69 |
## Excluded — invalidated or replaced (not in force)
| Original location | Why excluded | Replaced by |
|---|---|---|
| reply-132.md:60-69 (first answer) | based on a miscounted figure | reply-132.md:70-81 |
{ "continuity": { "sources": [ { "path": "docs/INDEX.md", "block": "Active", "label": "decision index" } ] } }
- Table rows are never cut in the middle: a row that does not fit the card budget is left out and the source is marked
truncated, so a half row (missing its resume condition) cannot read as a complete decision - Use
blockon the active heading so excluded answers never reach the card as "active"; keep the excluded heading free of the active heading's text (otherwise the card reports an ambiguous match) - Record who edits the index and which commit it was checked against at the top; an entry whose source is missing or
conflicting should say so (
unverified — do not act on it) rather than be dropped
Read-only MCP mode (v2.6.0+)
PASSBATON_READONLY=1 exposes only tools that do not change state (resume_card, session_history,
search_sessions, memory_search, memory_get, …) and opens an existing DB with SQLite's own
read-only flag, so a missed write path fails loudly instead of writing. Access counters and the
embedding cache are not updated, the embedding model is not loaded (keyword search only), and prompts are not listed. If the DB file does not exist the server answers from an empty in-memory DB, creates nothing on disk, and says so in every result. Use it to share one memory/session ledger with a second
agent (e.g. Codex) without letting it edit:
codex mcp add passbaton-ro --env WORKSPACE_ROOT=/path/to/workspace --env PASSBATON_READONLY=1 -- npx -y passbaton
What 2.6.0 stopped injecting
Measured on a real 2-month workspace DB (Ashfall, 2026-10-22):
| Before 2.6.0 | 2.6.0 |
|---|---|
Regex-extracted "decisions" (instead of … + 60 chars) stored as decision/7 and injected — 146 of 146 were sentence fragments, none ever looked up | stored as observation/2 + unverified, not injected; old rows are quarantined — tags kept, unverified+quarantined-2.6 appended, original type/importance saved in metadata.quarantine. To promote one you checked: remove unverified, restore type/importance from metadata.quarantine, keep quarantined-2.6 |
Directives taken from any user turn — 37 of 40 came from <task-notification> results, loop prompts and packets sent to other agents; Never delete X was stored as delete X | only from human messages under 300 chars — in longer messages only the phrase after #기억/#remember (≤ 200 chars, never truncated); never/always/must keep the whole line; one line yields one directive; a directive is stored only if its saved context re-extracts to the same text; old rows are re-checked against the current rules at injection time. A rejected #기억 (over 200 chars, not one complete sentence, or not re-extractable) is never saved on any host; the rejection notice <passbaton-notice> is shown only in Claude Code — Codex/Gemini hooks must print exactly one JSON object, so there no notice is given. Valid directives are saved the same way on all hosts |
Global (project NULL) memories of every type in every project — general-knowledge seeds filled all 5 Key Memories slots | global memories only of type reference/preference |
| A question-only turn overwrote the project State | State changes only when this turn wrote or committed files (shell edits included) or used Edit/Write — a research-only turn leaves State as it was |
| Hot files printed as full Windows paths | file names |
2.6.1 — compaction recovery and hooks-only installs
| Before 2.6.1 | 2.6.1 |
|---|---|
PreCompact took the first user line in the last 200 transcript lines as "what we were doing" and wrote it into the project State — a harness <task-notification> became ♻️ Recovered after compaction: <task-notification> (Ashfall, 2026-10-22) | PreCompact never touches State. It keeps the last human request per session (harness notifications, meta turns, compaction summaries, shell I/O and tool results are not human requests) and SessionStart(compact) shows it as "a request, not a result", with "no reply yet" only when no assistant text followed it (that is all "no reply yet" means — not that the work is unfinished). State is labelled "Last recorded state (from the last Stop, not from this compaction)". Short requests ("stop") count; a complete tag block quoted inside a human message is kept as the human wrote it — only a message that starts with a harness envelope is treated as machine output |
| — | The recovery row is shown only if it belongs to this compaction: PreCompact stores the last transcript uuid it saw, and SessionStart(compact) shows the row only when no conversation (human or assistant message) follows that uuid in the transcript. A row left over from an earlier compaction (e.g. the next PreCompact could not write because the DB was locked) is never shown as current. Without a transcript or uuid (Codex/Gemini transcripts carry none) no recovery block is shown — nothing is guessed. The same compaction may be shown again if SessionStart runs twice, however long the compaction took |
"Active file" came from the first backticked name.ext in recent text — passbaton@2.6.0 became a file | paths the Edit/Write/Read tools actually touched come first, resolved against the hook's input cwd; a path that cannot be found now is marked (not found now) / (not a file now) / (could not check) — never "deleted", which passbaton cannot know; text candidates count only if the file exists under the cwd or workspace |
A hooks-only install (MCP server never started) had no user_directives table — #기억 was silently dropped | the hooks create it (same definition as the server); UserPromptSubmit never creates sessions.db itself — only SessionStart does |
Codex CLI support (v1.16.0+)
Beyond Claude Code, this also supports OpenAI Codex CLI. If ~/.codex exists,
the installer registers the same hooks in ~/.codex/hooks.json (SessionStart,
UserPromptSubmit, PreCompact, Stop), and the hooks auto-detect the host and emit
the right output format (Codex's hookSpecificOutput.additionalContext).
The same local sessions.db is shared, so context carries across both agents:
what you did in Codex is available in Claude Code and vice versa.
Scope: session save + context injection work on both. Codex file-change
tracking (PostToolUse) isn't wired yet — session save already covers most of it
via transcript parsing. Codex's transcript_path is treated as an unstable
interface (it can be null at startup), so host detection uses an installer-injected
--codex marker rather than relying on the path.
Gemini CLI support (v1.17.0+)
Also supports Google Gemini CLI. If ~/.gemini exists, the installer registers
the hooks in ~/.gemini/settings.json (SessionStart, BeforeAgent, PreCompress,
SessionEnd — Gemini's event names), preserving your other settings. Same shared
local sessions.db, so context carries across all three agents.
Gemini's transcript format was verified against real ~/.gemini/tmp/.../chats/*.jsonl
files — it uses two shapes (a flat {type, content} line and an older
{"$set":{"messages":[…]}} diff line); the parser handles both. Like Codex,
transcript_path can be null at startup, so host detection uses a --gemini marker.
Honest scope note: session save (SessionEnd) and context output are verified working.
Gemini's SessionStart context injection is documented as advisory-only upstream
(gemini-cli#15413) — if your
Gemini build doesn't render the injected context on startup, that's an upstream limit,
not this tool. Session continuity still works via the saved history.
Migrating from v1
If you installed this as claude-session-continuity-mcp (v1.x), nothing breaks — the v1 claude-hook-* commands still ship as aliases in v2.
To move to the new name:
npm install -g passbaton # installs the new package
npm uninstall -g claude-session-continuity-mcp # optional: drop the old one
To rewrite your hook entries, run passbaton-hooks install once: it replaces the old claude-hook-* lines with the current form and matches on both names, so you won't end up with duplicates. (The automatic install during npm install -g adds missing hooks but leaves existing passbaton lines as they are.) Your existing sessions.db is untouched: all past sessions, memories, and solutions carry over.
Nothing else changes — same hooks, same database, same behavior.
Alias policy (2.8): the seven v1 bin names (claude-session-continuity-mcp, claude-session-hooks, claude-hook-session-start|user-prompt|post-tool|pre-compact|session-end) are kept as deprecated aliases for the whole 2.x line — each runs exactly the same file as its passbaton* name (a test pins this). They are removed only in a major release, with a release note, because a v1 hook entry that was never rewritten would otherwise fail silently. The MCP server now reports itself as passbaton (serverInfo, package version); the host-side server key that installs write (project-manager) is unchanged, so tool names stay the same.
Quick Start
Requires Node.js 22+. The native
better-sqlite3dependency only ships prebuilt binaries for Node 22, 24, and 26 (the currently supported lines — Node 18 and 20 are both end-of-life). On older Node it falls back to compiling from source, which fails without build tools. Node 22 and up install cleanly with no compiler needed.
Recommended: Global Installation
npm install -g passbaton
That's it! The postinstall script automatically:
- Registers MCP server in
~/.claude.json - Installs Claude Hooks in
~/.claude/settings.json
npm install-script policy (npm 11.16+ / v12). passbaton needs install-time scripts: its own postinstall (registers hooks and the MCP server) and
better-sqlite3's native build. npm 11 runs them with aninstall-scripts … not yet covered by allowScriptswarning; withstrict-allow-scripts=truethe install fails; npm v12 is announced to skip unapproved install scripts by default — passbaton would then install without hooks and without a working SQLite binding. Approve them explicitly:npm install -g passbaton --allow-scripts=passbaton,better-sqlite3,sharp,protobufjsIf an install ran with scripts skipped (
passbaton-hooks statusreportsbetter-sqlite3 does not load), run that same command again. Re-runningpassbaton-hooks installalone does not rebuild the SQLite binding.
Then check it and start Claude Code:
passbaton-hooks status
Installed Node.js just now? If Claude Code was already open before Node.js was installed, fully quit the app that launched it (VS Code, your terminal app) and start it again, then start Claude Code. Opening a new tab or window in the same app can keep the old PATH. No reboot is needed for this.
npm hides postinstall output by default, so if setup could not finish you may not see a message —
passbaton-hooks statusshows what is registered, andpassbaton-hooks install(re)writes it.
passbaton-hooksis a command installed by thepassbatonpackage — there is no npm package with that name, so don't run it throughnpx passbaton-hooks. Without a global install, usenpx passbaton install/npx passbaton status(same installer).
Troubleshooting: npm: command not found / node: not found in a hook
UserPromptSubmit hook error
Failed with non-blocking status code: /usr/bin/bash: line 1: npm: command not found
The app that launched Claude Code has a PATH without Node.js (typically because it was
opened before Node.js was installed). Hooks written by older versions call npm exec and
need npm on that PATH. Run passbaton-hooks install from a terminal that has Node.js
— it rewrites passbaton's hooks and MCP entry with absolute paths — then restart as above.
passbaton-hooks status tells you which entries still depend on PATH. Its check uses
the PATH of the terminal you run it in, not that of an already running Claude Code session.
Why Global (-g)?
This tool is designed to track all your Claude Code projects in a single unified database. Global installation is strongly recommended because:
| Reason | Detail |
|---|---|
| Single source of truth | One binary serves every project — no version drift between projects |
| Hooks are user-scoped | ~/.claude/settings.json lives in your home directory, not per-project |
| Cross-project context | Sessions from app-a and app-b share the same DB and search index |
| One update = everything refreshed | npm install -g <latest> updates all projects at once; no per-project reinstall |
| Hooks don't depend on PATH | Claude Code hooks are written as "<node>" "<package>/dist/hooks/<hook>.js" using the Node.js that ran the install, so they work even when the app running Claude Code has an old PATH. It is also fast: about 117 ms per fire vs 1,357 ms for npm exec -- … (one Windows PC; PostToolUse fires on every edit). If you move or reinstall Node.js, run passbaton-hooks install again (status reports a missing node or script). Codex / Gemini hooks keep the PATH-based form for now |
Add the database to your project's .gitignore. Since 2.4.0 passbaton creates
<project>/.claude/sessions.db on the first session in any project it recognises as a
workspace root, so a project that had no database before will get one:
.claude/sessions.db
.claude/sessions.db-shm
.claude/sessions.db-wal
.claude/*.log
Do not ignore all of .claude/ — settings.json there is meant to be committed.
Important: Even with global install, you can still disable the hook for specific projects (see below). Global ≠ forced on every project.
Disabling Hooks for Specific Projects
Global install does not mean "always on everywhere". You have three layers of control:
| Layer | File | Scope |
|---|---|---|
| 1. Global ON (default) | ~/.claude/settings.json | All projects |
| 2. Project-wide OFF | <project>/.claude/settings.json | Whole team (committed) |
| 3. Personal-only OFF | <project>/.claude/settings.local.json | Just you (gitignored) |
To disable hooks in a specific project, create the override file with empty hook arrays:
// <project>/.claude/settings.json (or settings.local.json for personal-only)
{
"hooks": {
"SessionStart": [],
"UserPromptSubmit": [],
"PostToolUse": [],
"PreCompact": [],
"Stop": []
}
}
Empty arrays override the global setting → that project's sessions are no longer tracked.
Updating to a New Version
npm install -g passbaton@latest
That's the only step — all projects pick up the new binary on next Claude Code restart. No need to reinstall in each project.
Alternative: Local Install (Not Recommended)
If you really want per-project install (e.g., locked version for one project):
cd <project> && npm install passbaton
Drawback: you must install separately in every project, npm exec may not find the local copy reliably from hook context (cwd-dependent), and you pay the npm exec startup cost (~1.4 s) on every hook fire instead of ~135 ms. Stick with -g unless you have a specific reason.
What Gets Installed
MCP Server (in ~/.claude.json):
{
"mcpServers": {
"project-manager": {
"command": "npx",
"args": ["passbaton"]
}
}
}
Claude Hooks (in ~/.claude/settings.json):
{
"hooks": {
"SessionStart": [{ "hooks": [{ "type": "command", "command": "passbaton-hook-session-start" }] }],
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "passbaton-hook-user-prompt" }] }],
"PostToolUse": [{ "matcher": "Edit", "hooks": [{ "type": "command", "command": "passbaton-hook-post-tool" }] }, { "matcher": "Write", "hooks": [{ "type": "command", "command": "passbaton-hook-post-tool" }] }],
"PreCompact": [{ "hooks": [{ "type": "command", "command": "passbaton-hook-pre-compact" }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "passbaton-hook-session-end" }] }]
}
}
Note (v2.2.1+): Full lifecycle coverage with 5 hooks. The installer probes whether passbaton-hook-* resolves on PATH and writes the bare name if so (≈10× faster per fire); otherwise it writes npm exec -- …, which also finds a local node_modules/.bin.
Installed Hooks (v1.5.0+)
| Hook | Command | Function |
|---|---|---|
SessionStart | passbaton-hook-session-start | Auto-loads project context on session start |
UserPromptSubmit | passbaton-hook-user-prompt | Auto-injects relevant memories + past reference search |
PostToolUse | passbaton-hook-post-tool | Tracks active files (Edit, Write) |
PreCompact | passbaton-hook-pre-compact | Structured handover context before compression |
Stop | passbaton-hook-session-end | Extracts commits, decisions, error-fix pairs from transcript |
Manual Hook Management
# Check hook status
passbaton-hooks status
# Reinstall hooks
passbaton-hooks install
# Remove hooks
passbaton-hooks uninstall
3. Restart Claude Code
After installation, restart Claude Code to activate the hooks.
Features
| Feature | Description |
|---|---|
| 🤖 Zero Manual Work | Claude Hooks automate all context capture/load |
| 🎯 Quality Memory Only | (v1.10.0) Only decisions, learnings, errors — no file-change noise |
| 🧠 Semantic Search | multilingual-e5-small embedding (94+ languages, 384d) |
| 🌍 Multilingual | Korean/English/Japanese + cross-language search (EN→KR, KR→EN) |
| 🔗 Git Integration | Commit messages auto-extracted from transcripts |
| 🕸️ Knowledge Graph | Memory relations (solves, causes, extends...) |
| 📊 Memory Classification | 5 types: observation, decision, learning, error, pattern |
| ✅ Integrated Verification | One-click build/test/lint execution |
| 📋 Task Management | Priority-based task management |
| 🔧 Error→Solution Archive | Session-end auto-records error-fix pairs; look them up with solution_find / solution_suggest (the v1.12.0 Bash-error auto-injection was removed in v2.8 — the installed hook never received Bash events) |
| 💰 Token Efficiency | (v1.11.0) Removed loadContext from UserPromptSubmit (saves 24-60K tokens/session) |
| 📑 Progressive Disclosure | (v1.11.0) memory_search returns index first, memory_get for full content |
| ⏳ Temporal Decay | (v1.11.0) Memory scoring with type-specific half-lives for relevance |
| 📝 Structured Handover | (v1.10.0) PreCompact saves work summary, active files, pending actions |
| 🚪 Smart Session End | (v1.10.0) Extracts commits, decisions, error-fix pairs from transcript |
| 🗑️ Auto Noise Cleanup | (v1.10.0) Auto-deletes stale observation memories (3d+) |
| 🔍 Past Reference Detection | (v1.8.0) "저번에 X 어떻게 했어?" auto-searches DB |
| 📝 User Directive Extraction | (v1.8.0) Auto-extracts "always/never" rules from prompts |
Feature toggles — everything is opt-in
(v2.1.0+) Six behaviours are individually toggleable; the rest are shown for
transparency but are always on (a hook's mere existence is controlled by your
settings.json, not by config) or not yet wired. Config lives in a plain,
hand-editable JSON file (~/.claude/passbaton.config.json) — separate from your data,
so it survives a db reset. No file = today's defaults (nothing changes for existing users).
passbaton config # grouped table; ●/○ = toggleable, · = always on
passbaton config set solutionCapture off # flip a toggleable feature
passbaton config set strictSolutionGate on # opt into the strict error→fix filter
passbaton config preset minimal # minimal | default | everything
passbaton config reset # back to defaults
passbaton config path # print the active config file path
Trying to set an always-on / not-yet-wired feature is rejected with a clear message.
Each toggleable feature also has an env override for one-off/CI use:
PASSBATON_<FEATURE>=0 (e.g. PASSBATON_SOLUTIONCAPTURE=0) wins over the config file.
On-by-default rule: a feature ships on only if it's silent, safe, and universally useful. Anything that speaks unprompted, guesses, or writes speculative rows ships off.
Legend: ●/○ = toggleable (on/off) · · = always on, not a config toggle · ⋯ = not yet wired.
Core (on by default)
| Feature | Key | Toggle | What it does |
|---|---|---|---|
| Session start injection | sessionStart | · always on | Restore prior context on start |
| Compaction handover+ | compactionHandover | ● toggleable | Before a compaction, carry over your working state plus hot files and last build status — the one gap platform auto-memory structurally can't cover |
| Session persist | sessionEnd | · always on | Save session state on exit |
| Auto memory surfacing | autoInject | · always on | Auto-surface relevant past memories on start |
| Task tracking | taskTracking | · always on | Read/write the task list via MCP + hooks |
| Hot-path pre-warm | hotPathPrewarm | ● toggleable | On start, surface the files you edit most in this project, ranked by real access count |
| Verification ledger | verificationLedger | ● toggleable | Warn on start if a recent session left the build red or issues open |
sessionStart/sessionEndare "always on" because a hook either runs or it doesn't — that's controlled by the hook registration in~/.claude/settings.json, not by config. To disable them, remove the hook there.
Cross-agent (on by default)
| Feature | Key | Toggle | What it does |
|---|---|---|---|
| Cross-agent share | crossAgentSync | · inherent | One local db shared across Claude Code / Codex / Gemini (not a toggle — it's how storage works) |
| Tool-use capture | postToolCapture | · always on | Observe tool use to build hot-paths (low-noise) |
| Solution capture | solutionCapture | ● toggleable | Auto-record error→fix pairs to a solution archive. Set off to skip it entirely (session save is unaffected) |
Experimental (off by default)
| Feature | Key | Toggle | What it does |
|---|---|---|---|
| Strict solution gate | strictSolutionGate | ○ opt-in | Stricter error→fix capture filter — fewer noise entries, but may drop some real ones |
| Trigger matching | triggerMatching | ⋯ not yet wired | (planned) Match prompt keywords to auto-inject solutions |
| Pattern mining | patternMining | ⋯ not yet wired | (planned) Mine work patterns and suggest workflows |
| Memory auto-store | memoryAutoStore | ⋯ not yet wired | (planned) Auto-write observation memories from prompts |
| Status line | statusLineInject | ⋯ not yet wired | (planned) Append a passbaton status line to session-start output |
| Hook trace | hookTrace | ○ opt-in | Diagnostics: one line per SessionStart / PostToolUse fire into <workspace>/.claude/hook-trace.log, with pid and the resolved ws_root. Off by default because PostToolUse fires on every edit. Capped at 5 MB (one generation kept). Logs absolute file paths in plaintext — add .claude/hook-trace.log to .gitignore if your repo tracks .claude/ |
The only genuinely user-flippable flags today are compactionHandover, hotPathPrewarm,
verificationLedger, solutionCapture (on) and strictSolutionGate, hookTrace (opt-in).
Turning on
hookTraceon Windows: edit~/.claude/passbaton.config.json(or runpassbaton config set hookTrace on). ThePASSBATON_HOOKTRACE=1env override works too, but hooks are launched fromsettings.jsonthroughcmd.exe, where aVAR=1 commandprefix is a syntax error — so the config file is the practical path.
Claude Hooks - Auto Context System
How It Works
SessionStart Hook (npx passbaton-hook-session-start):
- Auto-detects project: monorepo (
apps/project-name/) or single project (package.jsonroot folder name) - Loads context from
.claude/sessions.db - Injects: Current state, 3 recent sessions with commits/decisions, directives, pending tasks, filtered key memories
- Auto-cleans stale noise memories (3d+ auto-tracked, 14d+ auto-compact)
UserPromptSubmit Hook (npx passbaton-hook-user-prompt):
- Runs on every prompt submission
- (v1.11.0) No longer calls loadContext() — saves 24-60K tokens/session
- Injects relevant context (filtered: decisions, learnings, errors only)
PostToolUse Hook (npx passbaton-hook-post-tool):
- Tracks hot file paths and updates
active_context.recent_files - No longer creates observation memories (v1.10.0 — eliminates
[File Change]noise)
PreCompact Hook (npx passbaton-hook-pre-compact):
- Builds structured handover context: work summary, active file, pending action, key facts, recent errors
- No longer stores auto-compact memories (v1.10.0)
Stop Hook (npx passbaton-hook-session-end):
- Extracts commit messages from JSONL transcript (
git commit -mpatterns) - Extracts error-fix pairs (error → resolution within 3 messages)
- (v1.12.0) Auto-records error→fix pairs to solutions table for future reuse
- Extracts decisions ("because", "instead of", "chose" patterns)
- (v1.11.0) Single-pass transcript parsing (4 JSONL reads → 1)
- Stores structured metadata in
sessions.issuescolumn as JSON
Example Output (Session Start)
# my-app - Session Resumed
📍 **State**: Implementing signup form
🚧 **Blocker**: OAuth callback URL issue
## Recent Sessions
### 2026-02-28
**Work**: Completed OAuth integration
**Commits**: feat: add OAuth handler; fix: redirect config
**Decisions**: Use Server Actions over API routes
**Next**: Implement form validation
## Directives
- 🔴 Always use Zod for validation
## Pending Tasks
- 🔄 [P8] Implement form validation
- ⏳ [P5] Add error handling
## Key Memories
- 🎯 Decided on App Router, using Server Actions
- ⚠️ OAuth redirect_uri mismatch → check env file
Hook Management
# Check status
passbaton-hooks status
# Reinstall
passbaton-hooks install
# Remove
passbaton-hooks uninstall
# Temporarily disable
export MCP_HOOKS_DISABLED=true
Past Reference Detection (v1.8.0)
When you ask about past work, the UserPromptSubmit hook automatically searches the database:
You: "저번에 인앱결제 어떻게 했어?"
→ Hook detects "저번에" + extracts keyword "인앱결제"
→ Searches sessions, memories (FTS5), and solutions
→ Injects matching results into context automatically
Supported patterns (Korean & English):
| Pattern | Example |
|---|---|
| 저번에/전에/이전에 ... 어떻게 | "저번에 CORS 에러 어떻게 해결했지?" |
| ~했던/만들었던/해결했던 | "수정했던 로그인 로직" |
| 지난 세션/작업에서 | "지난 세션에서 결제 구현" |
| last time/before/previously | "How did we handle auth last time?" |
| did we/did I ... before | "Did we fix the database migration before?" |
| remember when/recall when | "Remember when we set up CI?" |
Output example:
## Related Past Work (auto-detected from your question)
### Sessions
- [2/14] 카카오 로그인 앱키 수정, 인앱결제 IAP 플로우 수정
### Memories
- 🎯 [decision] 테스트: 인앱결제 상품 등록 완료
### Solutions
- **IAP_BILLING_ERROR**: StoreKit 2 migration으로 해결
Why npm exec? (v1.4.3+)
Previous versions used absolute paths or npx:
// v1.3.x - absolute paths (broke on multi-project)
"command": "node \"/path/to/project-a/node_modules/.../session-start.js\""
// v1.4.0-1.4.2 - npx (required global install or hit npm registry)
"command": "npx passbaton-hook-session-start"
Now we use npm exec --:
"command": "passbaton-hook-session-start"
npm exec -- finds local node_modules/.bin first, then falls back to global. Works with both local and global installation without hitting npm registry.
Tools (v5 API) - 25 Focused Tools
1. Session Lifecycle (4) ⭐
// Start of session - auto-loads context
session_start({ project: "my-app", compact: true })
// End of session - auto-saves context
session_end({
project: "my-app",
summary: "Completed auth flow",
modifiedFiles: ["src/auth.ts", "src/login/page.tsx"]
})
// View session history
session_history({ project: "my-app", limit: 5 })
// Semantic search past sessions
search_sessions({ query: "auth work", project: "my-app" })
2. Project Management (4)
// Get project status with task stats
project_status({ project: "my-app" })
// Initialize new project
project_init({ project: "my-app" })
// Analyze project tech stack
project_analyze({ project: "my-app" })
// List all projects
list_projects()
3. Task Management (4)
// Add a task
task_add({ project: "my-app", title: "Implement signup", priority: 8 })
// Update task status
task_update({ taskId: 1, status: "done" })
// List tasks
task_list({ project: "my-app", status: "pending" })
// Suggest tasks from TODO comments
task_suggest({ project: "my-app" })
4. Solution Archive (3)
// Record an error solution
solution_record({
errorSignature: "TypeError: Cannot read property 'id'",
solution: "Use optional chaining: user?.id"
})
// Find similar solutions (keyword or semantic)
solution_find({ query: "TypeError property", semantic: true })
// AI-powered solution suggestion
solution_suggest({ errorMessage: "Cannot read property 'email'" })
5. Verification (3)
// Run build
verify_build({ project: "my-app" })
// Run tests
verify_test({ project: "my-app" })
// Run all (build + test + lint)
verify_all({ project: "my-app" })
6. Memory System (5)
// Store a classified memory
memory_store({
content: "State management with Riverpod makes testing easier",
type: "learning", // observation, decision, learning, error, pattern
project: "my-app",
tags: ["flutter", "state-management"],
importance: 8,
relatedTo: 23 // Connect to existing memory
})
// Search memories — returns index (id, type, tags, score) for token efficiency
memory_search({
query: "state management test",
type: "learning",
semantic: true, // Use embedding similarity
limit: 10
})
// Get full memory content by ID (v1.11.0)
memory_get({ memoryId: 23 })
// Find related memories (graph + semantic)
memory_related({
memoryId: 23,
includeGraph: true,
includeSemantic: true
})
// Get memory statistics
memory_stats({ project: "my-app" })
7. Knowledge Graph (2)
// Connect two memories with a typed relation
graph_connect({
sourceId: 23,
targetId: 25,
relation: "solves", // related_to, causes, solves, depends_on, contradicts, extends, example_of
strength: 0.9
})
// Explore knowledge graph
graph_explore({
memoryId: 23,
depth: 2,
relation: "all", // or specific relation type
direction: "both" // outgoing, incoming, both
})
Memory Types
| Type | Description | Use Case |
|---|---|---|
observation | Patterns, structures found in codebase | "All screens are separated in features/ folder" |
decision | Architecture, library choices | "Decided to use SharedPreferences for caching" |
learning | New knowledge, best practices | "Riverpod is better for testing" |
error | Occurred errors and solutions | "Provider.read() doesn't rebuild → use watch()" |
pattern | Recurring code patterns, conventions | "Avoid late keyword abuse" |
Relation Types
| Relation | Description | Example |
|---|---|---|
related_to | General relation | A and B are related |
causes | A causes B | Caching decision → folder structure change |
solves | A solves B | Riverpod learning → Provider bug fix |
depends_on | A depends on B | Folder structure → Caching decision |
contradicts | A conflicts with B | Two design decisions conflict |
extends | A extends B | late pattern → Extended to Riverpod learning |
example_of | A is example of B | Specific code is example of pattern |
Data Storage
SQLite database at ~/.claude/sessions.db:
| Table | Purpose |
|---|---|
memories | Classified memories (observation, decision, learning, error, pattern) |
memories_fts | Full-text search index (FTS5) |
memory_relations | Knowledge graph relations |
embeddings_v4 | Semantic search vectors (multilingual-e5-small, 384d) |
project_context | Fixed project info (tech stack, decisions) |
active_context | Current work state |
tasks | Task backlog |
solutions | Error solution archive |
sessions | Session history |
Environment Variables
| Variable | Default | Description |
|---|---|---|
WORKSPACE_ROOT | - | Workspace root path (required) |
MCP_HOOKS_DISABLED | false | Disable Claude Hooks |
LOG_LEVEL | info | Log level (debug/info/warn/error) |
LOG_FILE | - | Optional file logging path |
PASSBATON_READONLY | false | Read-only MCP mode — only non-mutating tools, DB opened read-only (v2.6.0) |
PASSBATON_CARD_CHARS | 3200 | Character budget for the SessionStart resume card (v2.6.0) |
Development
# Clone
git clone https://github.com/leesgit/passbaton.git
cd passbaton
# Install
npm install
# Build
npm run build
# Test
npm test
# Test with coverage
npm run test:coverage
Performance
| Metric | Value |
|---|---|
| Context load (cached) | <5ms |
| Memory search (FTS) | ~10ms |
| Semantic search | ~50ms |
| Build verification | Project-dependent |
Roadmap
- v2 API (15 focused tools)
- v4 API (24 tools - memory + graph)
- v5 Claude Hooks (auto-capture)
- Knowledge Graph with typed relations
- Memory classification (6 types)
- Semantic search (embeddings)
- Multilingual pattern detection (KO/EN/JA)
- Git commit integration
- 111 tests (6 test suites)
- GitHub Actions CI/CD
- Multilingual semantic search (v1.6.0 - multilingual-e5-small)
- Cross-language search EN↔KR (v1.6.0)
- Solution semantic search (v1.6.0)
- Fix hooks settings file path (v1.6.1 - settings.json, not settings.local.json)
- Auto-migrate legacy hooks (v1.6.1)
- Fix PostToolUse matcher format to string (v1.6.3)
- Fix README documentation for new hook format (v1.6.4)
- Empty session skip and techStack save improvements (v1.7.1)
- Past reference auto-detection in UserPromptSubmit hook (v1.8.0)
- User directive extraction ("always/never" rules) (v1.8.0)
- Memory quality overhaul — no more
[File Change]noise (v1.10.0) - Structured handover context in PreCompact (v1.10.0)
- Smart session-end: commit/decision/error-fix extraction from transcript (v1.10.0)
- Auto noise cleanup (3d+ observations, 14d+ auto-compact) (v1.10.0)
- 3 recent sessions display with structured metadata (v1.10.0)
- Token efficiency — remove loadContext from UserPromptSubmit, saves 24-60K tokens/session (v1.11.0)
- Single-pass transcript parsing, 4 JSONL reads → 1 (v1.11.0)
- Temporal decay for memory scoring with type-specific half-lives (v1.11.0)
- Progressive disclosure — memory_search returns index, memory_get for full content (v1.11.0)
- Memory consolidation via Jaccard similarity (v1.11.0)
- SessionEnd auto-records error-fix pairs to solutions table (v1.12.0)
- Cross-project solution search with current project prioritization (v1.12.0)
- sqlite-vec native vector search (v2 - when data > 1000 records)
- Web dashboard
- Cloud sync option
Contributing
PRs welcome! Please:
- Fork the repo
- Create a feature branch
- Add tests for new features
- Ensure
npm testpasses - Submit PR
License
MIT © Byeongchang Lee
Acknowledgments
- Model Context Protocol by Anthropic
- Xenova Transformers for embeddings
If this saves you from re-explaining your project, consider giving it a ⭐ — it genuinely helps others find it.