Technical data sheet · measured, not declared

codeindex

Zero-install, deterministic repo-indexing engine — one vendorable file, a library, a CLI, and 29 MCP tools.

Walks a repo, extracts symbols and imports across 16 languages (21 tree-sitter grammars), resolves cross-file references into a typed link-graph, and renders byte-identical artifacts every time it runs. Vendor the single engine.mjs, install it from npm or Homebrew, or run it from a container — nothing else to install, ever.

MIT license v2.22.0 Node ≥ 18 npm · @maxgfr/codeindex ghcr.io/maxgfr/codeindex 0 runtime deps

Install

npm i -g @maxgfr/codeindex

Same zero-dependency engine.mjs bundle behind all three — CLI, library import, and Docker image.

Key measured numbers

Fig. 01 — measured, reproducible

Benchmarks

Measured against universal-ctags, Serena (LSP over MCP) and Graphify with a reproducible harness (scripts/bench/) — median of 5 runs, one warmup discarded. Every number on this page comes from the same site/benchmarks.json committed to this repo; nothing here is rounded or typed in by hand. Full methodology and every scenario: BENCHMARKS.md.

Cold index — code-du-travail

2,823 files, every tool measured on the same repo. Lower is faster.

Warm reindex — against codeindex's own cold build

No competitor here exposes an incremental reindex, so the baseline is the cold build of the same repo: what a rerun costs once the cache is warm, and what one touched file costs. Lower is faster.

Axis by axis

One row per thing an agent-tooling team weighs. A filled tile marks the leader on that axis — hover any cell for the measured number. Where a rival wins it stays at the top of the table, unedited: ctags takes both cold-index rows, because a flat tags pass is a smaller job than a typed cross-file graph. Speed is the axis that matters least here, and the file cap showed why — codeindex used to finish next.js ahead of ctags by leaving 7,952 of its files out of the index, so every lookup answered for a prefix of the repo and called it the repo. The cap is gone: slower, and correct about the whole tree. The rows below are the axes that time is spent on.

codeindex leads a competitor leads tie / not this tool's job — = n/a (not measurable)

Fig. 02 — scored against tools that are not us

Measured quality

Speed is easy to measure and easy to trust. Extraction quality is neither — an engine scored only against expectations its own authors wrote cannot find the construct nobody thought to label. So the checks that lead here are the ones with an outside authority: an index built by the real TypeScript compiler, a declaration-level differential against a mature independent indexer over real repositories, the code-navigation queries each grammar's own authors publish, and each grammar's declared node vocabulary read straight from the parser.

Those found real gaps — C headers indexing to nothing, a JS/TS walk stopping one node short of every callback block — and each was fixed rather than argued with. Below them sits the hand-labelled corpus that covers what no external indexer reports at all (doc comments, complete signatures, call edges), shown with the corpus it is measured on, because a bare percentage hides the thing a reader most needs to judge it. Every number is reproducible with pnpm quality:report and ratcheted in both directions: losing quality fails CI, and gaining it fails too until the recorded baseline is refreshed in the same commit. The blind spots are listed rather than left implicit.

Fig. 03 — capabilities

Features

Everything below ships in the same zero-dependency bundle — no feature is an optional add-on package.

Keyless search + deterministic fuzzy

BM25 lexical ranking over symbol names, path segments and headings. A term with zero document frequency falls back to a byte-deterministic character-trigram match (Dice ≥ 0.6) — typo tolerance with no embeddings. Disable with --no-fuzzy.

Deterministic semantic tier

An opt-in static token → vector lookup table — no neural forward pass. Encode → mean-pool → L2-normalize → round-half-to-even int8 quantize; ranking is a pure integer dot product, byte-identical everywhere. No model on disk, no behavior change.

Rich embedding tier — v2.11.0

Point at a containerized HTTP endpoint (ghcr.io/maxgfr/codeindex-embed, all-MiniLM-L6-v2) via CODEINDEX_EMBED_ENDPOINT. Wins over the static tier by explicit intent; degrades to lexical on exit 0 if unreachable — never a silent fallback to the wrong model.

Call graph & caller index

Typed import / call / use / doc-link / mention edges at file and module level. Per-symbol callers and references resolve in-proc in sub-millisecond time on an already-loaded warm scan.

Graph analytics

Louvain communities, PageRank & betweenness centrality, dead-code detection, complexity × churn hotspots, change coupling, surprise-edge detection, and a tests→code map — all derived from the same link-graph.

SCIP export

index.scip via a hand-rolled zero-dependency protobuf encoder — validated against the official scip CLI's stats/lint.

Multi-ecosystem workspaces

npm/yarn, pnpm, lerna, nx, cargo, go.work, maven, gradle, uv and Composer — detected and named without running any of their own tooling.

Symbolic edits over MCP

replace_symbol_body, insert_after_symbol, insert_before_symbol — precise edits addressed by symbol name, not by line ranges that drift.

mermaid & repo map

Render a mermaid dependency diagram or a plain-text repo map directly from the graph — no separate diagramming step.

Byte-deterministic outputs

Two cold builds of an unchanged repo produce identical graph.json/symbols.json — confirmed on 7 of 7 measured repos this session. Graphify's graph.json differed on 5 of the 6 measurable repos; determinism is a value we hold across every repo, not just where it's easy.

Fig. 04 — usage

Quickstart

Five ways in — pick the one matching your integration.

Install it as a regular dependency and import the pure functions.

npm i @maxgfr/codeindex
import { scanRepo, ENGINE_VERSION } from "@maxgfr/codeindex";

const scan = scanRepo("/path/to/repo");

Fig. 05 — what each tool is

How it compares

These are architecturally different tools, not differently tuned ones, so the comparison is split across the page rather than collapsed into one verdict: the axis-by-axis scoreboard sits with the benchmarks, where its numbers come from, and the declaration-level differential against a mature third-party indexer sits with measured quality. What is left is the shape of each tool: what it models, what it covers, what it costs to install.

Tool comparison codeindex vs. universal-ctags, Serena and Graphify.
Tool Model Language coverage Install Worth knowing
codeindex Static byte-stable artifacts (graph.json/symbols.json), rebuilt each run with an incremental cache 16 regex extractors (always on) / 21 tree-sitter grammars — 15 committed, 6 pulled on demand npm i -g, brew, or docker — zero runtime deps Slower to build than a flat tags file, and its extraction is the only one here scored against outside oracles (see measured quality)
universal-ctags A flat tags file ~40 via broad, generic parser rules Binary install Fastest cold index at every size measured, and mature enough to serve as an oracle here — but definitions only: no call graph, no import resolution, no cross-file links
Serena LSP client orchestrating per-language language servers Any language with an LSP server 114 MB venv + per-language language servers Type-aware references no static tool claims — bought with the slowest activation and per-call latency measured here, and no on-disk artifact to reproduce
Graphify tree-sitter knowledge graph (graph.json); code-only mode is keyless, the docs/PDF tier needs an LLM and is excluded here 36 via tree-sitter 140 MB Python venv (uv tool) ~1 ms per lookup, but graph.json is not byte-identical across rebuilds on any of the 6 measurable repos, and file nodes are basename-keyed (no file-overview)