Development
Ganymede is a Cargo workspace, and docs/ is this site. The TypeScript for web-view UIs is in tlockney/ganymede-ui.
Build and test
cargo build --workspace
cargo test --workspace # uses an in-process fake kernel
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warningsThe end-to-end tests in crates/ganymede-cli/tests/ also use whatever real kernels and Jupyter are installed, and skip what is missing. The README lists the variables that make them strict. CI runs them on Linux and macOS with cdp-kernel and ipykernel, with jupyter execute and jupyter server as the reference. Python there is a test oracle and a kernel, never a dependency of the product. Nothing may skip in that job; the rust job, which has no real kernels, lists what it skipped in its run summary.
CI
- Pins. The cdp-kernel, ipykernel, nbclient, and
jupyter_serverversions the end-to-end jobs install are in one place, theenv:block at the top of.github/workflows/ci.yml. Bump them together in one pull request. A weekly scheduled run installs the latest releases instead, so drift shows up there first. - Rust versions. CI builds with the latest stable.
rust-versionin the workspaceCargo.tomlis the oldest supported Rust, and themsrvjob builds with exactly that; raise it on purpose. - Dependencies. The
auditjob runscargo deny(deny.toml): RustSec advisories fail it, duplicate versions show as warnings. It also runs weekly. Licenses are checked bycargo aboutagainstabout.toml. Dependabot (.github/dependabot.yml) opens weekly grouped bumps for Cargo, npm, and the actions; its pull requests are exempt from the linked-issue check. - Actions are pinned by full commit SHA, with the version in a comment. Let Dependabot move them; don't hand-edit a tag back in.
- Permissions. Every workflow starts from
contents: read(or nothing), and a job that needs more asks for it itself.
Rules
- The host knows no language. No code depends on a kernel's language or branches on it, and Python kernels arrive only as installed kernelspecs. Outside tests,
pythonappears in the host crates only where a file format is defined by Python's output (nbformat's float layout, the trust signature's scalar format), in PyPI's names (ganymede lab installfetches JupyterLab's wheels from there), and in comments that say what Python does. - The REST and websocket API is a compatibility target, not a design space: match
jupyter_server. - Each change is a pull request that references a GitHub issue, with CI green, and adds what it learned to the development log as its own file,
docs/notes/YYYY-MM-DD-<topic>.md. Don't append todocs/notes.md: one shared file makes every pair of open PRs conflict.
Releases
scripts/release.sh <level> bumps the workspace version, writes CHANGELOG.md, commits on release/vX.Y.Z, and opens an issue and a PR for it. Merging the PR tags vX.Y.Z, which runs the release workflow. It is a dry run without --execute. Releasing has the whole path, and what to do when a release fails partway.
This site
The landing page is plain HTML and CSS in site/. These docs are Markdown in docs/, built by VitePress:
cd docs && npm install
npm run dev # live preview
npm run build # into docs/.vitepress/distThe pages workflow builds both on every pull request that touches them, so a broken page or dead link fails the PR. It deploys them to GitHub Pages from main on every merge that touches them, and again after each release. install.sh installs the latest release, so docs for a feature that isn't released yet carry an "Unreleased" note, which the release removes (CLAUDE.md). site/install.sh goes live on merge too, and must work with the latest release. The design is cdp-kernel.org's; keep site/style.css and docs/.vitepress/theme/custom.css in step with each other.