Conventions¶
Layout¶
Projects sit directly at the repository root, one directory each. There is no root package, no uv workspace, and no topic grouping — with a handful of projects, groups added a category decision per project and bought nothing that this documentation does not express better in prose.
Package names are prefixed but otherwise flat: the xarray project is
distribution ocs-stack-xarray, module ocs_stack_xarray. The prefix is
load-bearing — a project studying polars cannot have a module named polars
while depending on it.
Per-project template¶
Projects follow the chapkit template:
- Python 3.13,
uvwith theuv_buildbackend, src layout - ruff at 120 columns, Google docstring convention
- mypy and pyright, both in strict mode, both required to pass
- pytest with coverage; tests exempt from docstring rules
- a
Makefilewithinstall,lint,test,coverage,run EXAMPLE=<name>,run-all,clean, andci(lint + test) README.mdandROADMAP.md— the roadmap is the syllabus and is kept accurate as examples land
Dependencies are added with uv add, never hand-pinned, and breaking API
changes get fixed rather than pinned around. Where a pin is genuinely forced,
the reason goes in a comment beside it.
Examples¶
Examples live in examples/PPNN_topic_name.py, where PP is the phase and
NN the number within it. Each is a self-contained lesson:
- a module docstring with What, Why (tied to what OCS does with it), and Run lines
# SECTION:banner comments- printed prose before each demonstration, and printed evidence after it — shapes, timings, task counts, error messages
- a
=== Summary ===block at the end
They must be deterministic, offline, and non-interactive. Anything needing a
real service still has to run without it: connect() in dask-distributed
probes the cluster, falls back to an in-process substitute, and prints what the
fallback cannot show.
Projects with infrastructure¶
A project needing real services adds a compose.yml (plus a Dockerfile when
the image needs more than an upstream one) and Makefile targets up, down,
ps, logs. up blocks until the service is genuinely ready rather than
returning on container start.
Where a container and the host both take part, the image is pinned to the
versions in uv.lock. A mismatch between a client library and its server is a
whole class of confusing failures.
Verification¶
Verification is not a clean type-check:
make verify PROJECT=xarray # lint, type-check, test, run every example
make verify-all # the whole repository
make docs-build # this site, its links, and the docs standard
make docs-check # just the docs standard
make docs-check enforces the rules this page states in prose, because a
documentation lapse is invisible in a way a broken build is not — a page is
never "broken", only quietly thinner than it promises to be. It checks that
every example carries a What/Why/Run docstring, # SECTION banners and a
summary; that every example has its own section in the project page of at
least 80 lines; that the page links the source; that the roadmap and the
examples directory agree in both directions; and that the project is wired
into the site at all — a reference stub, both nav entries, and a paths entry
for mkdocstrings, since a project can satisfy every other rule and still not
appear. It runs as part of make docs-build.
make docs-build passes --strict to mkdocs, so a warning fails the build.
That matters for one case check-links.sh cannot cover: it strips the anchor
before testing a path, so a link to a heading that does not exist resolves as
far as it is concerned. mkdocs validates those anchors, and without --strict
it would say so and exit 0 anyway. make docs-serve stays lenient, so a
half-written page still reloads.
make verify runs every example and reads its output, because compiling proves
it builds, not that it works. Every number quoted in these pages came out of an
example that actually ran; timing-derived figures are marked as
machine-dependent because they do not reproduce exactly.
Links to source¶
The project pages link to example and library source with paths relative to
the repository root, because that is how these files are usually read: in an
editor, or as markdown on GitHub. The published site is the one place those
paths cannot work — it contains docs/ and nothing else, so a relative source
link 404s there.
scripts/mkdocs_hooks.py rewrites them to absolute GitHub URLs at build time.
The markdown on disk stays relative, so scripts/check-links.sh can keep
checking it against the filesystem, and the built site gets a URL that
resolves. Links that stay inside docs/ are left alone.
Continuous integration¶
Two workflows in .github/workflows/:
ci.ymlruns on every push and pull request. Adiscoverjob asksscripts/verify.sh --listwhat the projects are and feeds that into a matrix, so adding a project cannot leave it silently untested — the same reasoning as the site-wiring check above. Each project then runsscripts/verify.sh <project>: lint, both type checkers, tests, and every example. A separate job runsmake docs-build.docs.ymlpublishes the site to GitHub Pages on every push tomain. It builds withmake docs-build, the same target CI runs, so a page that fails the documentation standard is never published.
The runners have no Docker daemon, which is a supported way to run this: every example that wants a real service falls back and prints what the fallback cannot show.
Documentation¶
Documentation is centralised in docs/ at the repository root and built as one
mkdocs site. Projects keep their own README.md and ROADMAP.md — those are
working files and render fine on their own — but there is a single site, a
single navigation, and a single search.
The API reference is generated by mkdocstrings, which reads the source
statically through griffe. That means the docs build needs none of the
projects' dependencies installed: mkdocs.yml simply points paths at each
project's src/ directory, and make docs-build runs the whole thing through
uvx without a root virtualenv.
Reading it offline¶
The site is the right shape at a desk and the wrong shape on a phone with no
signal. make offline produces the other shape:
dist/ocs-stack.html— every page in nav order in one self-contained file. No assets, no network, no sidebar; cross-page links become anchors within the document, and links to source outsidedocs/become the path in monospace, since a standalone file cannot follow them.dist/ocs-stack.pdf— the same file printed by headless Chrome, about 450 A4 pages.
scripts/build-book.py reads the nav out of mkdocs.yml rather than keeping
its own list, so a page added to the site is in the book without a second edit.
It uses the same markdown extensions, minus the mermaid fence, which has no
renderer in a standalone file.
make share serves the built site over Tailscale for reading on another
device with search intact. It binds to this machine's tailnet address rather
than 0.0.0.0, so the local network cannot reach it, and it does not use
tailscale funnel, which would publish it to the internet.
Working rules¶
- Never revert, always fix forward. Fix the root cause rather than removing functionality.
- Always test the unhappy path. Invalid input must raise a clear error, and tests must cover it.
- No emojis anywhere: code, comments, docs, commit messages, or output.
- Conventional commits, scoped to the project directory, with no attribution or co-authored-by lines.