Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/contributor/welcome.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ A few things that help:

- [Start here](https://github.com/manaflow-ai/cmux/blob/main/docs/start-here.md) covers what reviewers look for, what CI runs for you, and what happens next.
- If the CLA check asks, reply with the sentence it gives you.
- You don't need to run the app-host or UI tests locally. CI runs the ones your diff touches once a maintainer approves the first workflow run.
- The [verification ladder](https://github.com/manaflow-ai/cmux/blob/main/docs/contributor-verification.md) shows which checks fit your change. Say in the description which ones you ran.
- If we end up fixing the same problem another way, we'll credit you with a `Co-authored-by` trailer and link the fix here.
288 changes: 109 additions & 179 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,219 +2,149 @@

New here? Read [docs/start-here.md](docs/start-here.md) first: how to pick an
issue, what you can fix without a Mac, and what happens to your pull request.
This file is the mechanics.
This file is the mechanics: setup, checks, CI and the pull request checklist.

Be nice, assume the best: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).

For issues, RFCs, pull requests, and progress updates, follow the short [writing guide](STYLE.md).

Start with the [verification ladder](docs/contributor-verification.md) to choose the
smallest useful check for your change. It includes a local path that does not require
maintainer runner access or shared backend credentials.

## Finding something to work on

Issues carry a severity (`S1: critical` through `S4: cosmetic`), an `area:` label,
and sometimes `good first issue` or `help wanted`. [docs/triage.md](docs/triage.md)
says what each one means, how new issues get labeled automatically, and how to
correct a label that is wrong. Comment on an issue before you start working on it.
Be nice, assume the best: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Write issues
and pull requests the way the short [writing guide](STYLE.md) says.

## Prerequisites

These prerequisites are for native app development. For documentation or portable
contributor tooling, start with [fast checks](#fast-checks-before-committing-or-building)
and the [validation guide](skills/cmux-testing/references/local-vs-ci-validation.md).
Only needed for the native app. Docs, translations, Python tooling, `cmux-tui` and
`web/` work on Linux; see [what you can fix without a Mac build](docs/start-here.md#2-things-you-can-fix-without-a-mac-build).

- macOS 14+
- Xcode 26 (the pinned toolchain); Xcode 16.2 on Intel Macs running macOS 14.5 or later also builds the macOS app (best effort, [Swift 6.0 limits](skills/cmux-architecture/references/swift-6-0-compatibility.md))
- [Zig](https://ziglang.org/) (install via `brew install zig`)
- [Rust](https://rustup.rs) — `scripts/setup.sh` requires `rustup`, and every app build compiles
the bundled `cmux-cua` engine with `cargo`. The official installer puts both in `~/.cargo/bin`,
which is where `setup.sh` looks:
- Xcode 26 (the pinned toolchain). Xcode 16.2 on Intel Macs with macOS 14.5+ also
builds the app, best effort ([Swift 6.0 limits](skills/cmux-architecture/references/swift-6-0-compatibility.md)).
- The Xcode 26 Metal compiler, which is a separate download. Select the Xcode you
build with first (`DEVELOPER_DIR` overrides `xcode-select`), then:

```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
xcodebuild -downloadComponent MetalToolchain
```

Homebrew's `rustup` formula works too, but it is keg-only and no longer ships `rustup-init`, so
add `$(brew --prefix rustup)/bin` to `PATH` and run `rustup default stable` yourself.
- On Xcode 26 the Metal compiler is a separately downloaded component, and the build fails
without it. Select the intended full Xcode installation first (`DEVELOPER_DIR`, if
exported, overrides `xcode-select`), then install the component:
- [Zig](https://ziglang.org/): `brew install zig`
- [Rust](https://rustup.rs). Every app build compiles the bundled `cmux-cua` engine
with `cargo`, and `scripts/setup.sh` looks for `rustup` in `~/.cargo/bin`, where
the official installer puts it:

```bash
xcodebuild -downloadComponent MetalToolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```

Homebrew's `rustup` works too, but it is keg-only: add `$(brew --prefix rustup)/bin`
to `PATH` and run `rustup default stable` yourself.

## Getting Started

1. Clone the repository with submodules:
```bash
git clone --recursive https://github.com/manaflow-ai/cmux.git
cd cmux
```

2. Run the setup script:
```bash
./scripts/setup.sh
```

This will:
- Initialize git submodules (ghostty, homebrew-cmux)
- Install the pinned Rust toolchain
- Fetch a checksum-pinned prebuilt GhosttyKit.xcframework, falling back to building it
from source with Zig (force the source build with `CMUX_GHOSTTYKIT_NO_PREBUILT=1`)
- Create the necessary symlinks

After pulling setup or merge-driver changes into an existing clone, rerun
`./scripts/install-git-hooks.sh`. It replaces old checkout-relative merge-driver
commands with trusted copies outside the working tree. If you use a custom
`core.hooksPath`, the installer preserves it and prints the exact `pre-commit`
and `post-merge` lines to add. Wire both hooks; until `post-merge` is wired,
rerun the installer after pulling `main` and before merging an untrusted branch.

3. Build the debug app:
```bash
CMUX_DEV_BACKEND_MODE=local ./scripts/reload.sh --tag my-feature
```
`CMUX_DEV_BACKEND_MODE=local` points the build at the local dev origin. Without it, a tagged
build expects the maintainers' shared dev backend and exits before building.
The script prints the `.app` path. Cmd-click to open, or pass `--launch` to open automatically.

## Development Scripts

| Script | Description |
|--------|-------------|
| `./scripts/setup.sh` | One-time setup (submodules + xcframework) |
| `CMUX_DEV_BACKEND_MODE=local ./scripts/reload.sh --tag <tag>` | Build a tagged Debug app; add `--launch` to open it or `--build-only` for compile-only validation |

See [tagged builds](skills/cmux-dev-workflow/references/tagged-builds.md) for cache
reuse, Release variants, and restrictions that protect the running app.
```bash
git clone --recursive https://github.com/manaflow-ai/cmux.git
cd cmux
./scripts/setup.sh
CMUX_DEV_BACKEND_MODE=local ./scripts/reload.sh --tag my-feature
```

- `setup.sh` initializes the submodules, installs the pinned Rust toolchain, and
fetches a checksum-pinned prebuilt `GhosttyKit.xcframework` (it builds from source
with Zig if that fails; force that with `CMUX_GHOSTTYKIT_NO_PREBUILT=1`).
- `reload.sh --tag <tag>` builds a tagged Debug app and prints its path. Add
`--launch` to open it, or `--build-only` to only compile.
`CMUX_DEV_BACKEND_MODE=local` points it at the local dev origin; without it a
tagged build expects the maintainers' shared backend and stops. See
[tagged builds](skills/cmux-dev-workflow/references/tagged-builds.md) for cache
reuse and Release variants.
- After pulling changes to setup or the merge drivers, rerun
`./scripts/install-git-hooks.sh`. With a custom `core.hooksPath` it prints the
`pre-commit` and `post-merge` lines to add yourself; wire both.

Team members with a Stack account can also sign DEBUG builds in and attach an iOS
build: see [team dev setup](docs/team-dev-setup.md).

<a id="fast-checks-before-building-or-pushing"></a>

## Fast checks before committing or building

Run `python3 scripts/verify-local.py` on your reviewed checkout. It selects
affected static checks and parses changed Swift, including committed branch edits.
The base comes from local `upstream/HEAD`, then `origin/HEAD`; nothing is fetched.
Use `--list` to preview, `--all` for the full CI static recipe, or `--affected BASE`
to choose a different static comparison base.

Checks cover localization, project/test wiring, package grouping, generated policy
and feature flags. Unknown inputs or a missing base select the full static recipe.
CI also keeps the full static recipe. Failures print a focused rerun command:

```sh
python3 scripts/verify-local.py --only project --only test-wiring
```bash
python3 scripts/verify-local.py
```

Parsing does not replace typechecking, app tests or a build. Add `--receipt -`
for JSON stdout; see the [command guide](docs/verification-receipts.md) for piped
paths, explicit Swift inputs and evidence limits.

The command executes repository Python/shell code, including for help and list.
Use a [trusted checkout](docs/contributor-verification.md#trust-boundary).
Git push does not run it automatically.

## Team Dev Setup

Team members with a Stack account can make DEBUG builds sign in and attach an iOS build automatically; see [team dev setup](docs/team-dev-setup.md).

## Web and JS Tooling

Run Biome from the repository root with:
It picks the static checks your diff touches (localization, project and test
wiring, package grouping, generated policy, feature flags) and parses changed
Swift. It compares against your local `upstream/HEAD`, then `origin/HEAD`, and
fetches nothing. When a check fails it prints the rerun command, for example:

```bash
bun run biome:check
python3 scripts/verify-local.py --only project --only test-wiring
```

The root `biome.json` intentionally scopes `biome check .` to maintained web and JS/TS sources.
It excludes generated bundles, build outputs, vendored trees, and review-tool metadata such as
`.greptile/`.
Biome formatting and import sorting are disabled for now; do not wire this into required CI until
the remaining source lint diagnostics are paid down.

## Running Tests

Use the [contributor verification ladder](docs/contributor-verification.md): source checks,
focused package tests, app and test compilation, then isolated socket/UI checks and
physical dogfood where the change needs them. Record which layers actually ran in
your PR; a successful parse or build does not mean tests executed.

The guide covers local contributors first. Maintainer-only focused CI dispatch and
fleet access are optional paths, not prerequisites for contributing.

## What CI runs for you

You do not need runner access, a signing identity or a Mac build farm to get a
change tested. Opening the pull request is the request:

- Static checks run on every pull request, and the Linux guards run when your diff
touches what they cover. `python3 scripts/verify-local.py` runs the checks your
diff touches and `--all` runs the full recipe CI uses, so fix those before you push.
- Swift, package and tooling tests are routed from your diff. An edited suite runs,
and an app-source change runs the suites whose tests mention what you changed.
No label is needed for any of that.
`--list` previews, `--all` runs the full static recipe CI uses, and `--receipt -`
prints JSON ([details](docs/verification-receipts.md)). Parsing is not typechecking
or testing. The script runs repository code, so use a
[trusted checkout](docs/contributor-verification.md#trust-boundary). Git push does
not run it for you.

For `web/` and other JS/TS sources, run `bun run biome:check` from the repository
root. The root `biome.json` deliberately scopes that to maintained web and JS/TS
sources, excluding generated bundles, build outputs, vendored trees and review-tool
metadata. Formatting and import sorting are off for now, so do not wire this into
required CI until the remaining source lint diagnostics are paid down.

## Tests and CI

Go up the [verification ladder](docs/contributor-verification.md) as far as your
change needs: source checks, the owning package's tests, compiling the app and
tests, then runtime and physical checks. In the pull request, say which of those
you ran. A parse or a build is not a test run.

**You do not need to run the app-host or UI tests on your own Mac.** They launch
the app and need a disposable GUI session. If you can't run them, open the pull
request anyway and say so under Testing. Opening the pull request is the request
for CI:

- Static checks run on every pull request. The Linux guards run when your diff
touches what they cover.
- Swift, package, app-host and tooling tests are routed from your diff: an edited
suite runs, and an app-source change runs the suites whose tests mention what
you changed. No label needed.
- The broad macOS suite is label-gated. A maintainer adds `full-ci` when a change
needs those lanes; see [PR CI coverage](skills/cmux-testing/references/pr-ci-coverage.md).
It is not a review or merge requirement, and it is not a substitute for saying
what you ran.
- `cmuxUITests/` is not run in full by any pull request job. If your diff touches
that directory, the `suite-coverage` check fails until a maintainer runs the
affected classes and records it with `no-full-ci`, because a pull request from a
fork cannot dispatch those lanes itself.

Read which tests executed on the current commit rather than the color of the
checks list: a skipped job is green and is not coverage. If checks never start on
your first pull request, they are waiting on a maintainer to approve a workflow
run from a new contributor.
needs it ([PR CI coverage](skills/cmux-testing/references/pr-ci-coverage.md)).
It is not a merge requirement.
- No pull request job runs `cmuxUITests/` in full. If you touch that directory, the
`suite-coverage` check stays red until a maintainer runs the affected classes and
records it with `no-full-ci`.
- On your first pull request, checks wait for a maintainer to approve the workflow
run. That's a GitHub default for new contributors, not a judgement on the patch.

Read which tests actually ran on your commit, not the color of the checks list: a
skipped job is green and is not coverage.

## Ghostty Submodule

The `ghostty` submodule points to [manaflow-ai/ghostty](https://github.com/manaflow-ai/ghostty), a fork of upstream Ghostty. To change it, rebuild `GhosttyKit.xcframework`, or pull in upstream, follow the [cmux-ghostty skill](skills/cmux-ghostty/SKILL.md): push the submodule commit to the fork before committing the pointer in this repository. Fork changes and conflict notes are in [docs/ghostty-fork.md](docs/ghostty-fork.md).
`ghostty` points to [manaflow-ai/ghostty](https://github.com/manaflow-ai/ghostty),
our fork of Ghostty. To change it, rebuild `GhosttyKit.xcframework` or pull in
upstream, follow the [cmux-ghostty skill](skills/cmux-ghostty/SKILL.md): push the
submodule commit to the fork before committing the pointer here. Fork changes and
conflict notes are in [docs/ghostty-fork.md](docs/ghostty-fork.md).

## Pull Requests

- Describe the change as the [writing guide](STYLE.md) says and fill in the pull request template, including what ran.
- For a bug fix, commit the failing regression test before the fix; see [regression commits](skills/cmux-testing/SKILL.md#reproduce-and-repair).
- Fill in the template's `## Changelog` section: one `Added`/`Changed`/`Fixed`/`Removed` line for a user-visible change, or `none`. Don't edit [CHANGELOG.md](CHANGELOG.md); the release builds it from these lines.
- Sign the [CLA](CLA.md) once by commenting `I have read the CLA Document v2.2 and I hereby sign the CLA` on your pull request. The CLA check asks for it on your first pull request.

### What a good one looks like

- One change. A fix plus a reformat is two pull requests, and the reformat will
hold up the fix.
- A summary that names the problem, then what a person can do after the change.
- Testing that names the commands you ran and what they establish. If you could
not run a layer, say which one and why, once. "It compiles" and "I ran it" are
different claims and reviewers read them differently.
- No unrelated formatting, no generated files, no vendored trees, no drive-by
version bumps.
- User-facing strings localized: see the [localization skill](skills/cmux-localization/SKILL.md).

### How review and merge work

- Automated reviewers comment first, sometimes several of them, sometimes about
setup that has nothing to do with your diff. Answer what applies and ignore the
rest. Don't `@`-mention review bots to summon more of them.
- A maintainer reads the description before the diff. If the description doesn't
say what the change does and what you ran, review stalls there.
- Answer review comments in the thread, push fixups, and say when you're done.
An unanswered question is the most common reason a finished patch sits still.
If a pull request goes quiet and you want eyes on it, say so on the same pull
request instead of opening another.
- A change merges when the CI relevant to it is green, review comments are
answered, and no product decision is still open. That last one is the usual
cause of delay on an otherwise good patch: when the open question is what cmux
should do, a maintainer answers it before the code lands. Ask on the issue
rather than guessing in code.
- Merges are squash merges, so the pull request title and description become the
commit that ships. Write them for someone reading `git log` a year from now.
- `main` is what NIGHTLY builds from. When something lands broken we fix forward
instead of reverting, so a follow-up pull request is routine, not a reprimand.
- If we end up solving the same problem another way, we credit you with a
`Co-authored-by` trailer and link the change from your pull request.
- One change per pull request. A fix plus a reformat is two pull requests.
- Fill in the template: a summary of the problem and what someone can do after
the change, and Testing with the commands you ran.
- For a bug fix, commit the failing regression test before the fix
([regression commits](skills/cmux-testing/SKILL.md#reproduce-and-repair)).
- Under `## Changelog`, write one `Added`/`Changed`/`Fixed`/`Removed` line for a
user-visible change, or `none`. Don't edit [CHANGELOG.md](CHANGELOG.md); releases
build it from these lines.
- Localize user-facing strings ([localization skill](skills/cmux-localization/SKILL.md)).
- No unrelated formatting, generated files, vendored trees or version bumps.
- Sign the [CLA](CLA.md) once by commenting
`I have read the CLA Document v2.2 and I hereby sign the CLA` on your pull request.
The check matches commit author emails to your GitHub account, so commit with an
email linked to it.

Merges are squash merges, so the title and description become the commit that
ships. How review works and what makes a patch wait is in
[start here](docs/start-here.md#5-what-happens-next).

Agents working in this repository also follow [CLAUDE.md](CLAUDE.md) (also `AGENTS.md`).

Expand Down
4 changes: 3 additions & 1 deletion docs/contributor-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ is not automatically a safely isolated test runtime. A plain `xcodebuild test` o
your everyday desktop can launch/focus the app under test. UI tests also need a
logged-in GUI session and any permissions or display setup their fixture requires.
If that environment is unavailable, report the missing runtime check and ask a
maintainer to run the exact selected test on your pushed revision.
maintainer to run the exact selected test on your pushed revision. That is not a
reason to hold back the pull request: CI runs the app-host suites your diff
touches once it is open.

### Maintainer option: dispatch a selected test

Expand Down
Loading
Loading