Latest release View changelog →

Quick Start

cargo install cartog
cd your-project
cartog init                  # 1. scaffold .cartog.toml
cartog index                 # 2. build the graph (~95ms for 4k LOC)

cartog search validate       # find symbols by name (sub-ms)
cartog refs validate_token   # who calls/imports/references this?
cartog impact validate_token # what breaks if I change this?

# Optional — wire cartog into your editor for AI agents
cartog ide                   # Cursor, VS Code, Claude Desktop, Codex CLI, ...

Two commands to start: init writes .cartog.toml, index builds the graph. Add cartog ide only if you want MCP wired into an editor — CLI users can skip it.

Add semantic search (optional, still fully local)

cartog rag setup             # download models (~230MB, one-time)
cartog rag index .           # embed all symbols + documents
cartog rag search "authentication token validation"

Indexes both code (functions, classes, methods) and Markdown documents (READMEs, design docs, API docs). Models are downloaded once to ~/.cache/cartog/models/ and run locally via ONNX Runtime. No API keys needed.

Using Ollama instead? Add this to your .cartog.toml and skip rag setup:

[embedding]
provider = "ollama"

See Embedding Providers for full setup.

Search Modes

Cartog offers two search modes that complement each other:

Query type Command Speed Best for
Symbol name cartog search parse sub-ms You know the name
Natural language cartog rag search "error handling" ~100-400ms You know the behavior
Broad / unsure Run both in parallel sub-ms + ~300ms Catch names + semantics

Recommended Workflow

cartog index .          # 1. build the graph
cartog search foo       # 2. discover exact symbol names
cartog refs foo         # 3. find all usages
cartog callees foo      # 4. see what it depends on
cartog impact foo       # 5. assess blast radius
cartog index .          # 6. re-index after changes

Configuration

Cartog works with zero configuration: every setting has a default, and cartog init writes a .cartog.toml with all sections commented out. Uncomment only the keys you want to override; don't copy the whole file. Run cartog config to print the active (merged) configuration.

SectionDefaultOverride when…
[database].cartog/db.sqlite (git-root relative)you want the DB elsewhere
[embedding]provider = "local" (ONNX, CPU)using Ollama / a different model — see Embedding Providers
[reranker]provider = "local", model = "jinaai/jina-reranker-v1-turbo-en" (default)picking a different cross-encoder, or disabling rerank ("none")
[rag]tuned retrieval/rerank poolstuning hybrid-search recall vs. cost
[remote]unset (no sync)sharing an index over S3 — see Remote Storage
[security]redact_secrets = trueopting out of pattern redaction — see Secret Redaction
[lsp.<lang>]unset (server resolved from PATH)running a custom/Dockerized LSP server — see LSP Server Overrides

Example — override just the DB path and switch to Ollama, leaving everything else at its default:

# .cartog.toml — only the keys you change; the rest stay default
[database]
path = "~/.local/share/cartog/myproject.db"

[embedding]
provider = "ollama"              # default is "local"

Database path resolution priority: --db flag / CARTOG_DB env > .cartog.toml > git root auto-detection > cwd fallback.

Environment variables

Runtime overrides (per-machine / per-invocation), in addition to .cartog.toml:

VariableDefaultEffect
CARTOG_DBauto-detectDatabase path (same as --db).
CARTOG_ONNX_THREADSall coresCaps ONNX CPU threads for rag index + reranking. Overrides [embedding.local] intra_threads; 1 forces single-core.
CARTOG_WATCH_RAGunsetForce watcher auto-embed; overrides [embedding] auto_embed and --rag:
1 = force on
0 = force off
unset = auto-detect from the DB
CARTOG_SINGLE_WRITER10 disables MCP single-writer election (every serve opens read-write).
CARTOG_MCP_MAX_BYTES65536Max bytes per MCP tool response before truncation.
CARTOG_NO_UPDATE_CHECKunsetSet to skip the background self-update check.
CARTOG_UPDATE_CHECKunsetForce an update check regardless of cadence.
CARTOG_INSTALL_DIR~/.local/binInstall location used by install.sh.
CARTOG_VERSIONlatestPin the version install.sh fetches.
Example .cartog.toml — every section, with defaults annotated (read it, don't copy it wholesale)
# Every key below shows its DEFAULT value. Delete any line you don't
# override — cartog applies these defaults whether the file exists or not.

[database]
path = ".cartog/db.sqlite"        # DEFAULT (relative to git root)

[embedding]
provider = "local"               # DEFAULT — "local" (ONNX), "ollama", or "openai"
# model = "BAAI/bge-small-en-v1.5" # top-level for ALL providers; unset → built-in bge-small (local) / text-embedding-3-small (openai)
# dimension = 384                # auto-detected for built-in models
# auto_embed = true              # watcher auto-embed: unset → auto-detect (embed if repo already has embeddings); true/false force. Precedence: CARTOG_WATCH_RAG > this key > --rag

[embedding.local]                # only used when provider = "local"
# intra_threads = 4              # cap ONNX CPU threads (DEFAULT: all cores); CARTOG_ONNX_THREADS overrides

[embedding.ollama]               # only used when provider = "ollama"
base_url = "http://localhost:11434"
model = "nomic-embed-text"

[embedding.openai]               # only used when provider = "openai"; set model under [embedding]
base_url    = "https://api.openai.com/v1"  # or http://localhost:11434/v1 (Ollama), LM Studio, vLLM, ...
api_key_env = "OPENAI_API_KEY"   # env var NAME, not the key; unset = keyless local endpoint

[reranker]
provider = "local"               # DEFAULT — "local" or "none"

[rag]
retrieval_multiplier = 3         # DEFAULT — over-retrieve N× before fusion
rerank_max = 50                  # DEFAULT — cap candidates sent to reranker

[security]
redact_secrets = true            # DEFAULT — scrub secrets from the index

[remote]                         # UNSET by default (no remote sync)
url        = "s3://my-team-bucket/cartog/main"
# region   = "us-east-1"         # optional when endpoint is set
# endpoint = "https://minio.example.com"  # MinIO / R2 / floci
# path_style = true              # set true for most non-AWS endpoints
# Credentials are NEVER read from this file — see "Remote Storage (S3)".

Embedding Providers

Semantic search requires an embedding provider. Three options (all compiled into the default build; local ONNX stays the runtime default):

ProviderConfigSetupNotes
local (default) No config needed cartog rag setup ONNX Runtime via fastembed. ~230MB model download. Runs on CPU.
ollama [embedding]
provider = "ollama"
ollama pull nomic-embed-text HTTP API. GPU acceleration via Ollama. No model download by cartog.
openai [embedding]
provider = "openai"
Reachable OpenAI-compatible /v1 endpoint; API key in an env var One generic client for OpenAI, Mistral, Voyage, Jina, OVHcloud, and local /v1 servers (Ollama, LM Studio, vLLM) — switch vendors by changing base_url. API key read from the api_key_env env var, never stored in .cartog.toml; unset = keyless local. Dimension auto-detected. (Azure OpenAI's deployment-path shape is not supported.)

Default models (local provider)

RoleConfig valueHuggingFace repo (downloaded)DimSize
Embedding BAAI/bge-small-en-v1.5 Qdrant/bge-small-en-v1.5-onnx-Q (ONNX-quantized) 384 ~80MB
Reranker jinaai/jina-reranker-v1-turbo-en (default) jinaai/jina-reranker-v1-turbo-en ~150MB

The embedding config value is the fastembed model code you set under [embedding] model; cartog downloads the matching ONNX-quantized repo from HuggingFace into the shared model cache ($FASTEMBED_CACHE_DIR, else $XDG_CACHE_HOME/cartog/models, else ~/.cache/cartog/models). English-only — non-English identifiers and comments get degraded embeddings. Override the embedding model with any fastembed built-in via [embedding] model = "...".

The reranker is configurable the same way via [reranker] model (a fastembed reranker repo path). The default jina-reranker-v1-turbo-en is small, fast, and scores higher on BEIR than the older BAAI/bge-reranker-base (~1.1GB), which you can opt back into. Switching needs no re-index — the reranker runs at query time and isn't persisted.

Example: Ollama with GPU acceleration

# 1. Install cartog (Ollama provider is included by default)
cargo install cartog

# 2. Start Ollama and pull a model
ollama serve
ollama pull nomic-embed-text

# 3. Configure .cartog.toml
cat > .cartog.toml <<EOF
[embedding]
provider = "ollama"
model = "nomic-embed-text"

[embedding.ollama]
base_url = "http://localhost:11434"
EOF

# 4. Index and search — no rag setup needed
cartog index .
cartog rag index .
cartog rag search "authentication flow"

Example: Local with a different model

# Use a larger model for better quality
cat > .cartog.toml <<EOF
[embedding]
provider = "local"
model = "BAAI/bge-base-en-v1.5"
EOF

cartog rag setup     # downloads the configured model
cartog rag index .
cartog rag search "error handling"

Example: Disable re-ranking

# Skip the ~150MB reranker model download
cat > .cartog.toml <<EOF
[reranker]
provider = "none"
EOF

cartog rag setup     # only downloads embedding model (~80MB)

Remote Storage (S3)

A pre-built index can be shared across a team or CI instead of being re-indexed everywhere. cartog push uploads the local SQLite index to an S3-compatible bucket; cartog pull downloads it. This is opt-in: configure a [remote] section (or pass --remote), and the binary must be built with the default remote-s3 feature.

KeyDefaultMeaning
urlrequireds3://bucket/key target. --remote on push/pull overrides it.
regionAWS-resolvedAWS region (e.g. us-east-1). Optional when endpoint is set.
endpointAWS S3Custom endpoint for S3-compatible stores (MinIO, Cloudflare R2, floci).
path_stylefalseForce path-style addressing. Set true for most non-AWS endpoints.

Credentials never live in .cartog.toml. They are resolved exclusively from the standard AWS environment chain (env vars, ~/.aws profile, or IMDS). Any credential-shaped key in [remote] (access_key, secret_key, credentials, token, aws_*, …) is rejected at parse time so a secret can't be committed by mistake.

Example: AWS S3

# 1. Point .cartog.toml at your bucket
cat >> .cartog.toml <<EOF
[remote]
url    = "s3://my-team-bucket/cartog/main"
region = "us-east-1"
EOF

# 2. Provide credentials via the AWS environment (never in the toml)
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
# or: export AWS_PROFILE=my-profile

# 3. Build the index and push it
cartog index .
cartog push                       # uploads to [remote].url

# On another machine / in CI: fetch the prebuilt index
cartog pull                       # downloads, refuses if schema drifted

Example: MinIO / Cloudflare R2 / floci

# S3-compatible stores need an explicit endpoint + path-style addressing
cat >> .cartog.toml <<EOF
[remote]
url        = "s3://my-bucket/cartog/main"
endpoint   = "https://minio.example.com"
path_style = true
EOF

export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...

cartog push                       # or: cartog push --remote s3://other/key

Run cartog doctor to verify the remote: it does a HEAD against the configured URL and reports whether the bucket is reachable with the resolved credentials.

Secret Redaction

Cartog scrubs your code before it is stored or embedded, so secrets don't leak into the index, semantic search, or MCP responses. Two layers, both on by default:

  • Pattern redaction — common secret shapes (AWS / GitHub / Slack / Stripe keys, JWTs, and quoted key=value assignments) are stripped from symbol_content, signatures, docstrings, and embeddings. Toggle with [security] redact_secrets.
  • Sensitive-file deny-list — files like .env, *.pem, *.key, id_rsa are never indexed at all. This is always enforced, regardless of the redact_secrets flag.

Redaction is best-effort, not a guarantee. Changing redact_secrets forces a full re-index so the stored content matches the new policy.

Example: redaction settings

# Default: redaction ON — nothing to configure.

# Opt out of pattern redaction (the file deny-list still applies):
cat >> .cartog.toml <<EOF
[security]
redact_secrets = false
EOF

cartog index .                    # toggling redact_secrets re-indexes everything

LSP Server Overrides

By default cartog resolves each language's LSP server from PATH (rust-analyzer, gopls, …). [lsp.<lang>] overrides that with an explicit command — most usefully a Dockerized server, so cartog can resolve edges on a host without the language's native toolchain installed.

  • command is the full argv; command[0] is the executable (on PATH or an absolute path), the rest are its arguments.
  • ${ROOT} in any element expands to the indexed project root (host-absolute).
  • Path mirroring is required. cartog exchanges file:// URIs built from the host path, so a container must see the repo at the same path — hence -v ${ROOT}:${ROOT} -w ${ROOT}. A differing container path makes every definition resolve as "external" and is unsupported.
  • The override applies only to the keyed cartog language, which must be one cartog already supports. The server's stderr is logged to ${TMPDIR}/cartog-lsp/<language>.log.

Example: Dockerized Dart LSP

cat >> .cartog.toml <<EOF
[lsp.dart]
command = ["docker", "run", "--rm", "-i",
           "-v", "${ROOT}:${ROOT}", "-w", "${ROOT}", "cartog-lsp-dart:stable"]
EOF

cartog index --force .            # server spawns during the LSP resolution pass

cartog rag setup

Download embedding and re-ranker models from HuggingFace. Run once before using semantic search with the local provider.

cartog rag setup

Downloads ~230MB of ONNX models (embedding Qdrant/bge-small-en-v1.5-onnx-Q ~80MB + reranker jinaai/jina-reranker-v1-turbo-en ~150MB). Cached in ~/.cache/cartog/models/. Pinning [reranker] model fetches that reranker instead.

Note: When using Ollama, skip this — models are managed by the Ollama server.

# To also skip the reranker download:
[reranker]
provider = "none"

cartog rag index

Build the embedding index for semantic search. Indexes both code symbols and Markdown documents (.md files). Requires cartog index first. For the local provider, also requires cartog rag setup.

cartog rag index              # embed all symbols + documents
cartog rag index src/         # embed subdirectory
cartog rag index --force      # re-embed all

Example output

Embedded 697 symbols in 3.1s (224/s)
RAG index ready: 697 vectors, 384 dimensions, sqlite-vec

Semantic search — use natural language to find code by behavior or concept. Returns code only by default; use --kind document for docs or --kind all for both.

cartog rag search "validate authentication tokens"
cartog rag search "error handling" --kind function
cartog rag search "database connection" --limit 5
cartog rag search "deployment architecture" --kind document

Example output

Found 1 results (FTS: 1, vector: 0, merged: 1)

1. function auth_middleware  middleware/auth_mw.py:15-37  [fts5] score=0.0164
    def auth_middleware(request: Dict[str, Any]) -> Dict[str, Any]:
        """Verify authentication token and attach user context."""
        validate_request(request)

Combines keyword (BM25/FTS5) and vector similarity, merged via RRF, then re-ranked by a cross-encoder. By default, returns code only; use --kind document for docs or --kind all for both.

Available --kind values: function, class, method, variable, import, interface, enum, type-alias, trait, module, document, all.

Agent Skill

Install cartog as an agent skill for Claude Code, Cursor, Copilot, and other compatible agents:

npx skills add jrollin/cartog

Or install manually:

cp -r skills/cartog ~/.claude/skills/

The skill teaches your AI agent when and how to use cartog — including search routing, refactoring workflows, and fallback heuristics.

Claude Plugin

For Claude Code users, the simplest setup. Run each command separately:

/plugin marketplace add jrollin/cartog
/plugin install cartog@cartog-plugins

MCP Server

cartog serve runs cartog as a Model Context Protocol server over stdio, exposing the code graph as 16 tools any MCP-compatible client (Claude Code, Cursor, VS Code, Codex CLI, …) can call. No HTTP port, no daemon to manage — the client launches cartog serve as a subprocess and talks JSON-RPC over stdin/stdout.

cartog serve                  # MCP server only
cartog serve --watch          # + watcher; auto-embeds if the repo already has embeddings
cartog serve --watch --rag    # force auto-embed even on a not-yet-embedded repo

Most users never run cartog serve by hand: cartog ide writes it into your editor's MCP config and the editor spawns it on demand. Run it directly only when wiring a client cartog doesn't know about, or to debug the JSON-RPC stream.

The 16 tools

13 are read-only (safe to run without an approval prompt); 2 mutate the index and 1 arms a deferred self-update. Each ships with a description telling the agent when to reach for it.

ToolKindDescription
cartog_indexwriteBuild/update the code graph
cartog_mapreadCodebase orientation: file list + top symbols (call first in a new repo)
cartog_contextreadOne-shot task bundle: relevant symbols + bodies for a task
cartog_searchreadFind symbols by partial name
cartog_outlinereadFile structure (symbols, line ranges)
cartog_refsreadAll references to a symbol
cartog_calleesreadWhat a symbol calls
cartog_impactreadTransitive impact analysis
cartog_tracereadShortest call path between two symbols, bodies inline
cartog_hierarchyreadInheritance tree
cartog_depsreadFile-level imports
cartog_statsreadIndex summary
cartog_changesreadSymbols affected by recent git changes
cartog_rag_indexwriteBuild embedding index
cartog_rag_searchreadSemantic search
cartog_updatewriteArm a deferred self-update

Tool metadata & output

Every tool ships with MCP annotations (a human-readable title and behavior hints), and the 13 read-only tools (plus cartog_update) declare an outputSchema. Clients use these to label tools in their picker and to skip approval prompts for tools that can't change anything.

FieldRead tools (search, refs, trace, context, map, …)Write tools (cartog_index, cartog_rag_index, cartog_update)
titlee.g. "Search symbols", "Find references"e.g. "Index codebase"
readOnlyHinttrue — client can run without an approval promptfalse — mutates state (cartog_update writes the machine-level update file, not the index)
openWorldHintfalse — closed domain (the local index)false
outputSchemapresent — typed result schema

tools/list entry for cartog_search

{
  "name": "cartog_search",
  "annotations": {
    "title": "Search symbols",
    "readOnlyHint": true,
    "openWorldHint": false
  },
  "outputSchema": {
    "type": "object",
    "properties": { "results": { "type": "array", "items": { "$ref": "#/$defs/Symbol" } } },
    "required": ["results"]
  }
}

Read tools return structured content: the typed result (matching outputSchema) is mirrored in structuredContent alongside the human-readable text block, so schema-aware clients get validated, machine-readable data without re-parsing text.

tools/call result for cartog_search (query "validate")

{
  "content": [{ "type": "text", "text": "[ … pretty-printed JSON … ]" }],
  "structuredContent": {
    "results": [
      {
        "name": "validate",
        "kind": "function",
        "file_path": "api/v1/auth.py",
        "start_line": 16,
        "end_line": 22
      }
    ]
  }
}

The size cap (CARTOG_MCP_MAX_BYTES, default 64 KB) counts the text block plus the structured copy: structuredContent is dropped when the combined size would exceed the cap, so an oversized payload can't blow the caller's context window. The two index-write tools (cartog_index, cartog_rag_index) have no outputSchema and report readOnlyHint: false with destructiveHint: false (they only add to the index).

Progress notifications: cartog_index and cartog_rag_index emit standard MCP notifications/progress when the client includes a progressToken in the request's _meta. cartog_index emits 3 phase events (walking, parsing N files, storing N files) plus an optional fourth (resolving with LSP) when the LSP pass runs. cartog_rag_index emits preparing, one embedding processed/total per ~512-symbol batch, then storing. The message field is human-readable, not a contract. Clients that don't supply a progressToken see no notifications.

Wiring cartog into editors: cartog ide

cartog ide is the only verb that writes MCP configs. Run it once per machine, plus any time you install a new editor.

cartog ide                          # all installed clients, all scopes
cartog ide --scope project          # .mcp.json, .cursor/, .vscode/
cartog ide --scope user             # user-scope clients only
cartog ide --client cursor          # one client
cartog ide --dry-run                # preview with before/after diff

Clients: claude-code (project + user), claude-desktop, codex, cursor, gemini, opencode, vscode (project + user), windsurf, zed, antigravity, kiro (project + user), hermes. User-scope clients whose config directory is missing are skipped (treated as "not installed"). Codex stores all servers in one global ~/.codex/config.toml, so cartog writes a per-project [mcp_servers.cartog-<slug>-<hash8>] section. Idempotent: existing servers in each file are preserved.

Known limitation: configs containing JSON-with-comments (JSONC) are left untouched and reported as skipped. Configure those clients manually using the snippets below.

Bootstrap sequence

Two commands to start; the third is optional.

cartog init                  # 1. scaffold .cartog.toml (config only)
cartog index                 # 2. build the code graph

cartog ide                   # optional — wire MCP into installed editors

Edit .cartog.toml between steps 1 and 2 to change DB path or embedding provider before any heavy work runs. CLI-only users stop after step 2; run cartog ide only if you want cartog wired into MCP-aware editors.

Manual setup (per client)

Each block below shows the cartog ide one-liner that writes the right file, plus the manual JSON / TOML if you prefer to edit by hand.

Claude Code — project .mcp.json + user ~/.claude/settings.json
cartog ide --client claude-code
# or:
claude mcp add cartog -- cartog serve --watch                       # user scope
claude mcp add --scope project cartog -- cartog serve --watch       # project scope

Manual:

{
  "mcpServers": {
    "cartog": { "command": "cartog", "args": ["serve", "--watch"] }
  }
}

Every client gets --watch by default. The single-writer election makes concurrent watchers safe — the first serve owns the watcher, the rest attach read-only and ride its updates over WAL. Drop it with cartog ide --no-watch.

Cursor — project .cursor/mcp.json
cartog ide --client cursor

Manual:

{
  "mcpServers": {
    "cartog": { "command": "cartog", "args": ["serve"] }
  }
}
Codex CLI — user-global ~/.codex/config.toml
cartog ide --client codex

Codex stores all MCP servers in this one user-global file (no per-project config), so cartog ide writes a per-project [mcp_servers.cartog-<slug>-<hash8>] section.

[mcp_servers.cartog]
command = "cartog"
args = ["serve"]
Windsurf~/.codeium/windsurf/mcp_config.json
cartog ide --client windsurf
{
  "mcpServers": {
    "cartog": { "command": "cartog", "args": ["serve"] }
  }
}
VS Code (Copilot) — project .vscode/mcp.json + user Code/User/mcp.json
cartog ide --client vscode

Writes both scopes: project .vscode/mcp.json and the user file under VS Code's config dir (~/Library/Application Support/Code/User/mcp.json on macOS, ~/.config/Code/User/mcp.json on Linux, %APPDATA%\Code\User\mcp.json on Windows). Top-level key is servers, not mcpServers.

{
  "servers": {
    "cartog": { "type": "stdio", "command": "cartog", "args": ["serve"] }
  }
}
Zed~/.config/zed/settings.json
cartog ide --client zed
{
  "context_servers": {
    "cartog": { "command": "cartog", "args": ["serve"] }
  }
}
OpenCode~/.config/opencode/opencode.json
cartog ide --client opencode
{
  "mcp": {
    "cartog": {
      "type": "local",
      "command": ["cartog", "serve"],
      "enabled": true
    }
  }
}
Gemini CLI~/.gemini/settings.json
cartog ide --client gemini
{
  "mcpServers": {
    "cartog": { "command": "cartog", "args": ["serve"] }
  }
}
Claude Desktopclaude_desktop_config.json
cartog ide --client claude-desktop

macOS: ~/Library/Application Support/Claude/. Windows: %APPDATA%\Claude\. Restart Claude Desktop after editing.

{
  "mcpServers": {
    "cartog": { "command": "cartog", "args": ["serve"] }
  }
}
Antigravity~/.gemini/config/mcp_config.json
cartog ide --client antigravity

User-global only, shared by Antigravity 2.0, IDE and CLI (no per-project config).

{
  "mcpServers": {
    "cartog": { "command": "cartog", "args": ["serve"] }
  }
}
Kiro.kiro/settings/mcp.json or ~/.kiro/settings/mcp.json
cartog ide --client kiro

Workspace config takes precedence over user config.

{
  "mcpServers": {
    "cartog": { "command": "cartog", "args": ["serve"] }
  }
}
Hermes Agent~/.hermes/config.yaml
cartog ide --client hermes

YAML under the mcp_servers key; only the cartog entry is upserted.

mcp_servers:
  cartog:
    command: cartog
    args:
      - serve
Any other MCP client

Point your client at cartog serve over stdio. Command: cartog, args: ["serve"].

Teach your agent to use cartog

Wiring MCP is half the job. The other half is telling the LLM when to prefer cartog over grep + read. Drop the snippet from docs/agent-snippet.md into your project's AGENTS.md, CLAUDE.md, .cursor/rules/, or equivalent, and the agent will route navigation questions through cartog's 16 MCP tools instead of flooding context with raw text.

Maps the natural-language question to the right tool:

  • "where is X defined?" → cartog_search
  • "who calls / imports / inherits X?" → cartog_refs
  • "what does X call?" → cartog_callees
  • "what breaks if I change X?" → cartog_impact
  • "find code about <concept>" → cartog_rag_search
  • "orient me in this repo" → cartog_map

Falls back to grep / Read for string literals, comments, config values, or anything outside the indexed root.

cartog index <path>

Build or update the code graph. Run this first, then again after code changes.

cartog index .              # index current directory
cartog index src/           # index a subdirectory
cartog index . --force      # full re-index
cartog index . --no-lsp     # heuristic-only (~1-4s)

Example output

Indexed 70 files (0 skipped, 0 removed)
  697 symbols, 1921 edges (1019 resolved)
  1 files in unsupported languages not indexed (1 .toml)

Incremental by default — skips unchanged files (git + SHA-256), and within changed files, uses Merkle-tree diffing to update only modified symbols. The LSP pass skips edges already classified as unresolvable (typo, dyn dispatch, macro) or external (stdlib, deps, node_modules); both auto-retry when a matching symbol is added in-tree. --force resets those markers for a clean retry.

Files in unsupported languages are reported, not silently dropped: on a mixed repo the summary adds a line like 12 files in unsupported languages not indexed (8 .dart, 4 .swift) (also as files_unsupported / unsupported_by_ext under --json).

Find symbols by partial name. Results ranked: exact match → prefix → substring.

cartog search validate                   # prefix + substring match
cartog search validate --kind function   # functions only
cartog search config --file src/db.rs    # scoped to one file
cartog search parse --limit 5            # cap results

Example output

function  validate           api/v1/auth.py:16
function  validate_token     auth/tokens.py:38
function  validate_request   utils/helpers.py:16
function  validate_login     validators/user.py:41
function  validate_email     validators/common.py:15
method    _validate_payment  services/payment/processor.py:122
document  Validate           README.md:8

Available --kind values: function, class, method, variable, import, interface, enum, type-alias, trait, module, document.

cartog outline <file>

Show all symbols in a file with types, signatures, and line ranges. Use this instead of reading a file when you need structure.

cartog outline auth/tokens.py

Example output

from datetime import datetime, timedelta  L3
from typing import Optional  L4
from models.user import User  L7
from config import SECRET_KEY, TOKEN_EXPIRY  L9
class TokenError  L12-15
class ExpiredTokenError  L18-21
function generate_token(user: User, expires_in: int = TOKEN_EXPIRY) -> str  L30-35
function validate_token(token: str) -> Optional[User]  L38-52
function refresh_token(old_token: str) -> str  L60-64
function revoke_token(token: str) -> bool  L67-73

cartog refs <name>

All references to a symbol — calls, imports, inherits, type references.

cartog refs UserService                  # all reference types
cartog refs validate_token --kind calls  # only call sites

Example output (cartog refs validate_token)

imports  ..auth.tokens     middleware/auth_mw.py:7
calls    auth_middleware   middleware/auth_mw.py:28
imports  auth.tokens       auth/service.py:5
calls    get_current_user  auth/service.py:44
calls    change_password   auth/service.py:52
imports  .tokens           auth/__init__.py:4
calls    refresh_token     auth/tokens.py:62

Available --kind values: calls, imports, inherits, references, raises, implements, type-of.

cartog callees <name>

Find what a function calls — answers "what does this depend on?".

cartog callees validate_token

Example output

lookup_session    auth/tokens.py:45
TokenError        auth/tokens.py:47
datetime.utcnow   auth/tokens.py:49
ExpiredTokenError auth/tokens.py:50

cartog impact <name>

Transitive impact analysis — follows the caller chain up to N hops (default 3).

cartog impact validate_token --depth 3

Example output (indentation shows hop depth)

  imports  middleware/auth_mw.py:7
  calls    auth_middleware           middleware/auth_mw.py:28
  imports  auth/service.py:5
  calls    AuthService.get_current_user  auth/service.py:44
  calls    AuthService.change_password   auth/service.py:52
  calls    refresh_token             auth/tokens.py:62
    calls  auth_required.wrapper     auth/middleware.py:21
    calls  get_current_user          auth/middleware.py:61

Answers "what breaks if I change this?".

cartog trace <from> <to> [--depth N]

Find the shortest call path between two symbols, with each hop's body inline — answers "how does A reach B?". Follows only statically-resolved calls edges (dynamic dispatch is not traced); default depth 8.

cartog trace handle_request validate_token

Example output

handle_request → authenticate  api/routes.py:31
    def handle_request(req):
        return authenticate(req.user, req.token)
authenticate → validate_token  auth/service.py:18
    def authenticate(user, token):
        return validate_token(token)

cartog context <task> [--tokens N]

Build a one-shot context bundle for a task: semantic-search seeds, their 1-hop call neighbors, and high-centrality definitions in the same files — bodies inline, trimmed to a token budget (default 6000). The single call an agent makes to scope a task.

cartog context "validate an auth token" --tokens 6000

Example output

Context for 'validate an auth token' (5 symbols, ~520 tokens)

[Seed] function validate_token  auth/tokens.py:10
    def validate_token(token: str) -> bool:
        ...
[Neighbor] function generate_token  auth/tokens.py:20
[Central] class AuthService  auth/service.py:1

cartog hierarchy <class> [--mermaid]

Show inheritance relationships — both parents and children. Add --mermaid for a graph TD diagram you can paste into a PR description, doc, or mermaid.live.

cartog hierarchy AuthService
cartog hierarchy AuthService --mermaid

Example output

AuthService -> BaseService
AdminService -> AuthService

With --mermaid

graph TD
    AuthService["AuthService"] --> BaseService["BaseService"]
    AdminService["AdminService"] --> AuthService["AuthService"]

Rendered

graph TD
    AuthService["AuthService"] --> BaseService["BaseService"]
    AdminService["AdminService"] --> AuthService["AuthService"]

cartog deps <file> [--mermaid]

List symbols imported by a file — answers "what does this file depend on?". Add --mermaid for a graph LR diagram rooted at the file.

cartog deps auth/service.py
cartog deps auth/service.py --mermaid

Example output

Optional         L3
validate_token   L5
generate_token   L5
revoke_token     L5
User             L6
get_logger       L7

With --mermaid

graph LR
    auth_service_py["auth/service.py"]
    auth_service_py --> validate_token["validate_token (L5)"]
    auth_service_py --> generate_token["generate_token (L5)"]
    auth_service_py --> User["User (L6)"]

Rendered

graph LR
    auth_service_py["auth/service.py"]
    auth_service_py --> validate_token["validate_token (L5)"]
    auth_service_py --> generate_token["generate_token (L5)"]
    auth_service_py --> User["User (L6)"]

cartog stats [--savings]

Summary of the index — file count, symbol count, edge resolution rate. On an unindexed repo all counts are 0 and the output ends with a hint to run cartog index .. Pass --savings to show per-tool query counts and estimated tokens saved instead (or use the cartog savings alias).

cartog stats
cartog stats --savings

Example output

Files:    70
Symbols:  697
Edges:    1921 (1019 resolved)
Languages:
  python: 69 files
  markdown: 1 files
Symbols by kind:
  import: 256
  method: 217
  function: 90
  variable: 80
  class: 52

cartog savings

Shows tokens used by cartog vs an equivalent grep+read flow, plus the saved delta and a per-tool call-count breakdown. Visible alias of cartog stats --savings; shipped as a top-level verb so the retention hook is one keystroke away.

cartog savings
cartog --json savings

Example output

cartog · my-project · 5 queries

████████░░  ~83% tokens saved

Without cartog    ~8.5k tokens   (~1700 / query)
With cartog       ~1.4k tokens   (~280 / query)
──────────────────────────────────────────────
Saved             ~7.1k tokens   (~1420 / query)

By tool (call counts):
     2  search
     1  impact
     1  map
     1  refs

Baseline: ~1700 tokens for an equivalent grep+read sweep vs cartog's ~280.
Measured across 13 benchmark scenarios (see crates/cartog/benches/queries.rs).

The header reads cartog · <project> · <N> queries. By tool lists call counts — every tool uses the same flat baseline (1,420 tokens saved per call), so the breakdown shows which navigation patterns you rely on, not which tool saved the most.

Only queries returning a non-empty result count toward the totals. Empty-index calls, typo'd symbol names, and outline against missing files are skipped so the figure reflects real work.

Data comes from a local query_log table inside .cartog/db.sqlite. Nothing leaves the machine; no query payloads are recorded — only the tool name, call surface (cli / mcp), and a timestamp. Secondary read-only MCP attaches can't write, so multi-MCP-server setups under-report secondary traffic rather than double-counting it.

cartog doctor

Validate that all requirements are met and everything is working. Checks git repo, config, database, embedding provider, and reranker.

cartog doctor                 # check all requirements
cartog --json doctor          # structured JSON output

Example output

  [+] git: git repository at /your/repo
  [+] config: loaded from /your/repo/.cartog.toml
  [+] database: 70 files, 697 symbols at .cartog/db.sqlite
  [+] embedding: local model cached
  [+] reranker: jinaai/jina-reranker-v1-turbo-en cached
  [+] remote: not configured (local-only)

All 6 checks passed

Each check reports OK, Warn, or Error. Exits with code 1 if any check is an error. Run this when commands fail unexpectedly or after first setup.

cartog map

Token-budget-aware codebase summary — file tree + top symbols ranked by reference count. Add --mermaid for a graph TD rooted at "Repo"; the token budget still applies (renderer stops adding nodes before it overflows).

cartog map                    # default 4000 tokens
cartog map --tokens 2000      # compact
cartog map --tokens 8000      # detailed
cartog map --mermaid          # paste-into-PR diagram

Example output (truncated)

# Codebase Map (70 files)

  README.md
  api/v1/auth.py
  api/v1/payments.py
  api/v2/auth.py
  app.py
  auth/middleware.py
  auth/service.py
  auth/tokens.py
  ...

With --mermaid (truncated)

graph TD
    repo["Repo"]
    repo --> README_md["README.md"]
    repo --> auth_service_py["auth/service.py"]
    repo --> auth_tokens_py["auth/tokens.py"]
    auth_tokens_py --> auth_tokens_py__validate_token["validate_token (function)"]
    auth_tokens_py --> auth_tokens_py__generate_token["generate_token (function)"]
    ...

Rendered

graph TD
    repo["Repo"]
    repo --> README_md["README.md"]
    repo --> auth_service_py["auth/service.py"]
    repo --> auth_tokens_py["auth/tokens.py"]
    auth_tokens_py --> auth_tokens_py__validate_token["validate_token (function)"]
    auth_tokens_py --> auth_tokens_py__generate_token["generate_token (function)"]

--json wins over --mermaid when both are passed.

cartog changes

Show symbols affected by recent git changes.

cartog changes                    # last 5 commits + working tree
cartog changes --commits 10       # last 10 commits
cartog changes --kind function    # only functions

Example output

20 files changed in last 5 commits, 2 symbols affected

README.md:
  document webapp_py# webapp_py       L1-7
  document Validate## Validate        L8-13

cartog config

Print the resolved configuration (merged defaults, .cartog.toml, and env overrides). Useful for verifying [rag] tuning, watch debounce, and provider selection.

cartog config              # human-readable
cartog config --json       # JSON for scripts

Example output (truncated)

Config file: /your/repo/.cartog.toml
Database:    /your/repo/.cartog/db.sqlite

[embedding]
  provider:          local (default)
  model:             -
  dimension:         -

[embedding.ollama]
  base_url:          http://localhost:11434 (default)
  model:             nomic-embed-text (default)
  ...

cartog watch

Watch for file changes and auto-re-index. Keeps the graph fresh during development.

cartog watch                       # watch CWD; auto-embeds if the repo already has embeddings
cartog watch src/                  # watch subdirectory
cartog watch --rag                 # force auto-embed (even on a not-yet-embedded repo)
cartog watch --rag --rag-delay 60  # embed after 60s idle
cartog watch --debounce 10         # 10s debounce window
cartog watch --json                # NDJSON event stream

Refuses to start if another watch holds the lock for this database — fail-fast prevents two writers stomping each other:

Error: another cartog process holds the watch lock for this database
       (slot watch-a902bb89f9a6b484, PID 64311); stop it before running `cartog watch`

cartog serve

Start cartog as an MCP server over stdio. No banner — stdio is reserved for JSON-RPC traffic between the client and the server. Run cartog stats in another shell to confirm it's up; role: primary means the writer, role: read-only means an attached secondary. See MCP Server for the full tool list and client wiring.

cartog serve                  # MCP server only
cartog serve --watch          # + watcher; auto-embeds if the repo has embeddings
cartog serve --watch --rag    # force auto-embed even on a not-yet-embedded repo

cartog init

Scaffold a .cartog.toml template in the current project. That's all it does — config only, no editor wiring, no indexing. The next-steps hint points at cartog ide and cartog index.

cartog init                  # scaffold .cartog.toml
cartog init --dry-run        # preview without writing

Example output (already-initialized project, --dry-run)

+ .cartog.toml (/your/repo/.cartog.toml): already present, left untouched

Dry run only. Re-run without --dry-run to apply.

Never overwrites an existing .cartog.toml. Re-running is a no-op (still prints the next-steps hint).

cartog ide / cartog install

Wire cartog serve into one or all MCP-compatible editors. Two shapes:

  • cartog install <client>... — positional, brew/npm/pip convention, always non-interactive. Safe for scripts and agents.
  • cartog ide — original, supports an interactive multi-select picker (bare) and the long-form flags below.
cartog install cursor                 # one editor
cartog install cursor vscode codex    # several at once
cartog install                        # all detected editors
cartog install cursor --dry-run       # preview

cartog ide                            # interactive picker (TTY only)
cartog ide --client cursor            # single client via flag
cartog ide --scope project            # only project-scoped (.mcp.json, .cursor/, .vscode/)
cartog ide --dry-run                  # preview with before/after diff
cartog ide --no-watch                 # drop --watch from Claude Code args

Example output (cartog ide --client cursor --dry-run)

+ cursor (project, /your/repo/.cursor/mcp.json): would create
  --- after ---
    {
      "mcpServers": {
        "cartog": {
          "command": "cartog",
          "args": ["serve"]
        }
      }
    }

1 clients: 1 created, 0 updated, 0 unchanged, 0 skipped, 0 errors
Dry run only. Re-run without --dry-run to apply.

Example output (cartog install cursor vscode --dry-run)

+ cursor (project, /your/repo/.cursor/mcp.json): would create
  --- after ---
    {
      "mcpServers": {
        "cartog": { "command": "cartog", "args": ["serve"] }
      }
    }
+ vscode (project, /your/repo/.vscode/mcp.json): would create
  --- after ---
    {
      "servers": {
        "cartog": { "type": "stdio", "command": "cartog", "args": ["serve"] }
      }
    }

2 clients: 2 created, 0 updated, 0 unchanged, 0 skipped, 0 errors
Dry run only. Re-run without --dry-run to apply.

Clients: claude-code (project + user), claude-desktop, codex, cursor, gemini, opencode, vscode, windsurf, zed, antigravity, kiro (project + user), hermes. See the Bootstrap sequence for the recommended verb order.

cartog self <update|version|rollback|migrate-db>

Manage the installed cartog binary in place: upgrade, inspect, or roll back. cargo install cartog users get an exit-3 refusal pointing at cargo install cartog --force; release-tarball installs swap atomically with SHA256 verification and a <bin>.old rollback slot.

cartog self update              # upgrade to the latest stable
cartog self update --check      # exit 1 if outdated, 0 if current
cartog self version             # target + install source + last check
cartog self rollback            # restore the previous binary
cartog self migrate-db          # move legacy .cartog.db into .cartog/db.sqlite
cartog self migrate-db --dry-run # preview the move without touching disk

Example output (cartog self version)

cartog 0.18.0
  target:            aarch64-apple-darwin
  install source:    release-tarball
  last update check: 2026-05-27T14:50:47Z

Auto-check runs at most once per 24h on interactive sessions; suppressed under CARTOG_NO_UPDATE_CHECK=1, non-TTY stdout, and inside cartog serve/watch. migrate-db refuses to overwrite an existing .cartog/db.sqlite, refuses to run while a peer process holds the lock for this project's database (a peer on an unrelated project does not block it), and refuses symlinked sources. See docs/updates.md for the full exit-code matrix and state file location.

cartog completions <shell>

Generate shell completion scripts for bash, zsh, fish, powershell, or elvish.

cartog completions zsh   > ~/.zfunc/_cartog
cartog completions bash  > /usr/local/etc/bash_completion.d/cartog
cartog completions fish  > ~/.config/fish/completions/cartog.fish

Example output (first lines of cartog completions zsh)

#compdef cartog

autoload -U is-at-least

_cartog() {
  ...

cartog manpage

Emit a roff-formatted man page on stdout. Intended for packagers and distro maintainers.

The output filename must end in .1 (the section-1 extension man looks for) and the target directory must exist:

sudo mkdir -p /usr/local/share/man/man1
cartog manpage | sudo tee /usr/local/share/man/man1/cartog.1 > /dev/null
man cartog

Example output (first lines)

.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH cartog 1  "cartog 0.18.0"
.SH NAME
cartog \- Map your codebase. Navigate by graph, not grep.
  ...

JSON Output

All commands accept --json for structured output:

cartog --json refs validate_token
cartog refs validate_token --json    # equivalent
cartog --json stats

Token Budget

All query commands accept --tokens N to limit output:

cartog --tokens 500 search validate
cartog --tokens 200 outline src/db.rs

Uses len / 4 byte-to-token approximation. Ignored when --json is used.

Supported Languages

LanguageExtensionsSymbolsEdges
Python.py, .pyifunctions, classes, methods, imports, variablescalls, imports, inherits, raises, type refs
TypeScript.ts, .tsxfunctions, classes, methods, imports, variablescalls, imports, inherits, type refs, new
JavaScript.js, .jsx, .mjs, .cjsfunctions, classes, methods, imports, variablescalls, imports, inherits, new
Rust.rsfunctions, structs, traits, impls, importscalls, imports, inherits, type refs
Go.gofunctions, structs, interfaces, importscalls, imports, type refs
Ruby.rbfunctions, classes, modules, importscalls, imports, inherits, raises, rescue types
Java.javaclasses, interfaces, enums, methods, importscalls, imports, inherits, raises, type refs, new
PHP.phpclasses, interfaces, traits, enums, methods, functions, importscalls, imports, inherits, implements, references (traits, new)
Dart.dartclasses, mixins, extensions, enums, methods, functions, typedefs, importscalls, imports, inherits, implements, type refs
Swift.swiftclasses, structs, actors, protocols, enums, extensions, methods, functions, typealiasescalls, imports, inherits, implements, type refs
Kotlin.kt, .ktsclasses, data/sealed classes, interfaces, enums, objects, methods, functions, typealiasescalls, imports, inherits, implements, type refs
Markdown.mddocument sections (chunked by heading)

Performance

Indexing: 69 files / 4k LOC in 95ms (Python fixture, release build).

Query typeLatency
outline8-14 μs
hierarchy8-9 μs
deps25 μs
stats32 μs
search81-102 μs
callees177-180 μs
refs258-471 μs
impact (depth 3)2.7-17 ms

Edge Resolution: Heuristic vs LSP

ProjectLanguageHeuristicWith LSPTime
TS microservice (230 files)TypeScript37%81%13s
Vue.js SPA (739 files)Vue/TS/JS31%72%25s
Rust CLI (358 files)Rust25%44%72s
PHP webapp fixture (25 files)PHP82%84%22s

Troubleshooting

The most common issues and their quick fixes. The full list lives in docs/troubleshooting.md.

SymptomFix
"not initialized" / no resultsRun cartog init then cartog index . in the repo first.
cartog index seems to hangA cold index of a large repo takes a few seconds; re-run with RUST_LOG=info cartog index . if nothing happens after 60s.
MCP "Connection closed" on a 2nd editor windowExpected: single-writer election makes the 2nd instance read-only (14 of 16 tools). Ensure cartog --version is ≥ 0.17 and CARTOG_SINGLE_WRITER is unset.
.cartog.toml is ignoredCartog walks up to the git root; with no .git, put it in the cwd or pass --db. cartog config prints the resolved paths.
Missing symbols / low recallWait for the watcher (or run cartog index); check the file's language is supported and not .gitignored. A language server on PATH lifts edge resolution from ~25% to up to ~81%.
Anything elseRun cartog doctor — it checks git, config, database, and model status.