chore: formalize release workflow
This commit is contained in:
@@ -1,363 +1,140 @@
|
||||
---
|
||||
name: release-changelog
|
||||
description: >
|
||||
Generate user-facing release changelogs for Paperclip. Reads git history,
|
||||
merged PRs, and changeset files since the last release tag. Detects breaking
|
||||
changes, categorizes changes, and outputs structured markdown to
|
||||
releases/v{version}.md. Use when preparing a release or when asked to
|
||||
generate a changelog.
|
||||
Generate the stable Paperclip release changelog at releases/v{version}.md by
|
||||
reading commits, changesets, and merged PR context since the last stable tag.
|
||||
---
|
||||
|
||||
# Release Changelog Skill
|
||||
|
||||
Generate a user-facing changelog for a new Paperclip release. This skill reads
|
||||
the commit history, changeset files, and merged PRs since the last release tag,
|
||||
detects breaking changes, categorizes everything, and writes a structured
|
||||
release notes file.
|
||||
Generate the user-facing changelog for the **stable** Paperclip release.
|
||||
|
||||
**Output:** `releases/v{version}.md` in the repo root.
|
||||
**Review required:** Always present the draft for human sign-off before
|
||||
finalizing. Never auto-publish.
|
||||
Output:
|
||||
|
||||
---
|
||||
- `releases/v{version}.md`
|
||||
|
||||
Important rule:
|
||||
|
||||
- even if there are canary releases such as `1.2.3-canary.0`, the changelog file stays `releases/v1.2.3.md`
|
||||
|
||||
## Step 0 — Idempotency Check
|
||||
|
||||
Before generating anything, check if a changelog already exists for this version:
|
||||
Before generating anything, check whether the file already exists:
|
||||
|
||||
```bash
|
||||
ls releases/v{version}.md 2>/dev/null
|
||||
```
|
||||
|
||||
**If the file already exists:**
|
||||
If it exists:
|
||||
|
||||
1. Read the existing changelog and present it to the reviewer.
|
||||
2. Ask: "A changelog for v{version} already exists. Do you want to (a) keep it
|
||||
as-is, (b) regenerate from scratch, or (c) update specific sections?"
|
||||
3. If the reviewer says keep it → **stop here**. Do not overwrite. This skill is
|
||||
done.
|
||||
4. If the reviewer says regenerate → back up the existing file to
|
||||
`releases/v{version}.md.prev`, then proceed from Step 1.
|
||||
5. If the reviewer says update → read the existing file, proceed through Steps
|
||||
1-4 to gather fresh data, then merge changes into the existing file rather
|
||||
than replacing it wholesale. Preserve any manual edits the reviewer previously
|
||||
made.
|
||||
1. read it first
|
||||
2. present it to the reviewer
|
||||
3. ask whether to keep it, regenerate it, or update specific sections
|
||||
4. never overwrite it silently
|
||||
|
||||
**If the file does not exist:** Proceed normally from Step 1.
|
||||
## Step 1 — Determine the Stable Range
|
||||
|
||||
**Critical rule:** This skill NEVER triggers a version bump. It only reads git
|
||||
history and writes a markdown file. The `release.sh` script is the only thing
|
||||
that bumps versions, and it is called separately by the `release` coordination
|
||||
skill. Running this skill multiple times is always safe — worst case it
|
||||
overwrites a draft changelog (with reviewer permission).
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Determine the Release Range
|
||||
|
||||
Find the last release tag and the planned version:
|
||||
Find the last stable tag:
|
||||
|
||||
```bash
|
||||
# Last release tag (most recent semver tag)
|
||||
git tag --sort=-version:refname | head -1
|
||||
# e.g. v0.2.7
|
||||
|
||||
# All commits since that tag
|
||||
git log v0.2.7..HEAD --oneline --no-merges
|
||||
git tag --list 'v*' --sort=-version:refname | head -1
|
||||
git log v{last}..HEAD --oneline --no-merges
|
||||
```
|
||||
|
||||
If no tag exists yet, use the initial commit as the base.
|
||||
The planned stable version comes from one of:
|
||||
|
||||
The new version number comes from one of:
|
||||
- An explicit argument (e.g. "generate changelog for v0.3.0")
|
||||
- The bump type (patch/minor/major) applied to the last tag
|
||||
- The version already set in `cli/package.json` if `scripts/release.sh` has been run
|
||||
- an explicit maintainer request
|
||||
- the chosen bump type applied to the last stable tag
|
||||
- the release plan already agreed in `doc/RELEASING.md`
|
||||
|
||||
---
|
||||
Do not derive the changelog version from a canary tag or prerelease suffix.
|
||||
|
||||
## Step 2 — Gather Raw Change Data
|
||||
## Step 2 — Gather the Raw Inputs
|
||||
|
||||
Collect changes from three sources, in priority order:
|
||||
Collect release data from:
|
||||
|
||||
### 2a. Git Commits
|
||||
1. git commits since the last stable tag
|
||||
2. `.changeset/*.md` files
|
||||
3. merged PRs via `gh` when available
|
||||
|
||||
Useful commands:
|
||||
|
||||
```bash
|
||||
git log v{last}..HEAD --oneline --no-merges
|
||||
git log v{last}..HEAD --format="%H %s" --no-merges # full SHAs for file diffs
|
||||
```
|
||||
|
||||
### 2b. Changeset Files
|
||||
|
||||
Look for unconsumed changesets in `.changeset/`:
|
||||
|
||||
```bash
|
||||
git log v{last}..HEAD --format="%H %s" --no-merges
|
||||
ls .changeset/*.md | grep -v README.md
|
||||
```
|
||||
|
||||
Each changeset file has YAML frontmatter with package names and bump types
|
||||
(`patch`, `minor`, `major`), followed by a description. Parse these — the bump
|
||||
type is a strong categorization signal, and the description may contain
|
||||
user-facing summaries.
|
||||
|
||||
### 2c. Merged PRs (when available)
|
||||
|
||||
If GitHub access is available via `gh`:
|
||||
|
||||
```bash
|
||||
gh pr list --state merged --search "merged:>={last-tag-date}" --json number,title,body,labels
|
||||
```
|
||||
|
||||
PR titles and bodies are often the best source of user-facing descriptions.
|
||||
Prefer PR descriptions over raw commit messages when both are available.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Detect Breaking Changes
|
||||
|
||||
Scan for breaking changes using these signals. **Any match flags the release as
|
||||
containing breaking changes**, which affects version bump requirements and
|
||||
changelog structure.
|
||||
Look for:
|
||||
|
||||
### 3a. Migration Files
|
||||
- destructive migrations
|
||||
- removed or changed API fields/endpoints
|
||||
- renamed or removed config keys
|
||||
- `major` changesets
|
||||
- `BREAKING:` or `BREAKING CHANGE:` commit signals
|
||||
|
||||
Check for new migration files since the last tag:
|
||||
Key commands:
|
||||
|
||||
```bash
|
||||
git diff --name-only v{last}..HEAD -- packages/db/src/migrations/
|
||||
```
|
||||
|
||||
- **New migration files exist** = DB migration required in upgrade.
|
||||
- Inspect migration content: look for `DROP`, `ALTER ... DROP`, `RENAME` to
|
||||
distinguish destructive vs. additive migrations.
|
||||
- Additive-only migrations (new tables, new nullable columns, new indexes) are
|
||||
safe but should still be mentioned.
|
||||
- Destructive migrations (column drops, type changes, table drops) = breaking.
|
||||
|
||||
### 3b. Schema Changes
|
||||
|
||||
```bash
|
||||
git diff v{last}..HEAD -- packages/db/src/schema/
|
||||
```
|
||||
|
||||
Look for:
|
||||
- Removed or renamed columns/tables
|
||||
- Changed column types
|
||||
- Removed default values or nullable constraints
|
||||
- These indicate breaking DB changes even if no explicit migration file exists
|
||||
|
||||
### 3c. API Route Changes
|
||||
|
||||
```bash
|
||||
git diff v{last}..HEAD -- server/src/routes/ server/src/api/
|
||||
git log v{last}..HEAD --format="%s" | rg -n 'BREAKING CHANGE|BREAKING:|^[a-z]+!:' || true
|
||||
```
|
||||
|
||||
Look for:
|
||||
- Removed endpoints
|
||||
- Changed request/response shapes (removed fields, type changes)
|
||||
- Changed authentication requirements
|
||||
If the requested bump is lower than the minimum required bump, flag that before the release proceeds.
|
||||
|
||||
### 3d. Config Changes
|
||||
## Step 4 — Categorize for Users
|
||||
|
||||
```bash
|
||||
git diff v{last}..HEAD -- cli/src/config/ packages/*/src/*config*
|
||||
```
|
||||
Use these stable changelog sections:
|
||||
|
||||
Look for renamed, removed, or restructured configuration keys.
|
||||
- `Breaking Changes`
|
||||
- `Highlights`
|
||||
- `Improvements`
|
||||
- `Fixes`
|
||||
- `Upgrade Guide` when needed
|
||||
|
||||
### 3e. Changeset Severity
|
||||
Exclude purely internal refactors, CI changes, and docs-only work unless they materially affect users.
|
||||
|
||||
Any `.changeset/*.md` file with a `major` bump = explicitly flagged breaking.
|
||||
Guidelines:
|
||||
|
||||
### 3f. Commit Conventions
|
||||
- group related commits into one user-facing entry
|
||||
- write from the user perspective
|
||||
- keep highlights short and concrete
|
||||
- spell out upgrade actions for breaking changes
|
||||
|
||||
Scan commit messages for:
|
||||
- `BREAKING:` or `BREAKING CHANGE:` prefix
|
||||
- `!` after the type in conventional commits (e.g. `feat!:`, `fix!:`)
|
||||
## Step 5 — Write the File
|
||||
|
||||
### Version Bump Rules
|
||||
|
||||
| Condition | Minimum Bump |
|
||||
|---|---|
|
||||
| Destructive migration (DROP, RENAME) | `major` |
|
||||
| Removed API endpoints or fields | `major` |
|
||||
| Any `major` changeset or `BREAKING:` commit | `major` |
|
||||
| New (additive) migration | `minor` |
|
||||
| New features (`feat:` commits, `minor` changesets) | `minor` |
|
||||
| Bug fixes only | `patch` |
|
||||
|
||||
If the planned bump is lower than the minimum required, **warn the reviewer**
|
||||
and recommend the correct bump level.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Categorize Changes
|
||||
|
||||
Assign every meaningful change to one of these categories:
|
||||
|
||||
| Category | What Goes Here | Shows in User Notes? |
|
||||
|---|---|---|
|
||||
| **Breaking Changes** | Anything requiring user action to upgrade | Yes (top, with warning) |
|
||||
| **Highlights** | New user-visible features, major behavioral changes | Yes (with 1-2 sentence descriptions) |
|
||||
| **Improvements** | Enhancements to existing features | Yes (bullet list) |
|
||||
| **Fixes** | Bug fixes | Yes (bullet list) |
|
||||
| **Internal** | Refactoring, deps, CI, tests, docs | No (dev changelog only) |
|
||||
|
||||
### Categorization Heuristics
|
||||
|
||||
Use these signals to auto-categorize. When signals conflict, prefer the
|
||||
higher-visibility category and flag for human review.
|
||||
|
||||
| Signal | Category |
|
||||
|---|---|
|
||||
| Commit touches migration files, schema changes | Breaking Change (if destructive) |
|
||||
| Changeset marked `major` | Breaking Change |
|
||||
| Commit message has `BREAKING:` or `!:` | Breaking Change |
|
||||
| New UI components, new routes, new API endpoints | Highlight |
|
||||
| Commit message starts with `feat:` or `add:` | Highlight or Improvement |
|
||||
| Changeset marked `minor` | Highlight |
|
||||
| Commit message starts with `fix:` or `bug:` | Fix |
|
||||
| Changeset marked `patch` | Fix or Improvement |
|
||||
| Commit message starts with `chore:`, `refactor:`, `ci:`, `test:`, `docs:` | Internal |
|
||||
| PR has detailed body with user-facing description | Use PR body as the description |
|
||||
|
||||
### Writing Good Descriptions
|
||||
|
||||
- **Highlights** get 1-2 sentence descriptions explaining the user benefit.
|
||||
Write from the user's perspective ("You can now..." not "Added a component that...").
|
||||
- **Improvements and Fixes** are concise bullet points.
|
||||
- **Breaking Changes** get detailed descriptions including what changed,
|
||||
why, and what the user needs to do.
|
||||
- Group related commits into a single changelog entry. Five commits implementing
|
||||
one feature = one Highlight entry, not five bullets.
|
||||
- Omit purely internal changes from user-facing notes entirely.
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Write the Changelog
|
||||
|
||||
Output the changelog to `releases/v{version}.md` using this template:
|
||||
Template:
|
||||
|
||||
```markdown
|
||||
# v{version}
|
||||
|
||||
> Released: {YYYY-MM-DD}
|
||||
|
||||
{If breaking changes detected, include this section:}
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
> **Action required before upgrading.** Read the Upgrade Guide below.
|
||||
|
||||
- **{Breaking change title}** — {What changed and why. What the user needs to do.}
|
||||
|
||||
## Highlights
|
||||
|
||||
- **{Feature name}** — {1-2 sentence description of what it does and why it matters.}
|
||||
|
||||
## Improvements
|
||||
|
||||
- {Concise description of improvement}
|
||||
|
||||
## Fixes
|
||||
|
||||
- {Concise description of fix}
|
||||
|
||||
---
|
||||
|
||||
{If breaking changes detected, include this section:}
|
||||
|
||||
## Upgrade Guide
|
||||
|
||||
### Before You Update
|
||||
|
||||
1. **Back up your database.**
|
||||
- SQLite: `cp paperclip.db paperclip.db.backup`
|
||||
- Postgres: `pg_dump -Fc paperclip > paperclip-pre-{version}.dump`
|
||||
2. **Note your current version:** `paperclip --version`
|
||||
|
||||
### After Updating
|
||||
|
||||
{Specific steps: run migrations, update configs, etc.}
|
||||
|
||||
### Rolling Back
|
||||
|
||||
If something goes wrong:
|
||||
1. Restore your database backup
|
||||
2. `npm install @paperclipai/server@{previous-version}`
|
||||
```
|
||||
|
||||
### Template Rules
|
||||
Omit empty sections except `Highlights`, `Improvements`, and `Fixes`, which should usually exist.
|
||||
|
||||
- Omit any empty section entirely (don't show "## Fixes" with no bullets).
|
||||
- The Breaking Changes section always comes first when present.
|
||||
- The Upgrade Guide always comes last when present.
|
||||
- Use `**bold**` for feature/change names, regular text for descriptions.
|
||||
- Keep the entire changelog scannable — a busy user should get the gist from
|
||||
headings and bold text alone.
|
||||
## Step 6 — Review Before Release
|
||||
|
||||
---
|
||||
Before handing it off:
|
||||
|
||||
## Step 6 — Present for Review
|
||||
1. confirm the heading is the stable version only
|
||||
2. confirm there is no `-canary` language in the title or filename
|
||||
3. confirm any breaking changes have an upgrade path
|
||||
4. present the draft for human sign-off
|
||||
|
||||
After generating the draft:
|
||||
|
||||
1. **Show the full changelog** to the reviewer (CTO or whoever triggered the release).
|
||||
2. **Flag ambiguous items** — commits you weren't sure how to categorize, or
|
||||
items that might be breaking but aren't clearly signaled.
|
||||
3. **Flag version bump mismatches** — if the planned bump is lower than what
|
||||
the changes warrant.
|
||||
4. **Wait for approval** before considering the changelog final.
|
||||
|
||||
If the reviewer requests edits, update `releases/v{version}.md` accordingly.
|
||||
|
||||
Do not proceed to publishing, website updates, or social announcements. Those
|
||||
are handled by the `release` coordination skill (separate from this one).
|
||||
|
||||
---
|
||||
|
||||
## Directory Convention
|
||||
|
||||
Release changelogs live in `releases/` at the repo root:
|
||||
|
||||
```
|
||||
releases/
|
||||
v0.2.7.md
|
||||
v0.3.0.md
|
||||
...
|
||||
```
|
||||
|
||||
Each file is named `v{version}.md` matching the git tag. This directory is
|
||||
committed to the repo and serves as the source of truth for release history.
|
||||
|
||||
The `releases/` directory should be created with a `.gitkeep` if it doesn't
|
||||
exist yet.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Full workflow summary:
|
||||
|
||||
# 1. Find last tag
|
||||
LAST_TAG=$(git tag --sort=-version:refname | head -1)
|
||||
|
||||
# 2. Commits since last tag
|
||||
git log $LAST_TAG..HEAD --oneline --no-merges
|
||||
|
||||
# 3. Files changed (for breaking change detection)
|
||||
git diff --name-only $LAST_TAG..HEAD
|
||||
|
||||
# 4. Migration changes specifically
|
||||
git diff --name-only $LAST_TAG..HEAD -- packages/db/src/migrations/
|
||||
|
||||
# 5. Schema changes
|
||||
git diff $LAST_TAG..HEAD -- packages/db/src/schema/
|
||||
|
||||
# 6. Unconsumed changesets
|
||||
ls .changeset/*.md | grep -v README.md
|
||||
|
||||
# 7. Merged PRs (if gh available)
|
||||
gh pr list --state merged --search "merged:>=$(git log -1 --format=%aI $LAST_TAG)" \
|
||||
--json number,title,body,labels
|
||||
```
|
||||
This skill never publishes anything. It only prepares the stable changelog artifact.
|
||||
|
||||
@@ -1,402 +1,234 @@
|
||||
---
|
||||
name: release
|
||||
description: >
|
||||
Coordinate a full Paperclip release across engineering, website publishing,
|
||||
and social announcement. Use when CTO/CEO requests "do a release" or
|
||||
"release vX.Y.Z". Runs pre-flight checks, generates changelog via
|
||||
release-changelog, executes npm release, creates cross-project follow-up
|
||||
tasks, and posts a release wrap-up.
|
||||
Coordinate a full Paperclip release across engineering verification, npm,
|
||||
GitHub, website publishing, and announcement follow-up. Use when leadership
|
||||
asks to ship a release, not merely to discuss version bumps.
|
||||
---
|
||||
|
||||
# Release Coordination Skill
|
||||
|
||||
Run the full Paperclip release process as an organizational workflow, not just
|
||||
an npm publish.
|
||||
Run the full Paperclip release as a maintainer workflow, not just an npm publish.
|
||||
|
||||
This skill coordinates:
|
||||
- User-facing changelog generation (`release-changelog` skill)
|
||||
- Canary publish to npm (`scripts/release.sh --canary`)
|
||||
- Docker smoke test of the canary (`scripts/docker-onboard-smoke.sh`)
|
||||
- Promotion to `latest` after canary is verified
|
||||
- Website publishing task creation
|
||||
- CMO announcement task creation
|
||||
- Final release summary with links
|
||||
|
||||
---
|
||||
- stable changelog drafting via `release-changelog`
|
||||
- prerelease canary publishing via `scripts/release.sh --canary`
|
||||
- Docker smoke testing via `scripts/docker-onboard-smoke.sh`
|
||||
- stable publishing via `scripts/release.sh`
|
||||
- pushing the release commit and tag
|
||||
- GitHub Release creation via `scripts/create-github-release.sh`
|
||||
- website / announcement follow-up tasks
|
||||
|
||||
## Trigger
|
||||
|
||||
Use this skill when leadership asks for:
|
||||
- "do a release"
|
||||
- "release {patch|minor|major}"
|
||||
- "release vX.Y.Z"
|
||||
|
||||
---
|
||||
- "do a release"
|
||||
- "ship the next patch/minor/major"
|
||||
- "release vX.Y.Z"
|
||||
|
||||
## Preconditions
|
||||
|
||||
Before proceeding, verify all of the following:
|
||||
|
||||
1. `skills/release-changelog/SKILL.md` exists and is usable.
|
||||
2. The `release-changelog` dependency work is complete/reviewed before running this flow.
|
||||
3. App repo working tree is clean.
|
||||
4. There are commits since the last release tag.
|
||||
5. You have release permissions (`npm whoami` succeeds for real publish).
|
||||
6. If running via Paperclip, you have issue context for posting status updates.
|
||||
2. The repo working tree is clean, including untracked files.
|
||||
3. There are commits since the last stable tag.
|
||||
4. The release SHA has passed the verification gate or is about to.
|
||||
5. npm publish rights are available locally, or the GitHub release workflow is being used with trusted publishing.
|
||||
6. If running through Paperclip, you have issue context for status updates and follow-up task creation.
|
||||
|
||||
If any precondition fails, stop and report the blocker.
|
||||
|
||||
---
|
||||
|
||||
## Inputs
|
||||
|
||||
Collect these inputs up front:
|
||||
|
||||
- Release request source issue (if in Paperclip)
|
||||
- Requested bump (`patch|minor|major`) or explicit version (`vX.Y.Z`)
|
||||
- Whether this run is dry-run or live publish
|
||||
- Company/project context for follow-up issue creation
|
||||
- requested bump: `patch`, `minor`, or `major`
|
||||
- whether this run is a dry run or live release
|
||||
- whether the release is being run locally or from GitHub Actions
|
||||
- release issue / company context for website and announcement follow-up
|
||||
|
||||
---
|
||||
## Step 0 — Release Model
|
||||
|
||||
## Step 0 — Idempotency Guards
|
||||
Paperclip now uses this release model:
|
||||
|
||||
Each step in this skill is designed to be safely re-runnable. Before executing
|
||||
any step, check whether it has already been completed:
|
||||
1. Draft the **stable** changelog as `releases/vX.Y.Z.md`
|
||||
2. Publish one or more **prerelease canaries** such as `X.Y.Z-canary.0`
|
||||
3. Smoke test the canary via Docker
|
||||
4. Publish the stable version `X.Y.Z`
|
||||
5. Push the release commit and tag
|
||||
6. Create the GitHub Release
|
||||
7. Complete website and announcement surfaces
|
||||
|
||||
| Step | How to Check | If Already Done |
|
||||
|---|---|---|
|
||||
| Changelog | `releases/v{version}.md` exists | Read it, ask reviewer to confirm or update. Do NOT regenerate without asking. |
|
||||
| Canary publish | `npm view paperclipai@{version}` succeeds | Skip canary publish. Proceed to smoke test. |
|
||||
| Smoke test | Manual or scripted verification | If canary already verified, proceed to promote. |
|
||||
| Promote | `git tag v{version}` exists | Skip promotion entirely. A tag means the version is already promoted to latest. |
|
||||
| Website task | Search Paperclip issues for "Publish release notes for v{version}" | Skip creation. Link the existing task. |
|
||||
| CMO task | Search Paperclip issues for "release announcement tweet for v{version}" | Skip creation. Link the existing task. |
|
||||
Critical consequence:
|
||||
|
||||
**The golden rule:** If a git tag `v{version}` already exists, the release is
|
||||
fully promoted. Only post-publish tasks (website, CMO, wrap-up) should proceed.
|
||||
If the version exists on npm but there's no git tag, the canary was published but
|
||||
not yet promoted — resume from smoke test.
|
||||
- Canaries do **not** use promote-by-dist-tag anymore.
|
||||
- The changelog remains stable-only. Do not create `releases/vX.Y.Z-canary.N.md`.
|
||||
|
||||
**Iterating on changelogs:** You can re-run this skill with an existing changelog
|
||||
to refine it _before_ the npm publish step. The `release-changelog` skill has
|
||||
its own idempotency check and will ask the reviewer what to do with an existing
|
||||
file. This is the expected workflow for iterating on release notes.
|
||||
## Step 1 — Decide the Stable Version
|
||||
|
||||
---
|
||||
|
||||
## Step 1 - Pre-flight and Version Decision
|
||||
|
||||
Run pre-flight in the App repo root:
|
||||
Use the last stable tag as the base:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-version:refname | head -1)
|
||||
git diff --quiet && git diff --cached --quiet
|
||||
git log "${LAST_TAG}..HEAD" --oneline --no-merges | head -50
|
||||
```
|
||||
|
||||
Then detect minimum required bump:
|
||||
|
||||
```bash
|
||||
# migrations
|
||||
LAST_TAG=$(git tag --list 'v*' --sort=-version:refname | head -1)
|
||||
git log "${LAST_TAG}..HEAD" --oneline --no-merges
|
||||
git diff --name-only "${LAST_TAG}..HEAD" -- packages/db/src/migrations/
|
||||
|
||||
# schema deltas
|
||||
git diff "${LAST_TAG}..HEAD" -- packages/db/src/schema/
|
||||
|
||||
# breaking commit conventions
|
||||
git log "${LAST_TAG}..HEAD" --format="%s" | rg -n 'BREAKING CHANGE|BREAKING:|^[a-z]+!:' || true
|
||||
```
|
||||
|
||||
Bump policy:
|
||||
- Destructive migration/API removal/major changeset/breaking commit -> `major`
|
||||
- Additive migrations or clear new features -> at least `minor`
|
||||
- Fixes-only -> `patch`
|
||||
|
||||
If requested bump is lower than required minimum, escalate bump and explain why.
|
||||
- destructive migrations, removed APIs, breaking config changes -> `major`
|
||||
- additive migrations or clearly user-visible features -> at least `minor`
|
||||
- fixes only -> `patch`
|
||||
|
||||
---
|
||||
If the requested bump is too low, escalate it and explain why.
|
||||
|
||||
## Step 2 - Generate Changelog Draft
|
||||
## Step 2 — Draft the Stable Changelog
|
||||
|
||||
First, check if `releases/v{version}.md` already exists. If it does, the
|
||||
`release-changelog` skill will detect this and ask the reviewer whether to keep,
|
||||
regenerate, or update it. **Do not silently overwrite an existing changelog.**
|
||||
Invoke `release-changelog` and generate:
|
||||
|
||||
Invoke the `release-changelog` skill and produce:
|
||||
- `releases/v{version}.md`
|
||||
- Sections ordered as: Breaking Changes (if any), Highlights, Improvements, Fixes, Upgrade Guide (if any)
|
||||
- `releases/vX.Y.Z.md`
|
||||
|
||||
Required behavior:
|
||||
- Present the draft for human review.
|
||||
- Flag ambiguous categorization items.
|
||||
- Flag bump mismatches before publish.
|
||||
- Do not publish until reviewer confirms.
|
||||
Rules:
|
||||
|
||||
---
|
||||
- review the draft with a human before publish
|
||||
- preserve manual edits if the file already exists
|
||||
- keep the heading and filename stable-only, for example `v1.2.3`
|
||||
- do not create a separate canary changelog file
|
||||
|
||||
## Step 3 — Publish Canary
|
||||
## Step 3 — Verify the Release SHA
|
||||
|
||||
The canary is the gatekeeper: every release goes to npm as a canary first. The
|
||||
`latest` tag is never touched until the canary passes smoke testing.
|
||||
|
||||
**Idempotency check:** Before publishing, check if this version already exists
|
||||
on npm:
|
||||
Run the standard gate:
|
||||
|
||||
```bash
|
||||
# Check if canary is already published
|
||||
npm view paperclipai@{version} version 2>/dev/null && echo "ALREADY_PUBLISHED" || echo "NOT_PUBLISHED"
|
||||
|
||||
# Also check git tag
|
||||
git tag -l "v{version}"
|
||||
pnpm -r typecheck
|
||||
pnpm test:run
|
||||
pnpm build
|
||||
```
|
||||
|
||||
- If a git tag exists → the release is already fully promoted. Skip to Step 6.
|
||||
- If the version exists on npm but no git tag → canary was published but not yet
|
||||
promoted. Skip to Step 4 (smoke test).
|
||||
- If neither exists → proceed with canary publish.
|
||||
If the release will be run through GitHub Actions, the workflow can rerun this gate. Still report whether the local tree currently passes.
|
||||
|
||||
### Publishing the canary
|
||||
## Step 4 — Publish a Canary
|
||||
|
||||
Use `release.sh` with the `--canary` flag (see script changes below):
|
||||
Run:
|
||||
|
||||
```bash
|
||||
# Dry run first
|
||||
./scripts/release.sh {patch|minor|major} --canary --dry-run
|
||||
|
||||
# Publish canary (after dry-run review)
|
||||
./scripts/release.sh {patch|minor|major} --canary
|
||||
```
|
||||
|
||||
This publishes all packages to npm with the `canary` dist-tag. The `latest` tag
|
||||
is **not** updated. Users running `npx paperclipai onboard` still get the
|
||||
previous stable version.
|
||||
What this means:
|
||||
|
||||
After publish, verify the canary is accessible:
|
||||
- npm receives `X.Y.Z-canary.N` under dist-tag `canary`
|
||||
- `latest` remains unchanged
|
||||
- no git tag is created
|
||||
- the script cleans the working tree afterward
|
||||
|
||||
After publish, verify:
|
||||
|
||||
```bash
|
||||
npm view paperclipai@canary version
|
||||
# Should show the new version
|
||||
```
|
||||
|
||||
**How `--canary` works in release.sh:**
|
||||
- Steps 1-5 are the same (preflight, changeset, version, build, CLI bundle)
|
||||
- Step 6 uses `npx changeset publish --tag canary` instead of `npx changeset publish`
|
||||
- Step 7 does NOT commit or tag — the commit and tag happen later in the promote
|
||||
step, only after smoke testing passes
|
||||
The user install path is:
|
||||
|
||||
**Script changes required:** Add `--canary` support to `scripts/release.sh`:
|
||||
- Parse `--canary` flag alongside `--dry-run`
|
||||
- When `--canary`: pass `--tag canary` to `changeset publish`
|
||||
- When `--canary`: skip the git commit and tag step (Step 7)
|
||||
- When NOT `--canary`: behavior is unchanged (backwards compatible)
|
||||
```bash
|
||||
npx paperclipai@canary onboard
|
||||
```
|
||||
|
||||
---
|
||||
## Step 5 — Smoke Test the Canary
|
||||
|
||||
## Step 4 — Smoke Test the Canary
|
||||
|
||||
Run the canary in a clean Docker environment to verify `npx paperclipai onboard`
|
||||
works end-to-end.
|
||||
|
||||
### Automated smoke test
|
||||
|
||||
Use the existing Docker smoke test infrastructure with the canary version:
|
||||
Run:
|
||||
|
||||
```bash
|
||||
PAPERCLIPAI_VERSION=canary ./scripts/docker-onboard-smoke.sh
|
||||
```
|
||||
|
||||
This builds a clean Ubuntu container, installs `paperclipai@canary` via npx, and
|
||||
runs the onboarding flow. The UI is accessible at `http://localhost:3131`.
|
||||
Confirm:
|
||||
|
||||
### What to verify
|
||||
1. install succeeds
|
||||
2. onboarding completes
|
||||
3. server boots
|
||||
4. UI loads
|
||||
5. basic company/dashboard flow works
|
||||
|
||||
At minimum, confirm:
|
||||
If smoke testing fails:
|
||||
|
||||
1. **Container starts** — no npm install errors, no missing dependencies
|
||||
2. **Onboarding completes** — the wizard runs through without crashes
|
||||
3. **Server boots** — UI is accessible at the expected port
|
||||
4. **Basic operations** — can create a company, view the dashboard
|
||||
- stop the stable release
|
||||
- fix the issue
|
||||
- publish another canary
|
||||
- repeat the smoke test
|
||||
|
||||
For a more thorough check (stretch goal — can be automated later):
|
||||
Each retry should create a higher canary ordinal, while the stable target version can stay the same.
|
||||
|
||||
5. **Browser automation** — script Playwright/Puppeteer to walk through onboard
|
||||
in the Docker container's browser and verify key pages render
|
||||
## Step 6 — Publish Stable
|
||||
|
||||
### If smoke test fails
|
||||
|
||||
- Do NOT promote the canary.
|
||||
- Fix the issue, publish a new canary (re-run Step 3 — idempotency guards allow
|
||||
this since there's no git tag yet).
|
||||
- Re-run the smoke test.
|
||||
|
||||
### If smoke test passes
|
||||
|
||||
Proceed to Step 5 (promote).
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Promote Canary to Latest
|
||||
|
||||
Once the canary passes smoke testing, promote it to `latest` so that
|
||||
`npx paperclipai onboard` picks up the new version.
|
||||
|
||||
### Promote on npm
|
||||
Once the SHA is vetted, run:
|
||||
|
||||
```bash
|
||||
# For each published package, move the dist-tag from canary to latest
|
||||
npm dist-tag add paperclipai@{version} latest
|
||||
npm dist-tag add @paperclipai/server@{version} latest
|
||||
npm dist-tag add @paperclipai/cli@{version} latest
|
||||
npm dist-tag add @paperclipai/shared@{version} latest
|
||||
npm dist-tag add @paperclipai/db@{version} latest
|
||||
npm dist-tag add @paperclipai/adapter-utils@{version} latest
|
||||
npm dist-tag add @paperclipai/adapter-claude-local@{version} latest
|
||||
npm dist-tag add @paperclipai/adapter-codex-local@{version} latest
|
||||
npm dist-tag add @paperclipai/adapter-openclaw-gateway@{version} latest
|
||||
./scripts/release.sh {patch|minor|major} --dry-run
|
||||
./scripts/release.sh {patch|minor|major}
|
||||
```
|
||||
|
||||
**Script option:** Add `./scripts/release.sh --promote {version}` to automate
|
||||
the dist-tag promotion for all packages.
|
||||
Stable publish does this:
|
||||
|
||||
### Commit and tag
|
||||
- publishes `X.Y.Z` to npm under `latest`
|
||||
- creates the local release commit
|
||||
- creates the local git tag `vX.Y.Z`
|
||||
|
||||
After promotion, finalize in git (this is what `release.sh` Step 7 normally
|
||||
does, but was deferred during canary publish):
|
||||
Stable publish does **not** push the release for you.
|
||||
|
||||
## Step 7 — Push and Create GitHub Release
|
||||
|
||||
After stable publish succeeds:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "chore: release v{version}"
|
||||
git tag "v{version}"
|
||||
git push origin HEAD:master --follow-tags
|
||||
./scripts/create-github-release.sh X.Y.Z
|
||||
```
|
||||
|
||||
### Verify promotion
|
||||
Use the stable changelog file as the GitHub Release notes source.
|
||||
|
||||
```bash
|
||||
npm view paperclipai@latest version
|
||||
# Should now show the new version
|
||||
## Step 8 — Finish the Other Surfaces
|
||||
|
||||
# Final sanity check
|
||||
npx --yes paperclipai@latest --version
|
||||
```
|
||||
Create or verify follow-up work for:
|
||||
|
||||
---
|
||||
- website changelog publishing
|
||||
- launch post / social announcement
|
||||
- any release summary in Paperclip issue context
|
||||
|
||||
## Step 6 - Create Cross-Project Follow-up Tasks
|
||||
|
||||
**Idempotency check:** Before creating tasks, search for existing ones:
|
||||
|
||||
```
|
||||
GET /api/companies/{companyId}/issues?q=release+notes+v{version}
|
||||
GET /api/companies/{companyId}/issues?q=announcement+tweet+v{version}
|
||||
```
|
||||
|
||||
If matching tasks already exist (check title contains the version), skip
|
||||
creation and link the existing tasks instead. Do not create duplicates.
|
||||
|
||||
Create at least two tasks in Paperclip (only if they don't already exist):
|
||||
|
||||
1. Website task: publish changelog for `v{version}`
|
||||
2. CMO task: draft announcement tweet for `v{version}`
|
||||
|
||||
When creating tasks:
|
||||
- Set `parentId` to the release issue id.
|
||||
- Carry over `goalId` from the parent issue when present.
|
||||
- Include `billingCode` for cross-team work when required by company policy.
|
||||
- Mark website task `high` priority if release has breaking changes.
|
||||
|
||||
Suggested payloads:
|
||||
|
||||
```json
|
||||
POST /api/companies/{companyId}/issues
|
||||
{
|
||||
"projectId": "{websiteProjectId}",
|
||||
"parentId": "{releaseIssueId}",
|
||||
"goalId": "{goalId-or-null}",
|
||||
"billingCode": "{billingCode-or-null}",
|
||||
"title": "Publish release notes for v{version}",
|
||||
"priority": "medium",
|
||||
"status": "todo",
|
||||
"description": "Publish /changelog entry for v{version}. Include full markdown from releases/v{version}.md and prominent upgrade guide if breaking changes exist."
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
POST /api/companies/{companyId}/issues
|
||||
{
|
||||
"projectId": "{workspaceProjectId}",
|
||||
"parentId": "{releaseIssueId}",
|
||||
"goalId": "{goalId-or-null}",
|
||||
"billingCode": "{billingCode-or-null}",
|
||||
"title": "Draft release announcement tweet for v{version}",
|
||||
"priority": "medium",
|
||||
"status": "todo",
|
||||
"description": "Draft launch tweet with top 1-2 highlights, version number, and changelog URL. If breaking changes exist, include an explicit upgrade-guide callout."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7 - Wrap Up the Release Issue
|
||||
|
||||
Post a concise markdown update linking:
|
||||
- Release issue
|
||||
- Changelog file (`releases/v{version}.md`)
|
||||
- npm package URL (both `@canary` and `@latest` after promotion)
|
||||
- Canary smoke test result (pass/fail, what was tested)
|
||||
- Website task
|
||||
- CMO task
|
||||
- Final changelog URL (once website publishes)
|
||||
- Tweet URL (once published)
|
||||
|
||||
Completion rules:
|
||||
- Keep issue `in_progress` until canary is promoted AND website + social tasks
|
||||
are done.
|
||||
- Mark `done` only when all required artifacts are published and linked.
|
||||
- If waiting on another team, keep open with clear owner and next action.
|
||||
|
||||
---
|
||||
|
||||
## Release Flow Summary
|
||||
|
||||
The full release lifecycle is now:
|
||||
|
||||
```
|
||||
1. Generate changelog → releases/v{version}.md (review + iterate)
|
||||
2. Publish canary → npm @canary dist-tag (latest untouched)
|
||||
3. Smoke test canary → Docker clean install verification
|
||||
4. Promote to latest → npm @latest dist-tag + git tag + commit
|
||||
5. Create follow-up tasks → website changelog + CMO tweet
|
||||
6. Wrap up → link everything, close issue
|
||||
```
|
||||
|
||||
At any point you can re-enter the flow — idempotency guards detect which steps
|
||||
are already done and skip them. The changelog can be iterated before or after
|
||||
canary publish. The canary can be re-published if the smoke test reveals issues
|
||||
(just fix + re-run Step 3). Only after smoke testing passes does `latest` get
|
||||
updated.
|
||||
|
||||
---
|
||||
|
||||
## Paperclip API Notes (When Running in Agent Context)
|
||||
|
||||
Use:
|
||||
- `GET /api/companies/{companyId}/projects` to resolve website/workspace project IDs.
|
||||
- `POST /api/companies/{companyId}/issues` to create follow-up tasks.
|
||||
- `PATCH /api/issues/{issueId}` with comments for release progress.
|
||||
|
||||
For issue-modifying calls, include:
|
||||
- `Authorization: Bearer $PAPERCLIP_API_KEY`
|
||||
- `X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID`
|
||||
|
||||
---
|
||||
These should reference the stable release, not the canary.
|
||||
|
||||
## Failure Handling
|
||||
|
||||
If blocked, update the release issue explicitly with:
|
||||
- what failed
|
||||
- exact blocker
|
||||
- who must act next
|
||||
- whether any release artifacts were partially published
|
||||
If the canary is bad:
|
||||
|
||||
Never silently fail mid-release.
|
||||
- publish another canary, do not ship stable
|
||||
|
||||
If stable npm publish succeeds but push or GitHub release creation fails:
|
||||
|
||||
- fix the git/GitHub issue immediately from the same checkout
|
||||
- do not republish the same version
|
||||
|
||||
If `latest` is bad after stable publish:
|
||||
|
||||
```bash
|
||||
./scripts/rollback-latest.sh <last-good-version>
|
||||
```
|
||||
|
||||
Then fix forward with a new patch release.
|
||||
|
||||
## Output
|
||||
|
||||
When the skill completes, provide:
|
||||
|
||||
- stable version and, if relevant, the final canary version tested
|
||||
- verification status
|
||||
- npm status
|
||||
- git tag / GitHub Release status
|
||||
- website / announcement follow-up status
|
||||
- rollback recommendation if anything is still partially complete
|
||||
|
||||
Reference in New Issue
Block a user