diff --git a/.claude/skills/release-manager/SKILL.md b/.claude/skills/release-manager/SKILL.md index ef7c7356f5..fd036a97b8 100644 --- a/.claude/skills/release-manager/SKILL.md +++ b/.claude/skills/release-manager/SKILL.md @@ -106,6 +106,8 @@ After publication, `release.yml` runs `.github/scripts/update-homebrew-formula.m Prereleases and preview builds do not advance the stable formula. Test a published preview with `HOMEBREW_VP_PR_VERSION=` as described in `docs/guide/homebrew.md`. +Before merging, run `gh workflow run test-homebrew.yml --ref release/vX.Y.Z` to install the release head through a disposable tap on macOS ARM64 and Linux x64/ARM64. A manual run does not restart the PR's CI; adding the `test: install-e2e` label does. Homebrew refuses to build inside an inherited sandbox that can write to its prefix (`Inherited sandbox permits writes to .../bin/brew`), so a sandboxed agent cannot run `brew install` itself: use this workflow, and ask the release manager to run the preview command locally. + ## 3. Write the release PR description The release tag does not exist yet, so read release files from the PR head branch and generate notes against `main`: @@ -164,9 +166,10 @@ Merging this PR will trigger the release workflow. - **Docs means the published docs site, not contributor files.** A `docs` commit that changes an RFC, `AGENTS.md`, the repo map, or a skill under `.claude/` belongs in Chore: a vite-plus user never reads those. Docs should hold only entries a reader could go and look at on the site or in the README. - **Describe behaviour, not resolution logic.** An entry states what a user now observes. Rules the implementation follows internally (target-selection signals, config precedence, detection order) belong in the RFC or the PR, not the changelog. If an entry needs a nested list to explain how a decision is reached, cut it down to the outcome. - **A breaking change needs its migration path.** State what existing installs or projects do by default, then how to move to the new behaviour deliberately, then what that costs. Link the guide rather than restating it, and say plainly when doing nothing is a valid choice. -- There is no separate Highlights section. Lead Features with up to 5 new capabilities a vite-plus user will notice, plus security fixes, then the remaining features; the upstream upgrade entry stays last. Bug fixes go in Fixes & Enhancements even when they are prominent. Skip developer-tooling-only conveniences. -- Entry format: `Description ([#N](https://github.com/voidzero-dev/vite-plus/pull/N)), by @author`. Describe the user-visible behavior, not the implementation. Group supporting implementation PRs under the user-visible change they enable instead of giving them separate entries, and fold a new feature's sub-mode (such as its `--watch` flag) into the feature's entry. In Refactor and Chore, keep one contributor's related refactors together in their own entry, and give a notable maintenance change (such as a security-release runtime bump) its own entry. Never include defensive edge cases or internal mechanics unless users need them to use or understand the feature; use concrete behavior instead of internal UI taxonomy that needs extra context. For a fix to an intermittent failure, say that it was rare and when it happened, so the entry does not read as if it always failed. When a PR carries over a superseded PR from another author (its body says so, or `gh pr view N --json commits` lists their commits), credit both authors. Credit only substantive commits: a `Merge branch 'main'` commit on a bot or contributor PR does not make its author a co-author. +- There is no separate Highlights section. Lead Features with up to 5 new capabilities a vite-plus user will notice, plus security fixes from upstream dependency upgrades, then the remaining features; the upstream upgrade entry stays last. Bug fixes go in Fixes & Enhancements even when they are prominent. A security fix in vite-plus's own code is a fix: put it first in Fixes & Enhancements with a `Security:` label, even when the same PR also adds behavior. Skip developer-tooling-only conveniences. +- Entry format: `Description ([#N](https://github.com/voidzero-dev/vite-plus/pull/N)), by @author`. Describe the user-visible behavior, not the implementation. Group supporting implementation PRs under the user-visible change they enable instead of giving them separate entries, and fold a new feature's sub-mode (such as its `--watch` flag) into the feature's entry. In Refactor and Chore, keep one contributor's related refactors together in their own entry, and give a notable maintenance change (such as a security-release runtime bump) its own entry. Never include defensive edge cases or internal mechanics unless users need them to use or understand the feature; use concrete behavior instead of internal UI taxonomy that needs extra context. For a fix to an intermittent failure, say that it was rare and when it happened, so the entry does not read as if it always failed. When a PR carries over a superseded PR from another author (its body says so, or `gh pr view N --json commits` lists their commits), credit both authors. Credit only substantive commits: a `Merge branch 'main'` commit on a bot or contributor PR does not make its author a co-author. Check `gh pr view N --json commits` for every PR, not only bot or carried-over PRs, and credit other authors whose commits change code (fixes, behavior, or dependency changes); test-, snapshot-, or doc-only touch-ups do not earn credit. - **Upstream dependency upgrade PRs** (`feat(deps): upgrade upstream dependencies`): consolidate all of them into one Features entry with net oldest-to-latest version changes (e.g. `vite 8.0.16 -> 8.1.2`), listing every PR number. Check the upgraded range for security fixes (search the upstream changelog for CVE/GHSA); if present, add a dedicated security entry quoting severity and linking the advisory. Cite only published advisories: a draft advisory that maintainer access lets you see is still embargoed, so keep it and its fix's security framing out of the notes and the announcement. When oxfmt or oxlint changed version, add one clause telling users the new versions can flag code that passed before, so they should run `vp fmt` after upgrading if their CI runs `vp check`; in ecosystem testing this is reliably the largest single class of post-upgrade CI failures. Put this entry last in Features as a parent bullet carrying the PR links and credits, with one sub-item per upgraded tool and one per user-facing consequence (new formatting or lint behavior, migration changes, compatibility notes); a single long line is hard to read. +- **vite-plus security advisories**: list the repository advisories (`gh api repos/voidzero-dev/vite-plus/security-advisories --jq '.[] | [.ghsa_id, .state, .summary] | @tsv'`) and match each draft advisory to its fix PR in the release. While the advisory is a draft, describe that PR by its behavior only: no GHSA ID, `Security:` label, or attack wording in the release PR, the release notes, or the announcement. Keep the credit for the reporter when they co-authored the fix, because the commit trailer already shows it. When the advisory is published (often soon after the npm publication), add the severity and the advisory link to the entry in the release notes and a `:lock:` line to the announcement. Link the repository advisory page (`https://github.com/voidzero-dev/vite-plus/security/advisories/GHSA-...`) until the global advisory database lists it. - **vite-task bumps** (`bump vite-task to `): expand the full rev range (compare `Cargo.toml` at `v` vs the release branch), run `git log ..` in the local vite-task checkout, and read vite-task's `CHANGELOG.md` at the new commit for wording. Promote user-visible upstream changes into Features / Fixes with `[vite-task#N](https://github.com/voidzero-dev/vite-task/pull/N)` links, crediting the upstream PR author (`gh pr view N --repo voidzero-dev/vite-task --json author`). Cross-repo link format is `[vite-task#N]` / `[vite#N]`, not `[owner/repo#N]`. - New Contributors: copy from `generate-notes`, exclude bots (`renovate[bot]`, `voidzero-guard[bot]`, `github-actions[bot]`), list as inline `@mentions`. `generate-notes` only covers vite-plus, so also add first-time contributors from the expanded vite-task range: an author is new when `gh api -X GET search/issues -f q='repo:voidzero-dev/vite-task is:pr is:merged author: merged:<' -q .total_count` is 0. List them with the others, without a repository label, in the release notes and the Discord thanks line. @@ -187,11 +190,12 @@ vite and rolldown are built from pinned commits, so link the commit. The npm-ins ### Style rules - No em dashes or en dashes anywhere in the title or body. Use commas, colons, or parentheses. -- Keep the PR title plain (`release: vX.Y.Z`) and the GitHub release title plain (`vite-plus vX.Y.Z`); the theme goes in the opening sentence. Lead the opening theme with the most important user-visible behavior. Avoid vague benefit-only wording that the body must explain. +- Keep the PR title plain (`release: vX.Y.Z`) and the GitHub release title plain (`vite-plus vX.Y.Z`); the theme goes in the opening sentence. Lead the opening theme with the most important user-visible behavior. Avoid vague benefit-only wording that the body must explain. When several tools were upgraded, do not name a few of them in the opening sentence; the upgrade entry covers them. - Say what a feature does. Do not compare it to the tool it replaces, and do not say it works "without" a dependency it never needed. - When naming a package version in prose or a heading, use one inline literal, `package@version`. Keep separate version columns in tables. - Link unfamiliar technical abbreviations to the relevant documentation section; keep the short abbreviation as the link text. - The Upgrade section is a `vp upgrade` code block. +- Put commands that readers run, such as install commands, in a code block indented under the list item, not in inline code. Start the fence on the line directly after the item text: a blank line inside one item makes the whole list loose, and GitHub then adds paragraph spacing between every item of that section. Check with `gh api markdown` (mode `gfm`) that no `
  • ` opens a `

    `. - Apply via a temp file, never a heredoc (heredoc quoting can escape backticks inside the table and break rendering): ```bash @@ -241,6 +245,8 @@ If the release manager says yes: > **Mandatory: open any test PR against the `vite-plus-ecosystem-ci` fork, never the upstream repo.** `gh pr create` inside a fork defaults its base repo to the parent (upstream), so pass `--repo vite-plus-ecosystem-ci/` (or run `gh repo set-default vite-plus-ecosystem-ci/` first). See TESTING.md. +Keep fork PR descriptions, comments, and commit messages free of references to vite-plus PRs or issues (`voidzero-dev/vite-plus#N` or their URLs). GitHub records each one as a cross-reference on the vite-plus timeline, and editing the text later does not remove it. Write plain text such as `vite-plus X.Y.Z` instead. + `test-pkg-pr-new-migrate` needs a **local** checkout on the fork's **tracked branch** (often not the default branch, e.g. `vue-core` tracks `minor`). Clone under one directory so the whole test environment cleans up in one step: ```bash @@ -265,7 +271,7 @@ Run `scripts/sync-forks.sh "$repo"` again immediately before you open each fork **Validate in the project's own CI.** Beyond the local `vp migrate`, exercise the prerelease in the fork's real CI by opening a draft PR on the fork, following "Smoke-test via a fork PR" in TESTING.md: branch `update-vite-plus-prerelease-test-` synced from `source`, apply the upgrade, open a **draft** PR on the fork (never upstream) **assigned to the release manager**, then watch its checks for upgrade-related failures. Offer this alongside the local sweep rather than treating it as an afterthought; it is the only level that exercises each project's own build and tests. Some projects' CIs install with a non-standard tool that cannot resolve preview builds through the bridge `.npmrc` (e.g. cnpmcore's `utoo`), so check the install step before trusting fork-CI results. -The workflow triggers only on the `labeled` event, not on new pushes. To rebuild after the head moves (e.g. after a step 5 merge from `main`), remove and re-add the label (this cancels an in-flight build for the branch). A stale build whose diff to the new head is test-only is still valid for smoke testing; ask before re-triggering. +The workflow triggers only on the `labeled` event, not on new pushes. To rebuild after the head moves (e.g. after a step 5 merge from `main`), remove and re-add the label (this cancels an in-flight build for the branch). The label events also start the PR's CI again and cancel the run in progress; that cancelled run's `done` check shows as failed until the new run finishes. A stale build whose diff to the new head is test-only is still valid for smoke testing; ask before re-triggering. Record each project's base commit, starting version, resolved preview commit, fork PR head, and CI run. After a new preview build, identify which projects ran again and which retain earlier evidence. Do not report a partial repeat as a full-catalog run on the new preview. @@ -317,7 +323,11 @@ Across the full catalog most failures are not regressions, and reporting them as - **Local TLS inspection.** If every package fails locally with `ERR_PNPM_META_FETCH_FAIL ... fetch failed` while `curl` to the bridge succeeds, a TLS-inspecting client (such as a corporate zero-trust agent) may be re-signing `registry-bridge.viteplus.dev` with a root that the OS trusts but Node does not. `node -e "fetch('https://registry-bridge.viteplus.dev/vite-plus')"` then fails with `SELF_SIGNED_CERT_IN_CHAIN`. Export that root certificate and set `NODE_EXTRA_CA_CERTS` for the harness and every control run. A follow-up "latest release is ..." message comes from pnpm's stale metadata cache and is a symptom, not the cause. -- **Preview-build artifacts.** These are caused by the `0.0.0-commit.` version string itself and cannot happen for a real npm release, so they are never blockers. The recurring ones: pnpm `ERR_PNPM_TRUST_DOWNGRADE` ("possible package takeover"), npm `ETARGET` from a `before`/min-release-age policy, bun `minimum release age`, `ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION` when a project declares `vite` as a peer (migrate writes the `npm:@voidzero-dev/vite-plus-core@...` alias there), `ERR_PNPM_TARBALL_URL_MISMATCH` or a failed supply-chain policy check against the bridge tarball URLs, Docker builds whose context does not carry the bridge `.npmrc`, project scripts that query `vite@*` or `vite-plus@*` and find nothing (the prerelease does not match `*`), and npm 10 failing with `Unable to resolve reference $vite-plus` on a nested `overrides` entry. Confirm the last two by swapping the commit version for a real release in a scratch copy. +- **Stale pnpm metadata from an earlier sweep.** If migrate's install fails with `ERR_PNPM_NO_MATCHING_VERSION` for a version that npm serves, and pnpm names an older "latest release", pnpm read metadata that it cached under the bridge registry during an earlier sweep. Delete the bridge-keyed metadata older than a day (on macOS: `find ~/Library/Caches/pnpm/v*/metadata* -path '*registry-bridge.viteplus.dev*' -type f -mtime +0 -delete`) and rerun the fork. + +- **Forks that report no checks.** Before you count a fork as passing, check its last workflow run (`gh api 'repos/vite-plus-ecosystem-ci//actions/runs?per_page=1'`). GitHub can disable Actions on a fork while the settings API still reports `enabled: true`; `gh workflow run` then fails with `Actions has been disabled for this repository`. Run the fork's main CI commands locally against the preview and report that evidence separately. + +- **Preview-build artifacts.** These are caused by the `0.0.0-commit.` version string itself and cannot happen for a real npm release, so they are never blockers. The recurring ones: pnpm `ERR_PNPM_TRUST_DOWNGRADE` ("possible package takeover"), npm `ETARGET` from a `before`/min-release-age policy, bun `minimum release age`, `ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION` when a project declares `vite` as a peer (migrate writes the `npm:@voidzero-dev/vite-plus-core@...` alias there), `ERR_PNPM_TARBALL_URL_MISMATCH` or a failed supply-chain policy check against the bridge tarball URLs, Docker builds whose context does not carry the bridge `.npmrc`, checks that compare the generated Vite+ block in `AGENTS.md` (preview builds compile the PR's docs preview origin into CLI output, while release builds use `https://viteplus.dev`), project scripts that query `vite@*` or `vite-plus@*` and find nothing (the prerelease does not match `*`), and npm 10 failing with `Unable to resolve reference $vite-plus` on a nested `overrides` entry. Confirm the last two by swapping the commit version for a real release in a scratch copy. - **Pre-existing failures.** Prove it rather than asserting it, with whichever control is cheaper: install the previous release into an isolated home and re-run the same command, or check whether the fork's base branch CI already fails. The isolated-home control is the highest-value technique in this step, since it converts a scary-looking failure into a one-line fact: ```bash @@ -360,11 +370,11 @@ Two long-run mechanics worth knowing: `vp migrate` installs Vite+ git hooks in t Two fork-CI blockers are worth fixing rather than reporting, both on the **test branch only** so the tracked branch stays clean against upstream. A fork whose workflows never trigger on `pull_request` reports "no checks" and proves nothing: add a minimal workflow that runs `vp run build` through whatever setup the project already uses. A fork whose workflows target third-party runners (self-hosted labels such as `blacksmith-*`) queues every job forever, because those labels only resolve for the upstream org: map them to GitHub-hosted equivalents, replacing the longest label first so an `-arm` suffix is not left half-rewritten. Runner-specific _actions_ need more than a label swap and are usually not worth fixing. The same applies to `depot-*` and `namespace-profile-*` labels. GitHub-hosted runners are smaller than most of these, so heavy suites can start timing out after the swap; classify those timeouts as fork infrastructure. -Closing a cycle's PRs with `--delete-branch` removes their test branches, but each closed PR's commits stay reachable at `refs/pull//head`. Find the previous cycle's PR per repository (`gh pr list --repo vite-plus-ecosystem-ci/ --state all`); an org-wide `gh search` stops at 100 results and silently misses forks. Before a new sweep, list the non-upgrade commits on the previous cycle's PRs (supply-chain exemptions, runner-label maps, added workflows), fetch them, and cherry-pick them onto the new test branches. Re-apply by hand when upstream drift makes a cherry-pick conflict. Commits made after the upgrade commit (formatting of migrate-written files, regenerated lockfiles, restored dependencies) only apply after migrating: retry them after the new upgrade commit, take the new lockfile when only lockfiles conflict, and drop a formatting commit that would add migrate-generated `package.json` or workspace keys before migrate runs. +Closing a cycle's PRs with `--delete-branch` removes their test branches, but each closed PR's commits stay reachable at `refs/pull//head`. Find the previous cycle's PR per repository (`gh pr list --repo vite-plus-ecosystem-ci/ --state all`); an org-wide `gh search` stops at 100 results and silently misses forks. Before a new sweep, list the non-upgrade commits on the previous cycle's PRs (supply-chain exemptions, runner-label maps, added workflows), fetch them, and cherry-pick them onto the new test branches. Re-apply by hand when upstream drift makes a cherry-pick conflict. Commits made after the upgrade commit (formatting of migrate-written files, regenerated lockfiles, restored dependencies) only apply after migrating: retry them after the new upgrade commit, take the new lockfile when only lockfiles conflict, and drop a formatting commit that would add migrate-generated `package.json` or workspace keys before migrate runs. Drop carried-over fixes that upstream made obsolete (for example a rule disable that upstream replaced with inline comments), and redo pins in restored dependencies with the currently bundled versions instead of carrying the old ones forward. When a re-applied commit changes `package.json` but its lockfile part conflicts, reinstall after re-applying and commit the refreshed lockfile; otherwise the frozen install in fork CI fails on the mismatch. ## 5. Release-branch CI -Match checks to the current PR head and the latest applicable workflow runs. Superseded canceled runs can leave failed aggregate checks in the PR rollup. Check required statuses with `gh pr checks --required`, and report required reviewer approval separately from technical CI readiness. +Match checks to the current PR head and the latest applicable workflow runs. Superseded canceled runs can leave failed aggregate checks in the PR rollup. Each label added to the release PR (for example a `test: *` label or `preview-build`) starts a new set of `pull_request` runs and cancels the set in progress. A cancelled matrix job can then show as a failed check with an unexpanded name such as `vp create ${{ matrix.template.name }}`; confirm that a later run of the same workflow passed on the same head. Check required statuses with `gh pr checks --required`, and report required reviewer approval separately from technical CI readiness. Fixes for CI failures go through a **separate PR to `main`**, never as commits on the release branch (the release-version syncs in step 2 are the exceptions). After the fix PR merges: @@ -430,6 +440,8 @@ d=$(mktemp -d); cd "$d" && npm init -y >/dev/null && npm install vite-plus@X.Y.Z The full package document can update before npm's separately cached installation metadata. The workflow's `.github/scripts/wait-for-npm-packages.ts` checks that installation metadata and the referenced tarball, then waits for settlement. Use that script to reproduce its availability check; local visibility does not prove the workflow runner sees the same state. +The native-addon tier can stop the release the same way. `Prepare and publish native addons` uploads every `@voidzero-dev/vite-plus-` package and then waits up to 600 seconds for each one. If npm does not serve one of them in that time, the step fails with `Timed out after 600s waiting for npm propagation`. No `vite-plus` or core version is published before this step passes, so installs still resolve the previous release. Check that package's full document, installation metadata, and tarball URL until all three serve the version, then use "Re-run failed jobs". The rerun log must show `is already published; skipping upload.` for every platform package. A rerun that starts before npm serves the package can time out again. + Local clients can also mislead in the other direction. npm and pnpm cache a lookup made before the version landed, so pass `--prefer-online` when a just-published version reports `ETARGET`/`notarget` locally. pnpm 11 and later default to a one-day `minimumReleaseAge`, so `pnpm create vite-plus` (an unpinned `pnpm dlx create-vite-plus`) resolves to the previous `create-vite-plus` until the new version is a day old; verify it with `--config.minimumReleaseAge=0`. ## 8. Post-release @@ -437,7 +449,20 @@ Local clients can also mislead in the other direction. npm and pnpm cache a look 1. **Polish the GitHub release notes** (ask first): the auto-created release body has only Published Packages and Installation. Build the polished notes from the final release PR body: - Drop the closing `---` / `Merging this PR ...` boilerplate. - Preserve every changelog section through **Full Changelog**, including any later revisions requested by the release manager. - - Append the generated Published Packages and Installation sections, omit the redundant `View the full commit` line, and end Installation with a Docker usage block (keep the explanation to one short sentence): + - Append the generated Published Packages and Installation sections, and omit the redundant `View the full commit` line. For a stable release whose Homebrew formula PR is merged, add a Homebrew block after the macOS/Linux script (the generated body does not include it yet): + + ````markdown + **Homebrew (macOS/Linux):** + + ```bash + brew tap voidzero-dev/vite-plus https://github.com/voidzero-dev/vite-plus + brew install voidzero-dev/vite-plus/vp + ``` + + See the [Homebrew guide](https://viteplus.dev/guide/homebrew) to switch from the script installer or the Homebrew Core `vite-plus` formula. + ```` + + - End Installation with a Docker usage block (keep the explanation to one short sentence): ````markdown **Docker:** @@ -471,7 +496,7 @@ Local clients can also mislead in the other direction. npm and pnpm cache a look --title "vite-plus vX.Y.Z" --notes-file /tmp/release-notes.md ``` - - Keep the review draft, body-only notes file, and live release aligned after requested edits. Read back the live title and body to verify the update. Normalize CRLF and LF before comparing the approved file with the live body, because GitHub can change line endings. Re-run the step 3 validation greps, plus `grep -c 'Merging this PR'` (must be 0). + - Keep the review draft, body-only notes file, and live release aligned after requested edits. Read back the live title and body to verify the update. Normalize CRLF and LF before comparing the approved file with the live body, because GitHub can change line endings. Re-run the step 3 validation greps, plus `grep -c 'Merging this PR'` (must be 0). After the merge, `generate-notes` also lists the release PR itself; that PR is not a changelog entry. - For a SemVer prerelease, confirm that GitHub marks the release as a prerelease. The automated release can create a prerelease tag without setting that flag. Add `--prerelease` when applying the approved notes, then verify `isPrerelease` with `gh release view vX.Y.Z --json isPrerelease`. 2. **Verify**: @@ -482,6 +507,8 @@ Local clients can also mislead in the other direction. npm and pnpm cache a look In an isolated Homebrew installation, update the tap, install or upgrade `voidzero-dev/vite-plus/vp`, and run `brew test voidzero-dev/vite-plus/vp`. Unset `HOMEBREW_VP_PR_VERSION` for stable verification. Confirm `"$(brew --prefix)/bin/vp" --version` reports the target version, so a script-install shim cannot mask the result. Keep the tap release pending if the formula update or these checks are incomplete. + On macOS, Homebrew refuses a prefix inside its temporary directory (`/private/tmp`), so put the isolated copy elsewhere or set `HOMEBREW_TEMP` to another directory. A sandboxed agent cannot run `brew install` (see step 2). Behind a TLS-inspecting proxy, an unpacked Homebrew copy runs `brew test` in developer mode, and its `bundle install` from `rubygems.org` fails the certificate check even with `SSL_CERT_FILE` set. In those cases, report the `Published Homebrew` checks of the formula PR as the evidence: on all four platforms they tap and install the formula, check `vp --version`, upgrade, and run `brew test`, `brew style`, and `brew audit --strict`. Give the release manager the local command to run, and say which evidence came from CI. + ```bash npm view vite-plus version # X.Y.Z npm view @voidzero-dev/vite-plus-core version # X.Y.Z @@ -501,7 +528,7 @@ Local clients can also mislead in the other direction. npm and pnpm cache a look ```bash release_vp=/absolute/path/to/vp - release_data=$(VP_DUMP_DIRS=1 "$release_vp" | awk -F '\t' '$1 == "data" { print $2 }') + release_data=$(VP_DUMP_DIRS=1 "$release_vp" | grep '^data' | cut -f2) test -n "$release_data" readlink "$release_data/current" "$release_vp" upgrade @@ -553,7 +580,7 @@ Local clients can also mislead in the other direction. npm and pnpm cache a look Never post to Discord yourself. Save the draft to a file, update that file after every requested revision, and hand the approved contents over in chat. Do **not** post it as a comment on the release PR: that PR is a code-review artifact, and an announcement draft there is noise for reviewers and a second copy that can drift from the approved wording. After the release manager approves the announcement or confirms announcements are complete, proceed directly to step 9; do not ask them to repeat a completed handoff. -4. **X drafts, when requested:** condense the theme, a few user-facing changes, the upgrade action, and the release link into a plain-text post. Check the standard 280-character limit using X's weighted count: URLs count as 23 characters, and some Unicode characters count as two. Save the post and any requested thank-you reply in separate temporary files and update them after revisions. Credit a contributor's specific change and use their supplied or verified X handle. +4. **X drafts, when requested:** condense the theme, a few user-facing changes, the upgrade action, and the release link into a plain-text post. Check the standard 280-character limit using X's weighted count: URLs count as 23 characters, and some Unicode characters count as two. Save the post and any requested thank-you reply in separate temporary files and update them after revisions. Credit a contributor's specific change and use their supplied or verified X handle. To verify a handle, read the contributor's GitHub profile (`gh api users//social_accounts` or the `twitter_username` field). When the profile has no X account, use the GitHub profile URL instead of an `@` name, because the same name on X can belong to another person. ## 9. Update this skill (post-release) @@ -561,6 +588,7 @@ After the release ships and announcements are approved or confirmed complete, re - Capture only what generalizes: a step whose instructions drifted from what actually worked, a gotcha or corrected mistake, or a command/flag that was wrong. Write it as **general guidance**, with no release-specific versions, project names, PR numbers, or one-off examples. - Be surgical: change only what was wrong or missing; do not reword content that was already correct. If nothing generalizes, make no change. +- Do not write positional shell or awk references (a dollar sign followed by a digit) in this file. The skill loader replaces them with the invocation arguments (`/release-manager X.Y.Z ...`), which corrupts the snippet. - This skill lives on `main`, so do not push to `main` directly: make the edit on a branch, commit it, present the local diff and summary to the release manager, and **only push or open a `docs(skill): ...` PR after explicit approval**. ## Checklist