Thanks for your interest in improving Driven. This guide covers how to set up the project, the gates your change must pass, the commit-message format, and the house rules.
By participating you agree to abide by the Code of Conduct.
- Rust stable (
rustup install stable) - Node.js 22+ and pnpm 10+
cargo install tauri-cli@^2 cargo-deny cargo-watch just(Windows users can installjustviascoop install just)- Linux build deps:
libwebkit2gtk-4.1-dev libxdo-dev libssl-devlibayatana-appindicator3-dev librsvg2-dev libsoup-3.0-devjavascriptcoregtk-4.1
Install the UI dependencies once:
pnpm --dir ui installDriven uses Conventional Commits because
releases and the changelog are automated with
release-please. The commit
subject drives the next version bump and the generated CHANGELOG.md entry, so
the format is not optional.
Format:
<type>(<optional scope>): <short summary>
<optional body>
<optional footer>
Common types:
feat: a new user-facing feature (bumps the minor version)fix: a bug fix (bumps the patch version)docs: documentation onlyrefactor: code change that neither fixes a bug nor adds a featuretest: adding or fixing testschore: tooling, deps, or maintenanceci: CI / workflow changesperf: a performance improvement
Breaking changes: add a ! after the type/scope (for example feat!:) and / or
a BREAKING CHANGE: footer. A breaking change bumps the major version.
Examples:
feat(restore): add full-text search over remote file names
fix(executor): retry resumable upload after a transient 5xx
docs: document the macOS auto-updater caveat in the README
All cargo commands run with SQLX_OFFLINE=true because the SQL is checked
against the committed .sqlx/ offline cache. The quickest path is the just
recipes:
just lint # cargo fmt --all -- --check ; cargo clippy --workspace --all-targets -- -D warnings ; pnpm --dir ui lint
just test # cargo test --workspace ; pnpm --dir ui test:unit
just deny # cargo deny check
just coverage # Rust + UI line coverage (must not regress vs main; see below)Run them individually if you prefer:
# Rust
SQLX_OFFLINE=true cargo build --workspace --all-targets
SQLX_OFFLINE=true cargo clippy --workspace --all-targets -- -D warnings
SQLX_OFFLINE=true cargo test --workspace
cargo fmt --all -- --check
cargo deny check
git diff --check # catches trailing whitespace and conflict markers
# UI (from the repo root)
pnpm --dir ui install
pnpm --dir ui lint
pnpm --dir ui exec prettier --check src
pnpm --dir ui build # vue-tsc --noEmit must be cleanIf you change a sqlx::query! / query_as!, regenerate the offline cache with
just sqlx-prepare (needs cargo install sqlx-cli) and commit the updated
.sqlx/ directory.
If you change a GitHub Actions workflow under .github/workflows/, run
actionlint on it.
Some tests gate-skip honestly when the host cannot satisfy a requirement (for example real-Google-Drive end-to-end tests with no credentials, or VSS / elevation tests without admin). A clean run is all-pass plus those honest skips, not a hidden failure.
cargo test --workspace must never touch your real login keychain, and it
does not. Any test that can reach a keychain entry - Driven stores the account
master key under dev.maxhogan.driven, the Google secrets under
driven.google.refresh_token / driven.google.client_creds, and S3 key pairs
under driven.s3.credentials - starts with:
let Some(_guard) = driven_test_fixtures::keychain::isolated() else {
return; // could not isolate; skip rather than write to a real keychain
};isolated() installs keyring-core's in-memory mock as the process-global
default credential store, proves it is the effective store before the test is
allowed to store anything, and returns None (so the test skips honestly) if it
cannot. Add that line to any new test that reaches the keychain, and add
driven-test-fixtures to your crate's [dev-dependencies] if it is not there
yet. Every crate that owns a keychain call site - driven-crypto,
driven-drive, driven-s3, driven-backend, src-tauri - already has the
wiring and a
the_test_suite_is_isolated_from_the_os_keychain guard test, so if the
mechanism ever breaks (a keyring upgrade, say) those fail loudly instead of
the suite quietly starting to write for real.
Why this matters more on macOS. macOS ties a keychain ACL to the identity
of the binary that was granted access, and every cargo test rebuild produces a
binary with a new identity. So a single test that reaches the real keychain
raises a modal "allow ... to access ..." prompt on every rebuild - clicking
"Always Allow" does not help, because the next build is a different app - and
the run blocks on the dialog until you answer it. (The same mechanism means
released builds re-prompt users on every update; see the macOS notes in the
README.) Ordering is load-bearing here: keyring 4.x's Entry::new installs
the platform-native store on its first call and overwrites whatever default is
already set, so installing the mock without first burning that latch is silently
undone. crates/driven-test-fixtures/src/keychain.rs documents the sequence -
use the helper rather than re-deriving it.
If you ran the suite before this isolation landed, you may have leftover test-only items in your login keychain. Remove them with:
security delete-generic-password -s "driven.google.refresh_token" -a "acct-with-token"
security delete-generic-password -s "driven.google.client_creds" -a "acct-byo"
security delete-generic-password -s "driven.s3.credentials" -a "acct-s3-round-trip"
security delete-generic-password -s "driven.s3.credentials" -a "acct-a"
security delete-generic-password -s "driven.s3.credentials" -a "acct-b"
security delete-generic-password -s "driven.s3.credentials" -a "acct-s3-delete"
security delete-generic-password -s "driven.s3.credentials" -a "acct-control-chars"A security: ... could not be found in the keychain for any of these just means
that one never leaked on your machine.
The coverage workflow (.github/workflows/coverage.yml) measures line
coverage on every PR and fails the check if either number regresses below
main (minus a 0.1pp epsilon that absorbs float jitter). It posts a sticky
PR comment with the main baseline, this PR's number, and the delta. New code
must come with tests that keep coverage from dropping.
Two numbers are gated:
- Rust - the library crates (
cargo llvm-cov --workspace --exclude src-tauri --exclude driven-chaos).src-tauriis a thin IPC layer overdriven-coreanddriven-chaosis the stress harness, so neither is part of the gate; put real logic indriven-core(where it is unit-tested) and keepsrc-tauricommands thin. - UI - the whole Vue/TS app (
ui/, vitest v8 coverage withall: true, so a new untested file lowers the total).
How the baseline works: every push to main recomputes coverage and caches it;
PRs compare against that cached number. The first main build that carries the
workflow has no prior baseline, so that one run is informational, then the gate
enforces from the next PR onward.
Run it locally before pushing:
just coverage # prints Rust + UI line-coverage totals
./scripts/coverage.sh # same, parsed to the exact percentages CI comparesjust coverage needs cargo install cargo-llvm-cov; the parsed script also
needs jq.
Maintainer setup (one-time): mark coverage as a Required status check in the
mainbranch protection rule so a regression actually blocks merge. The workflow failing is necessary but not sufficient until the check is required.
- Branch off
main, one branch per logical change. Name it<type>/<slug>(for examplefeat/schedule-windows). - Make your change with Conventional-Commit messages.
- Run the local gates above until green.
- Open a pull request against
main. CI runs the same gates plus the chaos harness and the coverage gate. - Keep the PR focused; smaller PRs review faster.
PRs land on main via Squash and merge - the whole branch becomes one
commit. Because that squash commit's subject is what release-please reads, set
the squash subject to a Conventional-Commit line that summarises the PR (GitHub
pre-fills it from the PR title, so title your PR in Conventional-Commit form,
for example feat(restore): point-in-time restore). Individual
work-in-progress commit messages on the branch do not need to be release-grade;
only the squash subject does. This keeps main linear and one-commit-per-PR.
If a change depends on another that is still in review, branch it off the
dependency's branch instead of main and say so in the PR description ("stacked
on #NN"). Its diff will show the parent's changes until the parent merges;
rebase onto main after the parent lands. Independent changes should branch off
main so each PR diff is self-contained.
Releases are cut by release-please: merging the maintained "chore: release" PR
tags v* and triggers the build / publish pipeline. Do not hand-edit version
numbers or CHANGELOG.md release sections; release-please owns those.
These keep the codebase clean across Windows, macOS, and Linux:
- ASCII only in source, docs, logs, and commit messages. Do NOT use em-dashes or
en-dashes; use the ASCII hyphen-minus (
-). Non-ASCII dashes render as garbage in Windows terminals and some viewers. - LF line endings only. The repo's
.gitattributesenforces this; do not commit CRLF. - Keep
driven-corefree of direct I/O: it holds the traits and pure logic; side-effecting implementations live in their own crates. - Never log file names, paths, or content from encrypted sources, and never put user data in telemetry.
- Match existing patterns and the design docs under
design/rather than introducing parallel approaches.
Thanks for contributing.