Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,34 @@ ask first.**

Writing to *another* repository is not an exception either. Do it only when told to work there.

### Do not add top-level files or folders

**Never add a new directory or file at the repository root on your own.** The root layout is the
maintainer's. Work belongs inside an existing module or directory — Python under `api/`, the client
under `app/`, documents under `docs/<topic>/`, CI under `.github/`, developer scripts under `tools/`,
temporary files under the git-ignored `.tmp/`. If a new top-level entry seems necessary, propose it
(what, why, and which existing directories you ruled out) and wait for approval; then add it to the
list below in the same change.

Approved tracked root entries (`api/tests/test_repo_layout.py` fails on anything else):

| Entry | Purpose |
|---|---|
| `api/`, `app/` | The model-serving API (Python) and the client (Kotlin) |
| `docs/` | Documents and the GitHub Pages guide |
| `tools/` | Developer tooling (release sync, web bundle sync) |
| `benchmarks/` | Measurement scripts |
| `script/` | Desktop UI driving script for the native-image build (`script/drive-desktop.ps1`) |
| `gradle/`, `build.gradle.kts`, `settings.gradle.kts`, `gradle.properties`, `gradlew`, `gradlew.bat` | Gradle build |
| `pyproject.toml` | Python project (uv) |
| `.github/` | CI workflows |
| `.idea/` | Shared IDE settings (partly tracked) |
| `README.md`, `AGENTS.md`, `PROJECT.md`, `LICENSE.md` | Root documents |
| `.gitignore`, `.gitattributes`, `.env.example` | Git and environment templates |

Local, git-ignored entries that tools create are fine: `.tmp/`, `.worktrees/`, `.venv/`, `.gradle/`,
`.kotlin/`, `.env`, `local.properties`, `uv.lock`, `.claude/`, `.DS_Store`.

## 3. Worktrees link large artefacts instead of copying them

A worktree is a full checkout. Copying large untracked artefacts (prebuilt runtimes, vendored trees,
Expand Down
40 changes: 40 additions & 0 deletions api/tests/test_repo_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
The repository root holds only the entries AGENTS.md §2 approves; anything else is a new top-level
file or folder that was added without approval.
"""
import re
import subprocess
from pathlib import Path


ROOT = Path(__file__).resolve().parents[2]


def approved_entries():
agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
table = agents.split("Approved tracked root entries", 1)[1].split("\n\n", 2)[1]
entries = set()
for row in table.splitlines()[2:]: # skip the header and the separator
first_cell = row.split("|")[1]
entries.update(name.rstrip("/") for name in re.findall(r"`([^`]+)`", first_cell))
return entries


def tracked_root_entries():
names = subprocess.run(
["git", "ls-files"], cwd=ROOT, capture_output=True, text=True, check=True
).stdout.splitlines()
return {name.split("/", 1)[0] for name in names}


def test_every_tracked_root_entry_is_approved():
unapproved = tracked_root_entries() - approved_entries()

assert unapproved == set(), (
f"top-level entries not approved in AGENTS.md §2: {sorted(unapproved)} "
"— propose them and add them to the list, or move the work into an existing directory"
)


def test_the_approved_list_is_read():
assert {"api", "app", "docs", "AGENTS.md", "pyproject.toml"} <= approved_entries()
Loading