# AGENTS.md — Tabras Engineering conventions for any agent or contributor working in this repo. Read alongside `README.md` (the design spec). README says *what* to build; this says *how* to write it. ## Non-negotiables ### Tests accompany all code - No code lands without tests. A feature and its tests are one unit of work, written together — not "tests later." - The deterministic core (`primitives.py`, `budget.py`, `game.py`) is pure logic with no model calls — it is fully testable and MUST be fully tested. This is where bugs hide and where balance lives. - Model-dependent code (`generator.py`, `boss.py`) is tested by mocking the model: assert the tool-call schema, the deck-context wiring, and that engine numbers (not model output) set magnitudes. Never test against live model output. ### Coverage is the bloat metric - Track code coverage on every change. Treat uncovered lines as **bloat**: if coverage is 50%, half the code is untested weight that should either be tested or deleted. - Target high coverage on the deterministic core specifically — it's pure and has no excuse to be uncovered. - Coverage is a deletion signal, not just a test signal: uncovered code is a prompt to ask "is this needed at all?" ### Functions do one thing - One function, one responsibility. If a function does two things, it's two functions. - As simple as possible, complex only where the problem genuinely requires it. Reach for complexity reluctantly and only when the domain demands it. - A function you can't describe in one line (see comments) is doing too much — split it. ### Terse, readable code - Terseness in service of readability, never against it. Shorter is better *only* when it reads more clearly. - No cleverness that costs a reader a second look. Obvious beats impressive. - Delete aggressively. The best code is no code; the second best is code that's plainly necessary. ### Comments: one line, at the function line - One-line comment at the function definition stating what it does. That's the default. - No block comments narrating internals, no inline play-by-play. If the body needs explaining, the function is too complex — simplify it instead of annotating it. - Comments state *what/why* at the boundary, never *how* line-by-line (the code is the how). ## In practice - The one-line function comment and the "describe in one line" rule are the same rule: if you can't write the comment, the function isn't doing one thing. - Coverage + terseness + one-thing functions reinforce each other: small single-purpose functions are trivial to test, which drives coverage, which exposes bloat to delete. - When in doubt: smaller function, clearer name, a test, delete the rest. ## What this looks like ```python # Convert a card's energy cost into its point budget. def budget_for(cost: int) -> int: return cost ``` Single purpose, one-line comment at the definition, terse, testable. A companion test asserts the mapping. No internal commentary. ## Order of work (per the README build order) Each step ships with its tests before the next begins: `primitives.py` → `budget.py` → `generator.py` + `game.py` → **playtest** → `boss.py` → `draft.py` → art → fine-tune. Do not move to the next file until the current one is tested and covered.