Coding Conventions
Canonical generic rules for humans and AI agents working on projects that follow the standard
front-end/,back-end/, optionalmobile/,docs/andscripts/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.jsoverutils.js. - Prefer
database-session.pyorsession.pyinside a clearly nameddb/folder overhelpers.py. - Never create vague dumping grounds such as
utils.*,helpers.*ormisc.*. - Group by business concern or feature rather than merely by file type.
- Use consistent filename prefixes when they improve scanning.
- Avoid
-v2,_new,_finaland similar version suffixes. Replace the old implementation or name the coexistence reason explicitly, such aslegacy-.
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.pyas 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, normallyback-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.jsonbeside 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/Dockerfileandback-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.yamlwhen 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.examplewith 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:
- run the relevant tests
- confirm imports/routes still resolve
- confirm previously working UI behaviour remains intact
- confirm migrations/schema expectations did not change accidentally
- confirm no new file violates the size/concern rules
- 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.