local-first context engine

Context your agent can explain.

repoctx gives an AI coding agent compact repository evidence — with a machine-readable reason for every hit, and a hard token budget per answer. The goal is fewer unnecessary file reads at the same task quality. See the economics.

measured: ~1/3 of a read-the-files loop No network calls No telemetry Byte-identical output TypeScript · TSX · JavaScript · C# parsed Python · Go · Java · Rust · 15 more outlined Specs, tickets and contracts too npm or NuGet — no runtime needed
principles

Built for agents that have to be trusted.

Three constraints, enforced — not aspirations. They are what make the output safe to feed a model and safe to run on a private codebase.

offline by construction

Nothing leaves the machine

No network calls, no telemetry, no embedding or LLM requests — ever. The ban is verified at compile time and re-checked in CI with networking switched off.

explainable

A reason for every hit

Each result carries machine-readable reasons — fts, symbol:loginUser, imported-by:…, test-of:… — so an agent (and you) can see why a file was chosen.

deterministic

Byte-identical output

The same index and the same query always produce the same bytes. Stable sort, no timestamps in results, no randomness. Diff-friendly and cache-friendly.

economics

The reads are the bill — not the answers.

Every token an agent reads is billed. The way to spend less is not to write shorter answers; it is to stop the agent from opening half the repository to find three functions.

Measured on this repository for the task “improve token budget packing in the context engine”: an agent that asks for file pointers and then opens the top three files pays ~886 tokens for the answer and ~5,336 for the reads. The answer is 14 % of the cost.

Ask for the evidence instead of a reading list, and the follow-up reads simply do not happen — the same task, answered in one call, at roughly a third of the tokens.

Exact o200k_base counts, measured at milestone M6 (ADR 0010). Your repository is not this one, which is why repoctx stats measures yours instead of asking you to believe these.

same task, two loops
pointers, then read the top 3 files6,222
context --detail slices, 3 slices embedded2,110
context --detail outline, 7 files surveyed2,151

And when a full read really is needed, the decision itself is cheap: an outline of that repository's main file costs 1,111 tokens against the file's 3,256 — an agent decides whether to read for a third of the price.

hard

A ceiling that is measured

--response-budget-tokens is enforced against the exact rendered response, with no first-item exception. If nothing useful fits, you get an error and a retry budget — never a partial answer billed anyway.

1,916 → 621

Never pay twice

Every span, symbol and pointer carries a receipt. Echo it — or use a --session, which costs zero output tokens — and that unit is acknowledged instead of resent. Freed slots buy new evidence, not markers.

11.5 %

Cheaper serialization

--format md avoids JSON's escape tax and --compact drops legacy duplicate fields: 217,869 against 246,297 tokens for identical evidence across 36 frozen tasks.

~100

Cheap prompt overhead

The block loaded into every prompt is a ~100-token pointer, capped by a test at 150. The full protocol arrives on demand, as a skill or via repoctx guide.

the other half

Cheaper, not thinner.

Any tool can cut tokens by returning less. These are the rules that keep a smaller answer from becoming a worse one — properties of the code and its tests, not intentions.

Symbol-aligned, never truncated

Up to three non-overlapping ranges per file, the matching symbol's range first, reconstructed so the line numbers match the delivered bytes exactly. Whole declarations — not a function cut in half.

The graph adds what search misses

Bounded two-hop expansion brings in the test that covers a file and the module that imports it, while vendor, generated and unrequested fixture/test/doc paths are penalized. Fewer tokens, and different ones.

Nothing is dropped silently

Every hit carries its reasons, and --explain names the omitted candidates with the constraint that excluded them. The opposite of a context window that quietly ran out.

Reuse never over-claims

A receipt suppresses exactly one delivered unit; --known asserts a whole file. Conflating them would tell the model it holds lines it was never sent — the dangerous way to save tokens.

Staleness is visible

Responses carry content and analysis state, --ensure-fresh re-indexes before querying, and memories whose files drifted come back flagged stale.

Regression-gated, not asserted

36 frozen retrieval tasks over four repositories, labelled with exact line ranges before the corpus was ever run. The tests reject any change that drops a previously delivered required file or labelled line.

the standard, applied to ourselves

A 94.7 % recall win — rejected.

An experimental packer raised required-file recall from 29/38 to 36/38, past the 90 % target on the roadmap. It got there by delivering one fragment of more files: relevant lines fell from 204 to 115, evidence-complete tasks from 9/30 to 3/30, and the follow-up reads those tasks still needed rose from 26 to 32. The headline metric improved while the agent's real cost got worse, so the patch was kept for reproduction and left out of the product.

A token saving that costs relevant evidence is not a saving.

What is not claimed. These corpora measure evidence retrieval and response cost — not a coding agent's task success or total session spend. “Reads replaced” credits a read that would probably have happened; it is an estimate, not a lower bound. Under a tight 2,000-token JSON ceiling the holdout still delivers only 29/38 required file occurrences (76.3 %; Markdown 32/38; unbudgeted 38/38) — the 90 % target is not met, and is recorded as open work. Full reasoning, evidence and limits · measurement methodology.

0.15.0 · optional read-cost guard

Less to read. Enough to solve the task.

The goal is the same quality at a lower total cost. These changes make context handling more reliable; their effect on completed-task cost still needs a real-agent comparison.

Try smaller evidence first

Before a large file read, the Claude Code guard can suggest an outline or exact source excerpts. It starts in observe mode, which counts without blocking. Enforce mode is an explicit, experimental choice.

Know when to read again

Generated sessions stop relying on old evidence after conversation changes such as compaction or restart. A child agent must not inherit its parent's claims. Manually named sessions remain the caller's responsibility.

Keep the agent moving

A repeated read can proceed through normal client handling. If the guard cannot safely save a redirect, it does not block. Client permissions still apply, and installation preserves other tools' hooks and settings.

Measure the whole task

A blocked read is not money saved. The comparison must include task quality, failed attempts, follow-up reads and repair work. Incomplete measurements cannot establish lower costs or equal quality.

Current limits. No total-cost or equal-quality improvement has been demonstrated for the guard. The full comparison still needs executable scenarios, cache controls and a usage adapter. The measured hook latency also misses its 100 ms target. RepoContext does not call another model to summarize files or write code. Setup and modes · Comparison status.

install

Four ways in.

Simplest route: npm install — it ships a self-contained binary, so no .NET runtime is involved. Already on .NET 10? Install the global tool. Prefer no package manager at all? Take a binary from Releases. Tool installs blocked by policy? Add it as a plain NuGet package.

npm new

No .NET runtime required — the package ships a self-contained binary. The natural route for a TypeScript / JavaScript repository. The package is repocontext-tool; the command it installs is repoctx.

# globally — puts repoctx on your PATH
npm install -g repocontext-tool
repoctx --version

# or pinned per repository, so the whole team gets the same version
npm install --save-dev repocontext-tool
npx repoctx --version

Prebuilt for Linux x64/arm64, macOS arm64/x64 and Windows x64/arm64. The platform payloads are optional dependencies, so npm downloads only the one matching your machine. Alpine and other musl-based distributions are not covered by the prebuilt binaries — use the .NET tool there.

.NET global tool

Needs the .NET 10 runtime. Puts repoctx on your PATH.

# from NuGet.org
dotnet tool install --global RepoContext.Tool

repoctx --version

Self-contained binary

No runtime. From GitHub Releases: linux-x64, win-x64, osx-arm64.

# download for your platform, then
tar -xzf repoctx-linux-x64.tar.gz
./repoctx --version

Plain NuGet PackageReference

dotnet tool install blocked by company policy? RepoContext.MSBuild delivers the same CLI through the NuGet restore your build already runs — a development-only dependency, nothing ships with your app.

# in exactly one project of the repository
dotnet add src/YourProject package RepoContext.MSBuild

# sets up init, index, local CLI payload, wrappers, and portable MCP config
dotnet build
./.repoctx/bin/repoctx context "change the login logic"
quickstart

Index once, then ask.

A genuine three-step flow. After the first build, indexing is incremental — it diffs by content hash and re-reads only what changed.

01 init

Set up the repo

repoctx init writes .repoctx/ and a config, and can add usage notes to your CLAUDE.md / AGENTS.md.

02 index

Build the index

repoctx index scans the tree into a local SQLite store — files, symbols and the import/dependent graph.

03 context

Ask for what matters

repoctx context "add logout" returns a ranked, explained, budgeted bundle — ready to hand to an agent.

commands

Fifteen commands, one contract.

Every command speaks --format text, json or md. The JSON is stable, snake_case and always carries schema_version.

CommandWhat it doesKey options
initCreate .repoctx/ and config; optionally wire up agent instructions.--agents
integrateWire RepoContext into the agents this repo already uses — instructions, skills, rules, MCP registration.--client · --check
guidePrint the full usage protocol on demand, so it never sits in every prompt.—
indexBuild or incrementally update the index (hash diff).--full
searchBM25 full-text search over content and symbols.--symbols · --top · --path
relatedA file's imports, dependents, linked tests and the documents describing it.--format
traceEvery file declaring or mentioning one exact key, link, symbol or path.--top · --path
contextRanked, explained bundle under exact response and read budgets.--response-budget-tokens · --detail · --path
outlineA compact symbol skeleton before a full-file read.--format
changedWorking-tree changes, impacted files, and optional delta hunks.--patch
architectureLOC tree, language mix, centrality, entrypoints.--format
primeA cache-stable repository primer for unfamiliar work.--files · --format
memoryStore and recall short agent-authored notes, decisions, and constraints.add · search · rm
statsLocal token-savings and optional cost dashboard; opens in your browser when run in a terminal.--format · --open · --no-open
mcpServe the same deterministic tools over stdio.—
model context protocol

Or skip the shell entirely.

GitHub Copilot, Claude Code, Cursor — anything that speaks MCP can call RepoContext directly. repoctx mcp runs a local, non-destructive server over stdio and exposes eight compact tools. Query calls may append aggregate counts to the local usage ledger.

  • repoctx_search — full-text and symbol search
  • repoctx_get_context — the ranked, budgeted bundle
  • repoctx_trace — one exact key, link, symbol or path
  • repoctx_get_related_files — imports, dependents, tests
  • repoctx_get_outline — compact symbol skeleton
  • repoctx_get_changes — changes, impact, delta hunks
  • repoctx_memory_add — retain a durable finding
  • repoctx_memory_search — recall local knowledge

Register it

# Claude Code — run inside your repo
claude mcp add repoctx -- repoctx mcp

# GitHub Copilot (VS Code) — .vscode/mcp.json
{ "servers": { "repoctx": {
    "type": "stdio", "command": "repoctx", "args": ["mcp"] } } }