- Rust stable toolchain (rustup recommended)
- just command runner
- uv (for Python attach tests)
- Optional:
cargo-llvm-covfor coverage reports
Run just doctor to verify your environment.
git clone https://github.com/nazq/heimdall.git
cd heimdall
cargo build
uv sync # install Python test deps (pexpect, pytest)
just doctor| Target | Description |
|---|---|
just check |
Run all quality checks (clippy + fmt + tests) |
just test |
Run unit and Rust integration tests |
just test-attach |
Run Python attach tests (requires uv sync) |
just test-all |
Full test suite (Rust + Python) |
just fmt |
Format code |
just fmt-check |
Check formatting without modifying files |
just clippy |
Lint with clippy |
just build |
Debug build |
just release |
Release build |
just install |
Build release and install hm to ~/.local/bin |
just cov |
Generate coverage report (requires cargo-llvm-cov) |
# Start a supervised session
just run my-session bash
# Attach to it from another terminal
just attach my-session
# List running sessions
just ls
# Check session status
just status my-session
# Kill a session
just kill my-sessionAll PRs must pass just check (clippy + format check + full test suite).
Tests exist to prove the system works, not to prove the code compiles. Every test must satisfy three criteria:
- Setup is correct — the test creates the right preconditions and waits for them (e.g. socket appears before connecting).
- The operation runs — the test actually exercises the code path it claims to test, not a happy-path shortcut.
- All invariants are asserted — don't assert one field when the response has five. If a STATUS_RESP has pid, idle_ms, alive, state, and state_ms, assert the ones that have known-good values. Skipping fields hides bugs.
The protocol is documented in docs/protocol.md. Protocol
tests come in two flavours, and both are required:
- Round-trip tests — pack through
pack_*, parse throughread_frame, verify fields match. These catch regressions but have a blind spot: if pack and parse have the same bug (e.g. both swap two fields), the test passes while the wire format is silently wrong. - Golden byte tests — assert that a known input produces an exact byte sequence. These pin the wire format to the documented spec and catch symmetric pack/parse bugs that round-trip tests cannot.
When adding a new frame type or modifying a payload layout, add both.
- Rust (
tests/integration.rs) — spawn realhmprocesses, connect over Unix sockets, send/receive protocol frames, assert responses byte-by-byte. These test the supervisor end-to-end without mocks. - Python (
tests/test_attach.py) — use pexpect over real PTYs to verify the terminal UX: alt screen, status bar, detach, signal forwarding, resize. Run viajust test-attach(requiresuv sync).
Both suites use temp directories for socket isolation and clean up processes in fixtures/teardown.
Conventional commits. One logical change per commit.
feat: add scrollback size config option
fix: handle SIGCHLD race on rapid child exit
deps: bump nix to 0.29
ci: add aarch64-linux to release matrix
docs: clarify process group signaling in ARCH.md
cargo fmt-- all code must be formatted.cargo clippy -- -W clippy::all-- no warnings allowed.
See docs/ for detailed documentation:
- Architecture -- design principles, process lifecycle, data flow
- Protocol -- wire format and message types
- Classifiers -- state detection and custom classifiers
- Configuration -- all config options