diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..b763f7b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,53 @@ +name: Bug report +description: Report something that isn't working as expected +type: Bug +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to file a bug! Please search existing issues first. + Note: game **rules and cards** are defined in the + [mundane meta repo](https://github.com/letsbuilda/mundane), not here. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear description of the bug, including what you expected to happen instead. + validations: + required: true + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Minimal steps, commands, or an action sequence / request body that triggers it. + placeholder: | + 1. Start the API: `uv run uvicorn mundane.api.app:app` + 2. POST /games/{id}/actions with `{"type": "...", ...}` + 3. See error + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: false + - type: textarea + id: environment + attributes: + label: Environment + description: Output of `uv run python -V` and `uv --version`, plus your OS. + placeholder: | + - Python: 3.14.x + - uv: 0.11.x + - OS: ... + validations: + required: false + - type: textarea + id: logs + attributes: + label: Logs / traceback + description: Paste any relevant output. This is automatically formatted as code, so no backticks needed. + render: shell + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..716023f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Game rules & card catalog (spec) + url: https://github.com/letsbuilda/mundane + about: The rules (SPEC.md) and cards (CARDS.md) live in the mundane meta repo, not here. + - name: Documentation + url: https://docs.letsbuilda.dev/mundane/ + about: Read the project documentation. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..64b5a67 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,36 @@ +name: Feature request +description: Suggest an idea or enhancement +type: Feature +body: + - type: markdown + attributes: + value: | + Thanks for the suggestion! For changes to game **rules or cards**, please open an issue in + the [mundane meta repo](https://github.com/letsbuilda/mundane) instead — this repo is the + engine and API implementation. + - type: textarea + id: problem + attributes: + label: Problem / motivation + description: What are you trying to do, and what is missing or hard today? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: What would you like to happen? + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + validations: + required: false + - type: textarea + id: context + attributes: + label: Additional context + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0d9c876 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +## Summary + + + +## Related issues + + + +## Checklist + +- [ ] `uv run nox -s lints` passes (prek + ruff format + ruff check + `mypy --strict` + ty) +- [ ] `uv run nox -s tests` passes, and I added/updated tests for any behavior change +- [ ] Docs in `docs/` updated if public API or behavior changed +- [ ] `uv.lock` updated if dependencies changed +- [ ] Game rules stay in `engine/` and HTTP stays in `api/` (no cross-layer leaks) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..a1f0ba7 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,37 @@ +# GitHub Copilot instructions — mundane-backend + +General project context, setup, commands, and architecture live in [`/AGENTS.md`](../AGENTS.md); +read it first. This file adds Copilot-specific guidance, especially for **code review**. + +**In one line:** a Python 3.14 + Litestar game **engine** plus a thin HTTP **API**. All game rules +live in `engine/` (no HTTP knowledge); `api/` is the only HTTP layer. Managed with `uv`; linted with +ruff (`select = ALL`, line-length 120); type-checked with `mypy --strict`; numpy-style docstrings. + +## Code review focus + +When reviewing pull requests in this repository, prioritize: + +- **Layering.** Flag any import of `mundane.api` from `mundane.engine`, or HTTP/Litestar concerns + leaking into `engine/`. Rules belong in `engine/`, HTTP in `api/`. +- **The one door.** Every game state change must go through `engine/rules.py::apply_action`. Flag + mutations that bypass it. `apply_action` must check `_require(...)` preconditions *before* + mutating, so a rejected move leaves state unchanged — call out any reordering that mutates first. +- **Illegal moves.** Invalid actions must raise `IllegalAction` (mapped to HTTP 422), never crash + with an unhandled exception or return a partially-mutated state. +- **Action schemas.** `api/schemas.py` is pure translation — no game rules there. Keep the + tagged-union `type` tags in lockstep with engine actions and preserve the dict round-trip identity. +- **Types & docs.** Code must pass `mypy --strict`; public functions need numpy-style docstrings. + Flag missing type annotations or docstrings. +- **Style.** Expect ruff `select = ALL` (line-length 120). Inline suppressions need a reason comment + (`# noqa: CODE (why)`); don't approve unjustified `# noqa` / `# type: ignore`. +- **Tests.** Behavior changes need tests in `tests/`. Tests run in random order (`pytest-randomly`), + so flag order- or shared-state-dependent tests. +- **Dependencies.** If `pyproject.toml` dependencies change, `uv.lock` must be updated in the same PR. +- **Litestar 3 is pre-release** (pinned to git `main`): don't recommend deprecated or removed + Litestar APIs; match the pinned version. + +## When generating code + +Apply the same rules and mirror the existing terse, well-commented style. Prefer reusing `_require`, +`CARD_LIBRARY`, the `GameStore` interface, and the existing serializers over inventing new +abstractions. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..57b601a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,136 @@ +# AGENTS.md + +Guidance for AI coding agents working in **mundane-backend**. This is the canonical, full set of +agent instructions; tool-specific files (e.g. `.github/copilot-instructions.md`) defer to it. +Humans should start with [`README.md`](README.md) and [`CONTRIBUTING.md`](CONTRIBUTING.md). + +## Project overview + +mundane-backend is *Magic: The Gathering without the magic* — a turn-based game **engine** plus a +thin **HTTP API**. The engine is a referee: a whole game is a fold over a stream of *actions*, and +one function, `apply_action(state, action)`, validates each action against the current state and +then transitions it. The API is a thin shell that translates HTTP requests into engine actions; all +the rules live in the engine. + +- **Stack:** Python 3.14+, [Litestar](https://litestar.dev) 3 (pre-release, tracked from git + `main`), Pydantic, Uvicorn. Managed with [`uv`](https://docs.astral.sh/uv/). +- **Rules & cards spec** live in the meta repo, not here: + [SPEC.md](https://github.com/letsbuilda/mundane/blob/main/game-docs/SPEC.md) and + [CARDS.md](https://github.com/letsbuilda/mundane/blob/main/game-docs/CARDS.md). + +## Setup + +Requires Python 3.14+ and `uv` >= 0.11.8. + +```bash +uv sync --group tests --group dev # create the venv and install everything +``` + +## Build & run + +```bash +uv run python examples/demo.py # run the demo scenario +uv run uvicorn mundane.api.app:app --reload # serve the API (OpenAPI at /schema) +``` + +## Testing + +```bash +uv run pytest +# as CI runs it (coverage + reports): +uv run coverage run -m pytest -v --junitxml=junit.xml --alluredir allure-results +``` + +- Tests live in `tests/`. `pythonpath = ["examples"]` lets tests import the demo. +- `pytest-randomly` randomizes test order — **keep tests order-independent and free of shared + state.** `--strict-markers` is enabled. +- Add or update tests for every behavior change. + +## Lint, format & type-check + +Run the whole local suite in one shot: + +```bash +uv run nox -s lints +``` + +That runs, in order: `prek run --all-files`, `ruff format .`, `ruff check --fix .`, +`mypy --strict src/ tests/ examples/`, `ty check .`. Individual tools: + +```bash +uv run ruff format . # CI checks with --check +uv run ruff check --output-format=github . +uv run mypy --strict src/ # CI scope; nox also checks tests/ and examples/ +uv run ty check . +``` + +`.github/workflows/ci.yaml` is the source of truth for the checks that must pass. + +## Code style + +- **ruff** with `line-length = 120` and `select = ["ALL"]`. Documented ignores: global `CPY001`; + `tests/*` allow `S101`/`PLR2004`; `examples/*` allow `T201`/`INP001`; `docs/*` allow `INP001`. +- Don't add blanket suppressions. If a lint must be silenced, scope it and give a reason, matching + the repo idiom: `# noqa: CODE (reason)`. +- **Docstrings:** numpy convention (pydocstyle). Match the existing terse, explanatory voice. +- **Types:** all code must pass `mypy --strict`; the Pydantic mypy plugin is enabled. +- isort first-party package is `mundane`. + +## Architecture & layout + +``` +src/mundane/ + engine/ # the game, with NO HTTP knowledge + state.py # Card, CardType, Player, StackItem, GameState + actions.py # PlayCard, CastInstant, PassPriority, IllegalAction + rules.py # apply_action + helpers — the one door + cards.py # CARD_LIBRARY (id -> Card) + effect functions + serialize.py # state/action -> JSON-ready data + game.py # Game: state + action log + submit() / export() + api/ + app.py # Litestar app, in-memory GameStore, exception handler + schemas.py # action JSON (tagged union) -> action dataclasses +examples/demo.py # runnable scenario +tests/ # pytest suite +docs/ # Sphinx docs +``` + +Invariants to preserve when changing code: + +- **The engine never imports from `api/`.** All rules live in `engine/`; all HTTP lives in `api/`. + Keep Litestar/HTTP concerns out of the engine. +- **`apply_action` is the one door.** Every state change goes through + `engine/rules.py::apply_action`. It runs `_require(...)` preconditions *first* — raising + `IllegalAction` and **mutating nothing** on a bad move — and only then transitions the state in + place and returns it, so it composes as a reducer: `reduce(apply_action, actions, initial)`. Never + mutate before validating, and never let an illegal move leave a partially-mutated state. +- **Actions are a tagged union.** Each action body carries a `type` discriminator whose tags match + the engine's actions. `api/schemas.py::parse_action` is *pure translation* — no game rules — and + raises `IllegalAction` (which the app maps to **HTTP 422**) for unknown or malformed bodies. Keep + the tags in lockstep with the engine; the `action_to_dict` -> `parse_action` round-trip is the + identity (there's a test for it). +- The game store is an in-memory dict behind a small `GameStore` interface (`create`/`get`/`save`); + games are volatile. Prefer extending this interface over reaching around it. + +## Making changes + +- Add/adjust tests in `tests/` for behavior changes. +- Update the Sphinx docs in `docs/` when public API or behavior changes. +- If you change dependencies, run `uv lock` and commit `uv.lock` (the `uv-lock` pre-commit hook + enforces this). +- Keep changes focused; ensure `nox -s lints` and `nox -s tests` pass before pushing. + +## Gotchas + +- **Litestar 3 is pre-release**, pinned to a git `main` commit in `uv.lock`. APIs can shift between + versions — use current Litestar 3 patterns and don't reintroduce removed/deprecated APIs. +- CI resolves dependencies with the **lowest** compatible versions (`resolution-strategy: lowest`) + and an `exclude-newer` window; don't assume the newest library APIs are present. +- GitHub Actions workflows are linted by **zizmor**; keep `persist-credentials: false` and SHA-pinned + actions. +- Never commit secrets. + +## For humans + +See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the contributor workflow. The Code of Conduct and +Security policy are provided org-wide by [`letsbuilda/.github`](https://github.com/letsbuilda/.github). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..085b93a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,116 @@ +# Contributing to mundane + +Thanks for your interest in contributing! This guide covers how to set up, develop, and submit +changes. For a project overview and API walkthrough, see the [README](README.md). If you use an AI +coding assistant, point it at [`AGENTS.md`](AGENTS.md). + +## Code of Conduct + +This project follows the +[Contributor Covenant Code of Conduct](https://github.com/letsbuilda/.github/blob/main/CODE_OF_CONDUCT.md), +maintained org-wide for @letsbuilda. By participating, you agree to uphold it. + +## Prerequisites + +- **Python 3.14+** +- **[`uv`](https://docs.astral.sh/uv/) >= 0.11.8** + +Litestar 3 is still in development; it is tracked from `main` and pinned to a commit SHA in +`uv.lock`, so builds are reproducible. + +## Getting set up + +```bash +git clone https://github.com/letsbuilda/mundane-backend +cd mundane-backend +uv sync --group tests --group dev # create the venv and install everything +``` + +## Running it locally + +```bash +uv run python examples/demo.py # run the demo scenario +uv run uvicorn mundane.api.app:app --reload # serve the API; OpenAPI docs at /schema +``` + +The README has the endpoint table and `curl` examples for exercising the API. + +## Development workflow + +1. Create a branch off `main`. +2. Make your change, **with tests**. +3. Run the checks below until everything is green. +4. Open a pull request, fill in the template, and link any related issue. + +## Checks (run before you push) + +Run everything CI runs, in one shot: + +```bash +uv run nox -s lints # prek + ruff format + ruff check --fix + mypy --strict + ty +uv run nox -s tests # pytest (equivalently: uv run pytest) +``` + +Or install the git hooks so the fast checks run automatically on commit: + +```bash +uv run prek install +``` + +`.github/workflows/ci.yaml` is the source of truth for what must pass: lint, tests, coverage +(uploaded to Codecov), plus the docs build (`.github/workflows/docs.yaml`). + +## Code style + +- **Formatting & linting:** [ruff](https://docs.astral.sh/ruff/) with `line-length = 120` and + `select = ["ALL"]`. Auto-fix with `uv run ruff check --fix .` and `uv run ruff format .`. +- **Types:** all code must pass `uv run mypy --strict`. +- **Docstrings:** numpy convention. +- If you must silence a lint, scope it narrowly and add a reason: `# noqa: CODE (why)`. + +## Architecture you should respect + +mundane is split so the rules are testable without HTTP — please keep these boundaries: + +- `src/mundane/engine/` is the game and has **no HTTP knowledge**; `src/mundane/api/` is the only + HTTP layer. Don't import `api` from `engine`. +- Every state change goes through **`apply_action(state, action)`** in `engine/rules.py` — "the one + door". It validates preconditions first (rejecting illegal moves with `IllegalAction` and changing + nothing), then transitions. Add new rules there. +- HTTP action bodies are a **tagged union** keyed by `type`; `api/schemas.py` only translates (no + rules) and maps bad bodies to HTTP 422. Keep the tags in step with the engine's actions. + +`AGENTS.md` has the same boundaries spelled out in more detail. + +## Tests + +- Add or update tests under `tests/` for any behavior change. +- Tests run in **random order** (`pytest-randomly`) — don't rely on ordering or shared state. +- Run `uv run pytest` locally; CI runs them with coverage and uploads to Codecov. + +## Docs + +Docs are built with Sphinx from `docs/` and published to . +Update them when you change public API or behavior. Build locally: + +```bash +uv sync --group docs +uv run sphinx-build --builder dirhtml --nitpicky docs site +``` + +## Dependencies + +Dependencies are managed by uv. If you add or upgrade one, run `uv lock` and commit the updated +`uv.lock` (the `uv-lock` pre-commit hook enforces this). + +## Pull requests + +- Keep PRs focused and reasonably small; explain *what* changed and *why*. +- Make sure CI is green and the PR checklist is complete. +- Changes are reviewed by the code owner (see [`.github/CODEOWNERS`](.github/CODEOWNERS)). + +## Security + +Please **don't** open public issues for security vulnerabilities. Use the +[security policy](https://github.com/letsbuilda/mundane-backend/security/policy) (provided org-wide) +to report them privately.