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
20 changes: 20 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## What changed

<!-- Short description of the change and why it is needed. -->

## Target branch

- [ ] This PR targets `staging` (normal feature, fix, docs, or chore work)
- [ ] This PR targets `main` and comes from `staging` (production promotion)

PRs into `main` are rejected unless the source branch is `staging`.

## Verification

- [ ] CI is green
- [ ] Change was exercised on the staging deployment
- [ ] No secrets or credentials are included

## Notes for reviewers

<!-- Anything reviewers should focus on, screenshots, or follow-ups. -->
23 changes: 23 additions & 0 deletions .github/workflows/promotion-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Promotion guard

on:
pull_request:
branches: [main]

permissions:
contents: read
pull-requests: read

jobs:
source-branch:
name: PRs to main must come from staging
runs-on: ubuntu-latest
steps:
- name: Verify source branch
if: github.head_ref != 'staging'
run: |
echo "::error::Changes reach main only by promoting the staging branch. Open this PR against staging instead."
exit 1

- name: Source branch allowed
run: echo "Source branch is staging."
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [main]
branches: [main, staging]
pull_request:

# A newer push to the same branch makes the older run pointless.
Expand Down
81 changes: 81 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Contributing

## Branch model

We use three layers of branches:

```
feature/fix branch -> staging -> main
(local work) (QA/verify) (production)
```

| Branch | Purpose | Direct pushes | Merges into it |
| --- | --- | --- | --- |
| Work branches (`feat/...`, `fix/...`, `chore/...`, `docs/...`) | Day-to-day development | Allowed by the author | — |
| `staging` | Integration and testing ground | Blocked | Work branches via PR |
| `main` | Production | Blocked | `staging` only, via PR |

`main` is production. Nothing is merged into `main` until it has run on `staging`.

## Day-to-day flow

1. Create a work branch off the latest `staging` (not `main`):

```bash
git fetch origin
git switch -c feat/my-change origin/staging
```

2. Make changes and run the checks locally:

```bash
npm test
cd frontend && npx tsc --noEmit && npm run lint && npm run build
cd ../openclaw-service && npx tsc --noEmit
```

3. Push the branch and open a pull request **targeting `staging`**:

```bash
git push -u origin feat/my-change
gh pr create --base staging --fill
```

4. Get CI green and one approving review, then merge into `staging`.

5. Verify the change on the staging deployment (or locally from `staging`).

## Promoting to production

Once `staging` is verified and fit for release:

1. Open a promotion pull request from `staging` into `main`:

```bash
gh pr create --base main --head staging --title "Promote staging to production" --fill
```

2. The `Promotion guard` check rejects any PR into `main` whose source branch is
not `staging`.

3. Get CI green and one approving review, then merge. `main` deploys to production.

## Hotfixes

Hotfixes follow the same path: branch off `staging`, open a PR into `staging`,
verify, then promote `staging` to `main`. If production is broken and an
expedited release is unavoidable, an administrator may temporarily relax branch
protection, but the fix must still be merged back into `staging` afterwards.

## CI

`.github/workflows/test.yml` runs on every pull request and on pushes to
`staging` and `main`:

- Contract and unit tests (`npm test`)
- Frontend typecheck, lint, and production build
- OpenClaw service typecheck

The `staging` and `main` branches require a pull request, green CI, one
approving review, and an up-to-date branch before merging. Force pushes and
branch deletion are blocked.
Loading