codeterrain 0.3.0__tar.gz → 0.3.1__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.1}/AGENTS.md +4 -3
- {codeterrain-0.3.0 → codeterrain-0.3.1}/CHANGELOG.md +23 -1
- {codeterrain-0.3.0 → codeterrain-0.3.1}/PKG-INFO +88 -59
- {codeterrain-0.3.0 → codeterrain-0.3.1}/README.md +87 -58
- {codeterrain-0.3.0 → codeterrain-0.3.1}/SECURITY.md +13 -12
- codeterrain-0.3.1/docs/charts/make_charts.py +391 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/commands.md +31 -31
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/configuration.md +8 -8
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/design-v0.2.md +3 -3
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/extractor-protocol.md +5 -5
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/how-it-works.md +4 -4
- codeterrain-0.3.1/docs/img/architecture-dark.svg +43 -0
- codeterrain-0.3.1/docs/img/architecture-light.svg +43 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/img/field-dark.svg +14 -13
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/img/field-light.svg +14 -13
- codeterrain-0.3.1/docs/img/prior-art-dark.svg +165 -0
- codeterrain-0.3.1/docs/img/prior-art-light.svg +165 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/img/speed-dark.svg +7 -7
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/img/speed-light.svg +7 -7
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/img/tokens-dark.svg +2 -2
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/img/tokens-light.svg +2 -2
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/limits.md +8 -8
- codeterrain-0.3.1/docs/prior-art.md +95 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/why-codeterrain.md +28 -22
- {codeterrain-0.3.0 → codeterrain-0.3.1}/packages/codeterrain-terraform/README.md +9 -10
- {codeterrain-0.3.0 → codeterrain-0.3.1}/packages/codeterrain-terraform/pyproject.toml +1 -1
- {codeterrain-0.3.0 → codeterrain-0.3.1}/packages/codeterrain-typescript/README.md +8 -9
- {codeterrain-0.3.0 → codeterrain-0.3.1}/packages/codeterrain-typescript/pyproject.toml +1 -1
- {codeterrain-0.3.0 → codeterrain-0.3.1}/pyproject.toml +2 -1
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/__init__.py +1 -1
- {codeterrain-0.3.0 → codeterrain-0.3.1}/uv.lock +3 -3
- codeterrain-0.3.0/docs/charts/make_charts.py +0 -239
- {codeterrain-0.3.0 → codeterrain-0.3.1}/.gitattributes +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/.github/workflows/ci.yml +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/.github/workflows/release.yml +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/.gitignore +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/CLAUDE.md +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/LICENSE +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/NOTICE +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/img/accuracy-dark.svg +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/img/accuracy-light.svg +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/img/logo.svg +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/docs/sandbox.md +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/packages/codeterrain-terraform/src/codeterrain_terraform/__init__.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/packages/codeterrain-terraform/tests/test_terraform.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/packages/codeterrain-typescript/src/codeterrain_typescript/__init__.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/packages/codeterrain-typescript/tests/test_typescript.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/_guard.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/_pytest_dump_plugin.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/_routes_runner.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/budget.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/cache.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/calls_join.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/calls_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/cli.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/code_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/config.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/executables.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/fixtures_order.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/fixtures_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/hookspecs.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/impact.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/languages.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/plugins/__init__.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/plugins/fastapi_routes.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/plugins/manifest_join.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/protocol.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/routes_view.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/sandbox.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/src/codeterrain/views.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/conftest.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/sample_repo/docs/route-access.json +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/sample_repo/pyproject.toml +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/sample_repo/sampleapp/__init__.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/sample_repo/sampleapp/main.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/sample_repo/sampleapp/other.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/sample_repo/tests/api/conftest.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/sample_repo/tests/api/test_items.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/sample_repo/tests/conftest.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/sample_repo/tests/test_root.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_budget.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_cache.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_calls_join.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_calls_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_cli.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_code_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_config.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_executables.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_fastapi_routes.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_fixtures_order.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_fixtures_static.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_hookspecs.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_impact.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_languages.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_manifest_join.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_protocol.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_pytest_dump_plugin.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_routes_runner.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_routes_view.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/tests/test_sandbox.py +0 -0
- {codeterrain-0.3.0 → codeterrain-0.3.1}/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,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.3.
|
|
3
|
+
## 0.3.1 (2026-11-02)
|
|
4
|
+
|
|
5
|
+
- The README's diagram of how it works is an image, as the charts are: PyPI does not render
|
|
6
|
+
Mermaid, so the 0.3.0 page showed its source.
|
|
7
|
+
- `codeterrain` is the command the docs use; `terrain` stays installed as a shorter name. Another
|
|
8
|
+
tool on PyPI installs a `terrain` command, and with both installed the last one wins.
|
|
9
|
+
- `codeterrain-terraform` 0.2.1 and `codeterrain-typescript` 0.1.1: their READMEs, which are their
|
|
10
|
+
PyPI pages, install from PyPI instead of saying the packs are not published. No code change.
|
|
11
|
+
- The README leads with `uv tool install codeterrain` from PyPI, and has a starting point for an
|
|
12
|
+
agent's instructions. Its claims are narrowed to what is measured: no "exact answers" headline;
|
|
13
|
+
`impact` lists the tests that statically reach a name, not the tests that would catch a change;
|
|
14
|
+
character counts measure an answer, not a session; the import tier's safeguards stop accidents,
|
|
15
|
+
not hostile code, so it is for code you trust. The field figures cover all 160 logged lookups,
|
|
16
|
+
with their period and how they were judged, two marked wrong among them.
|
|
17
|
+
- SECURITY.md and the rationale no longer describe the repository as private or a public release
|
|
18
|
+
as future work. SECURITY.md's third guarantee says what it covers: pytest and its known plugins
|
|
19
|
+
do not write into the repository by default; target code that opens a file by absolute path still
|
|
20
|
+
can.
|
|
21
|
+
- A prior-art page, `docs/prior-art.md`: what twelve comparable tools document answering, read from
|
|
22
|
+
their READMEs, with a chart. None documents pytest fixture resolution, front-end calls matched to
|
|
23
|
+
routes, or routes read from the application. It is a reading of their docs, not a benchmark.
|
|
24
|
+
|
|
25
|
+
## 0.3.0 (2026-11-02)
|
|
4
26
|
|
|
5
27
|
- `terrain refs` following an alias counts a read after an assignment on the same line
|
|
6
28
|
(`ALIAS = VALUE; print(ALIAS)`), and still not one inside the assignment's right-hand side.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codeterrain
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: terrain: answers about your code in under a second. Local, LLM-free structure facts for coding agents; Python first.
|
|
5
5
|
Project-URL: Homepage, https://github.com/benchli-sports/codeterrain
|
|
6
6
|
Project-URL: Issues, https://github.com/benchli-sports/codeterrain/issues
|
|
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
<h1 align="center">codeterrain</h1>
|
|
44
44
|
|
|
45
45
|
<p align="center">
|
|
46
|
-
<b><code>
|
|
46
|
+
<b><code>codeterrain</code>: answers about your code, in under a second.</b><br>
|
|
47
47
|
For coding agents, and the people working beside them.
|
|
48
48
|
</p>
|
|
49
49
|
|
|
@@ -63,34 +63,40 @@ Description-Content-Type: text/markdown
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
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
|
|
67
|
-
|
|
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
68
|
wrong guess costs a review round.
|
|
69
69
|
|
|
70
|
-
`
|
|
70
|
+
`codeterrain` answers those questions from a cache, the way the framework itself would answer them.
|
|
71
71
|
|
|
72
72
|
| | |
|
|
73
73
|
|:---|:---|
|
|
74
|
-
| **
|
|
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](docs/limits.md)). |
|
|
75
75
|
| **Fast** | Most warm answers take 0.3 to 0.5 s on a large backend, Python start-up included. |
|
|
76
|
-
| **
|
|
77
|
-
| **
|
|
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](SECURITY.md)). |
|
|
78
78
|
|
|
79
79
|
## Quick start
|
|
80
80
|
|
|
81
81
|
```sh
|
|
82
|
-
uv tool install
|
|
83
|
-
# or "
|
|
84
|
-
# to read front-end calls with tree-sitter, "[terraform,typescript]" for both
|
|
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
85
|
cd your-repo
|
|
86
|
-
|
|
86
|
+
codeterrain doctor # what it found, and which config it used
|
|
87
|
+
codeterrain fixtures # every pytest fixture, and which definition wins where
|
|
87
88
|
```
|
|
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
|
+
|
|
89
95
|
Ask about a function: where it is called, how each call passes its arguments, and which tests patch it.
|
|
90
|
-
This is `
|
|
96
|
+
This is `codeterrain` run on its own source:
|
|
91
97
|
|
|
92
98
|
```console
|
|
93
|
-
$
|
|
99
|
+
$ codeterrain refs is_test_module
|
|
94
100
|
function codeterrain.fixtures_static.is_test_module(rel) src/codeterrain/fixtures_static.py:327
|
|
95
101
|
6 calls (6 in code, 0 in tests), 0 with a shape problem, 0 used as a value, 0 patched
|
|
96
102
|
file:line in shape note
|
|
@@ -102,11 +108,12 @@ src/codeterrain/impact.py:272 _Fixtures.__init__ 1 posit
|
|
|
102
108
|
src/codeterrain/impact.py:424 _inherited_tests 1 positional
|
|
103
109
|
```
|
|
104
110
|
|
|
105
|
-
Ask which tests
|
|
106
|
-
(abridged)
|
|
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:
|
|
107
114
|
|
|
108
115
|
```console
|
|
109
|
-
$
|
|
116
|
+
$ codeterrain impact is_test_module --depth 2
|
|
110
117
|
17 tests reach codeterrain.fixtures_static.is_test_module (0 directly), through 15 callers within 2 levels; 2 tests patch it …
|
|
111
118
|
test depth how via
|
|
112
119
|
tests/test_cli.py::test_a_handler_is_matched_by_… 2 call at tests/test_cli.py:1186 …update <- …is_test_module
|
|
@@ -117,7 +124,7 @@ tests/test_cli.py::test_a_route_text_that_matches_… 2 call at tests/test
|
|
|
117
124
|
Ask what a fixture is and what depends on it (from the sample repository in `tests/sample_repo`):
|
|
118
125
|
|
|
119
126
|
```console
|
|
120
|
-
$
|
|
127
|
+
$ codeterrain fixture db
|
|
121
128
|
name: db
|
|
122
129
|
scope: session
|
|
123
130
|
autouse: no
|
|
@@ -137,21 +144,44 @@ The commands that import your application (`setup-order`, `routes`) run in the *
|
|
|
137
144
|
interpreter: pass `--python .venv/bin/python` or set `CODETERRAIN_PYTHON`. Add `--json` to any command
|
|
138
145
|
for the full data.
|
|
139
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
|
+
|
|
140
170
|
## What it answers
|
|
141
171
|
|
|
142
172
|
| Question | Command | What grep can't tell you |
|
|
143
173
|
|:---|:---|:---|
|
|
144
|
-
| Where is this defined? | `
|
|
145
|
-
| Who calls it, passes it, patches it? | `
|
|
146
|
-
| Who imports this module, and what does it import? | `
|
|
147
|
-
| Which tests
|
|
148
|
-
| Which fixtures does a test get, in what order? | `
|
|
149
|
-
| What fixtures exist, and who uses one? | `
|
|
150
|
-
| Where is a route handled, with what access? | `
|
|
151
|
-
| Which HTTP calls does a front end make? | `
|
|
152
|
-
| Which backend route does each call hit? | `
|
|
153
|
-
| Where is a Terraform resource, variable or module, and who reads it? | `
|
|
154
|
-
| What is the module tree, and which providers are pinned? | `
|
|
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 |
|
|
155
185
|
|
|
156
186
|
Every option is in **[docs/commands.md](docs/commands.md)**.
|
|
157
187
|
|
|
@@ -165,7 +195,7 @@ smaller Python code bases, an analytics project with a large fixture graph, and
|
|
|
165
195
|
<td align="center" width="25%"><h2>11 / 11</h2>test nodes where setup order equals <code>pytest --setup-plan</code></td>
|
|
166
196
|
<td align="center" width="25%"><h2>0.35 s</h2>median warm <code>fixtures</code> answer on the large backend</td>
|
|
167
197
|
<td align="center" width="25%"><h2>up to 8.8x</h2>fewer characters for an agent to read</td>
|
|
168
|
-
<td align="center" width="25%"><h2>
|
|
198
|
+
<td align="center" width="25%"><h2>160</h2>field lookups logged by coding agents; agent-assessed, not independently validated</td>
|
|
169
199
|
</tr>
|
|
170
200
|
</table>
|
|
171
201
|
|
|
@@ -195,18 +225,27 @@ than the grep; there the gain is the exact order, which grep can't show.
|
|
|
195
225
|
|
|
196
226
|
### In the field
|
|
197
227
|
|
|
198
|
-
Coding agents working on real tasks logged each `
|
|
199
|
-
was right, and what they would have done instead.
|
|
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%.
|
|
200
233
|
|
|
201
234
|
<picture>
|
|
202
235
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/field-dark.svg">
|
|
203
|
-
<img src="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/field-light.svg" alt="Outcomes of logged
|
|
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">
|
|
204
237
|
</picture>
|
|
205
238
|
|
|
206
|
-
- Each fully answered lookup saved the agent **about 1.
|
|
207
|
-
- **
|
|
208
|
-
|
|
209
|
-
|
|
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.
|
|
210
249
|
|
|
211
250
|
## Inferred token savings
|
|
212
251
|
|
|
@@ -214,41 +253,30 @@ An estimate from the two measurements above, not an A/B experiment:
|
|
|
214
253
|
|
|
215
254
|
| Step | Figure |
|
|
216
255
|
|:---|---:|
|
|
217
|
-
| Reading saved on a question `
|
|
218
|
-
| Lookups answered fully, in the field |
|
|
219
|
-
| Lookups answered partly (counted at half the saving) |
|
|
220
|
-
| **Inferred share of lookup reading saved on a large Python backend** | **about
|
|
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%** |
|
|
221
260
|
|
|
222
261
|
Lookups are only part of a session: reasoning, editing and test runs are untouched, and small code
|
|
223
262
|
bases save little. Tokens here are characters divided by four. A session-level A/B, the same lane run
|
|
224
|
-
with and without `
|
|
263
|
+
with and without `codeterrain`, is the next measurement. The method and the per-repository results are in
|
|
225
264
|
[docs/why-codeterrain.md](docs/why-codeterrain.md).
|
|
226
265
|
|
|
227
266
|
## How it works
|
|
228
267
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
subgraph static ["Static tier: reads files, never runs them"]
|
|
234
|
-
fixtures["fixtures and tests<br/>(ast)"]
|
|
235
|
-
code["code index<br/>(ast, SQLite)"]
|
|
236
|
-
calls["front-end HTTP calls<br/>(tree-sitter, or the JS/TS tokenizer)"]
|
|
237
|
-
end
|
|
238
|
-
subgraph sandbox ["Import tier: sandboxed subprocess"]
|
|
239
|
-
order["setup order<br/>(pytest --collect-only)"]
|
|
240
|
-
routes["routes<br/>(imports the apps)"]
|
|
241
|
-
end
|
|
242
|
-
static -->|incremental, changed files only| cache
|
|
243
|
-
sandbox -->|on refresh; served stamped stale| cache
|
|
244
|
-
```
|
|
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>
|
|
245
272
|
|
|
246
273
|
- **The static tier** reads source with Python's `ast`, and front-end files with tree-sitter when the
|
|
247
274
|
TypeScript pack is installed (chosen for speed and accuracy) or a tokenizer written in Python when
|
|
248
275
|
it is not. It never imports or runs the target, and re-reads only the files that changed.
|
|
249
276
|
- **The import tier** needs the real framework: pytest's fixture resolution, FastAPI's route table. It
|
|
250
277
|
runs in a subprocess using the target's own interpreter, with an allowlisted environment, dummy cloud
|
|
251
|
-
credentials, no proxy and a best-effort network guard.
|
|
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.
|
|
252
280
|
- **The cache** lives outside the checkout, in the git common directory or your user cache, and is
|
|
253
281
|
keyed on content hashes.
|
|
254
282
|
|
|
@@ -279,6 +307,7 @@ knowledge-graph tool, are in [docs/why-codeterrain.md](docs/why-codeterrain.md).
|
|
|
279
307
|
| [TypeScript pack](packages/codeterrain-typescript/README.md) | Front-end calls read with tree-sitter, and why it is the reader to install |
|
|
280
308
|
| [Design notes](docs/design-v0.2.md) | The decisions behind the front-end calls and the language packs |
|
|
281
309
|
| [Why codeterrain](docs/why-codeterrain.md) | The origin, the measurements, what is not proven yet |
|
|
310
|
+
| [Prior art](docs/prior-art.md) | What twelve comparable tools document answering, read from their docs |
|
|
282
311
|
| [Changelog](CHANGELOG.md) | What changed, release by release |
|
|
283
312
|
|
|
284
313
|
## Development
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1 align="center">codeterrain</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<b><code>
|
|
8
|
+
<b><code>codeterrain</code>: answers about your code, in under a second.</b><br>
|
|
9
9
|
For coding agents, and the people working beside them.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
@@ -25,34 +25,40 @@
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
27
|
A coding agent spends most of its reading on structure it has to rediscover every session: which
|
|
28
|
-
fixtures a test really gets, where a route is handled, who calls a function, which tests
|
|
29
|
-
|
|
28
|
+
fixtures a test really gets, where a route is handled, who calls a function, which tests reach
|
|
29
|
+
it. The answers are in the code, but finding them takes rounds of grep and file reads, and a
|
|
30
30
|
wrong guess costs a review round.
|
|
31
31
|
|
|
32
|
-
`
|
|
32
|
+
`codeterrain` answers those questions from a cache, the way the framework itself would answer them.
|
|
33
33
|
|
|
34
34
|
| | |
|
|
35
35
|
|:---|:---|
|
|
36
|
-
| **
|
|
36
|
+
| **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](docs/limits.md)). |
|
|
37
37
|
| **Fast** | Most warm answers take 0.3 to 0.5 s on a large backend, Python start-up included. |
|
|
38
|
-
| **
|
|
39
|
-
| **
|
|
38
|
+
| **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. |
|
|
39
|
+
| **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](SECURITY.md)). |
|
|
40
40
|
|
|
41
41
|
## Quick start
|
|
42
42
|
|
|
43
43
|
```sh
|
|
44
|
-
uv tool install
|
|
45
|
-
# or "
|
|
46
|
-
# to read front-end calls with tree-sitter, "[terraform,typescript]" for both
|
|
44
|
+
uv tool install codeterrain # Python 3.12+; the only dependency is pluggy
|
|
45
|
+
# or "codeterrain[terraform]" to add the Terraform language pack, "codeterrain[typescript]"
|
|
46
|
+
# to read front-end calls with tree-sitter, "codeterrain[terraform,typescript]" for both
|
|
47
47
|
cd your-repo
|
|
48
|
-
|
|
48
|
+
codeterrain doctor # what it found, and which config it used
|
|
49
|
+
codeterrain fixtures # every pytest fixture, and which definition wins where
|
|
49
50
|
```
|
|
50
51
|
|
|
52
|
+
The same command is also installed as `terrain`, a shorter name. Another tool on PyPI installs a
|
|
53
|
+
`terrain` command too; with both installed, the last one wins, while `codeterrain` is always this
|
|
54
|
+
one. To work on codeterrain itself, install from a checkout instead:
|
|
55
|
+
`uv tool install --editable <path-to-checkout>`.
|
|
56
|
+
|
|
51
57
|
Ask about a function: where it is called, how each call passes its arguments, and which tests patch it.
|
|
52
|
-
This is `
|
|
58
|
+
This is `codeterrain` run on its own source:
|
|
53
59
|
|
|
54
60
|
```console
|
|
55
|
-
$
|
|
61
|
+
$ codeterrain refs is_test_module
|
|
56
62
|
function codeterrain.fixtures_static.is_test_module(rel) src/codeterrain/fixtures_static.py:327
|
|
57
63
|
6 calls (6 in code, 0 in tests), 0 with a shape problem, 0 used as a value, 0 patched
|
|
58
64
|
file:line in shape note
|
|
@@ -64,11 +70,12 @@ src/codeterrain/impact.py:272 _Fixtures.__init__ 1 posit
|
|
|
64
70
|
src/codeterrain/impact.py:424 _inherited_tests 1 positional
|
|
65
71
|
```
|
|
66
72
|
|
|
67
|
-
Ask which tests
|
|
68
|
-
(abridged)
|
|
73
|
+
Ask which tests statically reach it, through its callers and the fixtures that call them
|
|
74
|
+
(abridged). A test that reaches a function does not necessarily check what you change, and an
|
|
75
|
+
empty list does not prove the change is untested:
|
|
69
76
|
|
|
70
77
|
```console
|
|
71
|
-
$
|
|
78
|
+
$ codeterrain impact is_test_module --depth 2
|
|
72
79
|
17 tests reach codeterrain.fixtures_static.is_test_module (0 directly), through 15 callers within 2 levels; 2 tests patch it …
|
|
73
80
|
test depth how via
|
|
74
81
|
tests/test_cli.py::test_a_handler_is_matched_by_… 2 call at tests/test_cli.py:1186 …update <- …is_test_module
|
|
@@ -79,7 +86,7 @@ tests/test_cli.py::test_a_route_text_that_matches_… 2 call at tests/test
|
|
|
79
86
|
Ask what a fixture is and what depends on it (from the sample repository in `tests/sample_repo`):
|
|
80
87
|
|
|
81
88
|
```console
|
|
82
|
-
$
|
|
89
|
+
$ codeterrain fixture db
|
|
83
90
|
name: db
|
|
84
91
|
scope: session
|
|
85
92
|
autouse: no
|
|
@@ -99,21 +106,44 @@ The commands that import your application (`setup-order`, `routes`) run in the *
|
|
|
99
106
|
interpreter: pass `--python .venv/bin/python` or set `CODETERRAIN_PYTHON`. Add `--json` to any command
|
|
100
107
|
for the full data.
|
|
101
108
|
|
|
109
|
+
## Telling your agent
|
|
110
|
+
|
|
111
|
+
An agent uses `codeterrain` when its instructions say when to. A starting point for `AGENTS.md` or
|
|
112
|
+
`CLAUDE.md`:
|
|
113
|
+
|
|
114
|
+
```markdown
|
|
115
|
+
## Code structure: ask codeterrain first
|
|
116
|
+
|
|
117
|
+
Before grepping or reading files for one of these, run `codeterrain` in the repository root
|
|
118
|
+
(local, under a second when warm; add `--json` for the full data):
|
|
119
|
+
|
|
120
|
+
- where a name is defined: `codeterrain def NAME`
|
|
121
|
+
- who calls, passes or patches it: `codeterrain refs NAME`
|
|
122
|
+
- which tests statically reach it: `codeterrain impact NAME` (a starting point, not proof of coverage)
|
|
123
|
+
- which fixtures a test gets, in order: `codeterrain setup-order NODEID`
|
|
124
|
+
- a fixture and what depends on it: `codeterrain fixture NAME`
|
|
125
|
+
- where a route is handled: `codeterrain route METHOD PATH`
|
|
126
|
+
- a front end's HTTP calls, and the backend routes they hit: `codeterrain calls`, `codeterrain callers`
|
|
127
|
+
|
|
128
|
+
When codeterrain says it cannot resolve something, fall back to grep. `setup-order` and the route
|
|
129
|
+
commands import the code: use them only on code you trust.
|
|
130
|
+
```
|
|
131
|
+
|
|
102
132
|
## What it answers
|
|
103
133
|
|
|
104
134
|
| Question | Command | What grep can't tell you |
|
|
105
135
|
|:---|:---|:---|
|
|
106
|
-
| Where is this defined? | `
|
|
107
|
-
| Who calls it, passes it, patches it? | `
|
|
108
|
-
| Who imports this module, and what does it import? | `
|
|
109
|
-
| Which tests
|
|
110
|
-
| Which fixtures does a test get, in what order? | `
|
|
111
|
-
| What fixtures exist, and who uses one? | `
|
|
112
|
-
| Where is a route handled, with what access? | `
|
|
113
|
-
| Which HTTP calls does a front end make? | `
|
|
114
|
-
| Which backend route does each call hit? | `
|
|
115
|
-
| Where is a Terraform resource, variable or module, and who reads it? | `
|
|
116
|
-
| What is the module tree, and which providers are pinned? | `
|
|
136
|
+
| Where is this defined? | `codeterrain def NAME` | Resolves a re-export to the real definition |
|
|
137
|
+
| 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 |
|
|
138
|
+
| 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 |
|
|
139
|
+
| 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 |
|
|
140
|
+
| Which fixtures does a test get, in what order? | `codeterrain setup-order NODEID` | pytest's own resolution, overrides and scopes included |
|
|
141
|
+
| What fixtures exist, and who uses one? | `codeterrain fixtures`, `codeterrain fixture NAME` | Which definition wins where, and every dependent |
|
|
142
|
+
| 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 |
|
|
143
|
+
| Which HTTP calls does a front end make? | `codeterrain calls` | Paths built from templates, constants and concatenation |
|
|
144
|
+
| Which backend route does each call hit? | `codeterrain callers` | Front-end calls matched to backend routes across repositories |
|
|
145
|
+
| 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 |
|
|
146
|
+
| 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 |
|
|
117
147
|
|
|
118
148
|
Every option is in **[docs/commands.md](docs/commands.md)**.
|
|
119
149
|
|
|
@@ -127,7 +157,7 @@ smaller Python code bases, an analytics project with a large fixture graph, and
|
|
|
127
157
|
<td align="center" width="25%"><h2>11 / 11</h2>test nodes where setup order equals <code>pytest --setup-plan</code></td>
|
|
128
158
|
<td align="center" width="25%"><h2>0.35 s</h2>median warm <code>fixtures</code> answer on the large backend</td>
|
|
129
159
|
<td align="center" width="25%"><h2>up to 8.8x</h2>fewer characters for an agent to read</td>
|
|
130
|
-
<td align="center" width="25%"><h2>
|
|
160
|
+
<td align="center" width="25%"><h2>160</h2>field lookups logged by coding agents; agent-assessed, not independently validated</td>
|
|
131
161
|
</tr>
|
|
132
162
|
</table>
|
|
133
163
|
|
|
@@ -157,18 +187,27 @@ than the grep; there the gain is the exact order, which grep can't show.
|
|
|
157
187
|
|
|
158
188
|
### In the field
|
|
159
189
|
|
|
160
|
-
Coding agents working on real tasks logged each `
|
|
161
|
-
was right, and what they would have done instead.
|
|
190
|
+
Coding agents working on real tasks logged each `codeterrain` lookup: what they asked, whether the answer
|
|
191
|
+
was right, and what they would have done instead. 160 lookups, 26 October to 2 November 2026, across
|
|
192
|
+
the code bases above. Each agent judged its own answer against what it then read or ran; nobody
|
|
193
|
+
checked the judgements independently. Every lookup has one outcome; the shares are rounded, so they
|
|
194
|
+
can add up to 101%.
|
|
162
195
|
|
|
163
196
|
<picture>
|
|
164
197
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/field-dark.svg">
|
|
165
|
-
<img src="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/field-light.svg" alt="Outcomes of logged
|
|
198
|
+
<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">
|
|
166
199
|
</picture>
|
|
167
200
|
|
|
168
|
-
- Each fully answered lookup saved the agent **about 1.
|
|
169
|
-
- **
|
|
170
|
-
|
|
171
|
-
|
|
201
|
+
- Each fully answered lookup saved the agent **about 1.8 tool calls**, by its own count.
|
|
202
|
+
- **Two answers were marked wrong.** In one, `fixture` answered a fixture of a live-test folder it
|
|
203
|
+
did not read with a unit-test fixture of the same name; `fixtures`, `fixture` and `doctor` now name
|
|
204
|
+
such a folder. The other, `routes` missing an app's support routes, was misjudged: a later lookup
|
|
205
|
+
found them in the view, below the token budget's cut, whose "more" notice a `grep` had removed.
|
|
206
|
+
- The partial answers came mostly from output cut by the token budget; the rest from a cache built
|
|
207
|
+
from a different checkout than the one being edited, reads `refs` does not see (through a module
|
|
208
|
+
alias, as a dict key), duck-typed calls, and route filters that matched less than expected.
|
|
209
|
+
- Most errors were the wrong command or arguments: `callers`, which is for routes, where `refs` was
|
|
210
|
+
meant.
|
|
172
211
|
|
|
173
212
|
## Inferred token savings
|
|
174
213
|
|
|
@@ -176,41 +215,30 @@ An estimate from the two measurements above, not an A/B experiment:
|
|
|
176
215
|
|
|
177
216
|
| Step | Figure |
|
|
178
217
|
|:---|---:|
|
|
179
|
-
| Reading saved on a question `
|
|
180
|
-
| Lookups answered fully, in the field |
|
|
181
|
-
| Lookups answered partly (counted at half the saving) |
|
|
182
|
-
| **Inferred share of lookup reading saved on a large Python backend** | **about
|
|
218
|
+
| Reading saved on a question `codeterrain` answers, large backend (4.4x to 6.9x fewer characters) | 77% to 86% |
|
|
219
|
+
| Lookups answered fully, in the field | 74% |
|
|
220
|
+
| Lookups answered partly (counted at half the saving) | 10% |
|
|
221
|
+
| **Inferred share of lookup reading saved on a large Python backend** | **about 61% to 68%** |
|
|
183
222
|
|
|
184
223
|
Lookups are only part of a session: reasoning, editing and test runs are untouched, and small code
|
|
185
224
|
bases save little. Tokens here are characters divided by four. A session-level A/B, the same lane run
|
|
186
|
-
with and without `
|
|
225
|
+
with and without `codeterrain`, is the next measurement. The method and the per-repository results are in
|
|
187
226
|
[docs/why-codeterrain.md](docs/why-codeterrain.md).
|
|
188
227
|
|
|
189
228
|
## How it works
|
|
190
229
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
subgraph static ["Static tier: reads files, never runs them"]
|
|
196
|
-
fixtures["fixtures and tests<br/>(ast)"]
|
|
197
|
-
code["code index<br/>(ast, SQLite)"]
|
|
198
|
-
calls["front-end HTTP calls<br/>(tree-sitter, or the JS/TS tokenizer)"]
|
|
199
|
-
end
|
|
200
|
-
subgraph sandbox ["Import tier: sandboxed subprocess"]
|
|
201
|
-
order["setup order<br/>(pytest --collect-only)"]
|
|
202
|
-
routes["routes<br/>(imports the apps)"]
|
|
203
|
-
end
|
|
204
|
-
static -->|incremental, changed files only| cache
|
|
205
|
-
sandbox -->|on refresh; served stamped stale| cache
|
|
206
|
-
```
|
|
230
|
+
<picture>
|
|
231
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/benchli-sports/codeterrain/main/docs/img/architecture-dark.svg">
|
|
232
|
+
<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">
|
|
233
|
+
</picture>
|
|
207
234
|
|
|
208
235
|
- **The static tier** reads source with Python's `ast`, and front-end files with tree-sitter when the
|
|
209
236
|
TypeScript pack is installed (chosen for speed and accuracy) or a tokenizer written in Python when
|
|
210
237
|
it is not. It never imports or runs the target, and re-reads only the files that changed.
|
|
211
238
|
- **The import tier** needs the real framework: pytest's fixture resolution, FastAPI's route table. It
|
|
212
239
|
runs in a subprocess using the target's own interpreter, with an allowlisted environment, dummy cloud
|
|
213
|
-
credentials, no proxy and a best-effort network guard.
|
|
240
|
+
credentials, no proxy and a best-effort network guard. Those stop accidents, not hostile code: it
|
|
241
|
+
imports and runs the target, so use it on code you trust.
|
|
214
242
|
- **The cache** lives outside the checkout, in the git common directory or your user cache, and is
|
|
215
243
|
keyed on content hashes.
|
|
216
244
|
|
|
@@ -241,6 +269,7 @@ knowledge-graph tool, are in [docs/why-codeterrain.md](docs/why-codeterrain.md).
|
|
|
241
269
|
| [TypeScript pack](packages/codeterrain-typescript/README.md) | Front-end calls read with tree-sitter, and why it is the reader to install |
|
|
242
270
|
| [Design notes](docs/design-v0.2.md) | The decisions behind the front-end calls and the language packs |
|
|
243
271
|
| [Why codeterrain](docs/why-codeterrain.md) | The origin, the measurements, what is not proven yet |
|
|
272
|
+
| [Prior art](docs/prior-art.md) | What twelve comparable tools document answering, read from their docs |
|
|
244
273
|
| [Changelog](CHANGELOG.md) | What changed, release by release |
|
|
245
274
|
|
|
246
275
|
## Development
|
|
@@ -11,16 +11,16 @@ What codeterrain does guarantee is about its own, parent process:
|
|
|
11
11
|
1. **It never executes target code.** The static tier (`fixtures`, `fixture`) reads files with `ast`
|
|
12
12
|
only. Every program it starts (`git`, the target interpreter) is resolved to an absolute path from
|
|
13
13
|
the absolute `PATH` entries, never from the current directory, never a `.bat` or `.cmd` on Windows,
|
|
14
|
-
and never a program a bare name finds inside the target repository or, for `
|
|
14
|
+
and never a program a bare name finds inside the target repository or, for `codeterrain callers`,
|
|
15
15
|
inside any front-end checkout it reads, whichever checkout it is asking about. The `git status` that
|
|
16
|
-
`
|
|
16
|
+
`codeterrain callers` runs in each checkout switches `core.fsmonitor` off, so a command named in that
|
|
17
17
|
checkout's `.git/config` is not run, and takes no optional locks, so it writes nothing there.
|
|
18
|
-
A language pack runs in
|
|
18
|
+
A language pack runs in codeterrain's own process. Before any pack is looked up, every import-path
|
|
19
19
|
entry inside the repository being read (and an empty or relative one) is removed, so neither a
|
|
20
20
|
pack, the parts of a namespace package nor anything a pack imports later resolves there; a pack
|
|
21
|
-
whose distribution or module still lies inside it is refused before it is imported. What
|
|
21
|
+
whose distribution or module still lies inside it is refused before it is imported. What codeterrain
|
|
22
22
|
itself runs from is the exception, when it lies inside the checkout: its virtual environment and
|
|
23
|
-
the folder its own code is imported from. A checkout inside
|
|
23
|
+
the folder its own code is imported from. A checkout inside codeterrain's environment is not trusted.
|
|
24
24
|
2. **It never hands the child anything you did not opt into.** The child's environment is an allowlist;
|
|
25
25
|
proxies, `PYTEST_ADDOPTS` and `PYTEST_PLUGINS` are dropped; AWS credentials are dummies; the home and
|
|
26
26
|
per-user data directories point at an empty directory; git reads no system or global config and has
|
|
@@ -28,10 +28,12 @@ What codeterrain does guarantee is about its own, parent process:
|
|
|
28
28
|
`pytest_args` and `respect_addopts` are honoured only from `--config` or your user config, and
|
|
29
29
|
ignored, with a warning, when the repository's `pyproject.toml` sets them. A user-config `pass_env`
|
|
30
30
|
applies to every repository you run on, trusted or not; a `--config` file scopes it to one run.
|
|
31
|
-
3. **It
|
|
31
|
+
3. **It does not have pytest write into the repository by default.** `addopts` are switched off,
|
|
32
32
|
`log_file` is blanked, and the known file-writing options of pytest and common plugins are refused,
|
|
33
33
|
from the project's options and from `pytest_args`. The child runs in a private temp directory,
|
|
34
|
-
outside the cache, which is removed afterwards.
|
|
34
|
+
outside the cache, which is removed afterwards. This stops reports and logs landing in the
|
|
35
|
+
checkout; it does not stop target code that opens a file by absolute path, which can write
|
|
36
|
+
anywhere you can.
|
|
35
37
|
4. **It ends the child and what it started.** A timeout kills the whole process tree. On Windows a job
|
|
36
38
|
object also kills what the child left behind after a normal exit; on POSIX its process group is.
|
|
37
39
|
|
|
@@ -76,12 +78,11 @@ passes the check.
|
|
|
76
78
|
|
|
77
79
|
| | |
|
|
78
80
|
|---|---|
|
|
79
|
-
| **Guaranteed** (each has a regression test) | the parent never runs target code or a repository-shipped executable; the environment allowlist; repository config cannot widen it; git in the child has no credential helper; no default writes into the target; tree kill on timeout and of stragglers after exit (except a POSIX grandchild that started its own session); redaction of the values codeterrain knows; terminal escaping; path confinement |
|
|
81
|
+
| **Guaranteed** (each has a regression test) | the parent never runs target code or a repository-shipped executable; the environment allowlist; repository config cannot widen it; git in the child has no credential helper; no default writes into the target by pytest or its known plugins; tree kill on timeout and of stragglers after exit (except a POSIX grandchild that started its own session); redaction of the values codeterrain knows; terminal escaping; path confinement |
|
|
80
82
|
| **Best-effort** | the network guard. It blocks accidental network calls from Python code in the child, and is not a boundary against hostile code: non-Python subprocesses, isolated-mode Python children, a freshly loaded `_socket`, target code that undoes the patch, `getfqdn`, the C socket class and C extensions are not covered. [docs/sandbox.md](docs/sandbox.md) lists every gap. |
|
|
81
|
-
| **Not covered** | secrets codeterrain does not know (a value the target reads from a file and echoes); loopback services, which are reachable by design; a POSIX grandchild that started its own session; hostile code reading the real home by absolute path or an OS credential store; a child that rewrites the cache it worked out from the root; a crash of a native parser a language pack uses (the TypeScript pack's tree-sitter parses front-end source in
|
|
83
|
+
| **Not covered** | secrets codeterrain does not know (a value the target reads from a file and echoes); loopback services, which are reachable by design; a POSIX grandchild that started its own session; hostile code reading the real home by absolute path or an OS credential store; a child that rewrites the cache it worked out from the root; a crash of a native parser a language pack uses (the TypeScript pack's tree-sitter parses front-end source in codeterrain's process: it runs nothing from that source, and a file over 2,000,000 bytes is not read, but a parser fault would end the command rather than report the file) |
|
|
82
84
|
|
|
83
85
|
## Reporting a vulnerability
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
open a public issue.
|
|
87
|
+
Report it privately on GitHub: on the repository's Security tab, choose "Report a vulnerability", and
|
|
88
|
+
include a reproduction if you have one. Do not open a public issue.
|