codeterrain 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codeterrain-0.3.0 → codeterrain-0.3.2}/AGENTS.md +4 -3
- {codeterrain-0.3.0 → codeterrain-0.3.2}/CHANGELOG.md +29 -1
- codeterrain-0.3.2/PKG-INFO +324 -0
- codeterrain-0.3.2/README.md +286 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/SECURITY.md +13 -12
- codeterrain-0.3.2/docs/charts/make_charts.py +391 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/commands.md +31 -31
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/configuration.md +8 -8
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/design-v0.2.md +3 -3
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/extractor-protocol.md +5 -5
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/how-it-works.md +4 -4
- codeterrain-0.3.2/docs/img/architecture-dark.svg +43 -0
- codeterrain-0.3.2/docs/img/architecture-light.svg +43 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/img/field-dark.svg +14 -13
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/img/field-light.svg +14 -13
- codeterrain-0.3.2/docs/img/prior-art-dark.svg +165 -0
- codeterrain-0.3.2/docs/img/prior-art-light.svg +165 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/img/speed-dark.svg +7 -7
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/img/speed-light.svg +7 -7
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/img/tokens-dark.svg +2 -2
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/img/tokens-light.svg +2 -2
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/limits.md +8 -8
- codeterrain-0.3.2/docs/prior-art.md +95 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/why-codeterrain.md +28 -22
- {codeterrain-0.3.0 → codeterrain-0.3.2}/packages/codeterrain-terraform/README.md +9 -10
- {codeterrain-0.3.0 → codeterrain-0.3.2}/packages/codeterrain-terraform/pyproject.toml +1 -1
- {codeterrain-0.3.0 → codeterrain-0.3.2}/packages/codeterrain-typescript/README.md +8 -9
- {codeterrain-0.3.0 → codeterrain-0.3.2}/packages/codeterrain-typescript/pyproject.toml +1 -1
- {codeterrain-0.3.0 → codeterrain-0.3.2}/pyproject.toml +2 -1
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/__init__.py +1 -1
- {codeterrain-0.3.0 → codeterrain-0.3.2}/uv.lock +3 -3
- codeterrain-0.3.0/PKG-INFO +0 -295
- codeterrain-0.3.0/README.md +0 -257
- codeterrain-0.3.0/docs/charts/make_charts.py +0 -239
- {codeterrain-0.3.0 → codeterrain-0.3.2}/.gitattributes +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/.github/workflows/ci.yml +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/.github/workflows/release.yml +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/.gitignore +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/CLAUDE.md +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/LICENSE +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/NOTICE +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/img/accuracy-dark.svg +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/img/accuracy-light.svg +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/img/logo.svg +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/docs/sandbox.md +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/packages/codeterrain-terraform/src/codeterrain_terraform/__init__.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/packages/codeterrain-terraform/tests/test_terraform.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/packages/codeterrain-typescript/src/codeterrain_typescript/__init__.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/packages/codeterrain-typescript/tests/test_typescript.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/_guard.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/_pytest_dump_plugin.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/_routes_runner.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/budget.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/cache.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/calls_join.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/calls_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/cli.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/code_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/config.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/executables.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/fixtures_order.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/fixtures_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/hookspecs.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/impact.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/languages.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/plugins/__init__.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/plugins/fastapi_routes.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/plugins/manifest_join.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/protocol.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/routes_view.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/sandbox.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/src/codeterrain/views.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/conftest.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/sample_repo/docs/route-access.json +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/sample_repo/pyproject.toml +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/sample_repo/sampleapp/__init__.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/sample_repo/sampleapp/main.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/sample_repo/sampleapp/other.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/sample_repo/tests/api/conftest.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/sample_repo/tests/api/test_items.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/sample_repo/tests/conftest.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/sample_repo/tests/test_root.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_budget.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_cache.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_calls_join.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_calls_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_cli.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_code_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_config.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_executables.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_fastapi_routes.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_fixtures_order.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_fixtures_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_hookspecs.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_impact.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_languages.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_manifest_join.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_protocol.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_pytest_dump_plugin.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_routes_runner.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_routes_view.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_sandbox.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.2}/tests/test_views.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# codeterrain: instructions for coding agents and contributors
|
|
2
2
|
|
|
3
|
-
codeterrain is a local, LLM-free, network-free command line tool (`
|
|
4
|
-
a repository's code from a cache, in under a second. Python
|
|
5
|
-
definitions and references; plus the HTTP calls of a
|
|
3
|
+
codeterrain is a local, LLM-free, network-free command line tool (`codeterrain`, also installed as
|
|
4
|
+
`terrain`) that answers questions about a repository's code from a cache, in under a second. Python
|
|
5
|
+
first: pytest fixture order, FastAPI routes, definitions and references; plus the HTTP calls of a
|
|
6
|
+
JavaScript or TypeScript front end. See
|
|
6
7
|
[README.md](README.md) for what it does and [docs/extractor-protocol.md](docs/extractor-protocol.md) for
|
|
7
8
|
the extractor protocol.
|
|
8
9
|
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.3.
|
|
3
|
+
## 0.3.2 (2026-11-02)
|
|
4
|
+
|
|
5
|
+
- The README's links to the docs, SECURITY.md and the CHANGELOG are absolute GitHub links. PyPI
|
|
6
|
+
resolves a relative link against its own project page, so on the 0.3.1 page every one of them
|
|
7
|
+
led nowhere.
|
|
8
|
+
|
|
9
|
+
## 0.3.1 (2026-11-02)
|
|
10
|
+
|
|
11
|
+
- The README's diagram of how it works is an image, as the charts are: PyPI does not render
|
|
12
|
+
Mermaid, so the 0.3.0 page showed its source.
|
|
13
|
+
- `codeterrain` is the command the docs use; `terrain` stays installed as a shorter name. Another
|
|
14
|
+
tool on PyPI installs a `terrain` command, and with both installed the last one wins.
|
|
15
|
+
- `codeterrain-terraform` 0.2.1 and `codeterrain-typescript` 0.1.1: their READMEs, which are their
|
|
16
|
+
PyPI pages, install from PyPI instead of saying the packs are not published. No code change.
|
|
17
|
+
- The README leads with `uv tool install codeterrain` from PyPI, and has a starting point for an
|
|
18
|
+
agent's instructions. Its claims are narrowed to what is measured: no "exact answers" headline;
|
|
19
|
+
`impact` lists the tests that statically reach a name, not the tests that would catch a change;
|
|
20
|
+
character counts measure an answer, not a session; the import tier's safeguards stop accidents,
|
|
21
|
+
not hostile code, so it is for code you trust. The field figures cover all 160 logged lookups,
|
|
22
|
+
with their period and how they were judged, two marked wrong among them.
|
|
23
|
+
- SECURITY.md and the rationale no longer describe the repository as private or a public release
|
|
24
|
+
as future work. SECURITY.md's third guarantee says what it covers: pytest and its known plugins
|
|
25
|
+
do not write into the repository by default; target code that opens a file by absolute path still
|
|
26
|
+
can.
|
|
27
|
+
- A prior-art page, `docs/prior-art.md`: what twelve comparable tools document answering, read from
|
|
28
|
+
their READMEs, with a chart. None documents pytest fixture resolution, front-end calls matched to
|
|
29
|
+
routes, or routes read from the application. It is a reading of their docs, not a benchmark.
|
|
30
|
+
|
|
31
|
+
## 0.3.0 (2026-11-02)
|
|
4
32
|
|
|
5
33
|
- `terrain refs` following an alias counts a read after an assignment on the same line
|
|
6
34
|
(`ALIAS = VALUE; print(ALIAS)`), and still not one inside the assignment's right-hand side.
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: codeterrain
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: terrain: answers about your code in under a second. Local, LLM-free structure facts for coding agents; Python first.
|
|
5
|
+
Project-URL: Homepage, https://github.com/benchli-sports/codeterrain
|
|
6
|
+
Project-URL: Issues, https://github.com/benchli-sports/codeterrain/issues
|
|
7
|
+
Author: Mitbolas GmbH
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
License-File: NOTICE
|
|
11
|
+
Keywords: code-index,coding-agents,fastapi,fixtures,pytest,static-analysis
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Framework :: FastAPI
|
|
15
|
+
Classifier: Framework :: Pytest
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: pluggy>=1.3
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: boto3; extra == 'dev'
|
|
27
|
+
Requires-Dist: codeterrain-terraform; extra == 'dev'
|
|
28
|
+
Requires-Dist: codeterrain-typescript; extra == 'dev'
|
|
29
|
+
Requires-Dist: fastapi>=0.115; extra == 'dev'
|
|
30
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
33
|
+
Provides-Extra: terraform
|
|
34
|
+
Requires-Dist: codeterrain-terraform>=0.2; extra == 'terraform'
|
|
35
|
+
Provides-Extra: typescript
|
|
36
|
+
Requires-Dist: codeterrain-typescript>=0.1; extra == 'typescript'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/logo.svg" alt="codeterrain logo" width="96">
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<h1 align="center">codeterrain</h1>
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
<b><code>codeterrain</code>: answers about your code, in under a second.</b><br>
|
|
47
|
+
For coding agents, and the people working beside them.
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
<p align="center">
|
|
51
|
+
Local only · no LLM · no network calls of its own · Python, pytest, FastAPI · JavaScript and TypeScript front ends · Terraform
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<p align="center">
|
|
55
|
+
<a href="#quick-start">Quick start</a> ·
|
|
56
|
+
<a href="#what-it-answers">What it answers</a> ·
|
|
57
|
+
<a href="#results">Results</a> ·
|
|
58
|
+
<a href="#inferred-token-savings">Token savings</a> ·
|
|
59
|
+
<a href="#how-it-works">How it works</a> ·
|
|
60
|
+
<a href="#documentation">Docs</a>
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
A coding agent spends most of its reading on structure it has to rediscover every session: which
|
|
66
|
+
fixtures a test really gets, where a route is handled, who calls a function, which tests reach
|
|
67
|
+
it. The answers are in the code, but finding them takes rounds of grep and file reads, and a
|
|
68
|
+
wrong guess costs a review round.
|
|
69
|
+
|
|
70
|
+
`codeterrain` answers those questions from a cache, the way the framework itself would answer them.
|
|
71
|
+
|
|
72
|
+
| | |
|
|
73
|
+
|:---|:---|
|
|
74
|
+
| **Accurate where it counts** | Setup order and routes come from pytest and FastAPI themselves: setup order equals `pytest --setup-plan` on every node tested. `refs`, `def` and `impact` read the code statically: they follow imports and re-exports, so an alias counts and a same-named function elsewhere does not, and a name whose meaning depends on run time is left unresolved rather than guessed ([limits](https://github.com/benchli-sports/codeterrain/blob/main/docs/limits.md)). |
|
|
75
|
+
| **Fast** | Most warm answers take 0.3 to 0.5 s on a large backend, Python start-up included. |
|
|
76
|
+
| **Shorter to read** | 4.4x to 8.8x fewer characters than the grep-and-read it replaces, on code bases with a real fixture graph. That measures the answer, not a whole session's tokens, cost or time. |
|
|
77
|
+
| **Careful with your repo** | The static tier never runs the target's code. Setup order and routes have to import it: they run in a subprocess with an allowlisted environment, dummy cloud credentials and a best-effort network guard, which stop accidents, not hostile code. Run those two on code you trust ([SECURITY.md](https://github.com/benchli-sports/codeterrain/blob/main/SECURITY.md)). |
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
uv tool install codeterrain # Python 3.12+; the only dependency is pluggy
|
|
83
|
+
# or "codeterrain[terraform]" to add the Terraform language pack, "codeterrain[typescript]"
|
|
84
|
+
# to read front-end calls with tree-sitter, "codeterrain[terraform,typescript]" for both
|
|
85
|
+
cd your-repo
|
|
86
|
+
codeterrain doctor # what it found, and which config it used
|
|
87
|
+
codeterrain fixtures # every pytest fixture, and which definition wins where
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The same command is also installed as `terrain`, a shorter name. Another tool on PyPI installs a
|
|
91
|
+
`terrain` command too; with both installed, the last one wins, while `codeterrain` is always this
|
|
92
|
+
one. To work on codeterrain itself, install from a checkout instead:
|
|
93
|
+
`uv tool install --editable <path-to-checkout>`.
|
|
94
|
+
|
|
95
|
+
Ask about a function: where it is called, how each call passes its arguments, and which tests patch it.
|
|
96
|
+
This is `codeterrain` run on its own source:
|
|
97
|
+
|
|
98
|
+
```console
|
|
99
|
+
$ codeterrain refs is_test_module
|
|
100
|
+
function codeterrain.fixtures_static.is_test_module(rel) src/codeterrain/fixtures_static.py:327
|
|
101
|
+
6 calls (6 in code, 0 in tests), 0 with a shape problem, 0 used as a value, 0 patched
|
|
102
|
+
file:line in shape note
|
|
103
|
+
src/codeterrain/code_static.py:1349 update 1 positional
|
|
104
|
+
src/codeterrain/code_static.py:1357 update.<listcomp> 1 positional
|
|
105
|
+
src/codeterrain/fixtures_static.py:322 unlisted_test_trees.<genexpr> 1 positional
|
|
106
|
+
src/codeterrain/fixtures_static.py:435 build 1 positional
|
|
107
|
+
src/codeterrain/impact.py:272 _Fixtures.__init__ 1 positional
|
|
108
|
+
src/codeterrain/impact.py:424 _inherited_tests 1 positional
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Ask which tests statically reach it, through its callers and the fixtures that call them
|
|
112
|
+
(abridged). A test that reaches a function does not necessarily check what you change, and an
|
|
113
|
+
empty list does not prove the change is untested:
|
|
114
|
+
|
|
115
|
+
```console
|
|
116
|
+
$ codeterrain impact is_test_module --depth 2
|
|
117
|
+
17 tests reach codeterrain.fixtures_static.is_test_module (0 directly), through 15 callers within 2 levels; 2 tests patch it …
|
|
118
|
+
test depth how via
|
|
119
|
+
tests/test_cli.py::test_a_handler_is_matched_by_… 2 call at tests/test_cli.py:1186 …update <- …is_test_module
|
|
120
|
+
tests/test_cli.py::test_a_route_text_that_matches_… 2 call at tests/test_cli.py:1142 …update <- …is_test_module
|
|
121
|
+
…
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Ask what a fixture is and what depends on it (from the sample repository in `tests/sample_repo`):
|
|
125
|
+
|
|
126
|
+
```console
|
|
127
|
+
$ codeterrain fixture db
|
|
128
|
+
name: db
|
|
129
|
+
scope: session
|
|
130
|
+
autouse: no
|
|
131
|
+
params: none
|
|
132
|
+
deps: _env_session
|
|
133
|
+
defined: tests/conftest.py:9
|
|
134
|
+
overrides: -
|
|
135
|
+
|
|
136
|
+
dependents (4):
|
|
137
|
+
fixture _reset_state tests/conftest.py:19 arg
|
|
138
|
+
fixture client tests/conftest.py:34 arg
|
|
139
|
+
fixture mod_cache tests/conftest.py:14 arg
|
|
140
|
+
…
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The commands that import your application (`setup-order`, `routes`) run in the **target's**
|
|
144
|
+
interpreter: pass `--python .venv/bin/python` or set `CODETERRAIN_PYTHON`. Add `--json` to any command
|
|
145
|
+
for the full data.
|
|
146
|
+
|
|
147
|
+
## Telling your agent
|
|
148
|
+
|
|
149
|
+
An agent uses `codeterrain` when its instructions say when to. A starting point for `AGENTS.md` or
|
|
150
|
+
`CLAUDE.md`:
|
|
151
|
+
|
|
152
|
+
```markdown
|
|
153
|
+
## Code structure: ask codeterrain first
|
|
154
|
+
|
|
155
|
+
Before grepping or reading files for one of these, run `codeterrain` in the repository root
|
|
156
|
+
(local, under a second when warm; add `--json` for the full data):
|
|
157
|
+
|
|
158
|
+
- where a name is defined: `codeterrain def NAME`
|
|
159
|
+
- who calls, passes or patches it: `codeterrain refs NAME`
|
|
160
|
+
- which tests statically reach it: `codeterrain impact NAME` (a starting point, not proof of coverage)
|
|
161
|
+
- which fixtures a test gets, in order: `codeterrain setup-order NODEID`
|
|
162
|
+
- a fixture and what depends on it: `codeterrain fixture NAME`
|
|
163
|
+
- where a route is handled: `codeterrain route METHOD PATH`
|
|
164
|
+
- a front end's HTTP calls, and the backend routes they hit: `codeterrain calls`, `codeterrain callers`
|
|
165
|
+
|
|
166
|
+
When codeterrain says it cannot resolve something, fall back to grep. `setup-order` and the route
|
|
167
|
+
commands import the code: use them only on code you trust.
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## What it answers
|
|
171
|
+
|
|
172
|
+
| Question | Command | What grep can't tell you |
|
|
173
|
+
|:---|:---|:---|
|
|
174
|
+
| Where is this defined? | `codeterrain def NAME` | Resolves a re-export to the real definition |
|
|
175
|
+
| Who calls it, passes it, patches it? | `codeterrain refs NAME` | Follows imports and aliases, flags calls that don't fit the signature, and separates tests that patch it out |
|
|
176
|
+
| Who imports this module, and what does it import? | `codeterrain importers MODULE`, `codeterrain imports MODULE` | Every import of it or of a name from it, relative imports resolved |
|
|
177
|
+
| Which tests statically reach it? | `codeterrain impact NAME` | Walks callers and fixtures the way pytest resolves them; what it cannot follow statically is listed, not dropped |
|
|
178
|
+
| Which fixtures does a test get, in what order? | `codeterrain setup-order NODEID` | pytest's own resolution, overrides and scopes included |
|
|
179
|
+
| What fixtures exist, and who uses one? | `codeterrain fixtures`, `codeterrain fixture NAME` | Which definition wins where, and every dependent |
|
|
180
|
+
| Where is a route handled, with what access? | `codeterrain routes`, `codeterrain route METHOD PATH` | Prefixes from router calls in other files, joined to your access manifest |
|
|
181
|
+
| Which HTTP calls does a front end make? | `codeterrain calls` | Paths built from templates, constants and concatenation |
|
|
182
|
+
| Which backend route does each call hit? | `codeterrain callers` | Front-end calls matched to backend routes across repositories |
|
|
183
|
+
| Where is a Terraform resource, variable or module, and who reads it? | `codeterrain def NAME`, `codeterrain refs NAME` (Terraform pack) | Terraform's own names (`aws_s3_bucket.logs`, `var.region`, `module.vpc`) and the block each reference sits in |
|
|
184
|
+
| What is the module tree, and which providers are pinned? | `codeterrain def --kind module`, `codeterrain def --kind required_provider` | Every module call with its source and version; provider aliases and what selects them |
|
|
185
|
+
|
|
186
|
+
Every option is in **[docs/commands.md](https://github.com/benchli-sports/codeterrain/blob/main/docs/commands.md)**.
|
|
187
|
+
|
|
188
|
+
## Results
|
|
189
|
+
|
|
190
|
+
Measured read-only on real code bases: a large FastAPI backend with a very large test suite, three
|
|
191
|
+
smaller Python code bases, an analytics project with a large fixture graph, and five front ends.
|
|
192
|
+
|
|
193
|
+
<table>
|
|
194
|
+
<tr>
|
|
195
|
+
<td align="center" width="25%"><h2>11 / 11</h2>test nodes where setup order equals <code>pytest --setup-plan</code></td>
|
|
196
|
+
<td align="center" width="25%"><h2>0.35 s</h2>median warm <code>fixtures</code> answer on the large backend</td>
|
|
197
|
+
<td align="center" width="25%"><h2>up to 8.8x</h2>fewer characters for an agent to read</td>
|
|
198
|
+
<td align="center" width="25%"><h2>160</h2>field lookups logged by coding agents; agent-assessed, not independently validated</td>
|
|
199
|
+
</tr>
|
|
200
|
+
</table>
|
|
201
|
+
|
|
202
|
+
<picture>
|
|
203
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/accuracy-dark.svg">
|
|
204
|
+
<img src="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/accuracy-light.svg" alt="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" width="760">
|
|
205
|
+
</picture>
|
|
206
|
+
|
|
207
|
+
<picture>
|
|
208
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/speed-dark.svg">
|
|
209
|
+
<img src="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/speed-light.svg" alt="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" width="760">
|
|
210
|
+
</picture>
|
|
211
|
+
|
|
212
|
+
`impact` is the one answer over a second: it walks callers and fixtures, and on the backend a heavily
|
|
213
|
+
tested function is reached by about a thousand tests. A cold build costs seconds for the static index
|
|
214
|
+
and about a minute and a half for the routes. After that, answers come from the cache, and a view is
|
|
215
|
+
stamped stale when files change.
|
|
216
|
+
|
|
217
|
+
<picture>
|
|
218
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/tokens-dark.svg">
|
|
219
|
+
<img src="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/tokens-light.svg" alt="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" width="760">
|
|
220
|
+
</picture>
|
|
221
|
+
|
|
222
|
+
**The saving scales with the code.** Where the fixture graph is large, an answer is a small fraction
|
|
223
|
+
of the grep-and-read it replaces. In a small suite with a couple of fixtures the answer can be longer
|
|
224
|
+
than the grep; there the gain is the exact order, which grep can't show.
|
|
225
|
+
|
|
226
|
+
### In the field
|
|
227
|
+
|
|
228
|
+
Coding agents working on real tasks logged each `codeterrain` lookup: what they asked, whether the answer
|
|
229
|
+
was right, and what they would have done instead. 160 lookups, 26 October to 2 November 2026, across
|
|
230
|
+
the code bases above. Each agent judged its own answer against what it then read or ran; nobody
|
|
231
|
+
checked the judgements independently. Every lookup has one outcome; the shares are rounded, so they
|
|
232
|
+
can add up to 101%.
|
|
233
|
+
|
|
234
|
+
<picture>
|
|
235
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/field-dark.svg">
|
|
236
|
+
<img src="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/field-light.svg" alt="Outcomes of 160 lookups agents logged from 26 October to 2 November 2026, one outcome each, as they judged them: answered 118 (74%), partly answered 16 (10%), no help 12 (8%), error 12 (8%, mostly the wrong command), wrong answer 2 (1%)" width="760">
|
|
237
|
+
</picture>
|
|
238
|
+
|
|
239
|
+
- Each fully answered lookup saved the agent **about 1.8 tool calls**, by its own count.
|
|
240
|
+
- **Two answers were marked wrong.** In one, `fixture` answered a fixture of a live-test folder it
|
|
241
|
+
did not read with a unit-test fixture of the same name; `fixtures`, `fixture` and `doctor` now name
|
|
242
|
+
such a folder. The other, `routes` missing an app's support routes, was misjudged: a later lookup
|
|
243
|
+
found them in the view, below the token budget's cut, whose "more" notice a `grep` had removed.
|
|
244
|
+
- The partial answers came mostly from output cut by the token budget; the rest from a cache built
|
|
245
|
+
from a different checkout than the one being edited, reads `refs` does not see (through a module
|
|
246
|
+
alias, as a dict key), duck-typed calls, and route filters that matched less than expected.
|
|
247
|
+
- Most errors were the wrong command or arguments: `callers`, which is for routes, where `refs` was
|
|
248
|
+
meant.
|
|
249
|
+
|
|
250
|
+
## Inferred token savings
|
|
251
|
+
|
|
252
|
+
An estimate from the two measurements above, not an A/B experiment:
|
|
253
|
+
|
|
254
|
+
| Step | Figure |
|
|
255
|
+
|:---|---:|
|
|
256
|
+
| Reading saved on a question `codeterrain` answers, large backend (4.4x to 6.9x fewer characters) | 77% to 86% |
|
|
257
|
+
| Lookups answered fully, in the field | 74% |
|
|
258
|
+
| Lookups answered partly (counted at half the saving) | 10% |
|
|
259
|
+
| **Inferred share of lookup reading saved on a large Python backend** | **about 61% to 68%** |
|
|
260
|
+
|
|
261
|
+
Lookups are only part of a session: reasoning, editing and test runs are untouched, and small code
|
|
262
|
+
bases save little. Tokens here are characters divided by four. A session-level A/B, the same lane run
|
|
263
|
+
with and without `codeterrain`, is the next measurement. The method and the per-repository results are in
|
|
264
|
+
[docs/why-codeterrain.md](https://github.com/benchli-sports/codeterrain/blob/main/docs/why-codeterrain.md).
|
|
265
|
+
|
|
266
|
+
## How it works
|
|
267
|
+
|
|
268
|
+
<picture>
|
|
269
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/architecture-dark.svg">
|
|
270
|
+
<img src="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/architecture-light.svg" alt="How it works: a codeterrain question is answered from a cache per repository, keyed on content, in under a second when warm. The static tier (fixtures and tests and the code index with Python's ast, front-end calls with tree-sitter or a tokenizer) fills it from changed files only and never runs the code. The import tier runs the code in a guarded subprocess and fills it on refresh with the setup order and the routes." width="760">
|
|
271
|
+
</picture>
|
|
272
|
+
|
|
273
|
+
- **The static tier** reads source with Python's `ast`, and front-end files with tree-sitter when the
|
|
274
|
+
TypeScript pack is installed (chosen for speed and accuracy) or a tokenizer written in Python when
|
|
275
|
+
it is not. It never imports or runs the target, and re-reads only the files that changed.
|
|
276
|
+
- **The import tier** needs the real framework: pytest's fixture resolution, FastAPI's route table. It
|
|
277
|
+
runs in a subprocess using the target's own interpreter, with an allowlisted environment, dummy cloud
|
|
278
|
+
credentials, no proxy and a best-effort network guard. Those stop accidents, not hostile code: it
|
|
279
|
+
imports and runs the target, so use it on code you trust.
|
|
280
|
+
- **The cache** lives outside the checkout, in the git common directory or your user cache, and is
|
|
281
|
+
keyed on content hashes.
|
|
282
|
+
|
|
283
|
+
More in [docs/how-it-works.md](https://github.com/benchli-sports/codeterrain/blob/main/docs/how-it-works.md), [docs/sandbox.md](https://github.com/benchli-sports/codeterrain/blob/main/docs/sandbox.md) and
|
|
284
|
+
[SECURITY.md](https://github.com/benchli-sports/codeterrain/blob/main/SECURITY.md).
|
|
285
|
+
|
|
286
|
+
## Why this exists
|
|
287
|
+
|
|
288
|
+
codeterrain started inside [benchli](https://www.benchli.com), the team sports platform built by Mitbolas GmbH. Its Python backend
|
|
289
|
+
is a large FastAPI service with a very large test suite, developed with coding agents. Most of the
|
|
290
|
+
agents' tokens went on re-mapping the same structure every session, and a hand-kept map went stale
|
|
291
|
+
within weeks. The goal became exact answers, straight from the code, locally and in under a second, in
|
|
292
|
+
every repository benchli and Mitbolas develop with agents. The full story, and why we did not adopt a
|
|
293
|
+
knowledge-graph tool, are in [docs/why-codeterrain.md](https://github.com/benchli-sports/codeterrain/blob/main/docs/why-codeterrain.md).
|
|
294
|
+
|
|
295
|
+
## Documentation
|
|
296
|
+
|
|
297
|
+
| | |
|
|
298
|
+
|:---|:---|
|
|
299
|
+
| [Commands](https://github.com/benchli-sports/codeterrain/blob/main/docs/commands.md) | Every command, option and output |
|
|
300
|
+
| [Configuration](https://github.com/benchli-sports/codeterrain/blob/main/docs/configuration.md) | Precedence, every key with its default, monorepos and non-default layouts |
|
|
301
|
+
| [How it works](https://github.com/benchli-sports/codeterrain/blob/main/docs/how-it-works.md) | The two tiers, the cache, extractor programs |
|
|
302
|
+
| [The sandbox](https://github.com/benchli-sports/codeterrain/blob/main/docs/sandbox.md) | What the import tier runs with, and every gap in the network guard |
|
|
303
|
+
| [Limits](https://github.com/benchli-sports/codeterrain/blob/main/docs/limits.md) | What a static walk does not see, and the bounds set on purpose |
|
|
304
|
+
| [Security](https://github.com/benchli-sports/codeterrain/blob/main/SECURITY.md) | The trust model: what is guaranteed and what is best-effort |
|
|
305
|
+
| [Extractor protocol](https://github.com/benchli-sports/codeterrain/blob/main/docs/extractor-protocol.md) | Writing an extractor or a language pack for another language or framework |
|
|
306
|
+
| [Terraform pack](https://github.com/benchli-sports/codeterrain/blob/main/packages/codeterrain-terraform/README.md) | `.tf` files in `def` and `refs`: resources, modules, providers |
|
|
307
|
+
| [TypeScript pack](https://github.com/benchli-sports/codeterrain/blob/main/packages/codeterrain-typescript/README.md) | Front-end calls read with tree-sitter, and why it is the reader to install |
|
|
308
|
+
| [Design notes](https://github.com/benchli-sports/codeterrain/blob/main/docs/design-v0.2.md) | The decisions behind the front-end calls and the language packs |
|
|
309
|
+
| [Why codeterrain](https://github.com/benchli-sports/codeterrain/blob/main/docs/why-codeterrain.md) | The origin, the measurements, what is not proven yet |
|
|
310
|
+
| [Prior art](https://github.com/benchli-sports/codeterrain/blob/main/docs/prior-art.md) | What twelve comparable tools document answering, read from their docs |
|
|
311
|
+
| [Changelog](https://github.com/benchli-sports/codeterrain/blob/main/CHANGELOG.md) | What changed, release by release |
|
|
312
|
+
|
|
313
|
+
## Development
|
|
314
|
+
|
|
315
|
+
```sh
|
|
316
|
+
uv run --extra dev ruff check .
|
|
317
|
+
uv run --extra dev pytest
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Run pytest as a single process (no `-n`). The suite spawns real pytest and interpreter subprocesses, and
|
|
321
|
+
a few tests skip on Windows (no `sendmsg`, no unix-domain sockets). CI also runs it against FastAPI below
|
|
322
|
+
and above 0.137 and pytest 8 and 9. Contributor and review rules are in [AGENTS.md](https://github.com/benchli-sports/codeterrain/blob/main/AGENTS.md).
|
|
323
|
+
|
|
324
|
+
Copyright Mitbolas GmbH. Licensed under Apache-2.0 (see LICENSE and NOTICE).
|