# Contributing to oxi `oxi` is a local desktop coding-agent chat app built in Rust with egui/eframe. See [README.md](README.md) for what it does and how it's laid out; this file covers the mechanics of sending a change. ## Requirements - A stable Rust toolchain (`rustup toolchain install stable`) with the `rustfmt` and `clippy` components. - A desktop environment supported by `eframe` (the app doesn't run headless). ## Building and running ```bash cargo run --release ``` Debug builds work too (`cargo run`), but the release profile is closer to what CI/release artifacts ship and is noticeably more responsive for UI work. Published binaries are built with `cargo build --profile dist`: the release profile plus thin LTO and a single codegen unit, about 11% smaller. It takes several minutes longer to build, so it is kept out of the everyday `--release` loop. Diagnostics go to `oxi.log` next to `settings.json` (Settings → About → Open log); set `OXI_LOG=debug` for more detail. Panics land in `crash.log` in the same folder. ## Before opening a PR CI (`.github/workflows/ci.yml`) runs these three checks on every push and PR; run them locally first so you're not waiting on CI to find a formatting nit: ```bash cargo fmt --all -- --check cargo clippy --all-targets -- -D warnings cargo test ``` `cargo clippy` is run with `-D warnings`, so any new lint warning fails CI — fix it or, if it's a deliberate false positive, silence it locally with a `#[allow(...)]` and a short comment explaining why. CI also runs `cargo audit` against `Cargo.lock` to catch known RustSec advisories in dependencies. If you add or bump a dependency and `cargo audit` flags it, either pick a version without the advisory or (if there's genuinely no fix yet) leave a note in the PR explaining why it's acceptable. ## Code layout | Path | What lives there | | --- | --- | | `src/app/` | The egui app: `OxiApp` and its state (`state.rs`), one module per surface (composer, sidebar, conversation, editor in `file_explorer/`, git panel, settings) and the per-frame drains of background work. | | `src/ui/` | Reusable widgets and painters: chrome, chat messages, the diff view, markdown. | | `src/agent/` | Agent runs: provider loops (`openai.rs`, `anthropic.rs`, `codex_responses.rs`), tools, approvals, MCP and ACP clients. | | `src/settings/`, `src/secrets.rs` | `settings.json` and the OS keychain. | | `src/session_store/` | Chat transcripts on disk (JSONL). | | `src/git/`, `src/git.rs` | libgit2 operations, snapshots of agent turns, hunk staging. | | `src/compute/` | SSH connections and tunnels to remote model runtimes. | | `src/router/` | Router (auto) provider: task classification, quotas, spend ledger. | | `src/fsutil.rs` | Crash-safe file replacement for oxi's own files and the agent's file tools; prefer it over `fs::write` for app state. | | `src/logging.rs` | `oxi.log` and `crash.log`. Use `log::warn!` / `log::error!` rather than `eprintln!`. | | `src/os_open.rs` | Opening files and folders with the OS; never through a shell. | A few conventions worth knowing before you dive in: - Tool implementations live under `src/agent/tools/`; path-based tools must go through `paths::resolve_under_cwd`/`resolve_under_cwd_for_create` so they can't escape the workspace root — reuse those helpers rather than resolving paths by hand. - Mutating tools (`bash`, `write`, `edit`, …) are gated by `src/agent/approval.rs`'s `ApprovalGate`; if you add a new mutating tool, classify it in `tools::tool_side_effect` rather than assuming it's safe to skip. Unknown names are treated as external and always ask. - Async work runs on the shared runtime in `src/runtime.rs`; don't build another `tokio::runtime::Runtime`. Blocking calls made from async code (tool runs, approval waits) go through `spawn_blocking` or `runtime::block_in_place`. - Secrets (provider API keys, OAuth tokens, SSH passwords) go through `src/secrets.rs`, which wraps the OS keychain. Don't add new plaintext-JSON credential storage — follow the pattern in `src/oauth/store.rs` or `src/compute/store.rs` instead. ## Tests Most modules keep their tests in an inline `#[cfg(test)] mod tests` block next to the code under test; a few larger areas (e.g. `src/agent/tools/tests.rs`) use a dedicated file. Match whichever convention the file you're touching already uses. The crate also builds as a library (`src/lib.rs`, with `src/main.rs` as a thin entry point over it), so `tests/` holds integration tests that exercise the public API (`oxi::settings`, `oxi::fsutil`) from outside the binary. Put a test there when it covers behaviour call sites rely on across modules, rather than one module's internals. A handful of tests are marked `#[ignore]` because they exercise the real OS keychain (`src/secrets.rs`) and aren't safe to run unattended in CI/sandboxed environments. Run them explicitly when touching that code: ```bash cargo test -- --ignored ``` For UI changes, review real GPU-rendered screenshots in an isolated sample workspace: ```bash OXI_GALLERY=/tmp/oxi-gallery cargo test --release --locked --lib render_gallery -- --ignored --nocapture OXI_POLISH_REVIEW=1 OXI_GALLERY=/tmp/oxi-polish cargo test --release --locked --lib render_gallery -- --ignored --nocapture OXI_RESIZE_REVIEW=1 OXI_GALLERY=/tmp/oxi-resize cargo test --release --locked --lib render_gallery -- --ignored --nocapture ``` The first command covers chat, tools, approvals, editor, Git and settings. The second captures empty and multiline composers at 1240, 760 and 520 points in all five themes. The resize review compares the presented frame with the next idle frame during a live answer, including a wrapping draft, long history and fractional display scales. `OXI_CHAT_NAV_REVIEW=1` drives chat switching (Cmd/Ctrl+Shift+O, Ctrl+Tab), draft saving, find in chat (Cmd/Ctrl+F) and Distraction Free mode with real key events. In release builds the first command also fails when a chat frame takes over 4 ms typically or misses a 60 Hz refresh. Rendering requires a GPU; the gallery uses temporary settings and a mock credential store. ## Commit / PR conventions - Keep PRs focused — one logical change per PR is easier to review and bisect than a bundle of unrelated fixes. - To try a change before it is released, add the `build` label to its pull request (or run **Actions → Preview build** for any branch). macOS, Linux and Windows builds are attached to the run, and the pull request gets a comment linking them. - To cut a release, run **Actions → Prepare release**. It opens (or refreshes) the `dev` → `master` pull request, titled with the version it will publish and carrying a preview of the release notes. - Merging into `master` starts the release workflow. It generates release notes and a SemVer bump from commits since the latest `v*` tag, builds every supported platform, then commits `CHANGELOG.md`, `Cargo.toml`, and `Cargo.lock`, tags and publishes the release with a `SHA256SUMS` file, then opens a `master` → `dev` sync PR and auto-merges it after CI passes. - CI runs on Linux (fmt, clippy, tests), macOS (clippy, tests) and Windows (clippy), plus `cargo audit`. A daily audit also opens an issue when a new RustSec advisory affects `dev`. - Use Conventional Commit prefixes where possible (`feat:`, `fix:`, `security:`, and `type!:` for breaking changes). They provide deterministic release notes and version selection if the optional LLM changelog service is unavailable. You normally should not edit `CHANGELOG.md` or bump the crate version by hand. ## Licensing of contributions oxi is released under the [MIT License](LICENSE). Contributions are accepted under the same license. ### Sign your commits By submitting a pull request you certify the [Developer Certificate of Origin](https://developercertificate.org/): that you wrote the contribution yourself, or otherwise have the right to submit it under the project's license. Certify it by adding a `Signed-off-by` line to every commit, which `git commit -s` adds for you: ``` Signed-off-by: Your Name ``` The name and email in the sign-off must match the commit author. ### Grant You keep the copyright to your contribution. Alongside the MIT license, you also grant the maintainer a perpetual, worldwide, irrevocable, royalty-free right to use, reproduce, modify, distribute and sublicense it, including under license terms that differ from the MIT License. This exists so the project can be relicensed or dual-licensed in the future without having to track down every past contributor, which is a practical dead end once a project has any real number of them. It does not let anyone take away what is already published: every release made under the MIT License stays MIT, permanently, and anyone may keep using and forking those releases under those terms.