Skip to content
Agentmart
Submit a tool

release

Release markuplint — branch promotion, version bump (lerna fixed mode), tag push, publish workflow monitoring, npm-state verification, GitHub Release notes…

Skill by markuplint

IndexedMIT★ 629
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/markuplint-release

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

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.

Use cases: productivity, ci-cd, version-control

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/release

What's inside

Skill name
release
Description
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.
License
MIT
Metadata
internal: true
Instructions
320 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Preconditions

- **Fixed versioning**: all packages share one version (`lerna.json` `version`).
- **The release branch is not the development branch.** See [Branch model](#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`) via npm OIDC Trusted Publishing. A stable version goes to `latest`, unless its major is below the major currently on `latest` (`npm view markuplint@latest version`): then it goes to `v<major>`, so a maintenance release never moves `latest` back (4.18.4 was the first, published under `v4`).
- **Publishing cannot be undone.** Confirm with the user at each gate.
- **Run this in the user's main checkout, not in a worktree.** The worktree rule in the root `CLAUDE.md` is for topic-branch work; a release needs `main` checked out where the user runs `yarn release`, and a worktree holding `main` blocks the main checkout from switching to it. If this skill is invoked from a worktree session, stop before any checkout or merge and hand the steps to the user for the main checkout.
  - The exception is a maintenance release (see [Branch model](#branch-model)): it is cut on the line's own branch, so a worktree checked out on that branch works and keeps the main checkout on `dev`. In a worktree, `yarn release` needs the same prefix as `yarn build` — `NX_WORKSPACE_ROOT_PATH=<worktree-absolute-path> yarn release` — otherwise Lerna resolves the Nx workspace root to the main checkout and dies in `git diff --name-only <tag> -- ../../../packages/...` with "is outside repository".
- 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 type | Cut from | How work gets there |
| --- | --- | --- |
| **Stable** (`X.Y.Z`) | `main` | Merge `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-alpha` | Branch off **the line being released**, run `yarn release:rc` there, then merge that branch back into that line |
| **Maintenance** (`4.x.y`, a patch of a superseded major) | `v4` itself | Run `yarn release` on `v4`; `.husky/pre-commit` guards only `dev`, and `publish.yml` publishes it under the `v4` dist-tag. Nothing is merged back: `v4` is never merged into another line |
| Development | `dev` (v5 line), `v6` (next major) | — never released from directly |

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

```bash
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, cut from `v6` (not from `dev`).

## Releasing while two lines are live

`dev` (v5, the shipped major) and `v6` (the next major) are developed in parallel. Every one of the following is an open decision — **treat a request to release anything from the v6 line as blocked until the user has decided each point explicitly**, and say so rather than improvising:

- **`publish.yml` does not build the native addon.** It runs `yarn build`, and `@markuplint/core` has no `build` script — only `build:napi` / `build:napi:debug` — so `lerna publish from-git` would ship `@markuplint/core` without its `*.node` binary, and no per-platform packages at all. Publishing cannot be undone, so a v6 publish is blocked until `publish.yml` cross-builds napi.
- **The version namespace is shared.** `lerna.json` is at the same version on both branches and versioning is fixed-mode, so both lines derive their next version from conventional commits into the same `v*` tag namespace and the same CHANGELOG lineage. The v6 line cannot rely on automatic derivation to reach `6.0.0`; the target version is a decision, not an inference — the automatic derivation in step 4 applies to the v5 line only.
- **dist-tags are a single slot per name.** The tag comes from the version string alone (`-alpha.*` → `alpha`, and so on), so a v6 prerelease and a v5 prerelease using the same suffix overwrite each other's tag. Run `npm dist-tag ls markuplint` before any prerelease and confirm which line owns which tag.
- **Merging `v6` into `main` retires the v5 line in one step.** Per [What the `main` merge switches over](#what-the-main-merge-switches-over), that merge flips the production docs, the JSON Schema `$ref` URLs, and VS Code completion to v6 for every existing v5 user simultaneously. There is no staged path. Do not merge `v6` into `main` for a prerelease — use a dedicated prerelease branch, which leaves `main` alone.

## 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:

| Surface | Wiring |
| --- | --- |
| npm packages | the `v*` tag push fires `publish.yml` |
| markuplint.dev (production docs) | `website/config.js` — no `NEXT_VERSION` ⇒ production URL, editUrl `main` |
| JSON Schema | `config.schema.json` and every rule `schema.json` `$ref` a `raw.githubusercontent.com/.../main/...` URL |
| VS Code completion | `vscode/package.json` — `markuplint.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.

The merge also makes `crates/` part of the stable line. The `no-breaking-change-on-stable-line` rule of `.commitlintrc.js` tells the lines apart by `crates/Cargo.toml`, so from then on it is silent in every checkout: re-key it to whatever marks the next major before the first commit on the new stable line.

# Steps

## 1. Determine the release type and target branch

Ask the user only for the release channel (stable / rc / beta / alpha) if it is not already clear — the semver bump is derived, see step 4 — then pick the branch from [Branch model](#branch-model). Everything below writes `<release-branch>` for that branch.

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

```bash
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:

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

For a **prerelease**, create the branch from the line being released if it does not exist yet — `git checkout -b v5-rc origin/dev` for the v5 line, `git checkout -b v6-alpha origin/v6` for the next major. Cutting a v6 prerelease from `dev` silently drops everything the v6 line adds.

## 2. Unmerged PRs

```bash
gh pr list --base <source-branch> --state open
```

`<source-branch>` is the development branch the release draws from — `dev` for the v5 line, `v6` for the next major. Present anything that looks release-relevant; confirm whether to continue. (PRs target the development branch, 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:

- On a release branch with a `crates/` directory, `yarn test` is not the whole gate: add `cargo fmt --check`, `cargo clippy --locked -- -D warnings`, and `cargo test --locked` (what `.github/workflows/rust.yml` enforces), and confirm `@markuplint/core` was built with `yarn build:napi` — `yarn build` alone does not produce the addon.
- `.github/workflows/test.yml` also triggers on `push` to `dev`, 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`, and `yarn test`, each run as its own command. For the v6 line, `test.yml` alone is not evidence either; `rust.yml` and `cargo-deny.yml` must be green too.
- 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 the pre-release checklist in the `bench-xref` skill (including its `--dry-run` review before `--write`).

## 4. Present release contents

```bash
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 version bump — present the diff only as a "what's going in" check.

On the stable line, also scan the range for a breaking-change marker. The `no-breaking-change-on-stable-line` rule of `.commitlintrc.js` blocks it at commit time only; for a commit merged on GitHub this scan is the one gate, and one hit makes `yarn release` cut a major, the number the next-major line owns:

```bash
git log <last-tag>..HEAD --format=%s | grep -E '^[a-z]+(\([^)]*\))?!:'
git log <last-tag>..HEAD --format=%B | grep -iE '^(\* +)?BREAKING[ -]CHANGE:'
```

Stop and present any hit. If the user decides to release anyway, pass the bump explicitly — `yarn release minor` (or `patch`): `lerna version` takes a positional bump over the recommendation of `--conventional-commits` and still generates the changelog. That changelog carries a "BREAKING CHANGES" section from the commit's note, so the minor release announces a breaking change; say so when presenting the choice.

## 5. Version bump (user-executed)

Before handing over, remove `vscode/package-lock.json` if it exists. It is a gitignored leftover of an npm-based extension build (`vscode/scripts/install.mjs` deletes it only on success; a manual `vsce package` or an interrupted build leaves it behind). `lerna version` updates every per-package `package-lock.json` it finds and passes it to `git add`, which refuses ignored paths — the run then dies after every manifest and CHANGELOG has been rewritten and staged, one step short of the commit (v5.0.1).

From markuplint/markuplint, MIT.

More from markuplint/markuplint

Compare and alternatives

    Featured in

    Signals

    Stars
    629
    Forks
    70
    Open issues
    52
    Last commit (this folder)
    Oct 22, 2026 (5 days ago)
    Last release
    Oct 21, 2026 (6 days ago)

    From GitHub (fetched Oct 24, 2026).

    Details

    License
    MIT
    First seen
    Oct 24, 2026
    Last checked
    Oct 24, 2026

    More from markuplint

    Cite this page

    release skill. Agent Mart. https://agentmart.dev/skills/markuplint-release (last updated 2026-10-24)