Generate the stable Paperclip release changelog at releases/vYYYY.MDD.P.md by reading commits, changesets, and merged PR context since the last stable tag.
62
73%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Fix and improve this skill with Tessl
tessl review fix ./.agents/skills/release-changelog/SKILL.mdGenerate the user-facing changelog for the stable Paperclip release.
Paperclip uses calendar versioning (calver):
YYYY.MDD.P (e.g. 2026.318.0)YYYY.MDD.P-canary.N (e.g. 2026.318.1-canary.0)vYYYY.MDD.P for stable, canary/vYYYY.MDD.P-canary.N for canaryThere are no major/minor/patch bumps. The stable version is derived from the intended release date (UTC) plus the next same-day stable patch slot.
Output:
releases/vYYYY.MDD.P.mdrelease Case, upserted by (caseType, key) when Cases are enabled, with a
body document revision containing the changelog bodyImportant rules:
2026.318.1-canary.0, the changelog file stays releases/v2026.318.1.mdStables promote a soaked beta, so the changelog describes the beta's
source commit, not the tip of master:
The release source is the commit the newest beta/v<beta-version>
tag points at ({beta-src} below). Resolve it with:
git fetch origin --tags
npm view paperclipai dist-tags # the beta dist-tag names the version
git rev-parse 'beta/v{beta-version}^{commit}'Commits on master after {beta-src} ship in the next release.
Never include them; they are input for a "what's next" section, not the
changelog.
During the soak, the file lives at releases/beta/v{beta-version}.md
on the branch release-notes/v{beta-version} (PR to master). The
release workflow pushes that branch with a generated skeleton when the
beta publishes; work on it and rewrite the skeleton in place. If the
branch does not exist (a beta cut before the automation), create it
from origin/master and seed the skeleton:
./scripts/draft-stable-notes.sh {beta-version}The PR must merge to master before the stable is dispatched: the
stable preflight reads the file from master and fails without it.
Never create releases/vYYYY.MDD.P.md yourself on this path — after
the stable ships, the workflow opens a canonicalization PR that renames
the beta-keyed file to it.
Fix path exception (patch releases from a candidate/release-*
branch): there the notes do go directly on the candidate branch as
releases/vYYYY.MDD.P.md, committed alongside the cherry-picked fixes.
Before generating anything, check whether the changelog already exists:
ls releases/beta/v{beta-version}.md 2>/dev/null # soak-window home
ls releases/vYYYY.MDD.P.md 2>/dev/null # canonicalized / fix path
git ls-remote origin 'refs/heads/release-notes/v{beta-version}'A release-notes/v{beta-version} branch holding only the generated
skeleton is the normal starting state, not a conflict — rewrite it in
place.
If it exists:
Find the last stable tag and the beta source commit:
git tag --list 'v*' --sort=-version:refname | head -1
beta_src="$(git rev-parse 'beta/v{beta-version}^{commit}')"
git log v{last}..${beta_src} --oneline --no-mergesThe changelog range is always v{last}..{beta-src} — never ..HEAD and
never ..origin/master.
The stable version comes from one of:
./scripts/release.sh stable --date YYYY-MM-DD --print-versiondoc/RELEASING.mdDo not derive the changelog version from a canary tag or prerelease suffix. Do not derive major/minor/patch bumps from API intent — calver uses the date and same-day stable slot.
Collect release data from:
.changeset/*.md filesgh when availableUseful commands:
git log v{last}..{beta-src} --oneline --no-merges
git log v{last}..{beta-src} --format="%H %s" --no-merges
ls .changeset/*.md | grep -v README.md
gh pr list --state merged --search "merged:>={last-tag-date}" --json number,title,body,labelsLook for:
BREAKING: or BREAKING CHANGE: commit signalsKey commands:
git diff --name-only v{last}..{beta-src} -- packages/db/src/migrations/
git diff v{last}..{beta-src} -- packages/db/src/schema/
git diff v{last}..{beta-src} -- server/src/routes/ server/src/api/
git log v{last}..{beta-src} --format="%s" | rg -n 'BREAKING CHANGE|BREAKING:|^[a-z]+!:' || trueIf breaking changes are detected, flag them prominently — they must appear in the Breaking Changes section with an upgrade path.
Use these stable changelog sections:
Breaking ChangesHighlightsImprovementsFixesUpgrade Guide when neededExclude purely internal refactors, CI changes, and docs-only work unless they materially affect users.
Guidelines:
releases/v<last-stable>.md) before writing. When they already
introduced a feature, this release's entry covers only what changed —
a default flip, a hardening, a completion — phrased against the prior
release ("last release introduced X; this release makes it the
default"), never re-describing the feature as if it debuted. A theme
that headlined the previous release does not headline again for
follow-through work; demote it to Improvements.When a bullet item clearly maps to a merged pull request, add inline attribution at the end of the entry in this format:
- **Feature name** — Description. ([#123](https://github.com/paperclipai/paperclip/pull/123), @contributor1, @contributor2)Rules:
Merge pull request #N from user/branch) to map PRs.([#10](url), [#12](url), @user1, @user2).The file path is the beta-keyed one from the Channel Process section
(releases/beta/v{beta-version}.md), but the content is titled with
the planned stable version. Resolve it with
./scripts/release.sh stable --date {planned-promotion-date} --print-version
(promotion is normally the beta publish date plus the 3-day soak). If the
promotion date slips, the version re-resolves at dispatch — the beta-keyed
filename makes that harmless; refresh the title when it happens.
The opening line of the changelog must be an H1 of the format # Paperclip {version}
(no braces), e.g. # Paperclip v2026.618.0. Always include the Paperclip prefix and
the v on the version.
Template:
# Paperclip vYYYY.MDD.P
> Released: YYYY-MM-DD
## Breaking Changes
## Highlights
## Improvements
## Fixes
## Upgrade Guide
## Contributors
Thank you to everyone who contributed to this release!
@username1, @username2, @username3Omit empty sections except Highlights, Improvements, and Fixes, which should usually exist.
The Contributors section should always be included. List every person who authored
commits in the release range, @-mentioning them by their GitHub username (not their
real name or email). To find GitHub usernames:
git log v{last}..{beta-src} --oneline --merges — the branch prefix (e.g. from username/branch) gives the GitHub username.user@users.noreply.github.com, the username is the part before @.gh api users/{guess} or the PR page.Never expose contributor email addresses. Use @username only.
Exclude bot accounts (e.g. lockfile-bot, dependabot) from the list.
Exclude specific folks from the list — the Contributors section credits
community contributors only. The canonical exclusion list (keep it here;
the Discord skill defers to it):
cryppadotta, forgottendev, devinfoley, sockmonster, scotttong,
nguyenm7, nickyleach, tonio-alucema
List contributors in alphabetical order by GitHub username (case-insensitive).
If there are no contributors left after exclusions, then just skip this section and don't mention it.
After writing releases/vYYYY.MDD.P.md, emit or refresh the top-level release
case when the run has Paperclip API context. Use skills/paperclip/references/cases.md
as the API contract. If the API returns 403 Cases are disabled, report that
Cases must be enabled and continue with the changelog file only.
Request:
POST /api/companies/:companyId/cases
{
"caseType": "release",
"key": "paperclip-release:vYYYY.MDD.P",
"title": "Paperclip vYYYY.MDD.P release",
"summary": "Stable Paperclip release notes for vYYYY.MDD.P.",
"status": "in_progress",
"fields": {
"schema_version": 1,
"version": "vYYYY.MDD.P",
"release_date": "YYYY-MM-DD",
"release_patch": 0,
"stable": true,
"channels": ["changelog", "blog_post", "tweet_storm"],
"artifacts": {
"changelog_path": "releases/vYYYY.MDD.P.md",
"github_release_url": null
},
"verification": {
"typecheck": "unknown",
"tests": "unknown",
"build": "unknown",
"smoke": "unknown"
},
"notes": null
}
}This fields schema deliberately exercises every generic field value type: string, number, boolean, array, object, and null. Keep the keys stable across runs and send the full object on every upsert because fields are replaced, not deep-merged.
Then write the changelog into the case body document:
PUT /api/cases/:releaseCaseId/documents/body
{
"title": "Paperclip vYYYY.MDD.P changelog",
"format": "markdown",
"body": "<contents of releases/vYYYY.MDD.P.md>",
"changeSummary": "Initial stable changelog"
}If updating an existing body document, fetch the case first and pass the latest
baseRevisionId. On 409 stale_base_revision, refetch, merge intentionally,
and retry once.
Before handing it off:
# Paperclip {version} (e.g. # Paperclip v2026.618.0) with the stable version only-canary language in the title or filenamerelease case exists or explain why Cases were unavailableThis skill never publishes anything. It only prepares the stable changelog artifact.
6d358e6
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.