Preconditions

SkillDocs & knowledge

Release markuplint — branch promotion, version bump (lerna fixed mode), tag push, publish workflow monitoring, npm-state verification, GitHub Release notes, and X post drafting. Use when asked to release, publish, or cut a new version.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Preconditions skill

What this skill tells your AI

The instructions your AI receives, as published by markuplint/markuplint in .claude/skills/release/SKILL.md and read by ahel’s review.

  • Fixed versioning: all packages share one version (lerna.json version).
  • The release branch is not the development branch. See Branch model — getting this wrong is the single most common mistake in this repository.
  • Pushing a v* tag triggers .github/workflows/publish.yml, which runs lerna publish from-git with a dist-tag derived from the version string (-alpha.*alpha, -beta.*beta, -rc.*rc, no prerelease suffix → latest) via npm OIDC Trusted Publishing.
  • Publishing cannot be undone. Confirm with the user at each gate.
  • The VS Code extension ships separately (yarn vscode:package to verify the VSIX build locally, then yarn vscode:release) and is out of scope here.

Branch model

Release typeCut fromHow work gets there
Stable (X.Y.Z)mainMerge dev (or the prerelease branch) into main first, then run yarn release on main
Prerelease (-rc.N / -beta.N / -alpha.N)A dedicated branch, e.g. v5-rc, v5-alphaBranch off dev, run yarn release:rc there, then merge that branch back into dev
Developmentdev (v5 line), v4 (v4 maintenance)— never released from directly

Verify the claim rather than trusting this table if anything looks off:

git log --oneline main --grep="chore(release)"   # stable releases live on main
git log --oneline v5-rc --grep="chore(release)"  # rc.0–rc.4 live on v5-rc

Three consequences that matter:

  • main looking "stale" is normal. It carries the last released version until the next release. Merging into main is the release act, not a chore someone forgot.
  • Never run yarn release on dev. .husky/pre-commit rejects every commit on dev, and Lerna only passes --no-verify when commitHooks is false (it defaults to true), so the version commit is refused. main and the prerelease branches are not guarded.
  • Prereleases are rare — the v5 cycle is the only one so far. Keep using a dedicated branch for them; the v6 line will need the same.

What the main merge switches over

For a stable release, merging into main flips four user-facing surfaces at once. There is no way to stage them separately, so treat the merge as the point of no return:

SurfaceWiring
npm packagesthe v* tag push fires publish.yml
markuplint.dev (production docs)website/config.js — no NEXT_VERSION ⇒ production URL, editUrl main
JSON Schemaconfig.schema.json and every rule schema.json $ref a raw.githubusercontent.com/.../main/... URL
VS Code completionvscode/package.jsonmarkuplint.defaultConfig $ref and jsonValidation[0].url point at that same schema

Website changes therefore reach production in the same step. Confirm with the user that the website is in a shippable state before merging.

Steps

1. Determine the release type and target branch

Ask the user for the release type if it is not already clear, then pick the branch from Branch model. Everything below writes <release-branch> for that branch.

git status must be clean. Anything dirty: report and wait for instructions.

git fetch origin
git checkout <release-branch>
git pull origin <release-branch>

For a stable release, merge the source branch in first and let the user review the diff before continuing:

git merge origin/dev          # or the prerelease branch, e.g. origin/v5-rc

For a prerelease, create the branch from dev if it does not exist yet (git checkout -b v6-rc origin/dev).

2. Unmerged PRs

gh pr list --base dev --state open

Present anything that looks release-relevant; confirm whether to continue. (PRs target dev, not the release branch.)

3. Pre-checks

yarn lint-check, yarn build, yarn test must all pass in this session, on the release branch after the merge. This is the only real gate:

  • .github/workflows/test.yml also triggers on push to dev (added alongside this skill's rewrite), so gh run list --branch dev --workflow=test.yml is real evidence for dev. It is not evidence for main or a prerelease branch — those still need this session's own yarn lint-check && yarn build && yarn test.
  • Yarn 4 does not run arbitrary pre/post lifecycle hooks, so the root prerelease script (build + test) is NOT executed by any yarn release* variant.
  • publish.yml does not run tests either — it installs, builds, and publishes.

If the release touches any rule the benchmark covers, also run yarn bench:xref --audit (see the bench-xref skill).

4. Present release contents

git describe --tags --abbrev=0
git log --oneline <last-tag>..HEAD

Show the current version (lerna.json). yarn release derives the next version from conventional commits automatically, so do NOT ask the user to choose a release type — present the diff only as a "what's going in" check.

5. Version bump (user-executed)

lerna version is an interactive command (selection/confirmation prompts) that cannot be driven through the ! prefix — the prompt renders but accepts no input. Ask the user to:

  1. Exit the Claude Code session (exit)
  2. Confirm they are on <release-branch>, then run the release command directly in the terminal and answer the prompts
  3. Return to this conversation with claude --continue
yarn release          # graduate (stable) — run on main

For a prerelease, on the dedicated branch:

yarn release:rc       # RC prerelease
yarn release:beta     # beta prerelease
yarn release:alpha    # alpha prerelease

Notes:

  • Graduating from a prerelease: --conventional-graduate targets every package carrying a prerelease id, and semver.inc returns the same base version for major, minor and patch alike — 5.0.0-rc.4 becomes 5.0.0 even when the range contains a feat!.
  • All variants use --no-push: the version commit and tag stay local until step 6.

6. Push the version commit and tag

Verify the tag exists locally, then push:

git tag --points-at HEAD
git push origin <release-branch> --follow-tags
git ls-remote --tags origin

7. Merge back

  • Stable: merge main back into dev so the version commit is not lost (git checkout dev && git merge origin/main, then push via a PR if dev is protected).
  • Prerelease: merge the prerelease branch into dev.

Do this before step 8 so a failure in verification does not leave the branches diverged.

8. Watch the publish workflow

The v* tag push fires publish.yml:

gh run watch --exit-status

On failure, show the log URL and go to step 10.

9. Verify npm state (the actual success gate)

Workflow success only means the publish process exited 0. Verify the registry:

npm view markuplint version
npm view markuplint dist-tags
npm view @markuplint/rules dist-tags
  • Version must match step 5; dist-tag must match the release type (latest / rc / beta / alpha).
  • Fixed mode can still partially publish. Spot-check core packages; if ANY mismatch is found, enumerate every public package (npx lerna list --json) and check each.
  • Confirm provenance attestations are present (npm view markuplint --jsondist.attestations).

Do not report the release as done before this step passes.

10. Failure handling

  • Workflow failed before anything published: fix the cause, gh run rerun.
  • Partial publish: published versions are immutable. Ask the user before retrying — options are gh run rerun (re-attempts the same tag) or a local npx lerna publish from-package (publishes only versions missing from the registry; requires local npm auth, which OIDC does not provide).
  • Wrong version published: unpublish is generally impossible. Propose npm deprecate <package>@<version> "<reason>" plus a corrected follow-up release — only with the user's explicit approval.

11. GitHub Release notes

Create a GitHub Release for the tag. All content in English.

gh release create v<VERSION> --title "v<VERSION>" --notes "$(cat <<'EOF'
<release notes body>
EOF
)"

Format:

## Highlights

- One-line summary of each major change (3-5 bullet points)

---

## Features

### @markuplint/<package>@<version>

- **Feature title** ([#PR](https://github.com/markuplint/markuplint/pull/N), [#Issue](https://github.com/markuplint/markuplint/issues/N))
  - Additional detail or context

---

## Bug Fixes

### @markuplint/<package>@<version>

- Fix description ([#PR](url))

---

## Other Changes

- `@markuplint/<package>`: Brief description of non-feature, non-fix changes

---

## Updated Packages

| Package | Version |
|---------|---------|
| markuplint | x.y.z |
| @markuplint/<package> | x.y.z |

---

**Full Changelog**: https://github.com/markuplint/markuplint/compare/vPREVIOUS...vCURRENT

Formatting rules:

  • Group changes by package, with ### @markuplint/<package>@<version> subheadings
  • Bold the main description of each feature or fix
  • Include PR and issue links where available; use external spec issue links (whatwg, w3c) when relevant
  • Separate sections with --- horizontal rules
  • Highlights summarizes the most impactful changes (not every change)
  • Other Changes uses a flat bullet list (no subheadings)
  • Updated Packages lists only packages with version bumps in this release
  • Omit sections that have no entries

12. X (Twitter) post

Generate an X post message and present it to the user for copying.

v<VERSION> released🎉 New features: <brief summary>. Bug fix for <brief summary>.
https://github.com/markuplint/markuplint/releases/tag/v<VERSION>

Rules:

  • 280 characters or fewer — count carefully; URLs always count as 23 characters (t.co)
  • Summary is a single paragraph with no line breaks; URL goes on the next line
  • Start with v<VERSION> released🎉 (no space before 🎉)
  • Omit the "Bug fix" sentence if there are no bug fixes; omit "New features" if there are none
  • No hashtags or mentions
  • End with the GitHub Release URL

Signals

GitHub stars
614
Forks
63
Last commit
Sep 2026
Hacker News mentions
20
Advanced
Catalog kind
skill
Gateway key
release-markuplint
Source
github.com/markuplint/markuplint