Skip to main content

codeterrain logo

codeterrain

terrain: exact answers about your code, in under a second.
For coding agents, and the people working beside them.

Local only  ·  no LLM  ·  no network calls of its own  ·  Python, pytest, FastAPI  ·  JavaScript and TypeScript front ends  ·  Terraform

Quick start  ·  What it answers  ·  Results  ·  Token savings  ·  How it works  ·  Docs


A coding agent spends most of its reading on structure it has to rediscover every session: which fixtures a test really gets, where a route is handled, who calls a function, which tests would catch a change to it. The answers are in the code, but finding them takes rounds of grep and file reads, and a wrong guess costs a review round.

terrain answers those questions from a cache, the way the framework itself would answer them.

Exact Setup order equals pytest --setup-plan on every node tested. refs follows imports and re-exports, so an alias counts and a same-named function elsewhere does not.
Fast Most warm answers take 0.3 to 0.5 s on a large backend, Python start-up included.
Cheaper to read 4.4x to 8.8x fewer characters than the grep-and-read it replaces, on code bases with a real fixture graph.
Safe to point at a repo The static tier never runs the target's code. The tier that has to import it runs in a sandbox with no credentials, and by default it cannot write into the repository.

Quick start

uv tool install <path-to-this-checkout>     # Python 3.12+; the only dependency is pluggy
# or "<path-to-this-checkout>[terraform]" to add the Terraform language pack, "[typescript]"
# to read front-end calls with tree-sitter, "[terraform,typescript]" for both
cd your-repo
terrain doctor                              # what it found, and which config it used

Ask about a function: where it is called, how each call passes its arguments, and which tests patch it. This is terrain run on its own source:

$ terrain refs is_test_module
function codeterrain.fixtures_static.is_test_module(rel)  src/codeterrain/fixtures_static.py:327
6 calls (6 in code, 0 in tests), 0 with a shape problem, 0 used as a value, 0 patched
file:line                                 in                             shape         note
src/codeterrain/code_static.py:1349       update                         1 positional
src/codeterrain/code_static.py:1357       update.<listcomp>              1 positional
src/codeterrain/fixtures_static.py:322    unlisted_test_trees.<genexpr>  1 positional
src/codeterrain/fixtures_static.py:435    build                          1 positional
src/codeterrain/impact.py:272             _Fixtures.__init__             1 positional
src/codeterrain/impact.py:424             _inherited_tests               1 positional

Ask which tests would catch a change to it, through its callers and the fixtures that call them (abridged):

$ terrain impact is_test_module --depth 2
17 tests reach codeterrain.fixtures_static.is_test_module (0 directly), through 15 callers within 2 levels; 2 tests patch it …
test                                                 depth  how                               via
tests/test_cli.py::test_a_handler_is_matched_by_…    2      call at tests/test_cli.py:1186    …update <- …is_test_module
tests/test_cli.py::test_a_route_text_that_matches_…  2      call at tests/test_cli.py:1142    …update <- …is_test_module
…

Ask what a fixture is and what depends on it (from the sample repository in tests/sample_repo):

$ terrain fixture db
name: db
scope: session
autouse: no
params: none
deps: _env_session
defined: tests/conftest.py:9
overrides: -

dependents (4):
fixture  _reset_state  tests/conftest.py:19  arg
fixture  client  tests/conftest.py:34  arg
fixture  mod_cache  tests/conftest.py:14  arg
…

The commands that import your application (setup-order, routes) run in the target's interpreter: pass --python .venv/bin/python or set CODETERRAIN_PYTHON. Add --json to any command for the full data.

What it answers

Question Command What grep can't tell you
Where is this defined? terrain def NAME Resolves a re-export to the real definition
Who calls it, passes it, patches it? terrain refs NAME Follows imports and aliases, flags calls that don't fit the signature, and separates tests that patch it out
Who imports this module, and what does it import? terrain importers MODULE, terrain imports MODULE Every import of it or of a name from it, relative imports resolved
Which tests would catch a change? terrain impact NAME Walks callers and fixtures the way pytest resolves them
Which fixtures does a test get, in what order? terrain setup-order NODEID pytest's own resolution, overrides and scopes included
What fixtures exist, and who uses one? terrain fixtures, terrain fixture NAME Which definition wins where, and every dependent
Where is a route handled, with what access? terrain routes, terrain route METHOD PATH Prefixes from router calls in other files, joined to your access manifest
Which HTTP calls does a front end make? terrain calls Paths built from templates, constants and concatenation
Which backend route does each call hit? terrain callers Front-end calls matched to backend routes across repositories
Where is a Terraform resource, variable or module, and who reads it? terrain def NAME, terrain refs NAME (Terraform pack) Terraform's own names (aws_s3_bucket.logs, var.region, module.vpc) and the block each reference sits in
What is the module tree, and which providers are pinned? terrain def --kind module, terrain def --kind required_provider Every module call with its source and version; provider aliases and what selects them

Every option is in docs/commands.md.

Results

Measured read-only on real code bases: a large FastAPI backend with a very large test suite, three smaller Python code bases, an analytics project with a large fixture graph, and five front ends.

11 / 11

test nodes where setup order equals pytest --setup-plan

0.35 s

median warm fixtures answer on the large backend

up to 8.8x

fewer characters for an agent to read

0

answers marked wrong in agents' logged lookups
Accuracy: setup order equal to pytest on 11 of 11 nodes; every route joined to the access manifest; 97% and 96% of front-end calls matched to a route; 99% of calls found by refs against grep in a monorepo Warm answers on the backend, Python start-up included: fixtures 0.35 s, routes 0.35 s, refs 0.36 s, def 0.45 s, setup-order 0.49 s, impact 1.17 s, against a goal of under 1 s

impact is the one answer over a second: it walks callers and fixtures, and on the backend a heavily tested function is reached by about a thousand tests. A cold build costs seconds for the static index and about a minute and a half for the routes. After that, answers come from the cache, and a view is stamped stale when files change.

Characters an agent reads, indexed to today's grep-and-read at 100: backend autouse fixtures 23, a route's handler and access 14, who uses the main fixture 67; analytics who uses the main fixture 11, fixtures of a test 29; a small suite's fixtures of a test 125

The saving scales with the code. Where the fixture graph is large, an answer is a small fraction of the grep-and-read it replaces. In a small suite with a couple of fixtures the answer can be longer than the grep; there the gain is the exact order, which grep can't show.

In the field

Coding agents working on real tasks logged each terrain lookup: what they asked, whether the answer was right, and what they would have done instead.

Outcomes of logged lookups: answered 79%, partly answered 12%, no help 2%, error 7% (mostly the wrong command), wrong answer 0%
  • Each fully answered lookup saved the agent about 1.3 tool calls, by its own count.
  • No answer was marked wrong. The partial answers came from output cut by the token budget, a cache built from a different checkout than the one being edited, and a value read through a module alias.
  • Most errors were the wrong command: callers, which is for routes, where refs was meant.

Inferred token savings

An estimate from the two measurements above, not an A/B experiment:

Step Figure
Reading saved on a question terrain answers, large backend (4.4x to 6.9x fewer characters) 77% to 86%
Lookups answered fully, in the field 79%
Lookups answered partly (counted at half the saving) 12%
Inferred share of lookup reading saved on a large Python backend about 65% to 73%

Lookups are only part of a session: reasoning, editing and test runs are untouched, and small code bases save little. Tokens here are characters divided by four. A session-level A/B, the same lane run with and without terrain, is the next measurement. The method and the per-repository results are in docs/why-codeterrain.md.

How it works

flowchart LR
    ask(["agent or you: terrain refs NAME"]) --> cache[("cache per repository,<br/>keyed on content")]
    cache -->|warm| answer(["answer in under a second"])
    subgraph static ["Static tier: reads files, never runs them"]
        fixtures["fixtures and tests<br/>(ast)"]
        code["code index<br/>(ast, SQLite)"]
        calls["front-end HTTP calls<br/>(tree-sitter, or the JS/TS tokenizer)"]
    end
    subgraph sandbox ["Import tier: sandboxed subprocess"]
        order["setup order<br/>(pytest --collect-only)"]
        routes["routes<br/>(imports the apps)"]
    end
    static -->|incremental, changed files only| cache
    sandbox -->|on refresh; served stamped stale| cache
  • The static tier reads source with Python's ast, and front-end files with tree-sitter when the TypeScript pack is installed (chosen for speed and accuracy) or a tokenizer written in Python when it is not. It never imports or runs the target, and re-reads only the files that changed.
  • The import tier needs the real framework: pytest's fixture resolution, FastAPI's route table. It runs in a subprocess using the target's own interpreter, with an allowlisted environment, dummy cloud credentials, no proxy and a best-effort network guard.
  • The cache lives outside the checkout, in the git common directory or your user cache, and is keyed on content hashes.

More in docs/how-it-works.md, docs/sandbox.md and SECURITY.md.

Why this exists

codeterrain started inside benchli, the team sports platform built by Mitbolas GmbH. Its Python backend is a large FastAPI service with a very large test suite, developed with coding agents. Most of the agents' tokens went on re-mapping the same structure every session, and a hand-kept map went stale within weeks. The goal became exact answers, straight from the code, locally and in under a second, in every repository benchli and Mitbolas develop with agents. The full story, and why we did not adopt a knowledge-graph tool, are in docs/why-codeterrain.md.

Documentation

Commands Every command, option and output
Configuration Precedence, every key with its default, monorepos and non-default layouts
How it works The two tiers, the cache, extractor programs
The sandbox What the import tier runs with, and every gap in the network guard
Limits What a static walk does not see, and the bounds set on purpose
Security The trust model: what is guaranteed and what is best-effort
Extractor protocol Writing an extractor or a language pack for another language or framework
Terraform pack .tf files in def and refs: resources, modules, providers
TypeScript pack Front-end calls read with tree-sitter, and why it is the reader to install
Design notes The decisions behind the front-end calls and the language packs
Why codeterrain The origin, the measurements, what is not proven yet
Changelog What changed, release by release

Development

uv run --extra dev ruff check .
uv run --extra dev pytest

Run pytest as a single process (no -n). The suite spawns real pytest and interpreter subprocesses, and a few tests skip on Windows (no sendmsg, no unix-domain sockets). CI also runs it against FastAPI below and above 0.137 and pytest 8 and 9. Contributor and review rules are in AGENTS.md.

Copyright Mitbolas GmbH. Licensed under Apache-2.0 (see LICENSE and NOTICE).

Metadata

Release files for codeterrain 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for codeterrain 0.3.0
File Size Uploaded Provenance Actions
codeterrain-0.3.0.tar.gz 340.3 kB With provenance Details for codeterrain-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for codeterrain 0.3.0
File Interpreter ABI Platform Actions
codeterrain-0.3.0-py3-none-any.whl Python 3 none any Details for codeterrain-0.3.0-py3-none-any.whl

Total release size: 486.8 kB

Release files / codeterrain-0.3.0.tar.gz

Download URL codeterrain-0.3.0.tar.gz
Size 340.3 kB
Tags Source
SHA-256 checksum
How to use checksums
e5c3a3c5b0f65d9a660423f6d37ce57db513c69bb6151a3ea5052356aec45d87
BLAKE2b-256 checksum
How to use checksums
e90f538c267b9224f11457e2f8b44b0ceee0897bd00e2ea11a7c9c3e9ac5e63d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / codeterrain-0.3.0-py3-none-any.whl

Download URL codeterrain-0.3.0-py3-none-any.whl
Size 146.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5e6d2609a5d8da3bce0ce24b6cf70bfc7d55539a3987a7ff237dd7a133279668
BLAKE2b-256 checksum
How to use checksums
1f5a4baf3a5ee4dcfe15176735d1647ea61d2e7ca7ce2b0b14b3f275041ee140
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Security for codeterrain 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for codeterrain-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page