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
53 changes: 53 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -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.
36 changes: 36 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Summary

<!-- What does this PR change, and why? -->

## Related issues

<!-- e.g. Closes #123 -->

## 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)
37 changes: 37 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -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.
136 changes: 136 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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).
Loading