paperclip/doc/RELEASING.md

423 lines
12 KiB
Markdown
Raw Normal View History

2026-03-09 08:49:42 -05:00
# Releasing Paperclip
Maintainer runbook for shipping a full Paperclip release across npm, GitHub, and the website-facing changelog surface.
2026-03-09 13:55:30 -05:00
The release model is branch-driven:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
1. Start a release train on `release/X.Y.Z`
2. Draft the stable changelog on that branch
3. Publish one or more canaries from that branch
4. Publish stable from that same branch head
5. Push the branch commit and tag
6. Create the GitHub Release
7. Merge `release/X.Y.Z` back to `master` without squash or rebase
2026-03-09 08:49:42 -05:00
## Release Surfaces
2026-03-09 13:55:30 -05:00
Every release has four separate surfaces:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
1. **Verification** — the exact git SHA passes typecheck, tests, and build
2. **npm**`paperclipai` and public workspace packages are published
3. **GitHub** — the stable release gets a git tag and GitHub Release
4. **Website / announcements** — the stable changelog is published externally and announced
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
A release is done only when all four surfaces are handled.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
## Core Invariants
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- Canary and stable for `X.Y.Z` must come from the same `release/X.Y.Z` branch.
- The release scripts must run from the matching `release/X.Y.Z` branch.
- Once `vX.Y.Z` exists locally, on GitHub, or on npm, that release train is frozen.
- Do not squash-merge or rebase-merge a release branch PR back to `master`.
- The stable changelog is always `releases/vX.Y.Z.md`. Never create canary changelog files.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
The reason for the merge rule is simple: the tag must keep pointing at the exact published commit. Squash or rebase breaks that property.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
## TL;DR
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### 1. Start the release train
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Use this to compute the next version, create or resume the branch, create or resume a dedicated worktree, and push the branch to GitHub.
2026-03-09 08:49:42 -05:00
```bash
2026-03-09 13:55:30 -05:00
./scripts/release-start.sh patch
2026-03-09 08:49:42 -05:00
```
2026-03-09 13:55:30 -05:00
That script:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- fetches the release remote and tags
- computes the next stable version from the latest `v*` tag
- creates or resumes `release/X.Y.Z`
- creates or resumes a dedicated worktree
- pushes the branch to the remote by default
- refuses to reuse a frozen release train
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### 2. Draft the stable changelog
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
From the release worktree:
2026-03-09 08:49:42 -05:00
```bash
2026-03-09 13:55:30 -05:00
VERSION=X.Y.Z
claude --print --output-format stream-json --verbose --dangerously-skip-permissions --model claude-opus-4-6 "Use the release-changelog skill to draft or update releases/v${VERSION}.md for Paperclip. Read doc/RELEASING.md and .agents/skills/release-changelog/SKILL.md, then generate the stable changelog for v${VERSION} from commits since the last stable tag. Do not create a canary changelog."
2026-03-09 08:49:42 -05:00
```
2026-03-09 13:55:30 -05:00
### 3. Verify and publish a canary
```bash
2026-03-09 13:55:30 -05:00
./scripts/release-preflight.sh canary patch
./scripts/release.sh patch --canary --dry-run
./scripts/release.sh patch --canary
PAPERCLIPAI_VERSION=canary ./scripts/docker-onboard-smoke.sh
```
2026-03-09 13:55:30 -05:00
Users install canaries with:
```bash
2026-03-09 13:55:30 -05:00
npx paperclipai@canary onboard
```
2026-03-09 13:55:30 -05:00
### 4. Publish stable
```bash
2026-03-09 13:55:30 -05:00
./scripts/release-preflight.sh stable patch
./scripts/release.sh patch --dry-run
./scripts/release.sh patch
git push public-gh HEAD --follow-tags
./scripts/create-github-release.sh X.Y.Z
```
2026-03-09 13:55:30 -05:00
Then open a PR from `release/X.Y.Z` to `master` and merge without squash or rebase.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
## Release Branches
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Paperclip uses one release branch per target stable version:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- `release/0.3.0`
- `release/0.3.1`
- `release/1.0.0`
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Do not create separate per-canary branches like `canary/0.3.0-1`. A canary is just a prerelease snapshot of the same stable train.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
## Script Entry Points
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- [`scripts/release-start.sh`](../scripts/release-start.sh) — create or resume the release train branch/worktree
- [`scripts/release-preflight.sh`](../scripts/release-preflight.sh) — validate branch, version plan, git/npm state, and verification gate
- [`scripts/release.sh`](../scripts/release.sh) — publish canary or stable from the release branch
- [`scripts/create-github-release.sh`](../scripts/create-github-release.sh) — create or update the GitHub Release after pushing the tag
- [`scripts/rollback-latest.sh`](../scripts/rollback-latest.sh) — repoint `latest` to the last good stable version
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
## Detailed Workflow
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### 1. Start or resume the release train
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Run:
2026-03-09 08:49:42 -05:00
```bash
2026-03-09 13:55:30 -05:00
./scripts/release-start.sh <patch|minor|major>
2026-03-09 08:49:42 -05:00
```
2026-03-09 13:55:30 -05:00
Useful options:
2026-03-09 09:06:45 -05:00
```bash
2026-03-09 13:55:30 -05:00
./scripts/release-start.sh patch --dry-run
./scripts/release-start.sh minor --worktree-dir ../paperclip-release-0.4.0
./scripts/release-start.sh patch --no-push
2026-03-09 09:06:45 -05:00
```
2026-03-09 13:55:30 -05:00
The script is intentionally idempotent:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- if `release/X.Y.Z` already exists locally, it reuses it
- if the branch already exists on the remote, it resumes it locally
- if the branch is already checked out in another worktree, it points you there
- if `vX.Y.Z` already exists locally, remotely, or on npm, it refuses to reuse that train
2026-03-09 09:06:45 -05:00
2026-03-09 13:55:30 -05:00
### 2. Write the stable changelog early
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Create or update:
2026-03-09 08:49:42 -05:00
- `releases/vX.Y.Z.md`
That file is for the eventual stable release. It should not include `-canary` in the filename or heading.
Recommended structure:
- `Breaking Changes` when needed
- `Highlights`
- `Improvements`
- `Fixes`
- `Upgrade Guide` when needed
2026-03-09 13:55:30 -05:00
- `Contributors` — @-mention every contributor by GitHub username (no emails)
2026-03-09 08:49:42 -05:00
Package-level `CHANGELOG.md` files are generated as part of the release mechanics. They are not the main release narrative.
2026-03-09 13:55:30 -05:00
### 3. Run release preflight
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
From the `release/X.Y.Z` worktree:
2026-03-09 09:06:45 -05:00
```bash
./scripts/release-preflight.sh canary <patch|minor|major>
# or
./scripts/release-preflight.sh stable <patch|minor|major>
```
2026-03-09 13:55:30 -05:00
The preflight script now checks all of the following before it runs the verification gate:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- the worktree is clean, including untracked files
- the current branch matches the computed `release/X.Y.Z`
- the release train is not frozen
- the target version is still free on npm
- the target tag does not already exist locally or remotely
- whether the remote release branch already exists
- whether `releases/vX.Y.Z.md` is present
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Then it runs:
2026-03-09 08:49:42 -05:00
```bash
2026-03-09 13:55:30 -05:00
pnpm -r typecheck
pnpm test:run
pnpm build
2026-03-09 08:49:42 -05:00
```
2026-03-09 13:55:30 -05:00
### 4. Publish one or more canaries
2026-03-09 08:49:42 -05:00
Run:
```bash
2026-03-09 13:55:30 -05:00
./scripts/release.sh <patch|minor|major> --canary --dry-run
2026-03-09 08:49:42 -05:00
./scripts/release.sh <patch|minor|major> --canary
```
2026-03-09 13:55:30 -05:00
Result:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- npm gets a prerelease such as `1.2.3-canary.0` under dist-tag `canary`
- `latest` is unchanged
- no git tag is created
- no GitHub Release is created
- the worktree returns to clean after the script finishes
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Guardrails:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- the script refuses to run from the wrong branch
- the script refuses to publish from a frozen train
- the canary is always derived from the next stable version
- if the stable notes file is missing, the script warns before you forget it
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Concrete example:
2026-03-09 09:06:45 -05:00
2026-03-09 13:55:30 -05:00
- if the latest stable is `0.2.7`, a patch canary targets `0.2.8-canary.0`
- `0.2.7-canary.N` is invalid because `0.2.7` is already stable
2026-03-09 09:06:45 -05:00
2026-03-09 13:55:30 -05:00
### 5. Smoke test the canary
2026-03-09 08:49:42 -05:00
Run the actual install path in Docker:
```bash
PAPERCLIPAI_VERSION=canary ./scripts/docker-onboard-smoke.sh
```
Useful isolated variants:
```bash
HOST_PORT=3232 DATA_DIR=./data/release-smoke-canary PAPERCLIPAI_VERSION=canary ./scripts/docker-onboard-smoke.sh
HOST_PORT=3233 DATA_DIR=./data/release-smoke-stable PAPERCLIPAI_VERSION=latest ./scripts/docker-onboard-smoke.sh
```
2026-03-09 13:55:30 -05:00
If you want to exercise onboarding from the current committed ref instead of npm, use:
```bash
./scripts/clean-onboard-ref.sh
2026-03-09 13:55:30 -05:00
PAPERCLIP_PORT=3234 ./scripts/clean-onboard-ref.sh
./scripts/clean-onboard-ref.sh HEAD
```
2026-03-09 08:49:42 -05:00
Minimum checks:
2026-03-09 13:55:30 -05:00
- `npx paperclipai@canary onboard` installs
- onboarding completes without crashes
- the server boots
- the UI loads
- basic company creation and dashboard load work
If smoke testing fails:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
1. stop the stable release
2. fix the issue on the same `release/X.Y.Z` branch
3. publish another canary
4. rerun smoke testing
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### 6. Publish stable from the same release branch
Once the branch head is vetted, run:
2026-03-09 08:49:42 -05:00
```bash
2026-03-09 13:55:30 -05:00
./scripts/release.sh <patch|minor|major> --dry-run
2026-03-09 08:49:42 -05:00
./scripts/release.sh <patch|minor|major>
```
2026-03-09 13:55:30 -05:00
Stable publish:
- publishes `X.Y.Z` to npm under `latest`
- creates the local release commit
- creates the local tag `vX.Y.Z`
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Stable publish refuses to proceed if:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- the current branch is not `release/X.Y.Z`
- the remote release branch does not exist yet
- the stable notes file is missing
- the target tag already exists locally or remotely
- the stable version already exists on npm
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Those checks intentionally freeze the train after stable publish.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### 7. Push the stable branch commit and tag
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
After stable publish succeeds:
2026-03-09 08:49:42 -05:00
```bash
2026-03-09 13:55:30 -05:00
git push public-gh HEAD --follow-tags
2026-03-09 08:49:42 -05:00
./scripts/create-github-release.sh X.Y.Z
```
2026-03-09 13:55:30 -05:00
The GitHub Release notes come from:
2026-03-09 08:49:42 -05:00
- `releases/vX.Y.Z.md`
2026-03-09 13:55:30 -05:00
### 8. Merge the release branch back to `master`
Open a PR:
- base: `master`
- head: `release/X.Y.Z`
Merge rule:
- allowed: merge commit or fast-forward
- forbidden: squash merge
- forbidden: rebase merge
Post-merge verification:
```bash
git fetch public-gh --tags
git merge-base --is-ancestor "vX.Y.Z" "public-gh/master"
```
That command must succeed. If it fails, the published tagged commit is not reachable from `master`, which means the merge strategy was wrong.
### 9. Finish the external surfaces
2026-03-09 08:49:42 -05:00
After GitHub is correct:
- publish the changelog on the website
2026-03-09 13:55:30 -05:00
- write and send the announcement copy
2026-03-09 08:49:42 -05:00
- ensure public docs and install guidance point to the stable version
2026-03-09 13:55:30 -05:00
## GitHub Actions Release
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
There is also a manual workflow at [`.github/workflows/release.yml`](../.github/workflows/release.yml).
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Use it from the Actions tab on the relevant `release/X.Y.Z` branch:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
1. Choose `Release`
2. Choose `channel`: `canary` or `stable`
3. Choose `bump`: `patch`, `minor`, or `major`
4. Choose whether this is a `dry_run`
5. Run it from the release branch, not from `master`
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
The workflow:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- reruns `typecheck`, `test:run`, and `build`
- gates publish behind the `npm-release` environment
- can publish canaries without touching `latest`
- can publish stable, push the stable branch commit and tag, and create the GitHub Release
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
It does not merge the release branch back to `master` for you.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
## Release Checklist
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### Before any publish
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- [ ] The release train exists on `release/X.Y.Z`
- [ ] The working tree is clean, including untracked files
- [ ] If package manifests changed, the CI-owned `pnpm-lock.yaml` refresh is already merged on `master` before the train is cut
- [ ] The required verification gate passed on the exact branch head you want to publish
- [ ] The bump type is correct for the user-visible impact
- [ ] The stable changelog file exists or is ready at `releases/vX.Y.Z.md`
- [ ] You know which previous stable version you would roll back to if needed
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### Before a stable
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- [ ] The candidate has already passed smoke testing
- [ ] The remote `release/X.Y.Z` branch exists
- [ ] You are ready to push the stable branch commit and tag immediately after npm publish
- [ ] You are ready to create the GitHub Release immediately after the push
- [ ] You are ready to open the PR back to `master`
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### After a stable
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
- [ ] `npm view paperclipai@latest version` matches the new stable version
- [ ] The git tag exists on GitHub
- [ ] The GitHub Release exists and uses `releases/vX.Y.Z.md`
- [ ] `vX.Y.Z` is reachable from `master`
- [ ] The website changelog is updated
- [ ] Announcement copy matches the stable release, not the canary
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
## Failure Playbooks
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### If the canary publishes but the smoke test fails
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Do not publish stable.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Instead:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
1. fix the issue on `release/X.Y.Z`
2. publish another canary
3. rerun smoke testing
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### If stable npm publish succeeds but push or GitHub release creation fails
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
This is a partial release. npm is already live.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Do this immediately:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
1. fix the git or GitHub issue from the same checkout
2. push the stable branch commit and tag
3. create the GitHub Release
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Do not republish the same version.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### If `latest` is broken after stable publish
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Preview:
2026-03-09 08:49:42 -05:00
```bash
2026-03-09 13:55:30 -05:00
./scripts/rollback-latest.sh X.Y.Z --dry-run
2026-03-09 08:49:42 -05:00
```
2026-03-09 13:55:30 -05:00
Roll back:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
```bash
./scripts/rollback-latest.sh X.Y.Z
```
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
This does not unpublish anything. It only moves the `latest` dist-tag back to the last good stable release.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Then fix forward with a new patch release.
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
### If the GitHub Release notes are wrong
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
Re-run:
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
```bash
./scripts/create-github-release.sh X.Y.Z
```
2026-03-09 08:49:42 -05:00
2026-03-09 13:55:30 -05:00
If the release already exists, the script updates it.
2026-03-09 08:49:42 -05:00
## Related Docs
- [doc/PUBLISHING.md](PUBLISHING.md) — low-level npm build and packaging internals
- [.agents/skills/release/SKILL.md](../.agents/skills/release/SKILL.md) — maintainer release coordination workflow
- [.agents/skills/release-changelog/SKILL.md](../.agents/skills/release-changelog/SKILL.md) — stable changelog drafting workflow