Conventions¶
How code, comments, documentation and commits are written in Commol. The tools check some of these rules. The rest depend on the author and the reviewer.
Quick reference¶
| Area | Rule | Checked by |
|---|---|---|
| Python style | Ruff, line length 88; PEP 8 names | ruff check (rules N), ruff format (pre-commit, CI) |
| Python types | Annotations on every signature; never Any |
ruff check (ANN, and TID251, which bans typing.Any, on commol/) checks the annotations; ty check (pre-commit, CI) checks the types |
| Python dead code | No unused code; a name used only in a string annotation goes in py-commol/.vulture_allowlist.py |
vulture (pre-commit, CI) |
| Rust style | cargo fmt; no Clippy warnings |
pre-commit, CI (-D warnings) |
| Rust docs | Every public item documented (missing_docs); no broken doc links |
clippy and cargo doc (CI) |
| Docstrings | NumPy style on every public module, class, function and argument | ruff rules D1 and D417 (pre-commit, CI) |
| Comments | Describe the code as it is | Review |
| Documentation | Mandatory with every change: definition of done | CI and review |
| Doc examples | Abstract names; every block runs | pytest -m docs (CI) and review |
| Field words | No application field in the user pages or the source of commol |
tests/docs/test_pages.py (pytest -m docs, CI) |
| Tests | One package per guide page; shared factories; every test checks a result, once, within its time budget: Tests | tests/suite/ (CI) and review; pytest 10 deprecations fail the run (filterwarnings) |
| Rules of values | The engine holds the range rule of each setting, the rule of each value of a model and every window; a Python model declares only the type | tests/suite/test_engine_rules.py (CI) |
| Commits | type(scope): description |
Review |
Python¶
- Every function and method signature has type hints. Never use
Any: use a precise type, aTypeVar, aProtocolor a union. - Public functions and methods have a NumPy-style docstring: a one-line
summary, then
Parameters,ReturnsandRaisesas needed. Private helpers may use a one-line docstring. - A public class name writes every letter of an acronym in capitals, as PEP 8
asks:
ABCSMC,SMCResult,ODESolver,PRCC. A method keeps the spelling of the authors who named it, soEnbPI(Xu and Xie 2021) stays as written. Rust names follow the Rust guidelines, in which an acronym is one word (OdeSolver,SmcSampler).tests/docs/test_reference.py::test_public_names_capitalize_acronymschecks the known acronyms ofcommol.__all__. - A family of configurations is one class per member, joined in a union:
ABCDistance(LossDistance,SeriesDistance),ObservationNoise,Eta(InformationMatchingEta,BootstrapCoverageEta). The user builds a member from its class, so each configuration has one spelling. A classmethod is only an alternative constructor or a preset of its own class (Priors.uniform_on_bounds(),PlotConfig.publication(),ParameterSample.equally_weighted(...),AxisPattern.pulse(...)); it never returns an instance of another class, such as a subclass. - A setting keeps the name its meaning has elsewhere:
confidence_levelfor the level of an interval, a plural noun for a count. See Design decisions;tests/suite/test_engine_rules.py::test_a_setting_has_one_namechecks it. - The engine holds the range of every setting; a Python model declares only
its type (no
ge,gt,le,lt, constrained number type orallow_inf_nan=False), and a model of an object with ranges listsRangeChecked(py-commol/commol/context/ranges.py) among its bases, whose validator calls the check of the engine for its object. The rules are incrates/commol-core/src/range.rs,crates/commol-calibration/src/ranges.rsandcrates/commol-inference/src/ranges.rs. See Design decisions;tests/suite/test_engine_rules.py::test_no_model_declares_a_range_of_a_settingchecks it. - The engine holds the rule of each value of a model, an initial amount, the
fractions of a stratification, an impulse or a point of a series
(
crates/commol-model/src/rules.rs), and the validator of its Python object calls it; see Design decisions. The engine computes every window, and Python has no window arithmetic (tests/suite/test_engine_rules.py::test_only_the_engine_computes_a_window). - Context data structures are Pydantic v2 models.
ModelBuildermethods returnSelf, so calls chain.- Log with the standard
loggingmodule. Library code never writes to the standard error of the process: a failure is part of the result and the caller logs it (CalibrationResult.failed). Output the user asks for, such as a verbose optimizer's table, goes to Python'ssys.stdout.
Rust¶
- Edition 2024. Every crate takes its version, edition and metadata from
[workspace.package]of the root manifest, and a dependency that more than one crate uses from[workspace.dependencies](py-commol/tests/suite/test_workflows.py). - Public items have rustdoc comments, and a new module starts with a
//!summary. - Hot paths avoid allocation: reuse buffers, access by index, keep evaluation contexts alive.
- No
println!oreprintln!in library code: return the information to the caller.
Comments and docstrings¶
Describe the code as it is now. Never describe how it came to be.
| Do not write | Write instead |
|---|---|
| "This replaces the old prefix matching" | "Matches compartments by label." |
| "Previously this was computed in Python" | Nothing: the history is in git. |
| "This is what makes calibration work" | The rule the caller must satisfy. |
"Kept in sync with builder.rs" |
A test that fails when they differ. |
Design reasons and invariants that cross components go to Design decisions, not to the source.
Documentation definition of done¶
Mandatory
Every change to this repository ships with the documentation it needs, in the agreed format and at the agreed quality. A change without it is not done, and its pull request is not merged. This applies to people and to agents, to features, fixes, refactors, dependency updates and CI changes.
1. Update the docs the change affects¶
| If the change… | Update |
|---|---|
| Adds or changes a public Python name, argument, default or error | Its docstring; for a new name in commol.__all__, a path entry in docs/api/ and a place in the table of docs/api/index.md; the guide page that uses it |
| Changes model behavior (expansion, rates, initial conditions, simulation) | The guide page in docs/guide/; Model structure if a type changes |
| Changes calibration or uncertainty | guide/calibration.md and its sub-pages, or guide/uncertainty/ |
| Changes an expression function or variable | guide/mathematical-expressions.md |
| Moves code, adds a module, or changes who owns what | Repository map; the "If you need to…" table of AGENTS.md, which the repository map includes |
| Adds or removes a crate, an engine or a layer, or changes the path of a simulation or a calibration | The diagrams and tables of Architecture |
| Adds or changes a rule that is not obvious from the code | The page of its area under Design decisions, with the test that guards it; a new area is a new page with a row on the index |
| Changes a workflow, a command, CI or the release | AGENTS.md; docs/development/ |
| Plans new work | An issue from a template of .github/ISSUE_TEMPLATE/ (Planning) |
| Is visible to users | docs/about/changelog.md, under [Unreleased] |
| Adds or changes a public Rust item | Its rustdoc comment |
When in doubt, search docs/ and AGENTS.md for the changed name and update
every match.
2. Use the agreed format¶
Pages
- One page, one job. The title says the outcome.
- Start with one paragraph (what it is, who it is for), then a quick path, then details, then a checklist or the next step.
- Prefer tables, checklists and short sections to long prose.
- Start every page with a link back to its index, and end it with previous/next links when it belongs to a sequence.
Text
- Simple technical English: short sentences, one idea per sentence, active voice, the answer first.
- Every claim about the code names the file, function or test behind it.
- Describe the present. History belongs in git and in the changelog.
Examples
- Use abstract names only: bins
A,B,C; categoriesgroup1,cat1; parametersk1,alpha. No names from epidemiology, chemistry or any other field.py-commol/tests/docs/test_pages.pychecks the user pages and the source ofcommolagainst a list of field words; a cited title is the only exception. - Every Python block runs, top to bottom within its page, in one namespace and
from a temporary directory (
py-commol/tests/docs/test_examples.py). Define what a block needs in an earlier block of the same page. -
A block that cannot run alone (a signature, a fragment) is marked on the line just before its fence, with a reason:
Use the marker only when the block cannot run; never to hide a failing example.
Code documentation
- Python: a NumPy-style docstring on every public function, method and class
(
Parameters,Returns,Raises). - Rust: a rustdoc comment on every public item; a
//!summary at the top of every new module. - Comments follow Comments and docstrings.
3. Pass the quality checks¶
| Check | Command | Enforced by |
|---|---|---|
| The site builds with no broken internal link or anchor | mkdocs build --strict (with validation: in mkdocs.yaml) |
CI (Documentation) |
| External links answer | lychee (.github/workflows/links.yaml) |
CI on documentation changes, weekly; not a required check |
| The documentation examples run | pytest -m docs (from py-commol/) |
CI (Python Coverage, and Test of Build) |
Every name in commol.__all__ has an API entry (… under docs/api/) and is in the table of docs/api/index.md |
pytest -m docs (tests/docs/test_reference.py) |
CI (Python Coverage, and Test of Build) |
Every crate under crates/ is named in Architecture |
pytest -m docs (tests/docs/test_reference.py) |
CI (Python Coverage, and Test of Build) |
Every test or definition a page names as <file>::<name> exists in that file |
pytest -m docs (tests/docs/test_reference.py) |
CI (Python Coverage, and Test of Build) |
| Public Python modules, classes, functions and arguments have docstrings | ruff check . ../.github/scripts (rules D1, D417, NumPy convention) |
pre-commit, CI |
Every path a page of AGENTS.md or docs/codebase/ names in code format exists, and a bare file name matches one file |
pytest -m docs (tests/docs/test_reference.py) |
CI (Python Coverage, and Test of Build) |
| Every area page of decisions has a row on the index, and no link points to a decision on the index | pytest -m docs (tests/docs/test_layout.py) |
CI (Python Coverage, and Test of Build) |
No run of 12 words, outside code blocks and the header rows of tables, is in two pages of AGENTS.md and docs/ (the changelog left out): a page links to the page that owns a text; the repository map and the workflow include the task table and the commands of AGENTS.md |
pytest -m docs (tests/docs/test_layout.py) |
CI (Python Coverage, and Test of Build) |
AGENTS.md stays within 110 lines, a page of docs/codebase/ within 300 and a page of docs/codebase/decisions/ within 250 |
pytest -m docs (tests/docs/test_layout.py) |
CI (Python Coverage, and Test of Build) |
| Public Rust items have docs, without broken links | #![warn(missing_docs)] in every crate (denied by clippy -D warnings); RUSTDOCFLAGS="-D warnings" cargo doc |
pre-commit (clippy), CI |
| The table in step 1 is complete for this change | — | Pull request checklist and review |
| The format in step 2 is followed | — | Review |
The pull request template repeats this checklist. A reviewer who finds a gap asks for it before approving.
Writing for people and agents¶
The same pages serve both. An agent reads the Markdown source by path, so:
- keep each page self-contained, with its links;
- name files and functions exactly, in code format;
- put the rule before the explanation.
Commits¶
Use Conventional Commits:
<type>(<scope>): <description>.
- The scope is required.
- The description is one short phrase, with no period.
- No body, and no co-author or AI attribution lines.
- Types:
feat,fix,docs,style,refactor,perf,test,build,ci,chore.
feat(transitions): add rate patterns
fix(simulation): correct the total of a subgroup
docs(codebase): add repository map
See Development workflow for branches and merge methods.
Navigation¶
Previous: Design decisions | Next: Tests