Files
atlas/CONTRIBUTING.md
Uzayer Masud 9a41c1485f ci: run only the Rust jobs a change affects
Every push ran all 27 jobs, including the ~25-minute macOS app build and
the engine compile, although most commits touch only the frontend. A new
`plan` job (scripts/ci-affected.mjs) diffs the push or PR, maps each file
to the cargo packages that read it, closes over reverse dependencies, and
gates the app, engine-dialect and per-crate jobs on the result. The crate
matrix moves to .github/ci-crates.json so the plan can filter it.

It fails closed: an unclassified file, a global input (lockfile, root
manifest, cargo config, ci.yml, the planner), a new branch, a force-push,
a push to main, a manual run and a new weekly scheduled run all run
everything. frontend always runs.

Several crates read files outside their own directory (the spawn audit
walks every Rust source, the app include_str!s docs and a TS file,
atlas-theme checks the mock fixtures), which a directory mapping cannot
see. They are declared in EXTRA_INPUTS, and tests/ci-affected.test.ts
scans first-party crates for such reads and fails on an undeclared one,
on an unclassified tracked file, and on a job that loses its plan gate.

A `CI passed` job waits on every job and is the one check to require in
branch protection, since a required job the plan skipped would otherwise
stay pending forever.
2026-09-30 05:28:07 +06:00

14 KiB

Contributing to Atlas

Thanks for wanting to help. Below is how to do it, and everything here applies to every contributor equally.

If you're not sure where to begin, #dev on Discord is the fastest way to get an answer.

Where to start

good first issue and help wanted are labelled for exactly this.

Areas where help goes furthest right now:

  • Linux and Windows testing of the production bundle — terminal font, PATH resolution, general GUI behaviour.
  • More ACP agents. atlas-acp already speaks the wire format, so adding Gemini CLI, OpenCode, or Kilo Code is mostly plugin discovery and auth.
  • LSP support for diagnostics and go-to-definition in the editor.
  • MCP server integration for tool-call extensibility.
  • Themes and additional colour palettes.

Reporting a bug

Check open and closed issues first. If one already covers it, a 👍 reaction is more useful than a duplicate.

The fastest way to report one is the feedback button in the app (status bar, or Settings) — its "Open a GitHub issue" link pre-fills the Bug Report form from whatever you typed. Filing directly on GitHub works the same way.

The form only requires one field: a freeform description. Write as much or as little as you have — a one-line note is a fine issue, and so is a long writeup with source references. If you have them, your Atlas version (Settings → About), your OS and version, which agent was selected, and how to trigger it all help, but none of them block you from filing.

Security issues

Don't open a public issue for a vulnerability or a potential attack vector. See SECURITY.md for how to report it privately.

Documentation

Open a PR directly. No issue needed for typos, clarifications, or filling in something that's missing.

New features

Open an issue first, or bring it to #feature-requests on Discord.

Most Atlas features cross three layers — React UI, a Tauri command, and a workspace crate — so agreeing the approach first saves you from building something that has to be restructured. ARCHITECTURE.md covers how those layers fit together.

Match the patterns already in the codebase: feature folder under src/features/<feature>/, Zustand store wrapped in createSelectors, Tailwind composed through cn(), IPC verbs grouped into a single commands/<domain>.rs. If your change doesn't fit any of them, propose the structure in the issue.

New heavy dependencies need discussion first. The current list is deliberate.

Design changes

Share the proposal in the issue before implementing anything that changes UI or UX.

Atlas has a lot of surfaces — a change that looks right in one panel often reads wrong across the other twelve. A design pass up front is faster than a rewrite after review.

Quickstart

macOS is the only currently-supported platform. Linux and Windows build from the same Tauri codebase but are untested — a Windows build is planned, and reports from either OS are genuinely welcome in the meantime.

You need:

  • Bun
  • Rust, stable
  • Xcode Command Line Tools

We suggest using mise to manage the Bun version — the repo ships an mise.toml pinning it, so mise install picks up the right version automatically. Not required; installing Bun directly works too.

No API keys, no .env file, no account. Atlas builds and runs from a clean clone:

git clone git@github.com:pacifio/atlas.git
cd atlas
bun install
bun run dev:app

The first Rust compile takes a few minutes; after that, seconds. bun run dev:app hot-reloads the frontend on save — Rust changes need a restart.

That's enough to build and run Atlas. If you're planning to submit a change and don't have write access, clone your fork instead of pacifio/atlas directly — see "Fork, branch, PR" below.

Other commands you'll use:

bun run dev             # Vite only, in your browser, on a fake backend — see "UI work in the browser"
bun run format          # oxfmt --write on src/
bun run lint            # oxlint on src/

A husky pre-commit hook runs lint-staged (oxfmt + oxlint on staged files) plus bun run typecheck, so most formatting/lint issues are caught before they ever reach CI (see Verification below).

UI work in the browser

bun run dev runs the whole app at localhost:1420 in a normal browser. No Rust is running: a dev-only fake backend in src/dev/mock-backend/ answers every invoke() and listen() with made-up data. You get instant hot reload, and a coding agent can open the page and check its own work.

  • Pick a state with ?scenario=<name>, e.g. ?scenario=git-conflict or ?scenario=chat-tools. The list is in scenarios/index.ts. Without one you get a project with ordinary data.
  • Need a state that doesn't exist yet? Add a scenario file with fake answers for the commands that screen calls. Don't set up a real repo or start a real agent session just to see a screen.
  • Blank or broken panel? Check the badge in the bottom-right corner. It lists commands nothing answered yet (they return null). Add an answer to scenarios/base.ts or to your scenario.
  • Type your fakes with the frontend's own API types, so bun run typecheck catches it when Rust changes a return shape.

The fake backend is never part of a build, and it switches itself off inside the Tauri window. It can't fake the Browser tab, drag-and-drop from Finder or native window controls, and Chrome doesn't render exactly like the app's WebKit view. Check your change in bun run dev:app before opening the PR.

If you're working on the Claude Code agent specifically, you also need the claude CLI on your PATH. The native Atlas agent needs nothing extra.

.env is optional — copy .env.example only if you want to point telemetry at your own PostHog project. Left blank, telemetry is permanently inert.

Fork, branch, PR

Every change comes in through a pull request. Maintainers branch directly on pacifio/atlas; everyone else forks first. The rest of the flow is identical either way.

# 1. Fork pacifio/atlas on GitHub, then clone your fork
git clone git@github.com:<you>/atlas.git
cd atlas

# 2. Point `upstream` at the canonical repo
git remote add upstream git@github.com:pacifio/atlas.git
git fetch upstream

Next, find the current version branch — check the branch list on GitHub and look for the highest-numbered one (e.g. 0.2.5). Ask in #dev on Discord if it's not obvious.

# 3. Branch from it, not from main
git checkout -b <you>/short-slug upstream/0.2.5

# 4. Push to your fork, then open the PR against that same version branch
git push -u origin <you>/short-slug

Name your branch <you>/<short-slug> — a few words describing the change, e.g. alex/fix-sidebar-collapse. If there's a GitHub issue for it, lead with the number: alex/42-fix-sidebar-collapse.

To pick up changes made while you were working:

git fetch upstream
git rebase upstream/0.2.5

Leave Allow edits from maintainers checked when you open the PR. It lets small fixes land without another round trip.

Branching model

Most open-source projects merge every PR straight into main, because main is deployed continuously — there's no fixed "next release," just a constantly moving target. Atlas doesn't work that way: it ships as a numbered, installable build with an auto-updater, so main has to always equal exactly what's been released, nothing ahead of it. That means work needs somewhere to collect before it becomes a release, instead of landing on main directly.

That somewhere is a version branch — one per upcoming release (0.2.5, 0.2.6, …). PRs target the version branch, not main. Once the version branch is ready to ship, it gets merged into main in a single PR, and that merge is the release.

you/fix-sidebar-collapse ──┐
you/add-vim-keybindings ───┼──►  0.2.5  ──►  main     (this merge = the 0.2.5 release)
someone/fix-x ──────────────┘
Branch Purpose Merges into
main Always equals the latest release, nothing more —
0.2.4, 0.2.5, … Collects everything going into the next release main, and that merge is the release
<you>/<short-slug> One issue's worth of work The current version branch

PR straight into main only when the change has no version-branch dependency and doesn't need to wait for the next release — a doc fix or a one-line hotfix, say. When in doubt, target the version branch.

Releases are tagged alpha-X.Y.Z, with occasional exp-X.Y.Z-X.Y.Z snapshots.

Versioning

The version lives in four places: package.json, src-tauri/Cargo.toml, src-tauri/tauri.conf.json, and the Settings "About" label in src/features/settings/components/settings-panel.tsx. The scripts change all four together.

./bump.sh          # patch bump: 0.2.3 -> 0.2.4
./bump.sh 0.3.0    # explicit version
./debump.sh        # inverse of bump.sh

Run bump.sh once per release, on the version branch, before opening the PR into main. Never edit the four files by hand.

Verification

bun run lint                       # oxlint on src/
bun run format:check               # oxfmt --check on src/
bun run typecheck                  # frontend typecheck (app + test code)
bun run test                       # frontend and cross-cutting tests
bun run test:rust                  # every Rust crate + src-tauri --lib
cargo check --workspace            # Rust typecheck: every workspace member + the app

Rust tests run offline and need no API keys. Every crate under crates/ except atlas-kb-server (see below) is a member of the root cargo workspace, sharing one Cargo.lock and one target/ at the repo root, so -p <crate> works from anywhere — as does running from inside the crate's own directory, which is what CI does:

cargo test -p atlas-native-agent                          # the native agent
cargo test -p atlas-native-agent --test engine_turn       # a single file
cd crates/atlas-native-agent && cargo test                # same thing, from the crate

Run bun run test:rust from the repository root to test every crate and the Tauri library in one pass; it stops at the first failure.

The exception, crates/atlas-kb-server, is a template binary the knowledge-export command compiles on demand at runtime under its own release profile. Profiles are workspace-global, so joining the workspace would rebuild it under the app's — hence it stays out, keeps its own Cargo.lock, and is built with --manifest-path.

Frontend tests run under Vitest:

bun run test                            # everything
bun run test src/lib/time-ago.test.ts   # one file
bun run test:watch                      # re-run on save

Tests live next to the code they cover (src/lib/time-ago.test.ts), except for ones that check the repo as a whole, which live in tests/. Three of those run on every PR and are worth knowing about:

  • tests/ipc-contract.test.ts — every invoke("name") in the frontend resolves to a registered #[tauri::command], every command is wired into generate_handler!, and every registered command has a frontend caller. Rename a command without updating its callers and this is what tells you, instead of a dead button at runtime; leave a command behind after its last caller goes and it tells you that too.
  • tests/ci-coverage.test.ts — every crate in crates/ is in the CI matrix (.github/ci-crates.json), so a new crate can't merge with its tests unrun.
  • tests/ci-affected.test.ts — CI runs only the Rust jobs a change affects, planned by scripts/ci-affected.mjs from the diff and the crate dependency graph. A crate that reads a file outside its own directory (include_str! of something under docs/, a test walking the repo) has to declare it in that script's EXTRA_INPUTS, or an edit to that file would skip the crate's tests. This suite finds such reads and tells you when one is undeclared. To see what CI would run for your branch: node scripts/ci-affected.mjs --base origin/<version-branch>.

For a new IPC module, copy the pattern in src/features/settings/lib/byok-api.test.ts: mock invoke and assert the command name and payload. Whether the command exists is already covered.

Rendering and interaction still need a real window — Vitest covers logic and the IPC seam, not the UI itself.

Pull request checklist

Opening a PR pre-fills the checklist from the PR template — work through it before asking for review.

Telemetry

Atlas ships one narrow PostHog pipeline, in src-tauri/src/telemetry/. It's anonymous, coarse, and opt-out. Changing it has its own rules.

Never sent, under any circumstance:

  • Prompt or response text
  • File contents, or absolute paths
  • Knowledge-base or chat content
  • API keys and credentials
  • Terminal input or output
  • Browser URLs

New events need discussion in the issue before they're built, and any change to the pipeline updates TELEMETRY.md in the same PR.

New markdown files

.gitignore ignores *.md apart from explicit exceptions, so a new doc won't show up in git status. Add it with git add -f, or add an exception to .gitignore.

Code of conduct

Participation is covered by our Code of Conduct.