codetree-claude
v1.6.1
Published
MCP server & CLI: codebase index + cache for Claude Code, Cursor, OpenAI Codex, Google Antigravity/Gemini. Model Context Protocol tools (codetree_read, codetree_search, …), hooks on Claude, rules on other IDEs. Token savings, SQLite index, symbol search,
Maintainers
Keywords
Readme
CodeTree
Persistent codebase index + Model Context Protocol (MCP) server for AI coding assistants. Cuts redundant Read / Grep / Glob token usage by routing AI tool calls through a cached SQLite index with symbol-aware APIs.
CodeTree works with Anthropic Claude Code, Cursor, OpenAI Codex, Google Antigravity / Gemini, and any MCP-compatible editor or CLI. A single codetree init wires the same MCP server into every tool's config format.
Table of Contents
- Why CodeTree
- Features
- Quick Start
- Supported AI Tools
- Architecture
- Component Design
- Data Flow Diagrams
- MCP Tools Reference
- CLI Commands
- Configuration
- Repository Layout
- How
codetree initWires Each IDE - Reliability & Cross-Platform Notes
- Development
- Testing
- Contributing
- License
Why CodeTree
Teams using AI coding assistants spend a large fraction of their context window / tokens on re-reading the same files. Every chat re-runs Glob, Grep, and Read, and the model sees the same source again and again.
CodeTree solves this by maintaining a local, persistent SQLite index of your repo and serving requests through MCP tools that return:
- Cached file content (mtime-validated, never silently stale)
- Compact diffs when the file is mostly unchanged
- Symbol outlines instead of full bodies
- Symbol-aware search that bypasses raw text grep
- Token-efficient project tree instead of
Glob "**/*"
For Claude Code, CodeTree additionally installs PreToolUse hooks that intercept native Read/Grep/Glob calls and redirect them to the cache. For Cursor / Codex / Antigravity, the model is steered to the same MCP tools via project rules and AGENTS.md / GEMINI.md.
Features
- Persistent SQLite index — survives restarts; cold start uses cached postings
- In-memory LRU + compressed SQLite content cache — two-tier read path
- Symbol extraction for JS/TS/JSX/TSX, Python, Java/Kotlin/C#, Go, Rust, Ruby
- Token-postings index for fast multi-token content scans (avoids full-corpus grep)
- mtime-validated reads — branch switches, external edits, merges all reflected
- File watcher (chokidar) + safety-net rescan every 10 min for missed events
- Singleton IPC server — deterministic per-project port; duplicates exit cleanly
- Hooks for Claude Code —
PreToolUse,PostToolUse,SessionStart,Pre/PostCompact - Multi-IDE setup — one
codetree initconfigures Claude, Cursor, Codex, Antigravity - VS Code extension — sidebar with token savings, indexed files, symbols, dashboard
- Cross-platform — Windows
\r\nsafe, forward-slash paths in MCP configs - Per-tool telemetry — read/search/glob hits, misses, tokens saved, scan-cache hit rate
Quick Start
Option 1 — npm global
npm install -g codetree-claude
cd your-project
codetree initOption 2 — per-project dev dependency
npm install --save-dev codetree-claude
npx codetree initOption 3 — VS Code extension
Install "CodeTree — Cache for Claude Code" from the VS Code Marketplace. The extension activates CodeTree in the editor and shows the live dashboard.
After codetree init, reload your editor (Cursor / VS Code: Cmd/Ctrl+Shift+P → Developer: Reload Window). The MCP server starts automatically per project on the next AI session.
Supported AI Tools (multi-IDE)
| Product | What CodeTree configures | How tokens are saved |
|---------|--------------------------|----------------------|
| Claude Code (Anthropic) | Root .mcp.json, .claude/settings.json hooks, CLAUDE.md, .claude/rules/codetree.md | Hooks redirect native Read/Grep/Glob/Edit/Write to codetree_* when the cache can serve |
| Cursor | .cursor/mcp.json, .cursor/rules/codetree.mdc | MCP tools + alwaysApply rules steer the agent |
| OpenAI Codex | .codex/config.toml [mcp_servers.codetree], AGENTS.md | MCP tools + agent instructions (merge-safe TOML) |
| Google Antigravity / Gemini | .antigravity/mcp.json, AGENTS.md, GEMINI.md | MCP + portable docs |
| VS Code | Marketplace extension "CodeTree — Cache for Claude Code" | Sidebar dashboard + auto-mirroring MCP into .cursor/mcp.json |
| Any MCP host | Same node …/dist/server/mcp-server.js entry | Register the MCP server manually if your tool uses a custom config path |
Note: Token-saving hooks (PreToolUse interception) are a Claude Code feature. Cursor / Codex / Antigravity rely on MCP + project rules — the model is steered to call
codetree_read,codetree_search, etc., instead of native tools. The indexer, cache, and MCP tool surface are identical for all hosts.
Architecture
CodeTree is composed of six layered subsystems, each with a single responsibility. The whole stack is one Node process per project, started on demand by the MCP host.
┌──────────────────────────────────────────────────────────────────────┐
│ AI client │
│ (Claude Code / Cursor / Codex / Antigravity / any MCP host) │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Steering layer │ │
│ │ - Claude Code: PreToolUse hooks + .claude/rules │ │
│ │ - Cursor: .cursor/rules/codetree.mdc (alwaysApply) │ │
│ │ - Codex: AGENTS.md + .codex/config.toml │ │
│ │ - Antigravity: AGENTS.md + GEMINI.md + .antigravity/mcp.json │ │
│ └────────────────────────────┬───────────────────────────────────┘ │
└───────────────────────────────┼──────────────────────────────────────┘
│ MCP / stdio
┌───────────────────────────────▼──────────────────────────────────────┐
│ MCP Server (src/server/mcp-server.ts) │
│ - Registers 10 tools, dispatches CallToolRequest │
│ - Routes telemetry through HTTP IPC │
└──────────┬──────────────────────────────────┬────────────────────────┘
│ │
┌──────────▼──────────────┐ ┌──────────▼────────────────────────┐
│ Tool Handlers │ │ IPC Server (singleton, HTTP) │
│ src/server/tools/*.ts │ │ src/server/ipc.ts │
│ - codetree_read │ │ - Deterministic per-project port │
│ - codetree_search │ │ - /check, /check-search, │
│ - codetree_structure │ │ /check-glob (hook decisions) │
│ - codetree_outline │ │ - /stats, /status, /mcp-call │
│ - codetree_probe │ │ - Owner / shared mode coordination│
│ - codetree_find_refs │ └──────────┬────────────────────────┘
│ - codetree_summary │ │
│ - codetree_memory │ │ same process
│ - codetree_edit │ │
│ - codetree_write │ │
└──────────┬──────────────┘ │
│ │
┌──────────▼──────────────────────────────────▼────────────────────────┐
│ Indexer (src/indexer/indexer.ts) │
│ - fullScan / indexFile / safetyNetRescan │
│ - In-memory token postings (Map<token, Set<relPath>>) │
│ - readCached / readForScan (mtime revalidation, disk fallback) │
│ - looksBinary heuristic, file size cap │
│ - Uses ExtractorRegistry to pull symbols + dependencies │
└──────────┬──────────────────────┬────────────────┬───────────────────┘
│ │ │
┌──────────▼──────────┐ ┌────────▼──────────┐ ┌──▼──────────────────┐
│ Storage │ │ ContentCache LRU │ │ FileWatcher │
│ src/storage/ │ │ (in-memory) │ │ src/indexer/ │
│ database.ts │ │ cache.ts │ │ watcher.ts │
│ - sql.js (WASM) │ │ - hash-keyed │ │ - chokidar │
│ - files, │ │ - mtime/size/ │ │ - debounce 300ms │
│ symbols, │ │ hash metadata │ │ - .git/HEAD watcher │
│ dependencies, │ │ │ │ → branch switch │
│ content_cache, │ │ │ │ cache invalidation│
│ token_postings, │ │ │ │ - error handler │
│ trigrams, │ │ │ │ (EMFILE/ENOSPC) │
│ previous_content │ │ │ │ │
│ - persistToDisk │ │ │ │ │
└─────────────────────┘ └───────────────────┘ └──────────────────────┘Layer Responsibilities
| Layer | File(s) | Responsibility |
|-------|---------|----------------|
| Steering | templates/, setup/install.ts | Idempotent install of MCP configs + rules into each IDE |
| MCP Server | src/server/mcp-server.ts | Stdio transport, tool registration, lifecycle management |
| Tool Handlers | src/server/tools/*.ts | One file per MCP tool; pure functions of (config, indexer, db) |
| IPC Server | src/server/ipc.ts | HTTP server on deterministic port; serves hook decisions + telemetry |
| Indexer | src/indexer/indexer.ts | The orchestrator — read / scan / extract / postings / token cache |
| Storage | src/storage/database.ts, cache.ts | SQLite (sql.js WASM) + in-memory LRU |
| Watcher | src/indexer/watcher.ts | chokidar + branch-switch detection + safety-net rescan |
| Hooks (Claude only) | src/hook/*.ts | Bundled standalone scripts invoked by Claude Code per tool call |
| CLI | src/cli.ts | init, status, stats, reindex, doctor, help |
Component Design
Indexer
The Indexer class (src/indexer/indexer.ts) is the heart of CodeTree.
fullScan()— incremental scan of the project tree. Each file goes throughindexFile(), which short-circuits on(size, mtime)match (the cheap path) and only re-extracts on real change.indexFile()— reads file, runslooksBinaryheuristic (>5% unprintable bytes ⇒ skip), computes content hash, extracts symbols + dependencies via the language-specific extractor, writes to SQLite, populates LRU + content-cache, and updates the token-postings index.readCached(relPath)— two-tier read: in-memory LRU first (with periodic mtime revalidation), then SQLite content cache (with mtime check against disk). On mismatch, re-indexes and serves fresh.readForScan(relPath)— used by content-search loops. LikereadCachedbut never returns null for files that exist: falls back to directreadFileSyncand re-seats both caches. Counts hits/misses for scan telemetry.safetyNetRescan()— cheap incremental sweep (mostly statSync + short-circuit) run every 10 min as backup against missed chokidar events on Windows / OneDrive / network drives.- Token postings (
Map<lowercase-token, Set<relPath>>) — drivescodetree_searchcontent search,codetree_find_refs, andcodetree_probe. Persisted to SQLite for instant cold start. Tokens are extracted with/[A-Za-z_][A-Za-z0-9_]{2,}/(≥3 chars, capped per-file at 4000).
Storage
src/storage/database.ts — SQLite via sql.js (WASM, no native build). Tables:
| Table | Purpose |
|-------|---------|
| files | path, hash, size, mtime, language, line count, indexed-at |
| symbols | name, kind, line span, signature, exported flag — keyed by file path |
| dependencies | source path → target import specifier (resolved is null in current pass) |
| content_cache | zlib-deflated file content; capped by cache.contentCacheMaxMB |
| token_postings | per-file token list (for postings cold start) |
| trigrams | content trigrams (for fuzzy content search) |
| previous_content | older deflated content for diff-based reads (24h TTL, capped at 200 rows) |
src/storage/cache.ts — small in-memory LRU keyed by relPath. Stores raw text plus hash/mtime/size metadata. Hit/miss counters exposed on /stats.
IPC Server
src/server/ipc.ts (~1000 lines, single class IpcServer) exposes an HTTP API on a deterministic per-project port. Two responsibilities:
- Hook decisions —
/check,/check-search,/check-globanswer Claude Code hooks in <50 ms. - Telemetry —
/stats,/status,/clear-cache,/mcp-call. Per-tool counters:readHits,searchHits,globHits,mcpCalls,tokensSaved,scanCacheHits,scanHitRate.
The IPC server is a singleton per project: it picks a deterministic port from the project root path. If another instance is already bound, it retries with backoff (200/400/800/1200 ms). On failure the duplicate process exits cleanly so two instances never race on the same SQLite write lock.
Hooks (Claude Code only)
Bundled into dist/hook/*.js (esbuild standalone scripts) and invoked by Claude Code via .claude/settings.json:
| Hook | When it fires | What it does |
|------|---------------|--------------|
| pre-tool-use | Before every Read/Grep/Glob | Asks IPC /check*; on cache hit, exits 2 with redirect message → Claude calls codetree_* instead |
| post-tool-use | After every tool call | Updates session memory |
| session-start | New chat session | Injects compact project summary (capped at 2 KB) |
| pre-compact | Before context compaction | Saves current insights for recall |
| post-compact | After compaction | Restores compact session context |
All hooks must complete in <50 ms and fail open (any error ⇒ exit 0, native tool runs).
Data Flow Diagrams
Flow 1 — AI agent reads a file (Claude Code)
Agent: Read("src/api/auth.ts")
│
▼
Claude Code ──── PreToolUse hook ────► pre-tool-use.js
│
▼
IpcServer /check?path=…
│
┌───────────────┴───────────────┐
│ │
cached not cached
│ │
▼ ▼
exit 2 + stderr message exit 0 (allow)
"Use codetree_read …" │
│ │
▼ ▼
Agent calls codetree_read Native Read runs
│ │
▼ ▼
Indexer.readCached → LRU/SQLite Disk read
│
▼
JSON: { content, hash, lineCount,
symbols, language,
unchanged_since_last_read? }Flow 2 — AI agent reads a file (Cursor / Codex / Antigravity)
Agent (steered by .cursor/rules/codetree.mdc or AGENTS.md)
│
▼ (decides to call codetree_read directly)
MCP Server ──► CallToolRequest("codetree_read", {file_path, session_id?})
│
▼
Tool handler (src/server/tools/codetree-read.ts)
│
├── session+hash short-circuit? → unchanged_since_last_read: true (no body)
├── expected_hash match? → unchanged: true (no body)
├── outline_only or auto-fallback → symbols + metadata only (no body)
└── focus_symbol set? → return only symbol body + context
│
▼
Indexer.readCached(relPath)
│
├── LRU hit? → mtime revalidate periodically → return content
├── SQLite content_cache hit? → mtime check vs disk → re-index if stale
└── miss → null (handler falls back to direct disk read for non-scan paths)
│
▼
Response → MCP Server → JSON over stdio → Agent
│
└── (in parallel) IPC /mcp-call?tool=codetree_read&tokens=N updates telemetryFlow 3 — Indexer lifecycle (server startup)
MCP host spawns: node dist/server/mcp-server.js
│
▼
loadConfig() ──► Database.init() (sql.js loads .codetree/index.db)
│
▼
ContentCache(memoryLimitMB) ──► Indexer.init() (load token postings from SQLite)
│
▼
IpcServer.start() ──► bind deterministic port
│
├── port owned by another live codetree → retry (200/400/800/1200ms) → exit if duplicate
├── port acquired → owner mode
└── port already ours → shared mode (skip scan + watcher)
│
▼ (owner mode only)
indexer.fullScan() ──► walk project, indexFile() per file (incremental)
│
▼
FileWatcher.start() ──► chokidar + .git/HEAD watcher + safetyNetRescan timer
│
▼
Server.connect(StdioServerTransport()) ──► ready for AI host requestsFlow 4 — File change → index update
Editor saves file → chokidar 'change' event → debounce 300ms
│
▼
Indexer.indexFile(absPath)
│
┌────────────────────────┴────────────────────────┐
│ │
size+mtime same size or mtime changed
│ │
▼ ▼
short-circuit: skip extract read file → looksBinary?
(reseat content_cache if evicted) │
┌────────────────────┴────────────────────┐
│ │
binary text
│ │
▼ ▼
delete from index hash → ExtractorRegistry
│
▼
upsert files / symbols / deps
save trigrams + token postings
save previous content (for diffs)
update LRU + content_cacheFlow 5 — codetree init setup
codetree init
│
▼
findGitRepos(cwd) ──► for each repo:
1. createDefaultConfig → .codetreerc.json
2. updateGitignore → adds .codetree/
3. installHook → .claude/settings.json
4. installMcpServer → .mcp.json (Claude)
5. installClaudeMd → CLAUDE.md
6. installClaudeRules → .claude/rules/codetree.md
7. installCursorMcp → .cursor/mcp.json
8. installCursorRules → .cursor/rules/codetree.mdc
9. installAntigravityMcp → .antigravity/mcp.json
10. installCodexMcp → .codex/config.toml
11. installAgentsMd → AGENTS.md (block)
12. installGeminiMd → GEMINI.mdAll install steps are idempotent — re-running codetree init only refreshes generated blocks.
MCP Tools Reference
All ten tools are available on every MCP host (Claude Code, Cursor, Codex, Antigravity, …).
| Tool | Replaces / complements | What it does |
|------|------------------------|--------------|
| codetree_read | Read | Cached read; supports outline_only, expected_hash (returns diff or "unchanged"), session_id short-circuit, focus_symbol narrowing, offset/limit pagination. Mtime-validated. |
| codetree_outline | Read (lite) | File metadata + exported symbols + imports. No body. Cheapest browse primitive. |
| codetree_probe | Grep (lite) | Returns only { file_path, line_numbers[] } per match. No snippets. Use for "does X mention Y?" triage. |
| codetree_search | Grep | Symbol-name search + optional content search via token postings. Pagination via next_cursor. file_pattern narrows before scanning. |
| codetree_find_refs | — | Cross-file references for a symbol; uses postings to narrow candidates. |
| codetree_structure | Glob | Project file tree. format='text' ≈ 40% fewer tokens than JSON. path/depth/pattern narrow. Auto-collapses huge repos. |
| codetree_summary | — | Whole-project overview: language breakdown, top files by symbol count, total estimated tokens. Use at session start. |
| codetree_memory | — | Save / recall insights across sessions. get_context is aggressively trimmed. |
| codetree_edit | Edit | Edit without prior Read. \r\n-safe on Windows. Updates index in place. |
| codetree_write | Write | Write file without prior Read. Creates parent dirs. Updates index. |
Token-saving knobs (selected)
| Tool | Knob | Default | Effect |
|------|------|---------|--------|
| codetree_read | outline_only | false | Symbols + metadata only |
| codetree_read | exported_symbols_only | true | Drop private symbols |
| codetree_read | expected_hash | — | Unchanged ⇒ no body; changed ⇒ compact diff |
| codetree_read | session_id | — | "Already-read this session" short-circuit |
| codetree_read | focus_symbol | — | Return only the named function / class body + context |
| codetree_search | file_pattern | — | Narrow candidate pool before the scan cap |
| codetree_structure | format='text' | 'json' | Compact indented tree |
CLI Commands
codetree init # Configure CodeTree + Claude + Cursor + Antigravity + Codex in every git repo under cwd
codetree status # Per-project: running? indexed file count? ready?
codetree stats # Aggregated cache hit rates + estimated tokens / cost saved
codetree reindex # Drop .codetree/index.db (rebuilds on next session)
codetree doctor # Verify MCP paths, hook scripts, IPC port (run after Node/npm changes)
codetree help # Show helpcodetree init discovers all .git directories under the current working directory and configures each one independently.
Configuration
Edit .codetreerc.json in your project root (a default is created by codetree init):
{
"projectRoot": ".",
"ignore": [".git", "node_modules", "dist", "build", "coverage", ".next", "__pycache__", ".cache", ".turbo", ".vercel", ".output"],
"ignoreBinary": true,
"maxFileSize": 1048576,
"cache": {
"memoryLimitMB": 256,
"dbPath": ".codetree/index.db",
"contentCacheMaxMB": 512
},
"ipc": { "port": 0, "host": "127.0.0.1" },
"watch": { "enabled": true, "debounceMs": 300, "usePolling": false },
"symbols": {
"enabled": true,
"languages": ["javascript", "typescript", "python", "java", "go", "rust", "ruby", "csharp"]
},
"telemetry": { "enabled": true, "statsFile": ".codetree/stats.json" },
"tokenSave": {
"read": {
"includeSymbolsByDefault": true,
"maxSymbols": 40,
"exportedSymbolsOnlyByDefault": true,
"unchangedShortCircuit": true,
"outlineFallbackBytes": 81920
},
"search": { "defaultLimit": 20, "snippetMaxChars": 120 },
"structure": { "compactText": false },
"sessionStart": { "maxBytes": 2048 }
}
}The schema is enforced via zod in src/config.ts — invalid values fall back to defaults rather than crashing the server.
Repository Layout
codetree/
├── src/
│ ├── cli.ts # `codetree` CLI entry point
│ ├── config.ts # zod schema + project-root discovery
│ ├── doctor.ts # `codetree doctor` checks
│ ├── diff.ts # compact diff format for codetree_read
│ ├── index.ts # programmatic API exports
│ ├── utils.ts # findGitRepos, findIpcPort, formatNumber
│ │
│ ├── server/
│ │ ├── mcp-server.ts # MCP stdio entry; lifecycle + shutdown
│ │ ├── ipc.ts # HTTP IPC server (singleton per project)
│ │ └── tools/
│ │ ├── codetree-read.ts
│ │ ├── codetree-outline.ts
│ │ ├── codetree-probe.ts
│ │ ├── codetree-search.ts
│ │ ├── codetree-find-refs.ts
│ │ ├── codetree-structure.ts
│ │ ├── codetree-summary.ts
│ │ ├── codetree-memory.ts
│ │ ├── codetree-edit.ts
│ │ └── codetree-write.ts
│ │
│ ├── indexer/
│ │ ├── indexer.ts # the orchestrator
│ │ ├── watcher.ts # chokidar + .git/HEAD + safety-net rescan
│ │ ├── hasher.ts # xxhash content + quick (size,mtime) hash
│ │ ├── ignore.ts # gitignore-compatible ignore filter
│ │ ├── extractor-registry.ts # routes file → language extractor
│ │ └── extractors/ # JS/TS, Python, Java, Go, Rust, Ruby, C#, generic
│ │
│ ├── storage/
│ │ ├── database.ts # sql.js wrapper + schema + migrations
│ │ ├── cache.ts # in-memory LRU
│ │ └── disk-manager.ts # disk-budget management
│ │
│ ├── hook/
│ │ ├── pre-tool-use.ts # bundled standalone (esbuild)
│ │ ├── post-tool-use.ts
│ │ ├── session-start.ts
│ │ ├── pre-compact.ts
│ │ ├── post-compact.ts
│ │ └── hook-client.ts # tiny HTTP client → IPC server
│ │
│ └── setup/
│ ├── install.ts # all install* functions used by `codetree init`
│ └── template-load.ts # reads templates/ and copies into project
│
├── templates/ # bundled with npm package; copied by init
│ ├── claude-md-codetree.snippet.md
│ ├── claude-rules-codetree.md
│ ├── cursor-codetree.mdc
│ ├── agents-codetree.snippet.md
│ ├── gemini-codetree.snippet.md
│ └── README.md
│
├── vscode-extension/ # standalone published extension
│ ├── src/
│ │ ├── extension.ts # activation, commands, MCP mirror
│ │ ├── dashboard.ts # webview dashboard (live token savings)
│ │ └── tree-views.ts # sidebar trees (stats / files / symbols)
│ ├── media/ # icons
│ └── package.json # publisher: rishuni30
│
├── scripts/
│ ├── bundle-hook.js # esbuild bundle for hook/*.ts
│ └── postinstall.js
│
├── docs/
│ ├── INSTALL_PATH_AND_IGNORE_ISSUES.md
│ └── INTEGRATION_CURSOR_ANTIGRAVITY_CODEX.md
│
├── test/
│ ├── diff.test.ts
│ ├── extractors.test.ts
│ ├── utils.test.ts
│ └── integration.mjs # end-to-end against a live server
│
├── dist/ # build output (gitignored — but shipped on npm)
├── .codetree/ # local index DB + stats (gitignored)
├── .codetreerc.json # project config
├── .codetreerc.default.json # template default
├── .mcp.json # Claude Code MCP entry for THIS repo
├── .gitignore # node_modules/, dist/, .codetree/, *.tsbuildinfo
├── package.json # name: codetree-claude
├── tsconfig.json
├── CHANGELOG.md
├── LICENSE # MIT
└── README.md # this fileHow codetree init Wires Each IDE
| IDE | MCP config | Rules / instructions |
|-----|------------|----------------------|
| Claude Code | Root .mcp.json | CLAUDE.md + .claude/rules/codetree.md + hooks in .claude/settings.json |
| Cursor | .cursor/mcp.json | .cursor/rules/codetree.mdc (alwaysApply) |
| OpenAI Codex | .codex/config.toml ([mcp_servers.codetree]) | AGENTS.md (also: trust the project in Codex if required) |
| Google Antigravity | .antigravity/mcp.json | AGENTS.md (full table) + GEMINI.md (Gemini pointer) |
All written files are safe to commit (the team setup) except .codetree/ itself which is gitignored. Re-running codetree init only refreshes the CodeTree-managed blocks.
Reliability & Cross-Platform Notes
Never stale
- External edits — periodic mtime revalidation on read; immediate re-index on mismatch
- Branch switches —
.git/HEADwatcher invalidates relevant cache - Missed watcher events — safety-net rescan every 10 min (1 min after startup)
Cross-platform
- Windows
\r\n—codetree_editnormalizes for matching, preserves style on write - Paths — forward slashes in MCP JSON for portability across OSs
- Shutdown —
SIGINT/SIGTERM/SIGHUP+transport.onclose+stdin.end/stdin.close(Windows doesn't reliably deliver SIGTERM to Node child processes)
Safe at scale
- Large files —
outlineFallbackBytes(default 80 KB) auto-degrades to outline mode rather than truncating mid-byte; per-call slice capped at 5000 lines - Large repos — structure view paginates via
next_cursor; auto-collapses on huge trees - Hooks (Claude) — redirect only when CodeTree can serve; otherwise native tools run
- Graceful degradation — non-critical failures wrapped so core reads do not break
- Single owner — duplicate processes detect via deterministic IPC port and exit cleanly to avoid SQLite write-lock contention
Telemetry
Per-tool counters available on IPC /stats:
readHits / readMisses searchHits / searchMisses globHits / globMisses
readTokensSaved searchTokensSaved globTokensSaved
sessionTokensSaved mcpTokensSaved mcpCalls
scanCacheHits / scanCacheMisses scanHitRate scanDiskReadBytes
hitRate (= cacheHits / (cacheHits + cacheMisses)) avgTokensPerHitA persistently low scanHitRate on a large repo is a clear signal that cache.contentCacheMaxMB is too small.
Development
Prerequisites
- Node.js ≥ 20
- npm (or pnpm / yarn — npm is what CI uses)
Setup
git clone <your-repo-url> codetree
cd codetree
npm install
npm run buildnpm run build runs tsc and then scripts/bundle-hook.js (esbuild) to produce standalone bundles for dist/hook/*.js.
Useful scripts
| Script | What it does |
|--------|--------------|
| npm run build | Full build: TypeScript compile + hook bundling |
| npm run build:hook | Re-bundle hook scripts only |
| npm run dev | tsc --watch |
| npm run lint | tsc --noEmit (no separate ESLint dependency) |
| npm test | Vitest run |
| npm run test:watch | Vitest watch mode |
| npm start | Run the MCP server directly (node dist/server/mcp-server.js) |
Local install for testing
npm pack
npm install -g ./codetree-claude-*.tgz
cd /path/to/test-project
codetree init
codetree doctorTesting
Unit tests (Vitest)
npm testCovers:
test/diff.test.ts— diff formattertest/extractors.test.ts— symbol + import extraction per languagetest/utils.test.ts— utility helpers
Integration tests
node test/integration.mjsSpins up a real MCP server against a test fixture and asserts:
/statsshape and arithmetic invariants (cacheHits == readHits + searchHits + globHits)- Phantom-hit regression (a
/checkagainst a path with no DB record correctly counts as a miss) /check-searchand/check-globtoken-saving credits- Cold-cache disk fallback for multi-word queries
scanStatsreachability- File-pattern narrowing on probe and content search
Contributing
- Fork and create a feature branch off
main. - Install dependencies (
npm install) and build (npm run build). - Add tests under
test/for any behavior change. - Run
npm run lint && npm test && node test/integration.mjs. - Update
CHANGELOG.mdunder a new## [x.y.z] - YYYY-MM-DDentry. - Open a PR with a clear description of the user-visible change and a reference to the symptom you observed (CHANGELOG entries on this project lead with the symptom — see
CHANGELOG.mdfor examples).
Coding conventions
- TypeScript strict mode; no
anycasts in committed code - Forward-slash paths in any string written to disk
- Tool handlers are pure functions of
(config, indexer, db)— easy to unit-test - Failures in non-critical paths are wrapped (
try { … } catch { /* non-critical */ }) so a corrupted index row never breaks the whole server
License
MIT © CodeTree contributors
Acknowledgements
Built on top of:
@modelcontextprotocol/sdk— MCP transportsql.js— SQLite in WebAssembly (no native build)chokidar— cross-platform file watchingxxhash-wasm— fast content hashingzod— config schemalru-cache— in-memory cacheesbuild— hook bundlingvitest— tests
