Skip to content
gativarshneyPublic

About

Local code indexing for AI coding assistants using Tree-sitter, SQLite, and the Model Context Protocol (MCP).

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

cidx

CI PyPI License: Apache-2.0 Python 3.11–3.13

A zero-config local code index for AI coding agents — with an optional, reproducible evaluation harness for anyone who wants to measure retrieval tools themselves.

Status: v1 feature-complete, building in public. Alpha releases on PyPI.

What this is

Most coding agents find code by grepping and reading files, which works, but on large repositories it burns tokens on files that never end up mattering. cidx parses a repository into a symbol and reference index (tree-sitter, SQLite), keeps it fresh as you save, and serves ranked, token-budgeted answers over MCP: where a symbol is defined, who uses it, what a file contains, what the repo looks like.

Zero-config means exactly that: no API keys, no vector database, no Docker, no cloud, no accounts. One SQLite file, stored outside your repo (%LOCALAPPDATA%\cidx on Windows, ~/.cache/cidx elsewhere). Read-only by design: the five MCP tools can only look things up, so a hostile file in a repo has no blast radius.

When it helps, and when it doesn't

cidx is not faster than grep: one rg and one cidx query over Django each take about 0.2–0.3 s. The difference is what comes back. The same question, asked both ways on Django (3,043 files; measured 2026-10-10, tokens estimated as characters ÷ 4):

Question rg -n -w over the repository cidx
Where is Model.save defined? (a common name) 1,899 lines, ~39,400 tokens; a def save search alone gives 37 candidates 1 line, ~36 tokens
Where is reverse_lazy defined? (an assignment, not a def) a def/class search finds nothing 1 line
Who uses get_object_or_404? (a unique name) 59 lines, ~1,430 tokens 15 lines, ~410 tokens, each tagged with how it resolved
Who calls Model.save? (a method called on objects) ~39,400 tokens of text matches 959 name matches, none resolved to Model.save; the answer says so

So it helps most on large repositories, common names, definitions grep can't recognize, and orienting in unfamiliar code (repo_map has no grep equivalent). It helps little for unique names or small repositories, and it is the wrong tool for free-text search. Type-aware references for method calls need a language server (LSP); cidx does not try to be one.

This compares single answers, not outcomes: whether an agent solves more tasks, or spends less, with cidx is what the evaluation harness below measures, and no paid run has been published. Commands and details: docs/verification.md.

What works today

  • Two languages: Python and TypeScript/JavaScript (including TSX/JSX), parsed error-tolerantly — half-written files still index.
  • Cold indexing honoring .gitignore via git, plus a fixed skip-list of build and dependency directories (.venv, node_modules, build, dist, …) applied even when a repo forgets to gitignore them, with size caps and stale-row reconciliation on every run.
  • Incremental updates: content-hash invalidation, one SQLite transaction per file, deletes/renames/branch switches converge; a crash can never half-write the index.
  • File watcher (cidx serve): debounced, coalescing, with a periodic reconciliation sweep as the dropped-event safety net.
  • Reference resolution with confidence tags — exact (same file), import (followed to source), name-only — recomputed deterministically so incremental and cold indexes always agree.
  • Ranking and token budgets: engineered features (match tier, kind, popularity, locality, recency) under a weighted linear scorer; responses fit ~700 tokens with truncation markers and freshness stamps; misses recommend grep (fail open).
  • Five read-only MCP tools over stdio: search_symbols, find_definition, find_references, outline_file, repo_map.
  • Proof of correctness: a property-based convergence suite (Hypothesis) asserts the incremental index equals a cold rebuild — including resolutions — across random edit/delete/rename storms, plus cidx check for users.
  • Optional evaluation harness: pinned datasets, 32 machine-checkable tasks (dev/holdout split), a scripted agent loop with a recorded-response stub model, and a scorer that recomputes every metric from raw JSONL logs alone. See benchmark/methodology.md.

Quickstart

pip install --pre cidx    # alpha release, so pip needs --pre

or run it without installing anything:

uvx cidx --version

From source, for contributors:

git clone https://github.com/gativarshney/cidx
cd cidx
pip install -e ".[dev]"

Index and query a repository:

cidx index --repo path/to/your/repo
cidx query save --repo path/to/your/repo                  # definitions (exact, then fuzzy)
cidx query save --references --repo path/to/your/repo     # usage sites with confidence
cidx query src/app.py --outline --repo path/to/your/repo  # every symbol in one file
cidx query --repo-map --repo path/to/your/repo            # popular files + top symbols
cidx check --repo path/to/your/repo                       # prove the index matches a cold rebuild
cidx stats --repo path/to/your/repo

Every command takes --json for machine-readable output.

Add cidx to any MCP-capable coding agent. cidx serve builds the index if none exists and keeps it fresh while serving; use an absolute repository path, with forward slashes on Windows:

{
  "mcpServers": {
    "cidx": { "command": "uvx", "args": ["cidx", "serve", "--repo", "/absolute/path/to/repo"] }
  }
}

(or "command": "cidx", "args": ["serve", "--repo", ...] when cidx is installed on the PATH). Client setup, what each of the five tools returns, a smoke test, and troubleshooting: docs/mcp.md.

Measured performance

Measured 2026-07-28 on a mid-range Windows 11 laptop (Python 3.13), against a synthetic 104,500-LOC corpus of 500 Python/TypeScript files; CI regression bounds guard these numbers. Per the project rules, numbers are reported as measured, never tuned to look good.

Metric Target (ARCHITECTURE.md) Measured
Cold index, ~100k LOC < 60 s 8.21 s (46,000 symbols, 23,000 refs)
Save to queryable, p95 < 150 ms 106 ms (20 edits, real watcher)
Query p95: find_definition < 50 ms 6.8 ms
Query p95: find_references < 50 ms 7.8 ms
Query p95: fuzzy search (FTS) < 50 ms 22.1 ms
Query p95: outline / repo_map < 50 ms 5.3 ms / 20.2 ms

That corpus is small. On Django (3,043 files, 76k symbols, 207k references; measured 2026-09-16 on the same laptop) the shape changes. Every run, and the commands to reproduce it, is in docs/verification.md:

Metric (Django) Measured
cidx index, empty cache 30.5 s with a warm file cache; 68–97 s cold
cidx serve from an empty cache until every file is indexed 123 s
cidx index again over the existing index 45.6 s (unfinished at 684 s before the foreign-key indexes)
Save → definitions queryable, real watcher 0.33 s for an isolated save; 1.8 s p50 back-to-back, because the whole-index re-resolution after each save takes ~1.5 s at this size
Query p95: find_definition / find_references 22.6 ms / 27.0 ms
Query p95: search_symbols (ranked) / repo_map 138.8 ms / 134.7 ms — over the 50 ms target at this scale
cidx check 24–59 s, no drift

Test suite: 277 tests (golden-file extractor tests, the property-based convergence suite, MCP-over-stdio integration tests), green on CI across {Ubuntu, macOS, Windows} × {Python 3.11, 3.12, 3.13}.

Known limitations (v1, stated on purpose)

  • TypeScript type-level declarations are not indexed: interface, type aliases, and enum have no symbol kind in the v1 schema. Value-level code (functions, classes, methods, consts, imports) is fully covered.
  • CommonJS exports are not tracked as bindings (module.exports = {...}); require(...) calls do appear as references.
  • No semantic or conceptual search — by design (ADR-007): retrieval works on names, references, and file structure, so "where is auth handled?" style questions are out of scope for v1.
  • Namespace imports are not followed. import * as core from "./core.js" binds core, so core.thing() resolves by unique global name at best, never through the import.
  • Reference resolution has no type checker. Confidence tags (exact, import, name-only) state how each reference was resolved rather than promising precision; the evaluation harness can measure it per tier. Method calls on objects (obj.save()) usually match by name only; when nothing in an answer resolved, the answer says so.
  • After a save, freshness costs grow with the repository. The saved file's own rows land in about 0.3 s, but references are then re-resolved across the whole index — milliseconds on a small project, about 1.5 s on Django — so back-to-back saves queue behind it. Every response carries an index_age_ms freshness stamp, and cidx check can prove convergence at any time.
  • Changes outside a watcher are caught only where an answer looks. When nothing is running cidx serve, every answer re-checks the files it cites and names any that changed since indexing (ADR-018), but a definition added in a file it does not cite stays invisible until cidx index or the watcher picks it up.
  • Ranked search and the repository map scan. search_symbols and repo_map take ~135 ms p95 on Django; exact lookups stay under 30 ms.

Evaluation harness (optional)

This repository also contains benchmark/: a complete, tested harness for measuring agent retrieval across tools (a grep-only baseline, cidx, and competitor adapters welcome by PR) on pinned repositories. It is optional — the project is complete without it. Anyone can run it with their own API credentials via scripts/run_benchmark.py, or develop against the free recorded-response stub model. The honesty rules in benchmark/methodology.md govern any results someone chooses to publish: raw JSONL logs alongside every table, every number recomputable from the logs, and losses shown exactly like wins.

Project documents

Who is building this

I'm Gati Varshney, a Google Summer of Code 2026 contributor.

This project is built in public. The architectural decisions are documented, the implementation is fully open source, performance claims are measured rather than asserted, and an evaluation harness ships with the repository so anyone can verify retrieval quality independently.

License

Apache-2.0. Benchmark result data additionally CC-BY-4.0.

About

Local code indexing for AI coding assistants using Tree-sitter, SQLite, and the Model Context Protocol (MCP).

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages