Back to initiatives

Coding Conventions

  • architecture
  • guide
  • conventions
  • codebase
  • engineering

Canonical generic rules for humans and AI agents working on projects that follow the standard front-end/, back-end/, optional mobile/, docs/ and scripts/ scaffold.

0. filename is the index

The highest-leverage rule is simple: a future developer or AI agent should be able to guess where a concern lives without searching the whole repository.

  • Name files for the concern they own.
  • Prefer user-settings.js over utils.js.
  • Prefer database-session.py or session.py inside a clearly named db/ folder over helpers.py.
  • Never create vague dumping grounds such as utils.*, helpers.* or misc.*.
  • Group by business concern or feature rather than merely by file type.
  • Use consistent filename prefixes when they improve scanning.
  • Avoid -v2, _new, _final and similar version suffixes. Replace the old implementation or name the coexistence reason explicitly, such as legacy-.

Good structure reduces human search time and AI token/credit usage.

1. standard repository boundaries

Generic project shape:

project/
├── front-end/
├── back-end/
├── mobile/                 # optional
├── docs/
│   └── canonical/
├── scripts/
├── compose.yaml            # optional
├── .env                    # local only
├── .env.example
├── .gitignore
└── README.md

Rules:

  • front-end/ owns browser-facing application code.
  • back-end/ owns server-side code, APIs, business logic, data access, integrations and workers.
  • mobile/ owns mobile clients when the project includes them.
  • docs/canonical/ owns reusable project rules.
  • scripts/ owns local developer/deployment/maintenance scripts.
  • Do not add architectural layers merely because a framework tutorial uses them.
  • Start shallow and add folders only when real code justifies them.

2. frontend structure

Default:

front-end/
├── src/
│   ├── components/
│   ├── features/
│   └── styles/
├── assets/
├── tests/
├── package.json
├── package-lock.json
├── Dockerfile              # only if containerised
└── .dockerignore

Use:

  • src/components/ for reusable UI components.
  • src/features/ for feature-specific UI.
  • src/styles/ for themes, tokens, layout and controls.
  • assets/ for images, icons and static source assets.
  • tests/ for frontend tests.

Framework-specific folders such as Astro public/, Next.js app/, or generated routing folders are added only when required.

The canonical UI rules live in docs/canonical/style-guide.md.

3. backend structure

Default Python/FastAPI shape:

back-end/
├── app/
│   ├── main.py
│   ├── features/
│   ├── db/
│   ├── integrations/
│   └── workers/
├── alembic/
├── tests/
├── pyproject.toml
├── alembic.ini
├── .venv/
├── Dockerfile
└── .dockerignore

Use:

  • app/main.py as a small application entry point.
  • app/features/ for business functionality grouped by feature.
  • app/db/ for shared SQLAlchemy engine/session/database infrastructure.
  • app/integrations/ for external systems and APIs.
  • app/workers/ for background jobs, queues, AI processing or scheduled work when required.
  • alembic/ for version-controlled database migrations.

Do not turn every internal service class into a separate deployable service. Keep server-side concerns in back-end/ until independent deployment is genuinely required.

4. feature-first organisation

As an application grows, organise code around features.

Example:

back-end/app/features/
├── users/
│   ├── router.py
│   ├── schemas.py
│   ├── model.py
│   ├── service.py
│   └── repository.py
└── projects/
    └── ...

A feature does not need every file shown above. Create only what it needs.

Frontend features follow the same mental model:

front-end/src/features/
├── users/
├── projects/
└── settings/

Prefer the owning feature over giant global models.py, services.py or views.js files when the codebase becomes large.

5. data tier

Keep the database runtime separate from Python data-access code.

Concern Standard location
PostgreSQL runtime Official postgres:16 image unless customisation is required
SQLAlchemy engine/session back-end/app/db/
Feature models Owning feature where practical
Alembic migrations back-end/alembic/
Seed/import/maintenance scripts scripts/database/ or clearly named backend concern

One PostgreSQL server may host multiple application databases. Prefer separate databases when independently migrated applications, such as Wagtail and FastAPI, share one PostgreSQL server.

6. Python environments

  • Put .venv/ inside the Python dependency boundary it belongs to, normally back-end/.venv/.
  • If an independently deployed Python application has its own pyproject.toml, it gets its own .venv/.
  • Never commit .venv/.
  • Declare Python dependencies in pyproject.toml.
  • Do not treat the virtual environment as source code.

7. JavaScript dependencies

  • Keep package.json beside the JavaScript application it describes.
  • Keep the lock file with it.
  • Never commit node_modules/.
  • Do not put frontend dependencies in the backend merely because both applications live in one repository.

8. Docker conventions

A Dockerfile defines how one image is built. Compose defines how multiple containers run together.

Rules:

  • Put each primary Dockerfile beside the application it builds.
  • Typical locations are front-end/Dockerfile and back-end/Dockerfile.
  • Use one Dockerfile with named multi-stage targets for development/test/production where practical.
  • Add another Dockerfile only when the build is genuinely different.
  • Use compose.yaml when multiple services need to run together.
  • Prefer the official PostgreSQL image instead of creating a PostgreSQL Dockerfile without a real need.
  • Static sites deployed directly to a platform such as Cloudflare Pages do not require Docker unless the project has another reason to containerise them.

For cross-repository infrastructure, a dedicated scripts/operations repository may own the deployment Compose file while each application repository continues to own its Dockerfile.

9. naming

Kind Convention Example
Folder, general kebab-case front-end, public-site
File, JS/TS kebab-case entity-detail.js
Python module/package snake_case user_settings.py
JS exported symbol camelCase renderUserSettings
Python symbol snake_case load_user_settings
Class PascalCase UserSettings
Constant UPPER_SNAKE_CASE ROUTE_PATHS
Route/page slug kebab-case /user-settings
Python private symbol leading _ _safe_identifier

Do not force kebab-case where the language or tooling makes it invalid.

10. imports and dependencies

  • Imports belong at the top of source files unless the language/framework requires otherwise.
  • Avoid circular imports.
  • Prefer clear project aliases/absolute imports when the toolchain supports them.
  • Do not add a dependency when the platform or existing code already provides the capability.
  • Remove abandoned dependencies when their owning implementation is removed.

11. one concern per file

A file should have one clear, summarised job.

Split when:

  • you regularly scroll past unrelated code
  • unrelated changes keep touching the same file
  • a new function could reasonably belong to two different concerns
  • a search returns many irrelevant matches from the same file
  • the file has become expensive for an AI agent to load and reason about

12. size discipline

Rule Limit Action
Source file 800 lines Split before adding more feature work
Markdown file 300 lines Split into named parts and keep an index
Function ~80 lines Extract a well-named concern
Top-level entities/file ~10 Reconsider responsibility
Files/directory ~25 Add a meaningful subdirectory
Imports/file ~20 Check whether the file owns too much

These are guardrails, not a reason to pre-split small code.

13. Markdown discipline

When a Markdown file exceeds 300 lines:

  • keep the original file as a short index
  • extract major concerns into a clearly named adjacent folder such as parts/
  • name each extracted file so the filename describes its content
  • add a short back-link to the index
  • do not create duplicate versions of canonical documents

14. comments

Default to no comment when naming makes the code obvious.

Comment when the why is non-obvious, for example:

  • a hidden platform constraint
  • a specific workaround
  • surprising behaviour
  • a security or compatibility requirement

Do not narrate obvious code or reference a temporary task. Task history belongs in commits/plans.

15. environment and secrets

  • Never commit .env, credentials, private keys, tokens or production secrets.
  • Commit .env.example with names and safe placeholders.
  • Keep environment-specific values out of source code.
  • Treat deployment configuration and application configuration as separate concerns.

16. tests and refactors

After moving/splitting code:

  1. run the relevant tests
  2. confirm imports/routes still resolve
  3. confirm previously working UI behaviour remains intact
  4. confirm migrations/schema expectations did not change accidentally
  5. confirm no new file violates the size/concern rules
  6. update documentation when paths or architecture changed

17. canonical documents

Every substantial project should maintain:

docs/canonical/
├── style-guide.md
├── coding-conventions.md
└── ai-agent-conventions.md

The README.md explains the particular project and points to these canonical rules. Project-specific architecture or invariants belong in separate clearly named documents rather than contaminating these generic conventions.