* ci(release): gate PRs on an authored release note Second slice of the release-notes pipeline (#1256 landed the format and generator): make the .changes/ habit survive contact with reality. - "Release note gate" job in ci.yml, PR-only: validates every .changes/*.md, then requires an added note (or the no-release-note label) when the PR touches runtime code under apps/ or libs/. Tests, e2e projects, the website, mock servers, shared testing helpers, snapshots and docs are auto-exempt. - Policy lives in tools/release/check-release-note-gate.mjs as a pure function fed PR files+labels as JSON — unit-tested (10 cases) instead of encoded in workflow bash. The failure message lists the triggering files and names the exact fix. - Labels are fetched live rather than from the stale event payload, so applying the label and re-running the check works without a new push. - The job is dependency-free Node: no pnpm install, runs in seconds. - release-notes and release-cut skills added under .claude/skills/ and mirrored to .codex/skills/; CLAUDE.md/AGENTS.md sections updated to point at the gate and the skills. The no-release-note label itself was created in the repository. Tests: 47 passing in release-tools (10 new gate cases); gate-step shell verified with shellcheck at the CI severity; ci.yml YAML-parse checked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(agents): make the release skills discoverable by Claude Code too `.codex/skills/**` was un-ignored so Codex picks up repository skills in any clone, but `.claude` was ignored wholesale — and Claude Code only discovers skills under `.claude/skills/`. The release-notes and release-cut skills therefore existed only on whichever machine authored them. Mirror both skills into `.claude/skills/` and opt them in by name rather than un-ignoring the directory: contributors keep personal skills there (i18n-fill, website, …) which must stay local and out of `git status`. CLAUDE.md/AGENTS.md updated so the "skills live under .codex/skills/" claim does not go stale, including the requirement to keep mirrored copies in sync. The CI gate and the CLAUDE.md/AGENTS.md section remain the load-bearing enforcement; skills only carry the detail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ci): only a note this PR authored satisfies the release-note gate Review follow-ups on #1257 (Codex P2 ×2, Greptile P1). - Drop `renamed` from the accepted statuses. The PR files API compares base…head, so a note created and then renamed inside the same PR still reports as `added`; a `renamed` entry means the file already existed on the base branch. Accepting it let a runtime-code PR pass by moving another PR's unconsumed note, which documents nothing and gives the generator no adding commit to resolve a PR link from. - Require a direct child of `.changes/`. `loadNotes()` reads only the immediate directory, so `.changes/sub/note.md` satisfied the old prefix check while never being validated or rendered into any release surface. Tests: renamed and nested notes now assert a failing gate (12 gate cases). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2.8 KiB
name, description
| name | description |
|---|---|
| release-notes | Write the .changes/ release note that every PR with a user-visible change must include. Use when creating or finishing a PR that changes behavior in apps/ or libs/, when the "Release note gate" CI check fails, or when deciding whether the no-release-note label applies. |
Release Notes (.changes/)
Every PR with a user-visible change adds one note file under .changes/.
At release time the notes become the GitHub release body, the CHANGELOG.md
section, and the website blog scaffold. CI enforces this: the Release note
gate check fails any PR that touches runtime code under apps/ or libs/
without an added .changes/*.md file or the no-release-note label.
File format
Name: .changes/<area>-<short-slug>.md — area matches the
conventional-commit scope of the PR.
---
type: feature
area: playback
issues: [1187]
screenshot: up-next-rail
---
Series now show an "Up Next" rail beside the player on wide windows: the rest
of the current season, watch progress, and click-to-play inline.
| Field | Required | Value |
|---|---|---|
type |
yes | breaking / feature / fix / perf / internal |
area |
yes | lowercase slug = conventional-commit scope |
issues |
no | [1187] or bare 1187 — issues this PR closes |
screenshot |
no | slug from the release screenshot manifest |
- No version field. The release version is chosen at release time.
- Never write a PR number. The generator resolves it from git.
- Unknown keys fail validation — this is what catches typos like
scopr:.
Writing the body
One to three sentences, present tense, max 400 characters, written for a user, not a reviewer:
- ❌ "Refactor
WebVideoControlsAdapterto hoist volume state" - ✅ "The player now remembers volume between episodes"
- ❌ "Fix off-by-one in
resolveEnrichmentSeasonNumber" - ✅ "Series with a season marker in the title no longer show the wrong season"
type: internal is for changes worth recording but invisible to users
(dependency bumps with behavior risk, packaging moves). They are excluded
from the release body and blog, and collapsed in CHANGELOG.md.
When to skip (no-release-note label)
Test-only changes, docs, CI/workflow plumbing, pure refactors with no
behavior change. The gate auto-exempts *.spec.ts, *.e2e.ts,
__snapshots__/, apps/website/, apps/*-e2e/, apps/*-mock-server/,
libs/shared/testing/ and *.md — if only those changed, no label needed.
Verify before finishing
pnpm run release:notes:validate
Full format reference: .changes/README.md. Gate policy:
tools/release/check-release-note-gate.mjs.