l-make-release
Release @takazudo/mdx-formatter — bump the version, sync platform packages, write the changelog, push, wait for CI, tag (which triggers the release.yml auto-publish of all 5 npm packages via the repo ...
/l-make-release
One-call orchestrator for releasing @takazudo/mdx-formatter and its four lockstep platform packages. Bumps the version, syncs the platform packages, writes a changelog doc (stable only), commits + pushes, waits for CI, pushes the v* tag — which triggers . to build the napi binaries, publish all five npm packages, and move both latest and next to every successful release — watches that run to completion, then creates the GitHub Release (stable only).
Invocation & autonomy
This skill is model-invocable: a rough natural-language request like "bump version", "cut a release", or "release it" may trigger it.
Default: fully autonomous end-to-end — NEVER ask for confirmation, NEVER stop and wait. Steps 1–3 are read-only; print the Step 3 proposal (current → new version + categorized changelog) for visibility only and proceed straight into Step 4. The tag push at Step 8 IS the publish trigger — do not pause to ask "tag?", "publish?", or any equivalent. The invocation itself is the authorization.
--confirm option (opt-in interactive mode). When the invocation includes --confirm (e.g. /, /), restore interactive behavior: present the Step 3 proposal and wait for explicit user confirmation before the first mutation (Step 4), and stop after Step 7 (CI green) with the tag NOT yet pushed — report the exact git tag / git push commands and let the user fire them. Use this when the version strategy or release notes need vetting.
Architecture (why this works)
The four platform packages (
npm/darwin-arm64,npm/darwin-x64,npm/linux-x64-gnu,npm/win32-x64-msvc) are pnpm workspace members, declared on the root as pinnedworkspace:X.Y.ZoptionalDependencies. They resolve locally at bump time — before the new versions exist on the registry — so the lockfile stays consistent and bump-commit CI is green. (The pre-workspace flow could never have green CI on the bump commit:ERR_PNPM_OUTDATED_LOCKFILE, see v1.2.1 history.)scripts/keeps all fivesync- napi- versions. mjs package.jsonversions + theworkspace:specifiers lockstep with the root version.release.ymlpublishes the platform packages withnpm publishand the root withpnpm publish(which rewritesworkspace:X.Y.Z→ exactX.Y.Zin the tarball). Every publish is idempotency-guarded, so re-running the workflow after a partial failure is safe.Stable releases only — no prereleases. The
-next.Nline was retired after1.3.0-next.5. Every release publishes underlatest, thenrelease.ymlmovesnextto the same version, sonextis just an alias oflatestand anyone still installing@nextlands on the stable release. Don't delete thenexttag:@nextinstalls would fail.The repo secret
NPM_TOKENis an automation token covering ALL@takazudopackages (root + platform). If a publish fails withE404 Not Found - PUTor a 2FA error, the token scope/type is the problem — fix it at npmjs.com → Access Tokens.
Boundaries
This skill never runs
npm publish/pnpm publishlocally. Publishing happens only inrelease.yml, triggered by the tag push.The GitHub Release is created AFTER the publish run succeeds (it does not trigger anything — the tag does).
Never create a prerelease (
X.Y.Z-next.Nor any other suffix), even if asked for "next". Ship a stable version instead.
Step 1: Preconditions
Verify ALL of the following. If any check fails, stop with a clear message.
Current branch is
main(git branch --show-current)Working tree is clean (
git status --porcelainreturns empty)ghCLI is authenticated (gh auth status)Tags are fresh and at least one
v*tag exists:git fetch --tags origin git tag -l 'v*' --sort=-v:refname | head -1
Step 2: Determine Next Version
Read the current version from the root package.json.
No argument
Current is a leftover prerelease
X.Y.Z-next.N: finalize it →X.Y.Z(analyze commits since the last stable tag, skipping-next.*tags).Current is stable
X.Y.Z: analyze commits since the last tag (Step 3) and pick autonomously:breaking changes → major
new features → minor
otherwise → patch
major / minor / patch argument
Stable release with that bump from the current stable version (e.g. patch: 1.2.1 → 1.2.2). If the current version is a leftover prerelease, bump from its base (1.3.0-next.5 + patch → 1.3.0, + minor → 1.3.0, + major → 2.0.0).
Guard: version must not already exist on npm
A partially recovered prior run may have published the computed version even though the local state suggests otherwise. Always check, and bump past any hit:
npm view "@takazudo/mdx-formatter@<version>" version 2>/dev/nullIf this prints a version, the computed version is taken — recompute (next patch) and re-check.
Step 3: Analyze Changes and Propose
git log <last-tag>..HEAD --onelineCategorize each commit by judgment (this repo uses [formatter] / [doc] / [misc] / [claude] scope prefixes, not conventional commits — read the subjects and classify):
Breaking Changes: API/CLI behavior changes consumers must adapt to
Features: new capabilities
Bug Fixes: fixes to formatting behavior, CLI, or shipped binaries
Other Changes: docs, CI, deps, internal refactors
Present the proposal:
Proposed bump: {current} → {new} ({type})
Breaking Changes:
- description (hash)
Features:
- description (hash)
Bug Fixes:
- description (hash)
Other Changes:
- description (hash)Only show sections that have entries.
Default (autonomous): the printout is for visibility only — proceed straight to Step 4.
With
--confirm: wait for explicit user confirmation before proceeding.
Step 4: Bump + Sync + Changelog
4a. Bump the root version
Update the version field in the root package.json to the new version (no v prefix).
4b. Propagate to platform packages
pnpm sync:napi-versionsThis rewrites the four npm/ versions and the root's workspace:X.Y.Z optionalDependencies.
4c. Regenerate the lockfile
pnpm install --lockfile-only --no-frozen-lockfile--lockfile-only updates pnpm-lock.yaml without touching node_modules (avoids the non-TTY purge abort). The platform entries are workspace links, so the new version always resolves. The expected diff is exactly the four specifier: workspace:<old> → workspace:<new> lines — if anything structural appears, stop and surface it.
4d. Write the changelog doc
Create doc/:
---
title: 'v{VERSION}'
sidebar_position: { MAJOR * 1000 + MINOR * 100 + PATCH }
---
# v{VERSION}
Released: {YYYY-MM-DD}
## Breaking Changes
- Description (commit-hash)
## Features
- Description (commit-hash)
## Bug Fixes
- Description (commit-hash)
## Other Changes
- Description (commit-hash)Rules:
Only include sections that have entries
Use today's date
Each entry: commit subject with the short hash in parentheses
The changelog category sorts
desc, so the position formula puts newer versions on top
When finalizing a leftover prerelease, analyze ALL commits since the last stable tag (skipping -next.* tags) so the changelog covers the whole prerelease line.
Step 5: Build and Test
pnpm build:rust && pnpm build && pnpm testpnpm build:rust refreshes crates/ so the tests exercise the current Rust code, not a stale local binary. If anything fails, stop — nothing has been committed yet, so a full reset is:
git checkout -- . && git clean -f doc/src/content/docs/changelog/(git clean removes the untracked changelog mdx written in Step 4d.)
Step 6: Atomic Commit + Push
ONE commit containing everything (single revert = full rollback):
git add package.json npm/*/package.json pnpm-lock.yaml
git add doc/src/content/docs/changelog/v{VERSION}.mdx
git commit -m "chore(release): Bump to v{VERSION}"
git push origin main
BUMP_SHA=$(git rev-parse HEAD)Step 7: Wait for CI on the Bump Commit
Delegate to / — do NOT reimplement polling:
Skill(skill="watch-ci", args="--branch main --commit <BUMP_SHA>")If CI fails, fix the issue, push the fix, and re-invoke /. Do not tag until CI is green.
With --confirm: STOP HERE. Report that the bump is pushed and CI is green, and print the exact commands for the user to fire:
git tag v{VERSION} && git push origin v{VERSION}Step 8: Tag → Auto-Publish
git tag v{VERSION}
git push origin v{VERSION}The tag push triggers release.yml: 4 platform binary builds → 4 platform package publishes → root package publish (prepublishOnly runs tsc + vitest against the shipped linux binary) → move both latest and next to the new version on all five packages.
Step 9: Watch the Release Run
Find the run (allow a few seconds for it to appear):
gh run list --workflow release.yml --limit 3 --json databaseId,displayTitle,status,headBranchWatch it to completion with a background poll (gh run view <id> --json status,conclusion every 30s until completed — background shell loop, NOT foreground sleep).
On success: proceed to Step 10.
On failure: fetch the failed logs (
gh run view <id> --log-failed). If clearly transient (network flake, runner eviction), retry once withgh run rerun <id> --failed— the idempotency guards make re-runs safe. Otherwise surface the failure summary and stop. Do NOT delete the tag; the next run of this skill bumps past the broken version.
Step 10: GitHub Release
NOTES=$(sed -n '/^Released:/,$ p' doc/src/content/docs/changelog/v{VERSION}.mdx)
gh release create v{VERSION} --title "v{VERSION}" --notes "$NOTES"Step 11: Verify + Final Report
npm view @takazudo/mdx-formatter dist-tags
for PKG in @takazudo/mdx-formatter @takazudo/mdx-formatter-darwin-arm64 @takazudo/mdx-formatter-darwin-x64 @takazudo/mdx-formatter-linux-x64-gnu @takazudo/mdx-formatter-win32-x64-msvc; do
npm view "${PKG}@${VERSION}" version
npm view "${PKG}@latest" version
npm view "${PKG}@next" version
doneVerify all five exact package versions and confirm both @latest and @next resolve to {VERSION}. Report: released version, observed latest / next dist-tags, release.yml run URL, GitHub Release URL, npm package page https:.
Failure Recovery
Build or test failure (Step 5)
Nothing is committed yet. Reset the working tree, fix the issue, re-run the skill.
CI fails on the bump commit (Step 7)
Fix, commit, push, re-invoke /. Do not tag until green.
release.yml fails (Step 9)
Transient →
gh run rerun <id> --failed(once).Real failure after a partial publish (e.g. platform packages live, root missing) → fix the cause, then rerun the original run (
gh run rerun <id>) or dispatch against its exact tag (gh workflow run release.yml --ref v{VERSION}) — the idempotency guards skip what is already live. Verify the tag still points at the intended release commit first.Dist-tag synchronization failure after all packages publish → re-run the workflow. Exact-version guards skip publishing, then the idempotent dist-tag step retries both tags.
E404 Not Found - PUT on publish = invalid/expired NPM_TOKEN (NOT provenance, NOT workflow config)
A 404 Not Found - PUT .../@takazudo%2f... when publishing a package that already exists on npm is npm's scope-mask for a 401/403 authorization failure — almost always a dead NPM_TOKEN. The publish job's "Verify npm auth" step (npm whoami) catches this up front: if it prints a username, the token authenticates; if it 401s, the token is invalid/expired.
This was a real incident: a token that published fine when freshly created later E404'd every publish ~6 weeks on — it had expired. Provenance and workflow config were red herrings (both a provenance run and a plain-publish run failed identically; the cause was the token). GitHub secrets are per-repository, so "the same token works in another repo" does not mean this repo's NPM_TOKEN secret holds a valid value.
Fix: generate a fresh Automation token on npmjs.com (Access Tokens → Generate → Classic → Automation; Automation tokens bypass the 2FA-for-writes gate that blocks CI), then gh secret set NPM_TOKEN (or via the repo Settings UI). Re-run the workflow — the idempotency guards make re-runs safe. If npm whoami succeeds but publish still 403s, the token authenticates but lacks write access to the scope/packages — fix the token's package permissions on npmjs.com.
2FA/OTP errors (
EOTP) → same fix: use an Automation token (they are exempt from interactive OTP).
Historical stable-only latest repair
The repair-tags.yml workflow remains for the September 2026 stable-only recovery. It does not implement the current both-tag policy. For a partial current release, rerun release.yml against the exact release tag so its idempotent publish checks and both-tag synchronization complete. Use the stable-only repair workflow only when deliberately restoring stable latest; follow scripts/ and verify that this is the intended policy for that incident.
Rolling back before the tag was pushed
The Step 6 commit is atomic — one revert undoes the bump, the lockfile, and the changelog doc:
git revert --no-edit <BUMP_SHA>
git push origin mainIf later commits already sit on top of the bump, do NOT revert; the stale version number is harmless — the next release bumps past it.
Never delete published things
Never delete a pushed v* tag, a published GitHub Release, or unpublish an npm version. Abandoned/broken versions are superseded by the next bump.