rose-merge-branch (CLI)¶
The rose-merge-branch CLI tool automates GitHub merge workflows with pre-flight checks and test validation. It supports two modes:
- Feature Mode: Squash merge feature branches to
develop - Release Mode: Merge commit from
developtomain(auto-detected)
Installation¶
The CLI tool is available after activating the Poetry shell in the backend directory:
Once in the Poetry shell, the rose-merge-branch command is available.
Usage¶
Options¶
| Option | Default | Description |
|---|---|---|
--skip-tests |
false |
Skip running frontend and backend tests |
--full |
false |
Run every check, ignoring change detection |
--confirm |
false |
Ask before a history-rewriting rebase and before merging |
--no-watch |
false |
Release mode only: merge without following the deployment workflows on GitHub |
--no-ci-wait |
false |
Run lint, type checks and frontend tests locally, then merge without waiting for the PR's GitHub checks |
--dry-run |
false |
Show what would happen without executing |
Examples¶
# Standard merge to develop with all checks (squash merge)
rose-merge-branch
# Skip tests (use with caution)
rose-merge-branch --skip-tests
# Preview what would happen
rose-merge-branch --dry-run
# Force every check even if the diff does not warrant it
rose-merge-branch --full
# Ask before rebasing and before merging
rose-merge-branch --confirm
# Run the CI-covered checks locally and merge without waiting for GitHub checks
rose-merge-branch --no-ci-wait
Release Mode Examples¶
# Checkout develop branch
git checkout develop
# Preview release merge (develop → main)
rose-merge-branch --dry-run
# Execute release merge, then follow the deploy workflows until they finish
rose-merge-branch
# Execute release merge and return immediately
rose-merge-branch --no-watch
Merge Modes¶
Behavior Matrix¶
| Current Branch | Target | Merge Type | PR Handling |
|---|---|---|---|
| Feature branch | develop |
Squash merge | Must exist |
develop |
main |
Merge commit | Auto-created if missing |
Feature Mode (Default)¶
When running from a feature branch:
- Target:
develop(deterministic) - Merge Type: Squash merge (combines all commits into one)
- PR: Auto-created from the branch commits (
gh pr create --fill) if it doesn't exist
Release Mode (Auto-detected)¶
When running from the develop branch:
- Target:
main(deterministic) - Merge Type: Merge commit (preserves commit history)
- PR: Auto-created if it doesn't exist
This mode is designed for releasing develop to main while preserving the full commit history.
Workflow Steps¶
The command executes 9 automated steps:
Step 1: Pre-flight Checks¶
- Verifies working directory is clean (no uncommitted changes)
- Validates current branch is not the target branch or main/master
- Fetches latest changes from origin
- Pushes any unpushed local commits
- Replays the branch on the target branch when it is behind:
- lists the incoming commits so you can see what changed
- fast-forwards silently when the branch has no commits of its own
- rebases and pushes with
--force-with-lease(asks first with--confirm) - aborts the rebase and stops on conflict, leaving the working tree untouched
- Runs
just target shared --yes, so the tests and evals run in remote mode and no preview namespace or corpus outlives the merge: it purges a preview corpus, unwires the branch or stops local Supabase, and removes the.rose-targetmarker. A failure stops the merge.
Step 2: PR Verification/Creation¶
Feature Mode:
- Checks if a Pull Request exists for the current branch
- Auto-creates it with
gh pr create --fillif missing, so the title and body come from the branch commits (the PR title becomes the squash commit subject) - Displays PR number, title, and URL
Release Mode:
- Checks if a PR exists for
develop → main - Auto-creates the PR titled
Release: develop → mainif it doesn't exist - Displays PR number, title, and URL
Step 3: Mergeability Check¶
- Checks GitHub PR mergeability status
- Handles UNKNOWN status with retries (GitHub may take time to compute)
- Detects merge conflicts, blocked status, or behind-base-branch issues
Steps 4–6: what runs locally¶
PR Checks (pr-checks-backend.yml, pr-checks-frontend.yml) already run ruff, Biome,
the frontend type-check and vitest on every PR to develop. A feature merge waits for
those checks in step 9, so it skips them locally: only backend pytest (step 6) and the
evals (step 7) run here, since CI has neither the suite nor the secrets.
--no-ci-wait runs them locally instead and merges without waiting. Release merges
always run them locally: PR Checks do not run on PRs to main.
Step 4: Lint Checks¶
- Runs
ruff checkandruff format --checkin thebackend/directory (skipped if no backend Python changes) - Runs
biome checkon changed frontend files (.ts,.tsx,.js,.jsx,.css) — only files modified in the branch are checked
Step 5: Type Checks¶
- Runs
just type-checkin thefrontend/directory (tsc across shared, widget, playground, client-backoffice) - Skipped if the branch only touches frontend markdown
Step 6: Test Execution¶
- Runs the vitest projects the diff can break, via
just test-projects <projects>infrontend/(a change underfrontend/shared/runs all of them, since every project aliasesshared/src) - Runs
just testin thebackend/directory when backend Python, dependency or test files changed - Narrows that to
just test --no-parallel apps/<app>/testswhen the diff is confined to a single leaf app (xdist takes longer to bring up its workers than one app's tests take to run) (BACKEND_LEAF_APPS, currently justcli) — nothing imports those, so nothing else can break. A package, a second app or a root config file in the diff runs the whole suite. - Both must pass for merge to proceed
Step 7: E2E Evaluation¶
- Runs
rose-eval e2e main-dataset --sample-size 5 - Runs
rose-eval features run --sample-size 1 - Gated on
EVAL_PATH_PREFIXES(everything underbackend/apps/andbackend/packages/) minusEVAL_EXEMPT_PREFIXES. Test-only edits inside a covered path do not count, and root-level changes such aspoetry.lockskip the eval. - Runs
just e2einbackend/apps/r4a_mcpwhen the diff touchesR4A_PATH_PREFIXES(the Rose for Agents connector, therose_mcpplumbing it reuses, the Ibanly demo vendor). It checks every R4A tool against known hosts on the branch's own code, with the local connector, the Ibanly Worker and your gcloud credentials, which CI does not have. Those paths are exempt from the chat eval above.--skip-evalskips both.
Keeping the gate honest as the backend grows: the default is inclusive, so a package or app
added later runs the eval until someone deliberately adds it to EVAL_EXEMPT_PREFIXES. Forgetting
costs eval minutes, never a missed chat regression. Adding an exemption is the deliberate act, and
apps/cli/tests/unit/test_merge_branch_gates.py checks that every exemption still points at a real
directory inside a covered tree. The same file asserts FRONTEND_PROJECTS matches the projects
declared in frontend/vitest.config.ts, so a new vitest project fails the suite instead of
silently never running.
Step 8: Cleanup¶
- Kills any dangling vitest processes (from frontend tests)
Step 9: Merge Execution¶
First polls the PR's GitHub validation checks and stops on any failing or cancelled
check, required or not. GitHub also attaches push-triggered Deploy ... runs on the
PR head commit; those deploy the test environment and are omitted from this gate.
PR-triggered validation jobs in deploy-named workflows remain in the gate.
A PR with no validation checks merges after a short registration grace period.
--no-ci-wait skips the wait; GitHub still refuses the merge while a required check
is pending or failing.
Feature Mode:
- Prompts (only with
--confirm): "Proceed with squash merge to develop?" - Executes
gh pr merge --squash
Release Mode:
- Prompts (only with
--confirm): "Proceed with merge commit to main?" - Executes
gh pr merge --merge(preserves history)
Step 10: Deployment Watch (release mode only)¶
After the merge commit lands on main, the tool follows every GitHub workflow run
triggered by that commit (Release and its deploy fan-out, Rose Security Scan, ...)
and renders a live table: one row per workflow with a per-job tally, plus a detail row
for each job still running or already failed.
The Release workflow deploys to production. Earlier Deploy ... runs on develop
deploy to test and are separate runs for a different commit.
- Polls every 15s, gives up after 30 minutes
- Exits
1if any run ends infailure,cancelledortimed_out, printing the run URL Ctrl-Cstops watching without affecting the deployments (exit0— the merge landed)--no-watchskips the step entirely- Feature merges to
developnever watch
Exit Codes¶
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Error (failed checks, tests, merge, or a failed deployment) |
2 |
Cancelled by user |
Prerequisites¶
- GitHub CLI (
gh): Must be installed and authenticated - Poetry environment: Run
poetry shellin the backend directory - Clean working directory: Commit or stash changes before running
- Existing PR: Not required — auto-created in both feature and release mode
Development Workflow Integration¶
This tool integrates with the Development Workflow:
Why Use This Tool?¶
- Consistency: Ensures all merges follow the same process
- Safety: Runs tests before merging to prevent breaking changes
- Automation: Handles pushing, PR creation, and cleanup
- Feedback: Clear step-by-step progress with colored output
- History Preservation: Release mode uses merge commits to preserve full history in main
Troubleshooting¶
"Working directory has uncommitted changes"¶
Commit or stash your changes:
"Rebase onto origin/develop hit a conflict"¶
The rebase was aborted, so your branch is exactly as it was. Resolve it by hand,
then re-run rose-merge-branch:
git rebase origin/develop
# fix the conflicts, then
git rebase --continue
git push --force-with-lease
"Failed to create PR"¶
The PR is auto-created in both modes, so this means gh pr create itself
failed (unauthenticated gh, no commits on the branch, protected base, etc.).
Create it manually to see the underlying error:
"PR has merge conflicts"¶
Resolve conflicts locally:
git fetch origin
git rebase origin/develop
# Resolve conflicts
git add .
git rebase --continue
git push --force-with-lease
"PR is blocked (check required status checks)" or "GitHub checks failed"¶
Run gh pr checks <number> and fix the failing check. If the same check also fails on
develop, fix develop first, then click Update branch on the PR: re-running the
check reuses the old base.