Documentation CI gates#
The docs build runs three independent quality gates on every PR. This page documents what each gate enforces and how to invoke it locally — useful both for contributors writing new docstrings and for debugging a CI failure.
Numpydoc validation#
The driver lives at tools/numpydoc_validate.py. It walks every
public callable reachable from femorph_solver.__all__ and runs
numpydoc.validate.validate() on each, then renders a markdown
summary table.
Where the report appears. In the docs GitHub Actions job,
the script’s output is appended to $GITHUB_STEP_SUMMARY so the
violation table is visible on every PR run without having to dig
into the build log.
What’s enforced. The enforced violation classes live in
[tool.numpydoc_validation] in pyproject.toml. The set is
the target — the bar Phase 1 closes the project to. Initial
classes:
GL08— docstring missing entirely.SS01/SS06— missing summary or summary on more than one line.PR01/PR02— parameters listed in the docstring do not match the signature.RT01— function returns a value but noReturnssection.
EX01 (no Examples block) is intentionally omitted from the
initial set — Phase 1 turns it on once every public method has been
touched.
How the gate is enforced. The CI step currently runs the
script with --warn-only so the table is visible but the build
does not fail on baseline violations. Once
#585 closes
the surface to bucket-A documentation, the --warn-only flag is
removed and the gate becomes hard.
Local invocation:
pre-commit run numpydoc-validate --hook-stage manual
# or directly
python tools/numpydoc_validate.py
python tools/numpydoc_validate.py --json /tmp/report.json
python tools/numpydoc_validate.py --all # fail on every class
Doctest gate#
Every docstring Examples block on an allowlisted module is
re-run by the test suite via tests.test_doctest_modules. A
failing doctest fails CI — the docstring is the spec of what the
API claims to do, and a broken example is an outdated spec.
Why an allowlist. Phase 0 cannot run
--doctest-modules src/femorph_solver/ end-to-end because dozens of
pre-existing >>> blocks are illustrative (use undefined names
like model or tip_node) rather than runnable. Promoting a
module to the allowlist is a single-line change in
tests/test_doctest_modules.py that lands in the same PR as the
docstring rewrite — see the Phase 1 children of #585.
Local invocation:
pytest tests/test_doctest_modules.py --no-cov
pytest --doctest-modules src/femorph_solver/<module>.py --no-cov # ad-hoc on one file
Linkcheck (nightly)#
A separate docs-linkcheck workflow runs Sphinx’s linkcheck
builder against every external URL referenced from the docs tree.
It runs on a 04:00 UTC cron rather than per-PR, because broken
external URLs are usually not introduced by the PR under review —
they break later, when an upstream site renames or removes a page.
On non-zero exit, ci-failure-tracker.yml files (or updates) a
ci-failure-main-labeled issue.
Configuration. The exclusion list is in conf.py under
linkcheck_ignore — typically used for the version-switcher
JSON (which is only valid against the deployed multi-version
build, not against arbitrary checkouts).
Local invocation:
make -C doc linkcheck
Sphinx strict mode (planned)#
SPHINXOPTS="-W --keep-going" is the eventual end-state, with
conf.py nitpicky=True enforcing that every cross-reference
resolves. Today the docs build emits ~700 unresolved cross-refs
because the API reference page (api/index.rst) is hand-curated
and does not autodoc every public symbol — so :class:`UnitSystem
and friends raise ref.class warnings.
The fix is the autosummary refactor tracked under #587 (Phase 3). Strict mode lands together with that refactor; turning it on first would indiscriminately fail every doc PR for warnings unrelated to the PR’s scope.