From 2c59cbd2c7e47774baa59a971c25a8ac935300b3 Mon Sep 17 00:00:00 2001 From: Jim Lancaster <40281233+zoltan57@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:23:26 -0500 Subject: [PATCH] Add AGENTS.md to make repo context auto-discoverable Nothing in this repo was auto-read by an agent at the start of a session. The .github/instructions files only attach once a matching file is edited, which is too late to steer strategy, and the canonical docs set is not discoverable without already knowing to look for it. AGENTS.md routes rather than duplicates: it states the authority order (docs/* first, docs/reviews/** explicitly non-canonical), the uv-only command set, the test-enforced boundaries, and the change protocol. The Traps section records failure modes this codebase has actually produced rather than generic advice: the message/detail split that leaked paths in one direction and degraded provenance in the other, the two competing atomicity invariants in workflows.py where the obvious simplification breaks multi-page durability, and the habit of changing a shared symbol without enumerating its consumers. Verified: cited test paths exist, full suite passes, ruff/ty clean. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2bd344a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,107 @@ +# AGENTS.md + +Orientation for AI agents working in this repository. This file is a **router**, not a spec: +it points at canonical authority and flags the traps that are expensive to discover by trial. +Where this file and `docs/*` disagree, `docs/*` wins. + +## What This Is + +A document transcription system that preserves durable archival records (Documents, Sources, +People) and executes page transcription asynchronously through vision/LLM providers. Its +defining constraint is **evidence**: every machine attempt is recorded append-only with +request/response provenance. Features that would lose, mutate, or obscure that history are +wrong regardless of how convenient they are. + +Stack: Python 3.12+ · FastAPI + NiceGUI · SQLModel/SQLAlchemy (SQLite-first, PostgreSQL- +compatible) · Pydantic V2 · asyncio worker · OpenRouter adapter. + +## Commands + +This is a `uv` project. **Nothing is on `PATH`** — `ruff`, `ty`, and `pytest` all require +`uv run`. Bare invocations fail with command-not-found. + +```bash +uv run ruff check . # lint (blocking in pre-commit) +uv run ruff format --check . # format (blocking in pre-commit) +uv run ty check # types (blocking in pre-commit) +uv run pytest -q -m "not external" # default verification run +``` + +`external` marks tests that hit live services; always exclude it unless explicitly asked. +All four commands are expected to pass clean — there is no tolerated baseline of failures. +If `ty` reports something, fix it or suppress it inline *with a rationale comment*; a bare +`ignore` will not survive review. + +## Authority Order + +Resolve every question in this order, and stop at the first that answers it: + +1. **`docs/*`** — canonical. Start at [`docs/index.md`](docs/index.md), which defines the + reading order. `docs/invariant/*` holds cross-version rules that outlive any release. +2. **`.github/instructions/*.md`** — active steering, auto-attached when you edit matching + paths. Covers services, UI, error handling, and documentation sync. +3. **`.github/skills/*`** — periodic audit procedures (code review, provenance, test + effectiveness). +4. **`tests/`** — deterministic enforcement. A guard test is the ground truth for whatever + rule it encodes. + +`docs/reviews/**` is **not** canonical. Those are dated, opinionated snapshots that were +accurate when written and may since have been fixed, superseded, or found wrong. + +## Layout + +| Path | Role | +| :--- | :--- | +| `src/transcription/ui/**`, `api/**` | Interface. No direct persistence access. | +| `src/transcription/services/**` | Domain logic and transaction ownership. | +| `src/transcription/db/**` | Models and persistence. | +| `src/transcription/providers/**` | Provider adapters; provider details stop here. | +| `src/transcription/worker.py` | Asyncio worker loop. | +| `tests/` | Includes boundary/contract guards, not just behavior tests. | + +## Enforced Boundaries + +These are not conventions — a test fails if you break them: + +- **No service-to-service imports** (`test_service_boundaries.py`). Compose in the caller. +- **No persistence access from pages/components** (`test_ui_boundaries.py`, allowlist-based). +- **No hand-rolled error notifications in UI** — use the shared error presenter. +- **No stringly-typed status literals** — use the enums (`test_model_contract_guards.py`). +- **Attempt history is append-only** (`test_v42_evidence.py`). +- **`docs/schema.md` stays field-accurate** with `db/models.py`. +- **Orphans are tracked, not tolerated** — `test_orphan_sweep.py` records each retained + orphan with rationale in `KNOWN_ORPHANS`. + +## Traps + +Non-obvious things that have already caused real bugs here: + +- **`AppError.message` vs `AppError.detail`.** `message` is user/API-facing and must stay + generic — never put exception text or filesystem paths in it. `detail` is internal-only and + is what reaches logs and `ExecutionAttempt.error_detail`. Putting root-cause data in + `message` leaks; removing it from `detail` silently degrades provenance. See + `docs/error_handling.md`. +- **Two competing atomicity invariants in `services/workflows.py`.** Intermediate pages must + commit individually (durability across a long multi-page job); the *final* page must commit + atomically with the terminal job status. Collapsing the batch into one transaction satisfies + the second and destroys the first. Both are guarded — `test_workflows_reliability.py` and + `tests/integration/test_pipeline_atomicity.py`. +- **Shared symbols have more consumers than the obvious one.** Before changing a model field, + exception attribute, or helper return value, grep for every consumer including tests. + Evidence and logging paths frequently read the same fields the UI does. +- **Import style:** ruff `isort` runs with `force-single-line = true`. One import per line. +- **Latent defects have ordering constraints.** Some code is unreachable only because of a + current setting or single-instance deployment. Fix it *before* the change that unblocks it, + not after. + +## Change Protocol + +- **Write the failing test first** for behavioral fixes, and confirm it actually fails for the + reason you think. Several bugs here were subtle enough that a test written afterward would + have passed against the broken code. +- **Update docs in the same change** when you alter a contract, behavior, or scope — see + `.github/instructions/documentation-sync.instructions.md`. +- **Do not commit unless asked.** Making a requested change is not consent to commit it. +- **Do not push or open PRs on your own initiative.** +- **Scope discipline:** fix what was asked plus what your change genuinely breaks. Pre-existing + unrelated issues are a separate conversation.