# The workflow One page. If something contradicts this file, this file wins. ## Branches There are exactly three kinds of branch. Nothing else is allowed to exist for long. | Branch | Role | Who writes to it | |---|---|---| | `dev` | **Default branch.** Integration. Everything lands here first. | Merged PRs only — never a direct push | | `main` | **Release branch.** Every commit here is a shipped version. | Promotion PRs from `dev` only | | `/` | One PR's work. Deleted on merge. | One agent or person. Exactly one. | **One branch, one owner, one PR.** The rule that fixes agents overwriting each other: two agents never share a branch. If two pieces of work are independent, they are two branches and two PRs. If they are not independent, they are one PR. Local hooks block commits made on `main` or `dev`, direct pushes to either branch, and staged commits larger than 15 files or 600 source lines. Lockfiles and snapshots do not count toward the line limit. Split larger work into independently valid microcommits before opening the PR. ## The loop ``` work branch ──PR──▶ dev ──promotion PR──▶ main │ │ │ │ ▼ ▼ │ Local Studio Dev.app Local Studio.app │ (nightly, auto) (release + DMG) ▼ checks + model review ``` 1. Cut a branch from `dev`. Never from `main`. 2. Push, open a PR into `dev`. The PR body lists its tasks; the PR is the unit of work. 3. CI runs. A review agent reads the diff and leaves comments. 4. Comments are addressed. Checks are green. Merge (squash). Branch auto-deletes. 5. Nightly, `dev` builds and installs **Local Studio Dev.app**. 6. When `dev` is good, open a promotion PR `dev → main`. That runs the full gate: e2e, a signed DMG, and a launch test of the DMG itself. 7. Merge to `main` → release: tag, GitHub Release, DMG attached. ## Two apps. Only ever two. | App | Built from | Bundle id | User data | |---|---|---|---| | `Local Studio.app` | `main` | `org.local.studio.desktop` | `~/Library/Application Support/Local Studio` | | `Local Studio Dev.app` | `dev` | `org.local.studio.desktop.dev` | `…/Local Studio Dev` | The dev app **mirrors the stable app's state one way on every launch** — your real projects and sessions, refreshed each time you open it — and writes only to its own directory. A broken dev build cannot corrupt the app you rely on. Credentials and device identity are deliberately not mirrored (see `desktop/logic/dev-channel-mirror.ts`). Install either with `scripts/install-desktop-app.sh [stable|dev]`. It replaces in place, keeps exactly one compressed rollback per channel outside `/Applications`, ejects stale DMGs and stops orphaned servers. Run `scripts/install-desktop-app.sh --migrate-rollbacks` to archive and unregister old discoverable rollback bundles without reinstalling either app. Never hand-roll a backup copy — that is how `/Applications` accumulated duplicate bundles that all showed up as applications. Repository automation has exactly three executable files: `scripts/project.mjs`, `scripts/install-controller.sh`, and `scripts/install-desktop-app.sh`. Package commands, CI, release jobs, Git hooks, and model operations dispatch through `project.mjs`. `npm run check:automation` fails if another executable or helper-script directory is added. ## Gates Nothing merges without these. They are required status checks, not conventions. **Into `dev`** — fast, every PR: `gates` · `controller` · `agent-runtime` · `frontend` · `desktop-package` · `Secret Scanning (TruffleHog)` · `CodeQL Analysis` · `Dependency Review` · `e2e` **Into `main`** — everything above, plus: `release-candidate` — builds the **signed DMG**, mounts it, launches the app from the mounted volume, and asserts `/api/desktop-health`. A DMG that does not launch must not be able to reach a release. Both branches: no direct pushes, no force pushes, squash merge only, branch deleted on merge, conversation resolution required. ## Review Review is done by a model on the PR, leaving comments — that is the required signal. GitHub will not let you approve your own pull request, so **required approvals is 0** and the enforcement lives in the checks instead: the review job must run and must resolve its comments before merge. ## Scheduled work | When | What | |---|---| | Nightly (02:00 UTC) | Build `dev`, run full e2e, publish `Local Studio Dev` DMG as a prerelease | | Weekly (Sun 00:00 UTC) | TruffleHog + CodeQL sweep | There is no beta channel. Two channels, two apps, no third version anywhere. ## Releasing `main` is release-only, so a merge to `main` *is* the release. semantic-release cuts the tag and the GitHub Release from conventional commits. Only `feat`, `fix`, `perf` and breaking changes cut a release. A README typo must not produce a notarized DMG build — that is how the repo reached 68 releases in weeks.