Thanks for your interest in contributing to Thurbox! This guide covers how to set up your environment, the conventions we follow, and how to get a change merged.
Thurbox is a multi-session coding-agent TUI orchestrator built with Rust. Before
diving in, skimming the README.md, CLAUDE.md, and
the design docs under docs/ will save you time.
Be respectful, constructive, and welcoming. We want Thurbox to be a project people enjoy contributing to — assume good faith, keep discussions on the technical merits, and help newcomers find their footing.
- Clone the repository — you can push branches directly, no fork needed.
- Create a branch off
mainfor your work (git switch -c feat/my-change). - Set up the toolchain (below).
- Make your change, with tests.
- Run the checks locally (
just lint && just test). - Push your branch and open a pull request with a clear description.
The reproducible dev environment is a Nix flake that pins the Rust
toolchain, tmux, shellcheck, Node, the cargo tooling, just, and the demo
stack.
nix develop # enter the pinned shell
# ...or, with direnv installed:
direnv allow # auto-enters the shell on cd (see .envrc)No Nix? Use the fallback installer:
scripts/install-dev-tools.sh # installs the dev tools (including prek)You'll also need tmux >= 3.2, shellcheck, bats, Node + npm (for the
website linters), and git. The full walkthrough — including the runtime
sandbox for trying thurbox in isolation — lives in
docs/DEVELOPMENT.md.
- MSRV: Rust 1.75, Edition 2021.
just is the task entrypoint — run just with no arguments for the full list.
| Task | What it does |
|---|---|
just build |
build the dev binaries (thurbox + thurbox-cli) |
just test |
cargo nextest run --all |
just lint |
fmt-check + clippy + cargo-deny + rumdl + shellcheck |
just fmt |
format Rust + website |
just arch |
architecture-rule + rustdoc checks |
just sandbox |
run thurbox in an isolated dev sandbox |
Thurbox is agent-neutral, so the repo works with any coding-agent CLI.
CLAUDE.md is the canonical guidance doc — Claude Code reads it
directly, and opencode loads it automatically as project
rules via its Claude-Code compatibility (it's picked up when no AGENTS.md
exists, so there's deliberately no AGENTS.md duplicating it).
Slash-command workflows and skills are checked in for both:
| Kind | Claude Code | opencode | Notes |
|---|---|---|---|
Commands (/refactor, /ship, /sync) |
.claude/commands/ |
.opencode/commands/ |
opencode does not auto-discover .claude/commands/, so the two are kept in sync by hand |
Skills (publish, ui-review) |
.claude/skills/ |
.claude/skills/ |
opencode auto-discovers .claude/skills/, so one copy serves both — don't mirror them under .opencode/skills/ (it would double-register) |
A minimal opencode.json declares the $schema for editor
validation. When you add or change a command, update both directories so the
two agents stay in sync.
Thurbox follows test-driven development — write a failing test first, make it pass, then refactor. Bug fixes start with a test that reproduces the bug.
cargo nextest run --all # run all tests (preferred runner)
cargo nextest run -E 'test(name)' # run a single test by nameThe TUI has in-process acceptance tests with insta snapshots
(src/app/acceptance.rs) and a black-box smoke test
(scripts/dev/smoke/tui-smoke.sh). Update snapshots with
INSTA_UPDATE=always cargo test (or cargo insta review). See the Testing
section of CLAUDE.md for the full picture.
CI runs a zero-warning policy — clippy and rustdoc warnings are promoted
to errors. Run these before pushing:
cargo fmt --all # format (100-char width)
cargo clippy --all-targets --all-features -- -D warnings # lint
rumdl check . # markdown lint
npm run lint:website # website lintersjust lint bundles the Rust + shell checks.
We use prek (a Rust-based pre-commit
framework) to run the same checks CI does, automatically, before each commit.
Install the hooks once after cloning:
prek installThis is the recommended way to catch failures early — the hooks run across three stages:
- commit-msg — conventional-commit validation (
cog verify) - pre-commit — fmt, clippy, check, nextest, architecture rules, cargo-deny, rustdoc, bats, shellcheck, rumdl, prettier, htmlhint, stylelint, eslint
- pre-push — commit-history check (
cog check)
If a hook fails, fix the reported issue and re-stage — the same checks gate your PR in CI, so a clean local run means a clean pipeline.
All commits must follow
Conventional Commits, enforced by
cocogitto via the commit-msg hook.
- Types:
feat,fix,perf,refactor,docs,style,test,chore,ci,build,revert - Scopes:
api,cli,ui,git,core,docs,deps,config,mcp
cog commit feat "add remote host picker"
cog commit fix "avoid panic on empty worktree" gitNote that commit type drives releases: feat → minor bump, fix/perf →
patch bump, while docs/chore/ci/style/test produce no release.
If a change invalidates or extends a documented decision, update the relevant doc in the same PR. Rationale lives in:
docs/CONSTITUTION.md— non-negotiable principlesdocs/ARCHITECTURE.md— architectural decisionsdocs/FEATURES.md— feature-level design choicesdocs/CONFIG.md— every config file/env var/DB setting
Comments should explain why, not what — see the Comments section of
CLAUDE.md.
Thurbox follows The Elm Architecture
(Event → Message → update → view → Frame) with one-directional module
dependencies enforced by tests/architecture_rules.rs:
session ← pure data types, no crate-internal references
agent → session
ui → session + app (read-only model/view state)
app → coordinator, imports all modules
A new module fails the architecture test until its dependencies are declared in
the allowlist. See docs/CONSTITUTION.md for the
non-negotiable rules.
- Keep PRs focused — one logical change per PR.
- Include tests for new behavior and bug fixes.
- Make sure
just lintandjust testpass locally. - Write a clear description of what changed and why.
- Update docs alongside code when a documented decision changes.
CI runs the same deterministic checks (clippy, nextest, cargo-deny, cog,
rumdl, shellcheck) that gate every merge — there are no LLM-gated checks.
By contributing, you agree that your contributions will be licensed under the MIT License, the same license that covers the project.