This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
IMPORTANT: Never work directly on main. Always create a feature branch before making changes:
git checkout -b feature-nameThis prevents confusion when PRs are squash-merged and keeps main clean. After a PR is merged, delete the local branch and pull main:
git checkout main && git pull && git branch -D feature-nameNo GitHub CLI: Do not use gh commands or GitHub CLI MCP tools. PRs are created manually through the GitHub web interface.
No cd && compound commands: Never combine cd with other commands using && (e.g., cd /path && git status). On Windows, Claude Code flags all cd && <command> compounds for manual approval as a bare-repository-attack mitigation, which blocks automated workflows. Instead:
- For git: use
git -C /path/to/repo <command>(e.g.,git -C /c/Users/apost/PycharmProjects/arxii status) - For other tools: use absolute paths or the tool's own directory flag
- The working directory is already the repo root in most sessions, so
cdis rarely needed anyway
This rule exists as a workaround for a Claude Code permission-check behavior on Windows (as of mid-2026). If future releases stop flagging cd && git compounds, this rule can be relaxed.
uv sync- Install Python dependenciesuv venv- Create virtual environmentpre-commit install- Install pre-commit hooks
arx test- Run Evennia tests (runarx manage migratefirst if fresh environment)arx test <args>- Run specific tests with additional argumentsarx shell- Start Evennia Django shell with correct settingsarx manage <command>- Run arbitrary Django management commandsarx build- Build docker images (runsmake build)
arx start- Start the Evennia server (PREFERRED for running the server)arx stop- Stop the Evennia serverarx stop --hard- Force-kill all Evennia processes (use when server hangs)arx reload- Reload the Evennia server (picks up code changes)arx ngrok- Start ngrok tunnel and auto-update .env for manual testing- Automatically updates
src/.envwithFRONTEND_URLandCSRF_TRUSTED_ORIGINS - Automatically updates
frontend/.envwithVITE_ALLOWED_HOSTS(for Vite dev server) arx ngrok --status- Check if ngrok is running and show current URLarx ngrok --force- Kill existing ngrok and restart with new tunnel- Note: ngrok URLs are ephemeral and dev-only.
frontend/.envis gitignored to prevent committing ngrok domains.
- Automatically updates
IMPORTANT: Always use arx start to run the server, NOT arx manage runserver. The arx start command properly starts the Evennia server with portal and server processes, while runserver is a Django-only command that doesn't fully initialize Evennia.
ruff check .- Run Python linting (includes import sorting, flake8 rules, and more)ruff check . --fix- Auto-fix Python linting issues where possibleruff format .- Format Python code (replaces black/isort, configured for line length 100)pre-commit run --all-files- Run all pre-commit hooks (now uses ruff)
pnpm dev- Start Vite development server with Django API proxypnpm build- Build production assets tosrc/web/static/dist/pnpm lint- Run ESLint on TypeScript/React filespnpm lint:fix- Run ESLint with auto-fixpnpm format- Format code with Prettierpnpm typecheck- Run TypeScript type checking
arx integration-test- Automated integration test environment (highly automated!)- Requires
ALLOW_INTEGRATION_TESTS=trueinsrc/.env(safety check) - See
src/integration_tests/QUICKSTART.mdfor usage guide - Automatically: starts ngrok, Django, frontend, registers test account, fetches verification email
- Human verification: click verification link, confirm UI, test login
- Press Ctrl+C to cleanup and restore everything
- Requires
Arx II is a web-first multiplayer RPG built on the Evennia framework. The React frontend is the primary game interface - all features should be designed for modern web UX (interactive components, visual feedback, responsive layouts). Telnet/MUD client access is a secondary compatibility goal, not the design target. Do not design features around text-command-and-response patterns; design for the web and let telnet support follow where it can.
The backend uses a sophisticated flow-based command system:
- Commands (
src/commands/) - Simple command classes that only interpret input and delegate to dispatchers - Dispatchers - Parse text using regex and call handlers with resolved objects
- Handlers (
src/commands/handlers/) - Perform permission checks and trigger flows - Flows (
src/flows/) - Core game logic engine that handles state changes and messaging - Triggers - React to events and can modify flow execution
- Flow Engine - Executes sequences of steps based on triggers and events
- Object States - Character, room, exit states that implement permission methods (
can_move,can_open, etc.) - Service Functions - Handle communication, movement, perception
- Scene Data Manager - Manages temporary scene state
Commands follow the pattern: Input → Dispatcher → Handler → Flow → Service Function
- Commands are intentionally simple and only glue components together
- All game logic lives in flows, triggers, or service functions
- Permission checks delegate to object states which emit intent events
- Built on Evennia framework with Django backend
- Custom typeclasses in
src/typeclasses/ - Server configuration in
src/server/conf/ - Web interface components in
src/web/
src/cli/arx.py- CLI entry point with typer-based commandssrc/flows/- Flow engine and game logicsrc/commands/- Command system with dispatchers and handlerssrc/typeclasses/- Evennia object definitionssrc/server/- Evennia server configurationdocs/- Documentation including command system overview
- Python 3.13+ managed by mise
- Node.js v20 for web assets
- uv for dependency management
- Environment file:
src/.env - Working directory should be
src/for Django commands
Before starting work on a new system or feature, consult docs/roadmap/ROADMAP.md.
The roadmap provides:
- Overview of all game systems and their current status
- Design principles that apply to every system
- Key design decisions and constraints for each domain
- What exists vs. what's still needed for MVP
Individual domain stubs (e.g., docs/roadmap/combat.md) contain detailed design points,
what's already built, and what's needed for MVP.
Before implementing features that touch multiple systems, consult docs/systems/INDEX.md.
The systems index provides:
- Quick reference of all existing systems, their models, and key functions
- Integration points showing how systems connect
- Common queries and code patterns to reuse
This prevents reinventing existing functionality. For example, before building something that needs character traits, magic, or progression - check the index to find existing models and helper functions.
Individual system docs (e.g., docs/systems/magic.md) contain:
- Complete model listings with field descriptions
- Copy-pasteable code examples for common operations
- API endpoint references
- Frontend integration details
For cross-app FK relationships and service function signatures, consult docs/systems/MODEL_MAP.md.
This is auto-generated via uv run python tools/introspect_models.py and contains:
- Every model's foreign keys and what points to it (reverse relations)
- Service function signatures with type hints
- Regenerate after major model changes to keep it current
FIXED: Custom makemigrations command prevents phantom Evennia library migrations
We have a custom makemigrations command that prevents Django from creating problematic migrations in Evennia's library when our models have ForeignKeys to Evennia models.
# SAFE - our custom command prevents phantom Evennia migrations
arx manage makemigrations
# Still works - specify specific apps when needed
arx manage makemigrations traitsDetails: See core_management/CLAUDE.md for full technical documentation of the solution.
- Use Evennia Models: Keep using Evennia's Account, ObjectDB, etc. - don't reinvent the wheel
- Extend via evennia_extensions: Use the evennia_extensions app pattern for data storage that extends Evennia models
- No Attributes: Replace all Evennia attribute usage with proper Django models through evennia_extensions
- Item Data System: Consider reusing ArxI's item_data descriptor system for routing data to different storage models
- No JSON Fields: Avoid JSONField - each setting/configuration should be a proper column with validation and indexing
- Proper Schema: Use foreign keys, proper data types, and database constraints
- Queryable Data: All data should be easily queryable with standard Django ORM
When editing Python files:
- Run
arx test <app>after making changes to that app (don't wait to be asked) - Run
ruff check <file>on changed files before moving on
Dead code removal (be careful - this is active development):
- Check for TODO/FIXME comments before removing anything that looks unused
- Stub methods, empty implementations, and unused imports may be intentional placeholders
- When in doubt, ASK before removing - false positives waste more time than leaving a stub
- Only remove code that is clearly obsolete (old implementations replaced by new ones)
When editing TypeScript files:
- Run
pnpm typecheckafter making changes - Run
pnpm linton changed files
When completing a task:
- Run relevant tests before claiming "done"
- Verify the change works as expected
- Type Annotations Required in Typed Apps: All functions in apps listed under
[tool.ty.src].includeinpyproject.tomlmust have type annotations for all arguments and return types. A pre-commit hook (check-type-annotations) enforces this via ruff ANN rules on staged files. If a function truly cannot be annotated, add an inline# noqa: ANNwith a comment explaining why. The typed apps list is maintained in bothpyproject.tomlandtools/check_type_annotations.py— keep them in sync when adding new apps - ty Type Checking: Strategic type checking via
tycovers complex business logic apps (see[tool.ty.src].include). Skip Django CRUD boilerplate - No Relative Imports: Always use absolute imports (e.g.,
from world.roster.models import Rosternotfrom .models import Roster) - relative imports are a flake8 violation for this project - Environment Variables: Use
.envfile for all configurable settings, provide sensible defaults in settings.py - No Django Signals: Never use Django signals (post_save, pre_save, etc.) - they create difficult-to-trace bugs. Always use explicit service function calls that can be tested and debugged easily
- Migrations: When model changes require migrations, use
arx manage makemigrations <app>to generate them. Always use thearx managecommands for migrations to ensure correct Django settings are loaded. After generating, apply witharx manage migrate - Line Length: Respect 100-character line limit even with indentation - break long lines appropriately
- Model Instance Preference: Always work with model instances rather than dictionary representations. Only serialize models to dictionaries when absolutely necessary (API responses, Celery tasks, etc.) using Django REST Framework serializers. This preserves access to model methods, relationships, and SharedMemoryModel caching benefits
- Avoid Dict Returns: Never return untyped dictionaries from functions. Use dataclasses, named tuples, or proper model instances for structured data. Dictionaries should only be used for wire serialization or when truly dynamic key-value storage is needed. Always prefer explicit typing over generic Dict[str, Any]
- Separate Types Files: Place dataclasses, TypedDicts, and other type declarations in dedicated
types.pyfiles within each app/module. This prevents circular import issues when the types need to be referenced across multiple modules. Import types usingfrom app.types import TypeName - Don't add ordering unless necessary: Ordering is not free. We should add it in viewsets, only at model.Meta level for sequential data that requires manual ordering, like Chapters or Episodes.
- Prefer Inheritance Over Protocols: Use concrete base classes with abstract methods instead of Protocol classes for type safety. All objects in our codebase inherit from shared base classes (BaseState, BaseHandler, etc.). When mypy compliance requires type annotations, prefer adding abstract methods to base classes rather than creating Protocol classes. This maintains clear inheritance hierarchies and ensures methods are actually implemented. Use Protocol only for true duck typing scenarios with external libraries.
- Service Functions Use Model Instances: Service functions should never accept slug strings for lookups. Always pass model instances or primary keys. Slugs are only for user-facing search APIs where users search by text and receive objects with IDs for subsequent operations. This applies to all internal service layer code.
- Avoid Denormalized Foreign Keys: When a model has a FK to a parent and optionally a FK to a child of that parent (e.g.,
condition+stagewhere stage implies condition), either make one FK derivable from the other or addclean()validation to ensure consistency. Don't create situations where FKs can contradict each other. If the child FK is nullable (null = applies to all), keep the parent FK for direct queries but validate the relationship. - TextChoices in constants.py: Place Django TextChoices/IntegerChoices in a separate
constants.pyfile rather than as nested classes inside models. This avoids circular import issues when serializers or other modules need to reference the choices, and makes it clearer these are shared constants. - No Queries in Loops: Never execute database queries inside loops, serializer methods that recurse, or while loops that traverse relationships. Use annotations, prefetch_related with bounded depth, or restructure to batch queries. Recursive serializers are acceptable only when paired with bounded prefetch_related in the view (e.g.,
prefetch_related("children__children__children")limits depth to 4 levels). - No Management Commands: Do not create Django management commands unless explicitly requested. Use existing tools: fixtures for seed data, the Django admin for data management, service functions for business logic, and the
arxCLI for development tasks. - No Backwards Compatibility in Dev: Never add legacy format support, backwards-compatibility shims, or dual-format handling. There is no production data, so old formats have no consumers. Accept only the current format. This avoids unnecessary code complexity and maintenance burden.
- PostgreSQL Only: This project uses PostgreSQL exclusively. Freely use PG-specific features: recursive CTEs, materialized views, JSONB operators, window functions,
DISTINCT ON, etc. Never add SQLite compatibility — tests run against Postgres via the Evennia test runner. If you find yourself writing database-agnostic workarounds, stop and use the Postgres feature directly.
For all Django development (models, views, APIs, tests), follow the guidelines in django_notes.md.
Key Django requirements:
- Use Django TextChoices/IntegerChoices for model field choices
- All ViewSets must have filters, pagination, and permission classes
- Use FactoryBoy for all test data with
setUpTestDatafor performance - Focus tests on application logic, not Django built-in functionality
IMPORTANT: When working on a new app, avoid multiple migrations during development django_notes.md gives a more in-depth explanation of this strategy.
IMPORTANT: Fixture files (JSON seed data) must NOT be committed to version control.
- Fixtures are gitignored via
**/fixtures/*.json - Seed data is managed separately from code (via admin, shared storage, or documentation)
- If you create fixture files for local testing, they stay local
- Never use
git add -fto force-add fixture files - Do NOT create management commands to seed data - use Django's fixture system instead
- Prefer SharedMemoryModel: Use SharedMemoryModel for frequently accessed lookup data (traits, configuration tables, etc.) for better performance
- Correct Import Path: Always import from
evennia.utils.idmapper.models.SharedMemoryModel - NEVER import from
evennia.utils.models- this path contains utilities that trigger Django setup during import and will break the Django configuration with "settings are not configured" errors - Example:
# CORRECT - this works from evennia.utils.idmapper.models import SharedMemoryModel # WRONG - this breaks Django setup from evennia.utils.models import SharedMemoryModel
- When to Use: SharedMemoryModel is ideal for:
- Trait definitions and conversion tables
- Configuration data that changes rarely
- Lookup tables for game mechanics
- Any model that's read frequently but modified infrequently