Files
mnemosyne/CLAUDE.md
2026-05-22 21:17:01 -04:00

8.7 KiB
Raw Blame History

🐾 Red Panda Approval™

The standard every change is judged against. Don't satisfy a checklist — satisfy the red pandas. Ask of each change: does this earn approval?

  1. Fresh Migration Test — migrations apply cleanly from an empty database.
  2. Elegant Simplicity — no unnecessary complexity; the obvious solution, done well.
  3. Observable & Debuggable — proper logging; failures say what broke and why.
  4. Consistent Patterns — follows Django conventions and the patterns already in this repo.
  5. Actually Works — passes all checks and serves a real user need.

Criteria 1 and 5 are externally verifiable — migrations apply or they don't; checks pass or they don't. Verify them, don't assert them. Criteria 24 are judgement calls: when in doubt, match what the repo already does rather than grading your own elegance.

If a paw print isn't leading the response, the rest of this file probably isn't being honoured either. Lead with one. 🐾


Conventions (always-on)

These are the rubric made concrete for the common case — writing models, views, forms, templates, and queries.

Models

  • Names: singular PascalCase (User, BlogPost, OrderItem).
  • Every model defines __str__ and get_absolute_url.
  • Every model has created_at = DateTimeField(auto_now_add=True) and updated_at = DateTimeField(auto_now=True).
  • TextChoices for status fields.
  • related_name on every ForeignKey; plural snake_case with correct English pluralisation.
  • Public-facing models: consider UUIDField primary key and is_active for soft deletes.

Field naming

  • Foreign keys: singular, no _id suffix (author, category, parent).
  • Booleans: prefixed (is_active, has_permission, can_edit).
  • Dates: suffixed (created_at, updated_at, published_on).
  • No abbreviations (description, not desc).

Views

  • Function-based views exclusively. Explicit logic over implicit inheritance. Extract shared logic into utility functions.
  • Business logic lives in service functions, not views and not save().

Forms

  • ModelForm with an explicit fields list — never __all__, never exclude.
  • Validate at the boundary; never trust client-side validation alone.

Queries

  • select_related() for FKs; prefetch_related() for reverse and M2M.
  • No queries inside loops (N+1). No .all() when you need a subset.
  • .only() / .defer() for large models. Comment non-obvious querysets.

URLs & identifiers

  • Public URLs use 12-char short UUIDs via shortuuid. Never expose sequential IDs (enumeration risk). Internal refs may use PKs.
  • Resource-based, namespaced URL names per app, trailing slashes, flat structure preferred.

Docstrings

  • Google style. Document public classes, functions, methods, modules.
  • Imperative one-line summary. Args:/Returns:/Raises: only when the signature doesn't already convey it. Don't restate type hints in prose.
  • Skip obvious one-liners and standard Django overrides.

Code organisation

  • PEP 8 import ordering (stdlib, third-party, local). Type hints on params.
  • CSS and JS in external files only — no inline styles, <style>, inline handlers, or <script> blocks.
  • File length: split by domain concept past ~500 lines; hard ceiling 1000.

Testing

  • Django TestCase (not pytest). Separate files per module: test_models.py, test_views.py, test_forms.py.

An app isn't done until it's reachable django-admin startapp builds an island. A complete-from-its-own-boundary app — models, views, urls, templates, tests all present and passing — is

Add to always-on Django CLAUDE.md — Conventions section

Insert this block under "Conventions (always-on)", as its own subsection. It is the universal Django definition-of-done. It fires for every app, not just registered tools.

An app isn't done until it's reachable

django-admin startapp builds an island. A complete-from-its-own-boundary app — models, views, urls, templates, tests all present and passing — is still unfinished if nothing in the running site links to it. "It works in isolation" is not done; "a user can reach it from the running site" is done.

Before reporting a new app complete, wire it into the site:

  1. INSTALLED_APPS — add the app's config.
  2. Root URLconfinclude() the app's urls.py in config/urls.py. An app whose URLconf isn't included has unreachable views, full stop.
  3. Navigation / discovery — register the app so it surfaces wherever this project expects apps to appear. This project uses an app registry (see Project Setup): the app registers itself in its own apps.py.ready() and the navigation template tag picks it up. Do not hand-edit nav templates or central list views — they read from the registry.
  4. Verify reachability — confirm the app's main page actually loads from the running site (not just that its tests pass). Per Red Panda criterion 5, this is externally verifiable: load the page, don't assert it works. Why this rule exists: an LLM reasons locally and closes the visible task at the app's own boundary. The wiring that makes an app reachable lives in other files (config/urls.py, INSTALLED_APPS, the registry) with no signal inside the new app pointing to them. Without this rule, the near-certain result is a fully-built, completely inaccessible app. The registry exists precisely so that "surface it" happens inside the app's own boundary (a register() call in ready()) — collapsing the wiring into the one place local reasoning will actually look.

The same principle generalises beyond Django: a new route that isn't mounted, a CLI subcommand not added to the dispatcher, a handler not registered — all the same failure. Done means connected, not written.


Always-on anti-patterns

The cross-cutting tripwires worth carrying everywhere. File-specific landmines (nginx, compose, broker) are in path-scoped rules.

  • Models: no .get() without handling DoesNotExist; no null=True on CharField/TextField (use blank=True, default=""); always specify on_delete; don't override save() for business logic; no Meta.ordering on large tables.
  • Security: secrets via env vars, never in settings.py; never commit .env; never DEBUG=True in production; never mark_safe() on user-supplied content; never disable CSRF.
  • Templates: {% url %} not {{ variable }} for URLs; no logic in templates; {% csrf_token %} in every form.
  • Imports/style: no import *; no mutable default args; no bare except:; don't silence linter warnings without a documented reason.

Environment

  • Virtual environment: ~/env/PROJECT/bin/activate (replace PROJECT).
  • pyproject.toml for config — no setup.py, no requirements.txt.
  • Dependencies floor-pinned with ceiling (Django>=5.2,<6.0). Exact == pins only in application lock files, never in reusable packages.
  • Dev DB: SQLite. Production DB: PostgreSQL.

Path-scoped rules to create (.claude/rules/)

These hold the landmines extracted from the standards doc. Each loads only when its paths match, keeping this file lean. Frontmatter shown.

  • nginx.mdpaths: ["nginx/**", "**/*.conf"] — reverse-proxy reference config: Docker DNS resolver + variable proxy_pass, $proxy_x_forwarded_proto map, access-log filtering, RFC1918 allowlists (all four ranges), always security headers.
  • docker-compose.mdpaths: ["docker-compose*.y*ml", ".env*"] — per-service environment: scoping (no shared env_file:), ${VAR} interpolation, .env.example annotation convention, the repr() parse diagnostic.
  • celery-tasks.mdpaths: ["**/tasks.py"] — idempotency, retry logic, pass IDs not instances, synchronous-by-default, broker URL percent-encoding, progress pattern {app}:task:{task_id}:progress.
  • migrations.mdpaths: ["**/migrations/**"] — never edit deployed migrations; RunPython needs a reverse; no non-nullable field without a default; meaningful --name; test forward and backward.
  • memcached.mdpaths: ["**/settings.py", ".env*"] — bind 0.0.0.0 not localhost; container can't reach 127.0.0.1; LAN hostname in KVDB_LOCATION; key pattern {app}:{model}:{identifier}:{field}.
  • frontend.mdpaths: ["**/templates/**", "**/static/**"] — DaisyUI+ Tailwind for new projects / Bootstrap 5 for existing; extend themis/base.html; no inline styles or scripts.

Reference docs (consult on demand, don't inline)

  • docs/ gotcha writeups: broker-URL/Kombu parsing, env-file parsing differences, nginx IP-caching. State the rule in the rule file; link the why here.
  • Preferred-packages list and per-app architecture: keep in docs/, not in this always-on file.