# Development
Contributor guide for conclave. The global Rust constitution (`~/CLAUDE.md`) applies on top of
everything here; project-specific conventions live in [`docs/DESIGN.md`](docs/DESIGN.md) §22.
## Toolchain
- **Pinned nightly** via [`rust-toolchain.toml`](rust-toolchain.toml) (`nightly-2025-12-22`),
**edition 2024**. `rustup` installs it automatically on the first build inside the repo. Nothing
in the stack requires nightly except the `#[coverage(off)]` attribute; dropping to stable later
is trivial.
- Components `rustfmt` and `clippy` are pinned alongside the channel.
## Commands
Everything routes through [`cargo-make`](https://github.com/sagiegurari/cargo-make) — it is the
sole entry point for dev commands.
```bash
cargo make ci # Canonical gate: fmt-check + clippy (-D warnings) + nextest
cargo make fmt # Format the tree
cargo make fmt-check # Verify formatting (no writes)
cargo make clippy # Lint with -D warnings
cargo make test # Run the suite via nextest
cargo make test-cargo # Fallback: plain `cargo test`
cargo make codecov # Emit coverage.lcov
cargo make codecov-html # Emit an HTML coverage report
cargo make build # Debug build
cargo make build-release # Optimized build
cargo make run -- --help # Run the binary
cargo make changelog # Regenerate CHANGELOG.md via git-cliff
```
First-time setup installs the binstall-provided tools:
```bash
cargo make tools # cargo-nextest + cargo-llvm-cov (via cargo binstall)
```
## Lints & formatting
Lints live in the Cargo `[lints]` table so they apply DRY across the lib and bin:
`deny(unused, clippy::unwrap_used, clippy::correctness, clippy::complexity, clippy::pedantic)`.
Tests relax `clippy::unwrap_used`. Suppressing `too_many_arguments`, `too_many_lines`, or
`needless_pass_by_value` is prohibited — extract a struct / helper / borrow instead. The only
sanctioned `allow`s are narrow, justified ones for macro-codegen false positives.
Formatting is `rustfmt` with [`rustfmt.toml`](rustfmt.toml) (`max_width = 200`, …). CI runs
`fmt --check`.
## Test layout (SOC)
Three tiers, mirroring `docs/DESIGN.md` §17 / §22:
- **Unit** — in-module `#[cfg(test)] mod tests`. Shared fixtures come from the always-compiled
`conclavelib::tests` factory (duplex transports, path/key fixtures) so unit and out-of-crate
suites build them from one place.
- **Integration** — `tests/*.rs`, one bounded subsystem per file, preferring in-memory
`tokio::io::duplex()` over real sockets.
- **E2E** — `tests/e2e.rs` spawns the real binary via `env!("CARGO_BIN_EXE_conclave")` inside a
`tempfile::TempDir`. E2E test names are prefixed `e2e_` so they can be selected with
`cargo nextest run -E 'test(/e2e_/)'`.
Flakiness controls for socket/timing-sensitive tests live in
[`.config/nextest.toml`](.config/nextest.toml): `retries = 2` and a serialized `network-heavy`
test group. **Every behavioral change ships a test — no exceptions.**
## Bridge ↔ Claude Code (`claude/channel`)
The bridge is an MCP server that injects inbound traffic into Claude Code via the experimental
`claude/channel` capability (DESIGN.md §4). The wire shape is validated against the installed CC in
CI with a **mock MCP client** (`test(/bridge_inject/)`) and a two-bridge e2e (`test(/e2e_channel/)`).
The **live-CC** check is manual and kept out of CI because Claude Code gates the capability behind a
development-channel flag (a normally-registered MCP server has `claude/channel` stripped):
1. Stand up a server and register + enroll this machine (M4 adds the `register` verb; until then use
the e2e's provisioning path as a reference).
2. Launch Claude Code with the bridge loaded as a **development channel** so the capability survives:
```bash
claude --dangerously-load-development-channels \
'server:conclave=conclave bridge --server wss://your.server --as my-session'
```
> The client dials `wss://` (rustls, PRD-0009 T-001) for a TLS-fronted server, or plain `ws://`
> for a local/tunnelless origin — `connect_async` selects TLS by URL scheme.
> `--dangerously-load-development-channels` is for local channel development only. The alternative
> is the `allowedChannelPlugins` managed-settings allowlist. Without one of these, CC strips
> `claude/channel` and no injection occurs.
3. From another session/machine, send a channel message; confirm it surfaces in the CC session as a
`<channel …>` tag, and that `send_channel` / `whisper` replies flow back.
If the capability shape ever drifts, the mock-client tests in `src/bridge/mcp.rs` are the canonical
record of the expected frames; update them alongside the validation.
**Reconnect & presence semantics.** The bridge sends a keepalive `Ping` every 20s; the server reaps
any session with no inbound frame for 60s, so a slept laptop or dropped wifi goes offline on its own.
A **connection drop** is recovered automatically — the bridge reconnects with exponential backoff
(200ms→30s) and re-subscribes its joined channels. A **bridge process death**, however, loses the
in-memory join state: a fresh process reconnects but needs a re-`join` (DESIGN.md §16). A whisper to
an offline/unknown session path returns a `NotFound` error to the sender (nothing is queued).
## Milestones & PRDs
Work is planned as PRDs in [`.prds/`](.prds/) (M0 = PRD-0001 … M5 = PRD-0006). Reference the task
ID in commits (e.g. `PRD-0001 T-004: cargo-make task graph`) and keep the task `status` in the PRD
frontmatter current as you go. Do not mark a UAT `verified` unless a real test exists and passes.
## Release
Versioning is SemVer; the changelog is Keep-a-Changelog via git-cliff. `cargo make release-bump`
performs the version bump + tag (`cargo release --no-publish`); publishing to crates.io is a
separate, manual step.