# AGENTS.md

This is a subscription SaaS built as a Django monolith with a REST API, background jobs, and Stripe billing.

## Project

Stack: Python 3.12, Django 5, Django REST Framework, PostgreSQL, Celery with Redis, Stripe, pytest-django, ruff.

Layout:

- `config/settings/base.py`, `config/settings/dev.py`, `config/settings/prod.py`
- `config/urls.py` root URL configuration
- `apps/accounts/` custom user model, authentication, teams
- `apps/billing/` Stripe customers, subscriptions, webhook handling
- `apps/core/` shared abstract models, mixins, and utilities
- `apps/<feature>/` one Django app per bounded feature
- Each app: `models.py`, `views.py`, `serializers.py`, `services.py`, `tasks.py`, `urls.py`, `admin.py`, `migrations/`
- `templates/` server rendered pages, `static/` assets

Entry points: `manage.py`, `config/settings/base.py`, `config/urls.py`, `apps/billing/webhooks.py`.

## Commands

```bash
uv sync                                        # install dependencies
uv run python manage.py runserver              # dev server on :8000
uv run python manage.py makemigrations         # create migrations
uv run python manage.py migrate                # apply migrations
uv run python manage.py showmigrations         # inspect state
uv run python manage.py shell_plus             # interactive shell
uv run python manage.py createsuperuser
uv run python manage.py collectstatic --noinput
uv run pytest                                  # test suite
uv run pytest apps/billing -k webhook          # one app or test
uv run ruff check . && uv run ruff format .
uv run celery -A config worker -l info         # background worker
uv run celery -A config beat -l info           # scheduled jobs
```

`DJANGO_SETTINGS_MODULE` defaults to `config.settings.dev` locally.

## Code style

- Business logic lives in `services.py`, not in views and not in serializers.
- Views are thin: permission check, deserialize, call a service, return a response.
- Query in the view or service, never in a template. Use `select_related` and `prefetch_related` for every relation you render.
- Custom managers and querysets for reusable filters. No copy pasted `filter()` chains.
- Model methods that mutate state wrap their writes in `transaction.atomic`.
- Use `get_user_model()`, never import the user model directly.
- Money is a `DecimalField` with explicit precision, never a float.
- Signals are avoided. If a side effect matters, call it explicitly from a service.
- Type hints on service functions. Line length 100, enforced by ruff.
- Every `TextChoices` and `IntegerChoices` is defined on the model, not as loose constants.

## Boundaries

Do not touch without explicit instruction:

- Existing files in any `migrations/` directory. Never edit, squash, or delete a migration that has been applied.
- `config/settings/prod.py` and any environment specific credential.
- `uv.lock`, `.env`, `.github/workflows/`, deployment manifests.
- `apps/billing/webhooks.py` and Stripe price configuration.

Needs human review: data migrations, changes to permission classes, anything that touches subscription state, and any change to the custom user model.

Generate migrations when models change, show the resulting file, and stop before applying to anything but the local database.

## Testing

- `uv run pytest` runs the suite, `uv run pytest apps/<app>` runs one app.
- Required tests: permission classes, service functions, Celery tasks, and every billing state transition.
- Use `pytest.mark.django_db` and the factories in `apps/<app>/tests/factories.py`. Do not add JSON fixtures.
- API tests go through `APIClient` and assert status code and response shape.
- Celery tasks are tested by calling the function directly, with `CELERY_TASK_ALWAYS_EAGER` reserved for integration tests.
- A bug fix ships with a failing-first regression test.

## Git workflow

- Branch from `main`: `feat/short-description`, `fix/short-description`.
- Conventional commits with the app as scope: `feat(billing): handle trial expiry`.
- Before creating a migration, rebase on `main` so the revision ordering stays linear. If two migrations collide, regenerate rather than hand merging.
- Code and its migration ship in the same commit.
- PR description lists migrations, new settings, and any manual step needed at deploy.
