Organisation-wide conventions for contributing to repositories of the Urban Futures Collective (Urban Model Platform, UMP-X frontend, MCP server, model servers and related projects).
This document is deliberately high-level and language-agnostic. It describes the standard we are converging on, not only the current state of every repository. Where a repository does not (yet) meet a rule, new and changed code should follow the rule; existing code is migrated opportunistically ("leave it better than you found it"), not in unrelated pull requests.
The key words below are used in the sense of RFC 2119:
| Keyword | Meaning |
|---|---|
| MUST / MUST NOT | Required. Deviations need an explicit, documented reason in the PR. |
| SHOULD / SHOULD NOT | Recommended best practice. Deviate only with good reason. |
| MAY | Optional; at the discretion of the repository maintainers. |
- Repository-specific rules (
CONTRIBUTING.md, tool configuration such aspyproject.toml,eslint.config.*,.editorconfig) take precedence over this document where they are more specific. - This document applies wherever a repository is silent.
- Automated tooling (formatter, linter, CI) is the source of truth for formatting. Style is not debated in code review.
Each repository's CONTRIBUTING.md SHOULD link to this document and describe only what is specific to that repository (setup, commands, architecture).
- All code, identifiers, comments, commit messages, pull requests, issues and technical documentation MUST be written in English.
- User-facing text in applications MUST NOT be hard-coded in a single language; it goes through the project's i18n mechanism (e.g.
i18n/in the frontend). German and English are the primary UI languages. - Be precise and neutral in reviews: comment on the code, not the person;
distinguish blocking remarks from suggestions (e.g. prefix with
nit:).
Every repository SHOULD contain:
| File | Purpose |
|---|---|
README.md |
What it is, how to run it locally, how to test, links to docs. |
LICENSE |
An OSI-approved licence. Licence choice is per repository but MUST be explicit. |
CONTRIBUTING.md |
Repository-specific setup and workflow; links to this document. |
CHANGELOG.md |
Human-readable changes (see §11). |
.env.example |
Every supported environment variable with a safe default or placeholder. |
.gitignore / .dockerignore |
Keep build artefacts, local data and secrets out. |
Agent instruction file (CLAUDE.md, AGENTS.md) |
MAY; if present, see §13. |
Organisation-wide files live in the .github repository and apply automatically unless a repository overrides them.
Repositories MUST NOT contain: secrets, credentials, private keys,
production configuration with real values, personal data, large binary data
sets (use Git LFS, releases or external storage), or IDE-specific settings that
override project tooling. Shared editor recommendations (e.g. .vscode/extensions.json)
MAY be committed.
Dead or legacy code (e.g. a previous major version kept "for reference") SHOULD NOT live on the default branch; use a tag or a branch instead. If it must stay temporarily, it MUST be clearly marked and excluded from linting, tests and packaging.
-
The default branch is
main. It MUST always be releasable (build and tests green). -
Work happens on short-lived branches created from the current integration branch and merged via pull request. Direct pushes to
mainSHOULD NOT happen (enforce via branch protection). -
An integration branch (
dev,staging) MAY be used where deployment environments require it; long-lived feature branches SHOULD be avoided. -
Branch names:
<type>/<short-kebab-description>, optionally with an issue number, using the same types as commits:feat/mcp-tool-catalog fix/123-results-accept-header docs/provider-configurationRelease lines MAY use
release/<version>(e.g.release/3.0).
-
Commit messages SHOULD follow Conventional Commits:
<type>(<optional scope>): <imperative summary, ≤ 72 chars> <optional body: what and why, wrapped at 72 chars> <optional footer: BREAKING CHANGE: …, Refs #123, Co-authored-by: …>Types:
feat,fix,docs,refactor,perf,test,build,ci,chore,revert. -
Use the imperative mood ("Add retry", not "Added retry").
-
One logical change per commit. Formatting-only changes go in separate commits.
-
Breaking changes MUST be marked (
feat!:orBREAKING CHANGE:footer).
- A PR SHOULD address one concern and stay reviewable (guideline: < 400 changed lines excluding generated files and lockfiles).
- The PR description MUST state what changes and why, how it was tested, and link the related issue. Screenshots for UI changes.
- At least one approving review from someone other than the author is required before merging. Changes to security-relevant code (auth, authorization, input handling, secrets) SHOULD get a second review.
- CI MUST be green before merge.
- Preferred merge strategy: squash merge with a Conventional-Commit title, unless the repository relies on individual commits (then rebase merge).
- Delete the branch after merging.
- Draft PRs are encouraged for early feedback.
- Formatting SHOULD be automated. Every repository configures a formatter
and a linter, runs them in CI, and SHOULD run them via
pre-commit(or an equivalent hook such aslint-staged). - One formatter and one linter per language per repository. Do not configure overlapping tools that disagree (e.g. two formatters with different line lengths).
- UTF-8, LF line endings, final newline, no trailing whitespace.
- No commented-out code in merged changes; Git keeps history.
TODO/FIXMEcomments SHOULD reference an issue (TODO(#123): …).- No debug output (
print,console.log,breakpoint()) in merged code; use the logging facility.
-
Names describe intent:
job_status, notjs;fetchProcesses, notgetData. -
Abbreviations only when established in the domain or the language (
id,url,ctx,crs,ogc). -
Follow the casing conventions of the language; never mix styles within one codebase:
Language Variables / functions Types / classes Constants Files / modules Python snake_casePascalCaseUPPER_SNAKE_CASEsnake_case.pyTypeScript / JS camelCasePascalCaseUPPER_SNAKE_CASEkebab-case.tsor framework conventionVue components — PascalCase— PascalCase.vueShell snake_case— UPPER_SNAKE_CASE(env)kebab-case.shEnv variables — — PROJECT_PREFIX_NAME(e.g.UMP_AUTH_ENABLED)— Helm / YAML keys camelCase(Helm) or as defined by the consumed standard— — kebab-case.yaml -
Booleans read as predicates:
is_public,has_results,anonymousAccess. -
Interfaces/ports are named after their role (
JobRepositoryPort,HttpClientPort); implementations after their technology (SqlJobRepository,AiohttpClientAdapter).
- Code explains what; comments explain why (constraints, workarounds, references to standards or issues).
- Every public module, class and function SHOULD have a docstring / JSDoc stating purpose, parameters, return value and raised errors where not obvious.
- Python: one docstring style per repository (Google or reST; Google recommended). TypeScript: TSDoc.
- Reference specifications precisely (e.g. "OGC API – Processes Part 1, Req. 34") rather than paraphrasing them.
- Single responsibility: if a function cannot be summarised in one sentence, split it.
- Prefer early returns over deep nesting; keep cyclomatic complexity low (linters SHOULD enforce a limit, e.g. 10).
- Prefer pure functions and immutable data where practical; isolate side effects at the edges.
- Avoid global mutable state. If unavoidable, encapsulate and synchronise it.
These are defaults for new repositories and the target for existing ones.
- Supported versions declared in
pyproject.toml(requires-python). - Data at boundaries is modelled with Pydantic (or dataclasses for pure
internal data); configuration via
pydantic-settings. asynccode MUST NOT call blocking I/O; never callasyncio.run()inside a running event loop or request handler.- Packaging:
pyproject.tomlwith a committed lockfile (Poetrypoetry.lockor uvuv.lock). One package manager per repository. - Source layout:
src/<package>/, tests intests/.
- TypeScript with
strict: true. Avoidany; useunknownand narrow. - Vue: Composition API with
<script setup lang="ts">; reusable logic incomposables/(useXyz); components small and presentational where possible. - Validate external data (API responses, LLM/tool outputs) at runtime, e.g. with Zod, not only with static types.
- Server-side secrets (API keys, LLM tokens) MUST stay in server routes / runtime config and never reach the client bundle.
package-lock.json(or the lockfile of the chosen manager) is committed; pin exact versions for fast-moving SDKs where breaking minor releases are common.
#!/usr/bin/env bashandset -euo pipefail. Check withshellcheck.- Prefer Python or Make targets for anything beyond simple glue.
- Multi-stage builds, minimal base images with pinned tags (ideally digests),
non-root user,
HEALTHCHECKwhere meaningful.
- Schema changes MUST go through migrations (e.g. Alembic). No ad-hoc DDL in application code.
- Queries MUST be parameterised; string interpolation into SQL is forbidden.
- Standards first. Where an open standard exists, implement it rather than inventing an API: OGC API – Processes / Features / Records, MCP for agent interfaces, OpenID Connect / OAuth 2.0 for auth, GeoJSON for vector data. Document deviations explicitly.
- Design decisions with lasting impact (architecture, technology choice,
API contract) SHOULD be recorded as short Architecture Decision Records
(
docs/adr/NNNN-title.md: context, decision, consequences). Design notes and specs MUST be kept in sync with the code or removed when obsolete. - Prefer composition over inheritance; prefer small, explicit pipelines or strategies over large conditional blocks.
- Public HTTP APIs MUST be described by a machine-readable contract (OpenAPI; JSON Schema for tool/process inputs).
- APIs are versioned (e.g.
/v1/…). Breaking changes require a new major API version and a deprecation period for the old one. - Errors are returned in a consistent, documented format (e.g. RFC 9457 Problem Details, or the exception format mandated by the implemented OGC standard) with correct HTTP status codes.
- Validate all input at the boundary; reject unknown or malformed input with a clear error rather than coercing it silently.
- Timeouts MUST be set for every outbound network call; retries use bounded exponential backoff and are only applied to idempotent operations.
- Geodata: state the coordinate reference system explicitly (EPSG code / CRS URI). GeoJSON is WGS 84 (EPSG:4326) per RFC 7946; other CRSs must be declared.
- Fail loudly and early. Do not swallow exceptions: no bare
except:/except Exception: pass/ emptycatch {}. Catch the narrowest exception you can handle meaningfully; otherwise let it propagate. - Use domain-specific exception types in the core; translate them to transport errors (HTTP status, MCP error) in the adapter layer.
- Use the project's logging facility, never
print. Prefer structured logging (key–value / JSON) in services. - Log levels:
DEBUGdiagnostics,INFOlifecycle events,WARNINGrecoverable anomalies,ERRORfailed operations that need attention. - Never log secrets, tokens, passwords, full authorisation headers or personal data. Mask identifiers where they are not needed.
- Include correlation identifiers (request ID, job ID) to make distributed flows traceable.
- Every behavioural change MUST come with tests; every bug fix SHOULD include a regression test that fails without the fix.
- Test pyramid: many fast unit tests, fewer integration tests, few end-to-end tests. Unit tests MUST NOT require network, external services or a specific machine.
- Prefer hand-written fakes implementing a port/interface over mocking concrete classes or patching internals; mock only at system boundaries (HTTP, clock, randomness).
- Tests MUST be deterministic and independent of execution order
(randomised order SHOULD be enabled, e.g.
pytest-randomly). - Naming:
test_<unit>_<behaviour>_<condition>(Python),describe/it('…')with readable sentences (TS). Test files mirror the structure of the code under test. - Coverage is reported in CI and treated as a signal, not a target. New code SHOULD be covered; coverage MUST NOT decrease without justification.
- Tooling defaults:
pytest(+pytest-asyncio) for Python,vitestfor TypeScript/Vue, Playwright for browser end-to-end tests where needed.
- Secrets never enter the repository, logs, error messages, container images or client bundles. Use environment variables, CI secrets or a secret manager. Enable GitHub secret scanning and push protection.
- Authentication and authorisation use established libraries (OIDC/JWT libraries with signature, issuer, audience and expiry validation); never parse or trust tokens manually. Authorisation is enforced server-side, centrally and deny-by-default.
- Input from users, remote providers, files and AI models is untrusted: validate it, encode output, use parameterised queries.
- Agent/LLM interfaces (MCP tools, chat features): tools MUST respect the calling user's permissions; tool descriptions and tool results are untrusted input (prompt injection); actions with side effects SHOULD require explicit confirmation.
- Dependencies: lockfiles committed; automated updates via Dependabot or
Renovate; vulnerability scanning (e.g.
pip-audit,npm audit, GitHub dependency review, container image scanning) in CI. - Vulnerabilities are reported privately as described in
SECURITY.md, never in public issues.
- Add a dependency only if it provides clear value over a small amount of own code; prefer well-maintained libraries with compatible licences.
- Declare version ranges in the manifest, pin exact versions via the lockfile.
- Document non-obvious upper bounds with a comment explaining why (e.g. an API removed in the next major release).
- Separate runtime, development and documentation dependencies
(dependency groups /
devDependencies). - Licences of dependencies MUST be compatible with the repository licence.
- Documentation lives next to the code (docs-as-code, Markdown) and is updated in the same PR as the change it describes. A PR that makes documentation wrong is incomplete.
README.mdstays short and current; longer material goes todocs/.- Document every configuration option (name, type, default, effect), ideally generated from or validated against the settings model.
- Prefer diagrams as code (Mermaid, PlantUML) or editable SVG over raster images.
- Releases follow Semantic Versioning 2.0.0:
MAJOR.MINOR.PATCH, pre-releases as3.0.0-alpha.8,3.0.0-rc.1(note the dot before the pre-release number, which keeps SemVer ordering correct beyond 9). - Git tags are
v<version>(e.g.v3.0.0). - Versions are bumped by tooling (e.g.
bump-my-version, release-please, semantic-release), never by hand in multiple files. - Independently released artefacts (application, Helm chart, client library) MAY carry independent versions; their compatibility MUST be documented.
CHANGELOG.mdfollows Keep a Changelog with anUnreleasedsection updated in each user-relevant PR (or generated from Conventional Commits).- Releases are created from tags via CI; container images are published with
immutable version tags (plus moving tags like
latestonly as convenience) and build provenance/attestation.
Every repository's CI SHOULD run on each pull request:
- Format check and lint
- Type check (where applicable)
- Unit and integration tests with coverage report
- Build (package, container image, frontend bundle)
- Dependency and security checks
- For charts/manifests:
helm lint/ schema validation
Additional rules:
- Pin third-party GitHub Actions to a major version or commit SHA; grant
workflows minimal
permissions. - The same commands run locally and in CI (via
make,npm run,poetry runoruv runtargets); document them in the README. - Deployments to shared environments happen only from CI, never from developer machines.
AI coding assistants are welcome tools. The following applies:
- The human author is responsible for every line submitted: they MUST understand, review and test AI-generated code exactly like their own.
- AI-generated code MUST meet all conventions in this document; "the assistant wrote it" is not a justification for deviations.
- Significant AI assistance SHOULD be disclosed (e.g.
Co-authored-by:trailer or a note in the PR description). - Never paste secrets, credentials, personal data or non-public data into external AI services.
- Repository instruction files for agents (
CLAUDE.md,AGENTS.md,.github/copilot-instructions.md) MAY be maintained. They MUST be consistent with this document and the repository'sCONTRIBUTING.md, SHOULD reference rather than duplicate them, and MUST be updated when the referenced conventions or architecture change.
Copy into PR templates or use as a self-check:
- Branch name and PR title follow Conventional Commits
- Change is focused on one concern
- Formatter, linter and type checker pass locally
- Tests added/updated; all tests pass
- No secrets, debug output or commented-out code
- Public APIs / config options documented;
CHANGELOG.mdupdated - Breaking changes marked and migration path described
- Security implications considered (auth, input validation, logging)
- AI assistance reviewed and, if significant, disclosed
These conventions evolve. Propose changes via a pull request to the
.github repository,
explaining the motivation. Changes that require migration in existing
repositories SHOULD include a migration note and are announced to all
maintainers.