Imported from witchesofthehill/manabrew (
AGENTS.md). Install upstream withnpx skills add witchesofthehill/manabrew. Copyright stays with the author.
Manabrew — Agent Guide
Manabrew is a Tauri desktop/web client for Magic: The Gathering, powered by a Rust rewrite of the Forge rules engine.
- UI: React + TypeScript under
src/, Tauri shell undersrc-tauri/ - Engine: Rust workspace under
manabrew-rs/crates/, the rules engine being ported from Java - Java reference:
forge/— read-only; the source of truth for parity - Parity harness:
manabrew-rs/crates/parity/— runs Rust and Java side-by-side and compares traces
The engine is incomplete. Most day-to-day work is finding parity bugs with yarn parity and fixing them.
Local full stack
./dev start builds and starts the web app, relay, Hub API, and relay-event ingester through compose.dev.yaml. The Hub runs its embedded SQLite migrations before listening and stores the host-visible database at ops/hub-data/dev/hub.db; ingested relay analytics live at ops/hub-data/dev/events/events.db. ./dev stop preserves both databases and ./dev clean deletes them together with the development cache volumes. Keep the Hub events bind writable because SQLite read-only connections still need WAL sidecars. Host tools such as DBeaver must use read-only connections while the stack runs; stop the stack before host-side writes because Docker Desktop does not safely coordinate SQLite write locks across the bind mount. Use ./dev logs to follow all services. The web container runs yarn vite straight from the bind mount, so it serves whatever src/wasm/ was last built on the host; run yarn ensure:wasm on the host after any Rust change or the browser keeps the old engine. Browser-hosted Forge games run the Web Image staged in packages/forge-wasm/, a separate artifact from the harness jar that yarn build:harness does not refresh; run yarn build:forge-wasm after any harness change. start, stop, and logs take optional service names (./dev stop relay, ./dev start relay); a per-service stop pauses the container without removing it.
Hub migrations are immutable once any environment has applied them. Extend the schema with the next numbered migration so existing and fresh databases converge through the same sequence.
Prime directive: root-cause, not symptom
Every engine fix must restore long-term correctness of the underlying mechanic — not patch the one card that triggered the bug report. If a single card seems to need a special case, that is almost always wrong: the general rule lives somewhere in Forge — find it, port it, mirror it.
Before writing the fix:
- Read the corresponding Java file in
forge/forge-game/. - Identify the rule the Rust port is missing, not the symptom that exposed it.
- Mirror Forge's logic — same file names, same symbol names, same control flow.
Symptom-only fixes will be rejected in review. Full workflow: docs/agents/ENGINE_BUGFIX_WORKFLOW.md.
Code-quality discipline
The project is large; every line is a long-term liability. Before adding code:
- Read first. Inspect the Java counterpart and the existing Rust file. Use the
scan-feature-parityskill to confirm names/paths. - Mirror Java structure exactly. Same file names (snake_case), same module layout, same method names. Do not invent. See
docs/agents/PARITY_PHILOSOPHY.md. - Extend before creating. Prefer adding to the existing file that already mirrors the Java side over a new one.
- No premature abstraction. Three similar lines beat a clever generic. No defensive code at internal boundaries. No speculative error handling.
- Bound the change. A bugfix touching 12 files needs justification. If it can be one or two, do that.
- Do not add comments. This is a hard rule, not a preference — the maintainer has repeatedly rejected over-commented diffs. The default for any new code (fields, methods, constants, blocks) is zero comments. Do not write doc-comments for self-explanatory members. Do not narrate what the code does — the reader can read code. A comment is allowed only when intent is genuinely unrecoverable from the code itself: a hidden invariant, a "keep in sync with X" parity constraint, a workaround for a specific upstream bug, a non-obvious quirk mirroring Java. Matching the comment density of surrounding code is not a justification. Never narrate the edit: no "this used to be X", no "previously we …", no "added to handle …". When unsure, write nothing. Good naming and small functions are the documentation.
Navigation map — read this before every task
Sub-AGENTS.md files are not auto-discovered by Codex or by Claude Code's parent-directory scan. Consult this table at the start of any task and read every file whose scope your change touches.
| File | Read it before |
|---|---|
src/AGENTS.md |
Any change under src/ |
src/components/game/AGENTS.md |
Any change under src/components/game/ (game board, modals, panels, zones) |
src/components/companion/AGENTS.md |
Any change under src/components/companion/ (paper-play life tracker) |
src-tauri/AGENTS.md |
Any change under src-tauri/ |
manabrew-rs/AGENTS.md |
Any Rust engine work — workspace map and engine module map |
manabrew-rs/crates/manabrew-engine/src/ability/effects/AGENTS.md |
Adding or modifying a *_effect.rs (most parity work) |
manabrew-rs/crates/parity/AGENTS.md |
Investigating a parity divergence or editing regression.json |
forge-harness/src/main/java/forge/harness/AGENTS.md |
Any change under forge-harness/ (parity/host/common package boundaries) |
scripts/AGENTS.md |
Adding or running a build/parity script |
website/AGENTS.md |
Any change under website/ (landing at manabrew.app, docs at docs.manabrew.app) |
Topic spinoffs (cross-cut multiple folders):
| File | Read it when |
|---|---|
docs/agents/PARITY_PHILOSOPHY.md |
Any engine work |
docs/agents/ENGINE_BUGFIX_WORKFLOW.md |
Investigating a parity divergence |
docs/agents/UI_THEME_RULES.md |
Any UI change that involves color |
docs/agents/RELAY.md |
Any relay (manabrew-server) work — trust boundary, reconnect/resync, identity and usernames |
docs/agents/HUB.md |
Any hub (manabrew-hub) work — auth and token audiences, migrations, Deck Hub flag |
docs/agents/SELF_HOSTED_NODE.md |
Any self-hosted node work — game-id reconciliation, reconnect, JVM sizing, updater |
docs/agents/LATENCY_ANALYSIS.md |
Measuring hosted latency from capture files — what the timestamps mean, and four traps that skew results |
docs/forge-dsl-semantics.md |
Any engine work touching abilities, triggers, replacements, static abilities, costs, SVars, or the stack |
docs/forge-dsl-grammar.md |
Parser / IR changes, or when interpreting card-script syntax |
These files start minimal and grow over time. If a section outgrows its file, split it into a new doc under docs/agents/ and add it to this map.
The forge/ submodule has no local AGENTS.md; treat it as read-only and use it only as the Java source of truth.
Before every commit
The pre-commit hook runs staged-file formatting and linting for every commit. Full-repository TypeScript and Rust gates run in build-checks.yml, where Rust checks are scoped to Rust-affecting changes.
1. Lint and format staged files
The hook runs npx lint-staged automatically. Do not run yarn lint:all solely because you are about to commit; it remains available for an explicit full local check.
If the staged checks fail, do not bypass them. Fix the underlying issue or use the applicable formatter:
yarn fix:all # eslint --fix + prettier --write + cargo fmt + tsc
yarn format:all # prettier --write + cargo fmt (formatting only — no lint or typecheck)
Never use --no-verify to skip the commit-msg or pre-commit hooks. If a hook fails, fix the cause.
2. Keep AGENTS.md current
Re-read every AGENTS.md whose scope overlaps the files you changed. If anything in those files is now inaccurate — a moved or renamed path, a deleted module, a removed convention, an outdated workflow step, a stale code example — update it in the same commit. Use the navigation map above to find which files apply.
Stale guidance is worse than missing guidance: it actively misleads future agents and slowly erodes trust in this whole system. Treat AGENTS.md files as part of the code — when the code moves, they move with it.
3. Write a Conventional Commits message
The commit-msg git hook (commitlint + @commitlint/config-conventional) rejects anything else.
Format: <type>(<scope>)?: <subject>
- Allowed types:
feat,fix,chore,docs,style,refactor,perf,test,build,ci,revert. - Subject: lowercase, no trailing period, ≤72 chars.
- Scope (optional): the area of the change —
engine,ui,parity,tauri,agents,lint, etc. - Breaking changes:
feat!: …or include aBREAKING CHANGE:footer.
Examples:
feat(engine): wire mana cost parser
fix(ui): clamp deck list height on small screens
refactor(staticability): unify layer dispatch
docs(agents): document SVar lazy resolution rule
chore: bump prettier
perf(carddb): avoid re-parsing SVars on card load
The PR body itself must follow .github/pull_request_template.md: Summary, Why, Test plan in that order (plus Demo for UI changes). Installers are not built per-PR — every merge to main releases via cargo xtask release (which creates the Release as a draft), and the resulting v* tag runs the full publish pipeline (publish.yml: build installers → attach them and publish the Release → deploy production). Deploys run as cargo xtask deploy from the CI runner at the tag checkout (config rsynced + tag-pinned images; no scripts and no git on the box). Relay-compatible releases deploy the web container early (in parallel with the installer builds); the relay and hub roll out in the final deploy, once installed clients have an installer to update to. /manifest.json redirects to the latest Release's copy (attached as a release asset), so it can only advertise a release whose installers exist. A semver-incompatible bump of manabrew-protocol/manabrew-server/manabrew-hub falls back to deploying everything only after the Release has all its assets.
Workflow rules
- Branch + PR — never push to
main. Never push code automatically; wait for an explicit push command. - Never commit on your own. Do not run
git commitunless the developer explicitly asks for a commit in this conversation. Finishing a task, passing checks, or having a clean diff is not permission to commit — leave the changes in the working tree and report what's ready. - Pull with merge, never rebase. When integrating
maininto a feature branch, or pulling someone else's work, usegit merge(orgit pullwithpull.rebase=false) — nevergit rebase, nevergit pull --rebase. The repo's history convention is merge-based; rebasing rewrites already-pushed commits and creates divergence for collaborators. This applies to every branch, every time. - No unit tests unless explicitly asked.
- UI work must reference
docs/STYLE_GUIDELINES.md(anddocs/agents/UI_THEME_RULES.mdfor colors).
Hygiene
If something in the project surprises you, flag it to the developer and add a note to the AGENTS.md file most relevant to the surprise so future agents avoid the same trap. Do not leave behind scratch .md files; use a repo-local tmp directory if you need scratch space.
