Metadata-Version: 2.3
Name: delver-codebase-mcp
Version: 0.1.2
Summary: Local-first code intelligence MCP server and CLI
Author: Nitin
License: MIT
Requires-Dist: fastmcp>=2.0
Requires-Dist: tree-sitter>=0.23
Requires-Dist: tree-sitter-language-pack>=0.5
Requires-Dist: networkx>=3.0
Requires-Dist: grep-ast>=0.4
Requires-Dist: fastembed>=0.8.0
Requires-Dist: numpy>=1.26.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# delver - Local-First Code Intelligence MCP Server and CLI

`delver` is a local-first code-intelligence library, CLI, and MCP server for AI agents and developers.

It parses your codebase with tree-sitter, stores symbols and relationships in a local SQLite graph (WAL + FTS5), resolves cross-file call/type/import edges with a deterministic 3-tier algorithm, ranks repository structure with PageRank, and exposes everything to agents over MCP stdio plus a human-usable CLI.

Same tree + same code = byte-identical index and tool output. No LLM calls anywhere in the pipeline. No summaries. Only what the AST actually says.

Version: 0.1.2. Python 3.12+. Managed by `uv`. Entry point: `delver = "delver.cli:main"`.

---

## Table of contents

1. What this does
2. Why it stands out
3. Requirements
4. Install
5. Uninstall
6. Commands reference (with examples)
7. MCP tools reference (with examples)
8. Supported languages
9. How it works (for contributors)
10. Development and tests
11. Troubleshooting
12. License

---

## 1. What this does

Typical flow:

```
delver init            # index once
delver explore "..."   # ask about code, get source grouped by file + call path
delver node <symbol>   # symbol details, caller/callee trail
delver callers ...     # who calls it, with file:line
delver install         # wire the MCP server into Claude / OpenCode / Cursor
```

What you get:

* **A real symbol graph, not grep.** Functions, methods, classes, structs, interfaces, traits, enums, type aliases, fields, properties, variables, constants, imports, components, routes. Edges: contains, calls, imports, exports, extends, implements, references, type_of, returns, instantiates, overrides, decorates.
* **Cross-file resolution.** Import-based resolution first (most precise), then same-file definitions, then a conservative global fallback. Chained `receiver.method()` calls resolve via known receiver types with a supertype walk (depth 5). When nothing distinguishes candidates, the reference stays unresolved. A wrong edge is treated as worse than no edge.
* **Agent-first outputs.** `delver_explore` is the primary tool: one call returns verbatim line-numbered source of the relevant symbols grouped by file plus the call path among them. `delver_node` reads one file or one symbol. The rest (`search`, `callers`, `callees`, `impact`, `repo_map`, `files`, `status`) answer narrow questions without extra reads.
* **Token-budget aware.** Adaptive output budgets by repo size (13K/18K/24K total caps), per-file caps, skeletonization (signatures-only for off-spine files), container outlines instead of concatenated method bodies, overload packing, and a line-folded repo map that fits `max_tokens`.
* **Repo map.** PageRank over the definition/reference graph with focus-file and mentioned-identifier boosts, rendered as a folded tree (kept lines with a vertical-bar marker, collapsed regions as an ellipsis marker) with 100-char line truncation.
* **Hybrid search.** FTS5 exact search by default. Optional local embedding re-rank of the FTS5 top-50 via FastEmbed. The model is never auto-downloaded; without it you get pure FTS5 plus a one-line note.
* **CLI parity.** Every graph query available over MCP is also available as `delver <subcommand>` for humans and scripts.

Recoverable conditions ("project not indexed", "symbol not found", "file not in index") return success-shaped text with guidance. Only real malfunctions surface as errors.

---

## 2. Why it stands out

Strengths only (no head-to-head claims). Each point is implemented behavior, not marketing:

* **Local-first and private.** The index lives outside your repo under `~/.delver/indexed-db/` (Windows: `%USERPROFILE%\.delver\indexed-db\`). Your repo stays clean. No code leaves the machine for indexing. No API keys.
* **Deterministic, AST-derived.** Extraction comes from tree-sitter grammars, not LLM summarization. Re-indexing unchanged code gives the same node IDs (`<language>:<relpath>:<kind>:<qualified_name>`) and the same output.
* **Multi-language with exact extractors.** 12 language dictionaries plus tsx/jsx aliases plus HTML, plus yaml/properties key-only leaves. Each has explicit AST node-type mappings and behavioral hooks (method classification, receiver types, visibility, async/static/const, import/package extraction, C/C++/C# pre-parse blanking for macros and directives).
* **Resolution with confidence, not guessing.** Import-resolved symbols short-circuit at 0.90-0.95 confidence. Qualified names, same-file preference, function-as-value rules, proximity narrowing, and word-overlap fallback each have fixed confidence values. Ambiguous names (50+ candidates) stay unresolved.
* **Method-on-type works.** Return types and declared variable types are normalized into the index so `obj.method()` binds to `Type::method`, including inherited methods via extends/implements edges.
* **Budget engine prevents context blowups.** Larger repos get more calls (1/2/3/4/5 by file count), never a bigger single response. Responses are cut at file-section boundaries under the cap and never tell the agent to use Read/Grep; they steer to another `delver_explore` or `delver_node` and label returned source as already read.
* **Secret-safe by default.** Config/data leaves (yaml/properties) are summarized by key only. Values are never stored in the index and never printed.
* **Safe transport and storage.** Pure stdio via FastMCP. Zero HTTP servers, zero ports, zero SSE, zero daemons, zero file watchers. Staleness is on-demand mtime+size delta sync before every query.
* **Surgical installer.** Auto-detects Claude Code, OpenCode, and Cursor. Writes only the `delver` entry, preserves sibling servers, comments, and formatting (including JSONC comments/trailing commas for OpenCode). Re-runs are byte-equal no-ops (`unchanged`). Uninstall reverses install exactly and prunes emptied wrappers/files/dirs.
* **Operational safety.** Filesystem-root and home-directory guard on index/destructive commands. Absolute binary paths in installed MCP entries. WAL mode check with a warning when the filesystem silently refuses WAL. Corrupt JSON configs are backed up before rewrite.

---

## 3. Requirements

* Python 3.12 or newer.
* `uv` for install and runs.
* Supported OS: Windows, Linux/WSL, macOS.
* Disk locations used (all outside indexed repos):
  * Index DB: `~/.delver/indexed-db/<hash>_<slug>.db` (Windows: `%USERPROFILE%\.delver\indexed-db\...`), plus `-wal`/`-shm` sidecars.
  * Embedding models: `~/.delver/models/`.
  * Tree-sitter grammar cache: `~/.delver/ts-cache/`.

Dependencies (from `pyproject.toml`): `fastmcp`, `tree-sitter`, `tree-sitter-language-pack`, `networkx`, `grep-ast`, `fastembed`, `numpy`. Dev group: `pytest`.

---

## 4. Install

### 4.1 Install the package

From the repo root:

```bash
uv sync
```

This creates the venv and installs `delver` with the `delver` console script.

Verify:

```bash
uv run delver --help
```

You should see all subcommands: `init index sync status uninit explore node callers callees impact repo-map files search install uninstall serve`.

### 4.2 Index a project

```bash
cd /path/to/your-project
uv run delver init
```

Or from anywhere:

```bash
uv run delver init /path/to/your-project
```

What happens:

1. Scans the project root, skipping `.git/`, `node_modules/`, `.venv/`/`venv/`, `dist/`/`build/`/`target/`/`__pycache__/`, `.delver/`, lockfiles, binaries, minified files, and files over 1 MB.
2. Parses each recognized file with tree-sitter, inserts nodes/edges/imports, collects unresolved refs.
3. Runs the 3-tier + chained-call resolution pass.
4. Prints a tree summary: files indexed, node count, edge count, elapsed time.

`init` is idempotent. Re-running prints "Already initialized" with a hint to use `index` or `sync`.

Other lifecycle commands:

```bash
uv run delver status
uv run delver sync
uv run delver index
```

* `status` shows files/nodes/edges, DB size, journal mode, pending unresolved refs, last sync time, and the DB path.
* `sync` does an mtime/size delta sync only and prints added/updated/removed counts (or "Already up to date").
* `index` forces a full re-extract of every file, then resolves.

All project paths are optional and default to the current directory. Global `--path` also works:

```bash
uv run delver --path /path/to/your-project status
```

Safety: `init`, `index`, `sync`, and `uninit` refuse filesystem roots (`/` , `C:\`) and the bare user home directory with exit code 2.

### 4.3 Install the MCP server into your agents

Zero flags = detected agents + global scope (project repos stay clean):

```bash
uv run delver install
```

Explicit selection:

```bash
uv run delver install --agents claude,opencode,cursor --location global
uv run delver install --agents claude --location local
uv run delver install --local
uv run delver install --agents claude --auto-allow
```

Flags:

* `--agents`: comma-separated subset of `claude,opencode,cursor`. Default: all detected agents. Non-interactive.
* `--location`: `global` (default) or `local`. `--local` is shorthand for `--location local`.
* `--auto-allow`: Claude only. Appends `mcp__delver__*` allow entries to `settings.json`.

Where entries go:

* **Claude Code:**
  * Global: `~/.claude.json` under `mcpServers.delver = {type: stdio, command: <abs path>, args: [serve]}`.
  * Local: `<root>/.mcp.json` (the only project-level file Claude Code reads; stale legacy entries are migrated out on install and uninstall).
  * Permissions (opt-in): `~/.claude/settings.json` (global) or `<root>/.claude/settings.json` (local).
* **OpenCode:**
  * Config dir is `$XDG_CONFIG_HOME/opencode` if set, else `~/.config/opencode` on every platform. Local: project root.
  * File selection: existing `opencode.jsonc` wins, else existing `opencode.json`, else create `.jsonc`.
  * Entry under `mcp` (not `mcpServers`): `{mcp: {delver: {type: local, command: [<abs path>, serve], enabled: true}}}` with `$schema` seeded for new files. Comments and trailing commas survive round-trips. Legacy `%APPDATA%/opencode` entries are swept on global install/uninstall.
* **Cursor:**
  * Global: `~/.cursor/mcp.json`. Local: `<root>/.cursor/mcp.json`. Same `mcpServers` shape as Claude plus `--path` injection (Cursor launches with the wrong cwd and no root URI): local installs get the absolute project path, global installs get `${workspaceFolder}`. Final args: `["serve", "--path", <pathArg>]`. Restart Cursor after changes.

Report vocabulary per file: `created`, `updated`, `unchanged` (byte-identical re-run, file untouched), `removed`, `not-found`.

After install, the server runs over stdio. Manual check:

```bash
uv run delver serve
```

Send a JSON-RPC `initialize` message over stdin to smoke-test; expect a JSON-RPC initialize response and no port binds.

### 4.4 Enable semantic re-rank (optional, one-time)

Pure FTS5 is the default. To enable hybrid embedding re-rank:

```bash
uv run delver search "login session" --semantic --download
```

This downloads the embedding model (default `bge-small-en-v1.5`) into `~/.delver/models/` and exits after printing the path (or continues with the query if one was given). Later runs:

```bash
uv run delver search "login session" --semantic
```

Without a downloaded model, `--semantic` degrades to pure FTS5 with a one-line success-shaped note. It never auto-downloads.

---

## 5. Uninstall

Reverse order: remove agent configs first, then optionally delete the index.

Remove MCP entries:

```bash
uv run delver uninstall
```

* Without `--agents`, uninstall sweeps all three targets (reports `not-found` harmlessly) so it covers agents that are no longer detectable but still have config entries.
* With `--agents`, only that subset is touched: `uv run delver uninstall --agents cursor --location global`.
* `--location` / `--local` select scope the same way as install.
* Every write is paired: injected keys removed, emptied wrappers (`mcpServers`/`mcp`) pruned, empty files deleted, empty config dirs pruned. Sibling servers and unrelated keys are never touched.

Delete the index for a project:

```bash
uv run delver uninit
uv run delver uninit -y
uv run delver unindex -y /path/to/project
```

* Prompts `[!] This will permanently delete the Delver index for <root>. Continue? (y/N)` unless `-y`/`--yes` is given. `unindex` is an alias.
* Deletes every DB file/sidecar for the project by hash-prefix glob (covers both current hybrid names and legacy hash names) plus strips the `delver` entry from local agent configs in that project.
* Prints `[OK] Removed Delver index for <path>`, or a warning when nothing was found (idempotent, exit 0).
* Other projects' DBs are untouched.

Full removal from a machine: run `uninstall` (global scope) for each project you installed, run `uninit` per indexed root, then delete the package checkout. The only leftovers would be `~/.delver/` caches you may delete manually.

---

## 6. Commands reference (with examples)

Global: `delver [--path <root>] <command> [args] [project]`. The trailing `project` positional overrides `--path`; both default to `.` (cwd).

### Lifecycle

| Command | What it does | Example |
|---|---|---|
| `init [path]` | Create DB, run full index + resolution, print files/nodes/edges | `delver init` / `delver init ./my-app` |
| `index [path]` | Force full re-extract of every file, then resolve | `delver index` |
| `sync [path]` | mtime delta sync only; prints added/updated/removed | `delver sync` |
| `status [path]` | Files/nodes/edges, DB size, journal mode, pending refs, last sync, DB path | `delver status` |
| `uninit [path] [-y]` (alias `unindex`) | Delete index DB + sidecars + local config entries | `delver uninit -y` |

Examples:

```bash
delver init
# T  Initializing Delver
# *  Scanning files -- 411 found
# *  Parsing code -- done
# *  Resolving references -- done
# -  Done (with bullet: Indexed 411 files (... nodes, ... edges in ...s))

delver status
# Project: D:\projects\my-app
# Files: 411  Nodes: 10023  Edges: 18979  Unresolved (pending): 0
# DB: C:\Users\you\.delver\indexed-db\d2b9342d_D_projects_my-app.db (... bytes)
# Journal mode: wal
# Last sync: ...

delver sync
# T  Syncing Delver ... Already up to date (0 files modified)

delver uninit -y
# [OK] Removed Delver index for D:\projects\my-app
```

### Query

| Command | What it does | Example |
|---|---|---|
| `explore <query...>` | Primary. Whole-tail query. Prints budget-capped markdown with source grouped by file + flow/blast-radius | `delver explore "sync delta indexer"` |
| `node <symbol> [--code]` | Symbol details (signature, location, caller/callee trail). `--code` adds full body | `delver node sync_delta` / `delver node UserService --code` |
| `search <query> [--kind] [--limit] [--semantic] [--download]` | Quick symbol name lookup, locations only | `delver search login --kind function --limit 10` |
| `callers <symbol>` | Caller list with file:line | `delver callers sync_delta` |
| `callees <symbol>` | Callee list with file:line | `delver callees loginUser` |
| `impact <symbol> [--depth N]` | Reverse-edge BFS blast radius | `delver impact AuthService --depth 2` |
| `repo-map [--tokens N] [--focus F...]` | PageRank-ranked folded repo map | `delver repo-map --tokens 1024 --focus src/auth.ts` |
| `files [--pattern G] [--subpath D] [--format tree\|flat\|grouped]` | Indexed file tree with language + symbol counts | `delver files --pattern "src/**/*.ts" --format tree` |

Notes:

* `explore` takes the whole tail as the query (free-text allowed). Parse happens before argparse, so quote multi-word queries: `delver explore "AuthService loginUser session-manager"`.
* `node` defaults to signature + docstring + trail (no body). Containers (class/interface/struct/enum) with `--code` return a structural outline (member names + signatures + line numbers), never concatenated bodies. Ambiguous names return every matching definition packed under a char budget (12000 chars, hard cap 16 rendered, 20 listed).
* File mode note: `delver_node` file view renders `<n><tab><line>` (cat -n style), default 2000 lines, 38000 char budget, with pagination note and a one-line blast-radius header (`used by N files: ...`).
* `search --kind` accepts: `function, method, class, interface, type, variable, route, component`.
* `repo-map --tokens` defaults to 1024. `--focus` boosts the files you are already working with.

### Setup

| Command | What it does | Example |
|---|---|---|
| `install [--agents ...] [--location ...] [--local] [--auto-allow]` | Detect agents, write MCP configs | `delver install --agents claude,opencode --location global` |
| `uninstall [--agents ...] [--location ...] [--local]` | Reverse install exactly | `delver uninstall --location local` |
| `serve` | Start FastMCP stdio server | `delver serve` |

---

## 7. MCP tools reference (with examples)

All tools accept optional `projectPath` to query a second indexed project. All are read-only. Every read runs the mtime delta sync first.

| # | Tool | Role | CLI equivalent |
|---|---|---|---|
| 1 | `delver_explore` | PRIMARY. Call first for almost any question or before an edit. Bag of symbol/file names or natural language. Returns verbatim source grouped by file + call path. `maxFiles` clamped 1..20; effective default comes from the budget engine (4/5/8 by repo size) | `delver explore` |
| 2 | `delver_node` | SECONDARY. File mode (Read-equivalent with offset/limit) or symbol mode (body + caller/callee trail). `includeCode` defaults false | `delver node` |
| 3 | `delver_search` | Quick symbol lookup, locations only. Optional `kind`, `limit` (default 10), `semantic` (default false) | `delver search` |
| 4 | `delver_callers` | Who calls `<symbol>`, with file:line | `delver callers` |
| 5 | `delver_callees` | What `<symbol>` calls | `delver callees` |
| 6 | `delver_impact` | Blast radius of changing `<symbol>` (BFS over reverse edges, `depth` default 2) | `delver impact` |
| 7 | `delver_repo_map` | PageRank-ranked folded repo map. `maxTokens` default 1024, `focusFiles`, `mentionedIdents`, `forceRefresh` | `delver repo-map` |
| 8 | `delver_files` | Indexed file tree with language + symbol counts. `path`, `pattern`, `format` (tree/flat/grouped), `includeMetadata`, `maxDepth` | `delver files` |
| 9 | `delver_status` | Index health: files/nodes/edges/DB size/journal mode | `delver status` |

Agent guidance baked into outputs: explore output never tells the agent to use Read; it steers to another `delver_explore` or `delver_node` and says "treat returned source as already Read".

Minimal MCP config shapes (command is the absolute delver binary on your machine):

Claude (`~/.claude.json`):

```json
{
  "mcpServers": {
    "delver": { "type": "stdio", "command": "C:\\path\\to\\delver.EXE", "args": ["serve"] }
  }
}
```

OpenCode (`~/.config/opencode/opencode.jsonc`):

```jsonc
{
  // your comments survive install/uninstall
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "delver": { "type": "local", "command": ["C:\\path\\to\\delver.EXE", "serve"], "enabled": true }
  }
}
```

Cursor (`~/.cursor/mcp.json`, global):

```json
{
  "mcpServers": {
    "delver": { "command": "C:\\path\\to\\delver.EXE", "args": ["serve", "--path", "${workspaceFolder}"] }
  }
}
```

Prefer `delver install` over hand-editing; it resolves the absolute path, picks the right file, and keeps formatting.

---

## 8. Supported languages

| Language key | Grammar | Extensions |
|---|---|---|
| python | python | .py, .pyi |
| typescript | typescript | .ts |
| tsx | tsx | .tsx |
| javascript | javascript | .js, .mjs, .cjs |
| jsx | jsx | .jsx |
| rust | rust | .rs |
| c | c | .c, .h |
| cpp | cpp | .cc, .cpp, .cxx, .hpp, .hh, .h++, .cu, .cuh, .metal |
| java | java | .java |
| csharp | c_sharp | .cs |
| go | go | .go |
| php | php | .php |
| ruby | ruby | .rb, .erb |
| lua | lua | .lua |
| html | html | .html, .htm |
| config leaves | n/a | .yml, .yaml, .properties, .ini (key-only nodes) |

Skipped always: binaries, `.git/`, `node_modules/`, venvs, `dist/`/`build/`/`target/`, `__pycache__/`, `.delver/`, lockfiles, minified `*.min.js`, files over 1 MB.

Node kinds: `file, module, class, struct, interface, trait, protocol, function, method, property, field, variable, constant, enum, enum_member, type_alias, namespace, parameter, import, export, route, component`.

Edge kinds: `contains, calls, imports, exports, extends, implements, references, type_of, returns, instantiates, overrides, decorates`.

---

## 9. How it works (for contributors)

```
files -> ExtractionOrchestrator (py-tree-sitter) -> SQLite (nodes/edges/files)
           |
           v
     ReferenceResolver (Tier 1 imports -> Tier 2 same-file -> Tier 3 global)
           |
           v
     Graph queries (callers/callees/impact) + FTS5 search
           |
           v
     MCP tools (delver_*) + CLI + repo map
```

Package layout:

```
delver/
  cli.py               # argparse CLI
  server.py            # FastMCP instance + tool registration
  types.py             # NodeKind, EdgeKind vocabularies
  db/
    schema.sql         # DDL (nodes, edges, files, unresolved_refs, FTS, metadata)
    connection.py      # PRAGMA order, WAL check, db_path_for, sidecar cleanup
    indexer.py         # mtime delta sync engine
    queries.py         # prepared statements for tools
  extraction/
    orchestrator.py    # walk + parse + store
    extractors.py      # language dictionaries + hooks
    languages.py       # language key -> grammar + extension map
  resolution/
    __init__.py        # 3-tier resolver
    name_matcher.py
    import_resolver.py
    chained_calls.py   # receiver-type bridges
  tools/
    __init__.py        # 9 tool handlers + register_tools
    budgets.py         # get_explore_budget / get_explore_output_budget
    render.py          # node/file/folding renderers
  repomap/
    __init__.py        # PageRank + folded tree + bounded cache
  search/
    __init__.py        # hybrid FTS5 + FastEmbed re-rank
  installer/
    __init__.py        # detect/run_install/run_uninstall orchestrator
    targets/           # claude.py, opencode.py, cursor.py
  tests/               # pytest suite, one file per phase
```

Key invariants (violations are bugs):

1. Transport is pure stdio. No ports, no SSE, no daemons.
2. Stateless execution. No watchers; every query runs `sync_delta` first.
3. DB never lives inside the indexed repo.
4. MCP tools are `delver_*`. CLI is `delver <subcommand>`.
5. Determinism. Same tree = same index and output.
6. Recoverable conditions are success-shaped text, never errors.
7. Explore output never suggests Read/Grep.
8. All shipped source is ASCII. Fold glyphs are emitted at runtime via escapes.

Budgets (repo size -> per-call caps): total 13K/18K/24K/24K/24K chars, per-file 3800/3800/6500/7000/7000, gap 7/8/12/15/15 lines, header/edge caps 5/6/10/15/15. Larger tiers never get a smaller per-file cap. Larger repos get more calls (1/2/3/4/5 at <500/<5k/<15k/<25k/>=25k files), never a bigger single response.

---

## 10. Development and tests

```bash
uv sync
uv run pytest delver/tests/ -q
uv run delver --help
```

Full suite must be green before any phase is considered done. Test files map to build phases: `test_skeleton.py`, `test_db.py`, `test_extraction.py` / `test_resolution.py`, `test_tools.py`, `test_repomap.py`, `test_installer.py`.

Terminal hygiene for contributions: report exit codes plus one-line summaries; on failure show at most the relevant lines.

---

## 11. Troubleshooting

* `Project not indexed. Run: delver init` - you queried before indexing. Run `delver init [path]`, then retry.
* `Symbol not found: X` - no node with that name exists. Try `delver search X` for close matches, or check `--kind`/`--limit`.
* `No callers found for X.` - symbol exists but has no incoming calls/references edges.
* `WARNING: journal mode is '...' (expected 'wal')` - the filesystem (network mount, WSL /mnt) silently kept the prior mode. Reads still work; concurrent reads may block.
* Semantic search falls back to FTS5 with a one-line note - the model is not under `~/.delver/models/`. Run `delver search --semantic --download` once (opt-in).
* `Error: Safety check blocked operation on system root or user home` (exit 2) - run from inside a project directory or pass a specific project path.
* `Cancelled - nothing was deleted.` - you declined the `uninit` prompt. Re-run with `-y` for scripts.
* Unparseable agent JSON - the installer backs it up to `<path>.backup` before any rewrite; fix or restore the backup and re-run.
* Cursor does not see the server - restart Cursor after MCP changes take effect.

---

## 12. License

MIT. See root `pyproject.toml` (`license = { text = "MIT" }`) and the repository LICENSE if present.
