chore(greptile): load nested AGENTS.md files as review context scoped to their directories (#45636)

* chore(greptile): load nested AGENTS.md files as review context scoped to their directories

* refactor(greptile): use a stdlib dataclass so the files.json check runs without uv

* test(greptile): write fixture AGENTS.md files through a helper instead of rebinding a loop variable
This commit is contained in:
Mateo Wang 2026-10-09 13:22:29 -07:00 • committed by GitHub
parent bbc8d66323
commit 3c56cd31f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 695 additions and 0 deletions

View file

@ -593,6 +593,12 @@ jobs:
- name: check_migrations_no_data_rewrites
run: uv run --no-sync python ./tests/code_coverage_tests/check_migrations_no_data_rewrites.py
- name: check_greptile_files_json
run: uv run --no-sync python ./tests/code_coverage_tests/check_greptile_files_json.py
- name: test_check_greptile_files_json
run: uv run --no-sync pytest -q --noconftest -p no:cacheprovider -c /dev/null tests/code_coverage_tests/test_check_greptile_files_json.py
- name: check_no_publicly_known_master_key
run: uv run --no-sync python ./tests/code_coverage_tests/check_no_publicly_known_master_key.py

543
.greptile/files.json Normal file
View file

@ -0,0 +1,543 @@
{
"files": [
{
"path": "litellm/_v2/AGENTS.md",
"description": "Conventions for code under litellm/_v2/",
"scope": [
"litellm/_v2/**"
]
},
{
"path": "litellm/_v2/cache/AGENTS.md",
"description": "Conventions for code under litellm/_v2/cache/",
"scope": [
"litellm/_v2/cache/**"
]
},
{
"path": "litellm/proxy/_experimental/mcp_server/AGENTS.md",
"description": "Conventions for code under litellm/proxy/_experimental/mcp_server/",
"scope": [
"litellm/proxy/_experimental/mcp_server/**"
]
},
{
"path": "litellm/rust_bridge/AGENTS.md",
"description": "Conventions for code under litellm/rust_bridge/",
"scope": [
"litellm/rust_bridge/**"
]
},
{
"path": "litellm/rust_bridge/trace/AGENTS.md",
"description": "Conventions for code under litellm/rust_bridge/trace/",
"scope": [
"litellm/rust_bridge/trace/**"
]
},
{
"path": "litellm/tracing/AGENTS.md",
"description": "Conventions for code under litellm/tracing/",
"scope": [
"litellm/tracing/**"
]
},
{
"path": "litellm-rust/AGENTS.md",
"description": "Conventions for code under litellm-rust/",
"scope": [
"litellm-rust/**"
]
},
{
"path": "litellm-rust/crates/cache-response/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/cache-response/",
"scope": [
"litellm-rust/crates/cache-response/**"
]
},
{
"path": "litellm-rust/crates/callbacks-legacy-python/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/callbacks-legacy-python/",
"scope": [
"litellm-rust/crates/callbacks-legacy-python/**"
]
},
{
"path": "litellm-rust/crates/coroutine/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/coroutine/",
"scope": [
"litellm-rust/crates/coroutine/**"
]
},
{
"path": "litellm-rust/crates/gateway/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/gateway/",
"scope": [
"litellm-rust/crates/gateway/**"
]
},
{
"path": "litellm-rust/crates/gateway-auth/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/gateway-auth/",
"scope": [
"litellm-rust/crates/gateway-auth/**"
]
},
{
"path": "litellm-rust/crates/gateway-inference/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/gateway-inference/",
"scope": [
"litellm-rust/crates/gateway-inference/**"
]
},
{
"path": "litellm-rust/crates/gateway-mcp/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/gateway-mcp/",
"scope": [
"litellm-rust/crates/gateway-mcp/**"
]
},
{
"path": "litellm-rust/crates/host/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/host/",
"scope": [
"litellm-rust/crates/host/**"
]
},
{
"path": "litellm-rust/crates/host/src/machine/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/host/src/machine/",
"scope": [
"litellm-rust/crates/host/src/machine/**"
]
},
{
"path": "litellm-rust/crates/host-http/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/host-http/",
"scope": [
"litellm-rust/crates/host-http/**"
]
},
{
"path": "litellm-rust/crates/host-native/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/host-native/",
"scope": [
"litellm-rust/crates/host-native/**"
]
},
{
"path": "litellm-rust/crates/host-python/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/host-python/",
"scope": [
"litellm-rust/crates/host-python/**"
]
},
{
"path": "litellm-rust/crates/http/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/http/",
"scope": [
"litellm-rust/crates/http/**"
]
},
{
"path": "litellm-rust/crates/inference/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/inference/",
"scope": [
"litellm-rust/crates/inference/**"
]
},
{
"path": "litellm-rust/crates/inference-chat/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/inference-chat/",
"scope": [
"litellm-rust/crates/inference-chat/**"
]
},
{
"path": "litellm-rust/crates/inference-messages/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/inference-messages/",
"scope": [
"litellm-rust/crates/inference-messages/**"
]
},
{
"path": "litellm-rust/crates/inference-ocr/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/inference-ocr/",
"scope": [
"litellm-rust/crates/inference-ocr/**"
]
},
{
"path": "litellm-rust/crates/inference-responses/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/inference-responses/",
"scope": [
"litellm-rust/crates/inference-responses/**"
]
},
{
"path": "litellm-rust/crates/inference-transcription/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/inference-transcription/",
"scope": [
"litellm-rust/crates/inference-transcription/**"
]
},
{
"path": "litellm-rust/crates/llms/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms/",
"scope": [
"litellm-rust/crates/llms/**"
]
},
{
"path": "litellm-rust/crates/llms/src/anthropic/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms/src/anthropic/",
"scope": [
"litellm-rust/crates/llms/src/anthropic/**"
]
},
{
"path": "litellm-rust/crates/llms/src/anthropic/batches/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms/src/anthropic/batches/",
"scope": [
"litellm-rust/crates/llms/src/anthropic/batches/**"
]
},
{
"path": "litellm-rust/crates/llms/src/anthropic/count_tokens/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms/src/anthropic/count_tokens/",
"scope": [
"litellm-rust/crates/llms/src/anthropic/count_tokens/**"
]
},
{
"path": "litellm-rust/crates/llms/src/anthropic/messages/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms/src/anthropic/messages/",
"scope": [
"litellm-rust/crates/llms/src/anthropic/messages/**"
]
},
{
"path": "litellm-rust/crates/llms/src/aws_textract/ocr/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms/src/aws_textract/ocr/",
"scope": [
"litellm-rust/crates/llms/src/aws_textract/ocr/**"
]
},
{
"path": "litellm-rust/crates/llms/src/azure_ai/messages/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms/src/azure_ai/messages/",
"scope": [
"litellm-rust/crates/llms/src/azure_ai/messages/**"
]
},
{
"path": "litellm-rust/crates/llms/src/base_llm/messages/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms/src/base_llm/messages/",
"scope": [
"litellm-rust/crates/llms/src/base_llm/messages/**"
]
},
{
"path": "litellm-rust/crates/llms/src/bedrock/messages/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms/src/bedrock/messages/",
"scope": [
"litellm-rust/crates/llms/src/bedrock/messages/**"
]
},
{
"path": "litellm-rust/crates/llms-types/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms-types/",
"scope": [
"litellm-rust/crates/llms-types/**"
]
},
{
"path": "litellm-rust/crates/llms-types/src/formats/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms-types/src/formats/",
"scope": [
"litellm-rust/crates/llms-types/src/formats/**"
]
},
{
"path": "litellm-rust/crates/llms-types/src/formats/messages/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms-types/src/formats/messages/",
"scope": [
"litellm-rust/crates/llms-types/src/formats/messages/**"
]
},
{
"path": "litellm-rust/crates/llms-types/src/providers/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/llms-types/src/providers/",
"scope": [
"litellm-rust/crates/llms-types/src/providers/**"
]
},
{
"path": "litellm-rust/crates/model-catalog/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/model-catalog/",
"scope": [
"litellm-rust/crates/model-catalog/**"
]
},
{
"path": "litellm-rust/crates/python-bridge/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/python-bridge/",
"scope": [
"litellm-rust/crates/python-bridge/**"
]
},
{
"path": "litellm-rust/crates/python-bridge/src/cache/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/python-bridge/src/cache/",
"scope": [
"litellm-rust/crates/python-bridge/src/cache/**"
]
},
{
"path": "litellm-rust/crates/python-bridge/src/cache/native/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/python-bridge/src/cache/native/",
"scope": [
"litellm-rust/crates/python-bridge/src/cache/native/**"
]
},
{
"path": "litellm-rust/crates/python-bridge/src/cache/python/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/python-bridge/src/cache/python/",
"scope": [
"litellm-rust/crates/python-bridge/src/cache/python/**"
]
},
{
"path": "litellm-rust/crates/python-bridge/src/routes/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/python-bridge/src/routes/",
"scope": [
"litellm-rust/crates/python-bridge/src/routes/**"
]
},
{
"path": "litellm-rust/crates/python-compat/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/python-compat/",
"scope": [
"litellm-rust/crates/python-compat/**"
]
},
{
"path": "litellm-rust/crates/secrets-aws/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/secrets-aws/",
"scope": [
"litellm-rust/crates/secrets-aws/**"
]
},
{
"path": "litellm-rust/crates/secrets-azure/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/secrets-azure/",
"scope": [
"litellm-rust/crates/secrets-azure/**"
]
},
{
"path": "litellm-rust/crates/secrets-cyberark/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/secrets-cyberark/",
"scope": [
"litellm-rust/crates/secrets-cyberark/**"
]
},
{
"path": "litellm-rust/crates/secrets-google/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/secrets-google/",
"scope": [
"litellm-rust/crates/secrets-google/**"
]
},
{
"path": "litellm-rust/crates/secrets-hashicorp/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/secrets-hashicorp/",
"scope": [
"litellm-rust/crates/secrets-hashicorp/**"
]
},
{
"path": "litellm-rust/crates/storage-clickhouse/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/storage-clickhouse/",
"scope": [
"litellm-rust/crates/storage-clickhouse/**"
]
},
{
"path": "litellm-rust/crates/traces/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/traces/",
"scope": [
"litellm-rust/crates/traces/**"
]
},
{
"path": "litellm-rust/crates/traces/src/normalize/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/traces/src/normalize/",
"scope": [
"litellm-rust/crates/traces/src/normalize/**"
]
},
{
"path": "litellm-rust/crates/traces/src/normalize/format/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/traces/src/normalize/format/",
"scope": [
"litellm-rust/crates/traces/src/normalize/format/**"
]
},
{
"path": "litellm-rust/crates/traces/src/normalize/instrumentation/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/traces/src/normalize/instrumentation/",
"scope": [
"litellm-rust/crates/traces/src/normalize/instrumentation/**"
]
},
{
"path": "litellm-rust/crates/traces/src/otlp/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/traces/src/otlp/",
"scope": [
"litellm-rust/crates/traces/src/otlp/**"
]
},
{
"path": "litellm-rust/crates/traces/src/resolve/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/traces/src/resolve/",
"scope": [
"litellm-rust/crates/traces/src/resolve/**"
]
},
{
"path": "litellm-rust/crates/traces-cache/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/traces-cache/",
"scope": [
"litellm-rust/crates/traces-cache/**"
]
},
{
"path": "litellm-rust/crates/traces-clickhouse/AGENTS.md",
"description": "Conventions for code under litellm-rust/crates/traces-clickhouse/",
"scope": [
"litellm-rust/crates/traces-clickhouse/**"
]
},
{
"path": "litellm-rust/docs/adrs/AGENTS.md",
"description": "Conventions for code under litellm-rust/docs/adrs/",
"scope": [
"litellm-rust/docs/adrs/**"
]
},
{
"path": "tests/AGENTS.md",
"description": "Conventions for code under tests/",
"scope": [
"tests/**"
]
},
{
"path": "tests/e2e/AGENTS.md",
"description": "Conventions for code under tests/e2e/",
"scope": [
"tests/e2e/**"
]
},
{
"path": "tests/e2e_harness/AGENTS.md",
"description": "Conventions for code under tests/e2e_harness/",
"scope": [
"tests/e2e_harness/**"
]
},
{
"path": "tests/integration/AGENTS.md",
"description": "Conventions for code under tests/integration/",
"scope": [
"tests/integration/**"
]
},
{
"path": "tests/integration/translation/AGENTS.md",
"description": "Conventions for code under tests/integration/translation/",
"scope": [
"tests/integration/translation/**"
]
},
{
"path": "tests/rust-python-harness/AGENTS.md",
"description": "Conventions for code under tests/rust-python-harness/",
"scope": [
"tests/rust-python-harness/**"
]
},
{
"path": "tests/rust-python-harness/strategies/e2e_parity/AGENTS.md",
"description": "Conventions for code under tests/rust-python-harness/strategies/e2e_parity/",
"scope": [
"tests/rust-python-harness/strategies/e2e_parity/**"
]
},
{
"path": "tests/rust-python-harness/strategies/trace_parity/AGENTS.md",
"description": "Conventions for code under tests/rust-python-harness/strategies/trace_parity/",
"scope": [
"tests/rust-python-harness/strategies/trace_parity/**"
]
},
{
"path": "tests/rust-python-harness/strategies/unit_tests_parity/AGENTS.md",
"description": "Conventions for code under tests/rust-python-harness/strategies/unit_tests_parity/",
"scope": [
"tests/rust-python-harness/strategies/unit_tests_parity/**"
]
},
{
"path": "tests/rust-python-harness/strategies/unit_tests_rust/AGENTS.md",
"description": "Conventions for code under tests/rust-python-harness/strategies/unit_tests_rust/",
"scope": [
"tests/rust-python-harness/strategies/unit_tests_rust/**"
]
},
{
"path": "tests/test_litellm_rust/AGENTS.md",
"description": "Conventions for code under tests/test_litellm_rust/",
"scope": [
"tests/test_litellm_rust/**"
]
},
{
"path": "tests/unit/AGENTS.md",
"description": "Conventions for code under tests/unit/",
"scope": [
"tests/unit/**"
]
},
{
"path": "tests/unit/rust_bridge/AGENTS.md",
"description": "Conventions for code under tests/unit/rust_bridge/",
"scope": [
"tests/unit/rust_bridge/**"
]
},
{
"path": "ui/litellm-dashboard/AGENTS.md",
"description": "Conventions for code under ui/litellm-dashboard/",
"scope": [
"ui/litellm-dashboard/**"
]
},
{
"path": "ui/litellm-dashboard/src/components/chat/AGENTS.md",
"description": "Conventions for code under ui/litellm-dashboard/src/components/chat/",
"scope": [
"ui/litellm-dashboard/src/components/chat/**"
]
},
{
"path": "ui/litellm-dashboard/src/components/lens/traces/AGENTS.md",
"description": "Conventions for code under ui/litellm-dashboard/src/components/lens/traces/",
"scope": [
"ui/litellm-dashboard/src/components/lens/traces/**"
]
}
]
}

View file

@ -0,0 +1,71 @@
from __future__ import annotations
import dataclasses
import difflib
import json
import os
import subprocess
import sys
from pathlib import Path, PurePosixPath
from typing import Final
REPO_ROOT: Final = Path(__file__).resolve().parents[2]
FILES_JSON: Final = PurePosixPath(".greptile/files.json")
WRITE_FLAG: Final = "--write"
@dataclasses.dataclass(frozen=True, slots=True)
class _ContextFile:
path: str
description: str
scope: tuple[str, ...]
def _nested_agents_md_paths(repo_root: Path) -> tuple[PurePosixPath, ...]:
result: Final = subprocess.run(
["git", "-C", os.fspath(repo_root), "ls-files", "-z", "--", "*/AGENTS.md"],
check=True,
stdout=subprocess.PIPE,
)
return tuple(sorted(PurePosixPath(os.fsdecode(path)) for path in result.stdout.split(b"\0") if path))
def _context_file(agents_md: PurePosixPath) -> _ContextFile:
return _ContextFile(
path=str(agents_md),
description=f"Conventions for code under {agents_md.parent}/",
scope=(f"{agents_md.parent}/**",),
)
def render(repo_root: Path) -> str:
files: Final = tuple(dataclasses.asdict(_context_file(path)) for path in _nested_agents_md_paths(repo_root))
return json.dumps({"files": files}, indent=2) + "\n"
def main(argv: tuple[str, ...]) -> int:
expected: Final = render(REPO_ROOT)
target: Final = REPO_ROOT / FILES_JSON
if WRITE_FLAG in argv:
target.parent.mkdir(exist_ok=True)
target.write_text(expected)
print(f"Wrote {FILES_JSON}")
return 0
actual: Final = target.read_text() if target.is_file() else ""
if actual == expected:
print(f"{FILES_JSON} lists every nested AGENTS.md")
return 0
sys.stdout.writelines(
difflib.unified_diff(
actual.splitlines(keepends=True), expected.splitlines(keepends=True), str(FILES_JSON), "expected"
)
)
print(
f"\n{FILES_JSON} is out of sync with the nested AGENTS.md files. Regenerate it with:\n"
f" python {Path(__file__).resolve().relative_to(REPO_ROOT)} {WRITE_FLAG}"
)
return 1
if __name__ == "__main__":
sys.exit(main(tuple(sys.argv[1:])))

View file

@ -0,0 +1,75 @@
import json
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Final
GUARD: Final = Path(__file__).resolve().parent / "check_greptile_files_json.py"
FILES_JSON: Final = Path(".greptile/files.json")
def _git(repo: Path, *args: str) -> None:
subprocess.run(["git", "-C", str(repo), *args], check=True, capture_output=True)
def _write_agents_md(path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("# conventions\n")
def _repo_with(tmp_path: Path, files: tuple[str, ...]) -> Path:
guard_copy: Final = tmp_path / "tests" / "code_coverage_tests" / GUARD.name
guard_copy.parent.mkdir(parents=True)
shutil.copy(GUARD, guard_copy)
for relative_path in files:
_write_agents_md(tmp_path / relative_path)
_git(tmp_path, "init", "-q")
_git(tmp_path, "add", "-A")
return guard_copy
def _run(guard: Path, *args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run([sys.executable, "-I", str(guard), *args], capture_output=True, text=True)
def test_write_scopes_each_nested_agents_md_to_its_own_directory(tmp_path: Path) -> None:
guard: Final = _repo_with(tmp_path, ("AGENTS.md", "tests/AGENTS.md", "litellm/_v2/cache/AGENTS.md"))
assert _run(guard, "--write").returncode == 0
written: Final = json.loads((tmp_path / FILES_JSON).read_text())
assert [(entry["path"], entry["scope"]) for entry in written["files"]] == [
("litellm/_v2/cache/AGENTS.md", ["litellm/_v2/cache/**"]),
("tests/AGENTS.md", ["tests/**"]),
]
assert _run(guard).returncode == 0
def test_new_agents_md_missing_from_files_json_fails(tmp_path: Path) -> None:
guard: Final = _repo_with(tmp_path, ("tests/AGENTS.md",))
assert _run(guard, "--write").returncode == 0
_write_agents_md(tmp_path / "ui" / "AGENTS.md")
_git(tmp_path, "add", "-A")
result: Final = _run(guard)
assert result.returncode == 1
assert '+ "path": "ui/AGENTS.md",' in result.stdout
def test_deleted_agents_md_left_in_files_json_fails(tmp_path: Path) -> None:
guard: Final = _repo_with(tmp_path, ("tests/AGENTS.md", "ui/AGENTS.md"))
assert _run(guard, "--write").returncode == 0
_git(tmp_path, "rm", "-q", "-f", "ui/AGENTS.md")
result: Final = _run(guard)
assert result.returncode == 1
assert '- "path": "ui/AGENTS.md",' in result.stdout
def test_missing_files_json_fails(tmp_path: Path) -> None:
guard: Final = _repo_with(tmp_path, ("tests/AGENTS.md",))
assert _run(guard).returncode == 1