Contributing#

The repository’s CONTRIBUTING.md is authoritative — this page mirrors it.

Thank you for your interest in contributing to mononet! This guide covers the development workflow.

License of contributions#

mononet is licensed under the Apache License 2.0. Contributions are accepted inbound=outbound under section 5 of that license: unless you state otherwise, any contribution you intentionally submit for inclusion is licensed under Apache-2.0, with no additional terms. No CLA is required. See NOTICE.md.

Development environments#

The repo ships four devcontainer flavors. Pick the one matching your hardware:

Flavor

When to use

default

CPU work: writing code, running unit tests, building docs.

gpu-torch

GPU benchmarks against the paper’s PyTorch baseline.

gpu-jax

GPU work with JAX (Flax NNX).

gpu-keras

GPU work with Keras 3 (backed by JAX with CUDA 12 by default).

In VS Code, Ctrl/Cmd+Shift+P → Dev Containers: Reopen in Container, then pick the flavor by name.

Outside devcontainers, you need Python ≥3.11, uv, and git.

The devcontainers also auto-provision your tooling on build: pre-commit hooks are installed, Claude Code plugins are set up, and this repo’s Claude sessions are shared with your host (see Claude Code). Working locally, you do those steps yourself.

The devcontainer .venv is container-private. Each flavor mounts the project virtualenv (/workspaces/mononet/.venv) as its own named Docker volume, isolated from any .venv on your host — so a host-side uv run and the container never clobber each other’s environment (their interpreters live at different paths and can’t be shared). It persists across rebuilds; if it goes stale, reset it with docker volume rm <compose-project>_mononet-venv (find the name via docker volume ls | grep mononet-venv) and rebuild.

GPU flavors: host driver preflight#

The three gpu-* flavors pass --gpus=all, so they cannot start unless the host NVIDIA driver is loaded and the NVIDIA Container Toolkit is installed. Docker’s native failure for this is opaque:

error running prestart hook #0: exit status 1 …
nvidia-container-cli: initialization error: nvml error: driver not loaded

To turn that into something actionable, the GPU flavors run .devcontainer/shared/gpu-preflight.sh as their initializeCommand (on the host, before the container starts). It checks for an NVIDIA GPU, a working nvidia-smi, and nvidia-container-cli, and on failure prints the specific cause plus the fix.

The most common cause is a kernel upgrade without a matching NVIDIA module build: the running kernel has no nvidia.ko, so NVML is dead. apt upgrade holds the module package back whenever it needs a driver-version bump, which makes this recur silently after routine updates. On Ubuntu:

sudo apt update
sudo apt install -y linux-modules-nvidia-<branch>-open-generic-hwe-24.04 nvidia-driver-<branch>-open
sudo modprobe nvidia_uvm && nvidia-smi          # no reboot needed if no module is loaded yet
docker run --rm --gpus all ubuntu:24.04 nvidia-smi -L   # verify the container path

Set MONONET_SKIP_GPU_PREFLIGHT=1 to bypass the check (e.g. a remote Docker context whose GPUs aren’t visible from your machine).

Claude Code (plugins & sessions)#

The Claude Code plugins this repo uses are declared in .devcontainer/claude-plugins.txt and installed by .devcontainer/shared/provision-claude-plugins.sh (idempotent, user scope). That one script is the source of truth for both environments:

  • In a devcontainer — nothing to do. post-create runs the script (plugins) and installs the pre-commit hooks, and this repo’s session transcripts are bind-mounted to/from your host, so a conversation started on the host continues in the container and vice-versa. You log in inside the container (auth is not shared).

  • Locally (host) — run it once:

    bash .devcontainer/shared/provision-claude-plugins.sh   # install the repo's Claude plugins
    

    Your host ~/.claude (settings, auth, other plugins) stays otherwise independent from the container’s.

To add a plugin, append a <marketplace-source>  <plugin@marketplace> line to .devcontainer/claude-plugins.txt, then re-run the script (host) or rebuild the container.

Host and container keep independent plugin/config state; only this repo’s sessions are shared. Avoid running Claude Code on the host and in the container for this project at the same time — concurrent writes can interleave the shared transcript.

Setup#

git clone https://github.com/davorrunje/mononet.git
cd mononet
uv sync                            # install runtime + dev + docs + lint + bench
uv run pre-commit install          # install git hooks (devcontainers do this for you)

If you skip the hooks, run the checks manually before pushing (see Lint, format, static analysis).

Running tests#

uv run pytest                      # full suite (skips backends not installed)
uv run pytest tests/core           # framework-agnostic tests only
uv run pytest tests/torch          # PyTorch-only tests
uv run pytest tests/jax            # JAX-only tests
uv run pytest tests/keras          # Keras-only tests
uv run pytest tests/equivalence    # cross-backend numerical equivalence

Set the active backend with MONONET_TEST_BACKEND={torch|jax|keras} when running the equivalence suite to mirror what a single CI matrix cell does.

Lint, format, static analysis#

uv run ruff check --exit-non-zero-on-fix    # lint
uv run ruff format                           # format
uv run mypy                                  # strict type check, this env's Python
./tools/typecheck-all.sh                     # strict type check, every supported Python
uv run bandit -c pyproject.toml -r mononet   # security scan
uv run semgrep scan --config auto --error    # semgrep
uv run pre-commit run --all-files            # everything pre-commit runs

pre-commit is the full gate for everything above except the sweep: on every commit it runs ruff, mypy (ambient), bandit, semgrep, plus the docs build, codespell, secret detection, and file-hygiene hooks — so a clean git commit means the change already passes almost everything CI enforces. The exception is type checking across versions: the typecheck hook checks only the interpreter in your environment, while CI runs mypy against every version in requires-python (one status check each). Run ./tools/typecheck-all.sh before pushing if you touched anything typing-sensitive. Each isolated leg installs the CPU backends (torch, jax, keras) so those layers are actually type-checked instead of resolving to Any — the first run downloads a few GB of wheels per Python version, and each version takes roughly half a minute once its environment is warm. The same checks run on demand whether or not the hooks are installed: uv run pre-commit run --all-files for the lot, or the individual commands above piecemeal.

Building docs#

./tools/build-docs.sh              # one-shot build
./tools/serve-docs.sh              # live preview

Benchmark notebooks under docs/docs/benchmarks/ are committed with their outputs and are not re-executed during a docs build. To re-execute them before a release, see “Release process” below.

Release process#

The full maintainer runbook — one-time Trusted-Publishing setup, the Bump Version action, TestPyPI rehearsal, and the GitHub-Release-triggered publish — lives in docs/about/releasing.md.

Commit messages#

We use Conventional Commits: feat:, fix:, chore:, docs:, refactor:, test:, ci:, build:.

Pull requests#

See PULL_REQUEST_GUIDE.md for repo-specific PR conventions. New issues go to the project’s GitHub Issues tab.

Coding conventions#

  • Python 3.11+, line length 88 (ruff).

  • MyST field-list docstrings on all public functions and classes (:param x: ..., :returns: ..., :raises X: ...). Types come from signature annotations, never :type:/:rtype:. See the spec for the canonical format.

  • Strict mypy throughout. Type hints on every function and method.

  • Stdlib dataclasses for simple value objects; avoid adding new runtime dependencies without discussion.

  • Tests use pytest. Per-backend tests live under tests/<backend>/ and use pytest.importorskip("<framework>") so they skip cleanly when the backend is not installed.

Reporting security issues#

See SECURITY.md.