l-make-release
Release @takazudo/zudo-sg end-to-end — bump packages/styleguide/package.json, write an English changelog page, regenerate packages/styleguide/CHANGELOG.md, run the local quality gate, commit + push to...
/l-make-release
End-to-end release orchestrator for @takazudo/zudo-sg, the styleguide engine package at packages/styleguide. It bumps the version, writes an English changelog page, regenerates packages/, runs the local quality gate, commits + pushes to main, waits for CI, then pushes the v<version> tag — which triggers . (build + verify + pnpm publish) — watches that run to success, and creates the GitHub Release. One invocation takes the release all the way to npm.
What this package is
Single npm package:
@takazudo/zudo-sg, atpackages/styleguide/. Version source of truth ispackages/'sstyleguide/ package. json versionfield. The rootpackage.jsonstaysprivate: trueand is never touched by this skill — the styleguide host site,apps/demo,doc/, andpackages/uiall have their own lifecycle and are not part of this release.Stable only (ADR
docs/decision 12). There is noadr/ styleguide- engine. md nextdist-tag, no prerelease suffix, nonext/stableargument for this package — unlike the zdtl/zudo-doc release skills this one is modeled on.v*.*.*always publishes to npmlatest.Pre-1.0 version judgement is Scheme B, not standard SemVer conventional commits: a breaking-change commit bumps minor (not major — the major stays at
0through the whole0.xline), everything else bumps patch. There is nofeat: -> minorrule here.
Invocation & confirmation
This skill is model-invocable: a rough natural-language request like "bump version", "cut a release", or "release zudo-sg" may trigger it. It must never mutate anything before the user explicitly confirms. Steps 1–2 are read-only (preconditions, version computation + change analysis); the first mutation is Step 3.
There is one gate: the Step 2 proposal. Confirming it authorizes the whole flow — bump, push, CI, tag, publish, and GitHub Release. Do not add a second "push the tag now?" prompt. The only things that can halt the flow after confirmation are a build/test/pack failure (Step 5, before anything is pushed) or a publish-workflow failure (Step 9, after the tag is pushed) — see Failure Recovery.
Cancel mode. / (or "cancel/abort the release") does NOT bump anything — it jumps straight to Cancelling a release to undo a not-yet-published release.
How publishing works (read before changing anything)
/l-make-release -> confirm bump (Step 2) -> bump + changelog + commit + push main -> CI green
│
skill pushes: git push origin v<version>
│
.github/workflows/publish-zudo-sg.yml fires
│
builds + verifies + `pnpm publish` -> npm
│
skill watches the run, then creates the GitHub Release The publish workflow triggers on a pushed v*.*.* tag — NOT on a GitHub Release. The skill creates the GitHub Release after the publish run succeeds, so a failed publish leaves no orphaned Release. The irreversible step is the tag push; confirming the Step 2 proposal is what authorizes it.
Boundaries
The skill never runs
npm publish/pnpm publishdirectly — that ispublish-zudo-sg.yml's job, triggered by the tag push.npm cannot re-publish a version. If the publish workflow fails after the tag is pushed (Step 9), the fix is to cut a new version, not retry the same one — see Failure Recovery.
This skill never mutates the root
package.json,apps/demo,doc/, orpackages/ui— the styleguide engine is the only release surface here.
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).ghCLI is authenticated (gh auth status).Local
mainis up to date withorigin/main(git fetch origin && git status -sb). If behind, pull first.Fetch tags so the changelog base is correct:
git fetch --tags origin.Latest CI on
mainis green — this repo has no single "test" workflow that runs on push tomain; check every workflow run at the currentmainHEAD:gh run list --branch main --limit 5 --json name,status,conclusion,headShaAll runs at the current head SHA must show
conclusion: success(most commonlyProduction Deploy,main-deploy.yml). If anything is stillin_progressor non-success, stop and report it rather than releasing against unverifiedmain.
Resume detection (run before requiring a clean tree)
A previous run may have committed the version bump without pushing the tag (e.g. CI on the bump was still running when the prior run ended). Detect this before assuming a cold start:
git fetch --tags origin
CUR=$(node -p "require('./packages/styleguide/package.json').version")
git tag -l "v$CUR" # empty output = no tag yet for the current versionIf
v$CURdoes NOT exist: the current version is un-tagged. Check the working tree first:Dirty (
git status --porcelainnon-empty) -> STOP. Ask the user to commit, stash, or discard before re-running; do NOT resume or bump over a dirty tree.Clean -> this is a RESUME. Find the commit that introduced the current version (do NOT assume it is
HEAD):BUMP_SHA=$(git log -1 --format=%H -S"\"version\": \"$CUR\"" -- packages/styleguide/package.json)Offer to RESUME from
$BUMP_SHA— this skips Steps 2–4 (bump / changelog / commit) and continues from Step 6 (CI wait) onward, tagging$BUMP_SHA. If$BUMP_SHAis notHEAD, later commits landed on top — surface that and let the user choose: tag$BUMP_SHAas-is, or abort and cut a fresh bump that includes the newer commits.
If
v$CURalready exists: the current version is released. Proceed with a normal cold-start bump (Steps 2–4). Require a clean working tree here too.
Step 2: Determine the next version and propose — THE GATE
Find the changelog base
git tag -l 'v*' --sort=-v:refname | head -1A
v*tag exists -> base = that tag. Normal case.No
v*tag exists at all -> this is the first release. There is no bootstrap tag to fall back to (unlike the zdtl model, which assumes an initialv1.0.0was created by hand — this repo does not: the strayv0.1.0tag was deliberately deleted, see #179). Instead, the base is the commit that introduced the package itself:BASE_SHA=$(git log --diff-filter=A --format=%H --follow -- packages/styleguide/package.json | tail -1)This finds the first commit that ever added
packages/— i.e. "all of history since the engine was born" — with no dependency on knowing which PR merged the epic.styleguide/ package. json
Analyze commits since the base
git log "${BASE_TAG:-$BASE_SHA}..HEAD" --oneline # human-readable proposal list (subject + hash)
git log "${BASE_TAG:-$BASE_SHA}..HEAD" # full form, read commit bodies too — a
# `BREAKING CHANGE:` footer can appear under a
# plain `feat:`/`fix:` subject with no `!`Categorize each commit (read both subject and body — do not rely on --oneline alone):
Breaking Changes:
!before the colon (feat!:,fix(scope)!:) OR aBREAKING CHANGEfooter in the body.Features:
feat:prefix (includingfeat(scope):).Bug Fixes:
fix:prefix (includingfix(scope):).Other Changes: everything else (
docs:,chore:,refactor:,ci:,test:,style:,perf:, …).
Only commits that touch packages/styleguide/**, ., or other engine-relevant paths are release-worthy for this package's own changelog entry — but the version-level judgement below still runs over the full commit range (a breaking commit anywhere in that range is a signal worth surfacing even if narrowly scoped; use judgement when a change is obviously unrelated to the engine, e.g. a pure apps/demo or doc/ content commit, and exclude it from both the level judgement and the changelog draft).
Determine the version (Scheme B, pre-1.0)
Every argument sets which component bumps; there is no channel argument at all — everything lands on latest.
No argument — judge the level from the categorization above:
any Breaking Change -> minor bump (
0.Y.Z->0.(Y+1).0) — the major stays at0, per Scheme B; a breaking0.xchange does NOT jump to1.0.0.else (any
feat:,fix:, or onlyOther Changes) -> patch bump (0.Y.Z->0.Y.(Z+1)).No-qualifying-commits case: if the range has nothing above
docs:/chore:/refactor:/ci:/test:/etc. (nofeat:,fix:, or breaking commit at all), the "else -> patch" rule still applies, but flag it explicitly at the proposal: "No feat:/fix:/breaking commits found since<base>— proposing a patch bump. Confirm this is intended."
major / minor / patch argument — force that exact bump on the current version's triple, ignoring the commit judgement:
major:{X+1}.0.0— the explicit escape hatch for a deliberate jump to1.0.0(or any other major), since Scheme B's auto-judgement never proposes one on its own.minor:X.{Y+1}.0patch:X.Y.{Z+1}
There is no next, stable, or any other argument — this package has no prerelease channel to opt into.
Validation
The computed version must be strictly greater than the current version (packages/'s version). If not, stop with an error showing both versions.
Present the proposal
Proposed bump: @takazudo/zudo-sg <current> -> <new> (<breaking|feature|fix|patch>, Scheme B)
Breaking Changes:
- description (hash)
Features:
- description (hash)
Bug Fixes:
- description (hash)
Other Changes:
- description (hash)Only show sections with entries. Wait for explicit user confirmation before proceeding to Step 3. Confirming here authorizes the full flow through pnpm publish and the GitHub Release.
Step 3: Bump + changelog
3a. Bump the version
Update version in packages/ to the confirmed new version (no v prefix). The root package.json is NOT touched.
3b. Write the English changelog page
Create doc/. On the very first real release, this is the seeded doc/ placeholder — finalize it in place rather than creating a second file (its Released: unreleased placeholder line and the sentence "Finalized by / when the first version actually ships" both get replaced).
Frontmatter (match the existing file's shape):
---
title: <version>
description: <one-line summary of the release>
sidebar_position: <computed — see below>
pagination_next: null
---
Released: <YYYY-MM-DD>
<one or two sentence intro>
### Features ← omit section if empty
- entry
### Bug Fixes ← omit section if empty
- entrysidebar_position: standard ascending order (rootCLAUDE.md: "Sidebar order is driven bysidebar_position") — read every existing file underdoc/and use one past the current highest value (the seededsrc/ content/ docs/ changelog/ zudo- sg/ 0.1.0.mdxstarts at1, so the first real second release is2, and so on). Note this is independent ofpackages/'s own ordering — that file is generated by sorting thestyleguide/ CHANGELOG. md titlefields as semver, newest first, regardless ofsidebar_position(doc/-> thesrc/ content/ docs/ changelog/ zudo- sg/ *. mdx changelogbuild plugin), so gettingsidebar_positionslightly wrong would only affect the doc site's sidebar, not the generated CHANGELOG.Avoid bare
<Uppercase...>generics in prose — the MDX sanitizer strips them even inside inline code (e.g. write "a Preact component", not<Component>, unless it's inside a real fenced code block).Prose only needs Features / Bug Fixes sections that actually contain engine-relevant entries from Step 2's categorization; drop
Breaking Changes/Other Changesheadings if nothing landed there for this package specifically.
3c. Regenerate packages/styleguide/CHANGELOG.md
No dedicated pnpm gen:changelog script exists in this repo — the changelog is a postBuild side effect of the doc site's build (doc/'s changelogs entry, sourceDir: -> outputFile:):
pnpm build:docConfirm packages/ now contains the new version's entry before proceeding.
Step 4: Local quality gate
pnpm b4pushThis runs the full local gate (format, lint:tokens, codegen drift, typecheck, unit tests, build, demo build, link check, HTML validate, Playwright smoke, and the doc site — see scripts/). If anything fails, stop and tell the user. Do not commit. This is the last halt point before anything reaches the remote.
Also run the release-specific artifact gate directly, since b4push does not include it:
pnpm --filter @takazudo/zudo-sg check
pnpm verify:styleguide-install
bash scripts/check-pack.shStep 5: Commit + push
git add packages/styleguide/package.json \
packages/styleguide/CHANGELOG.md \
doc/src/content/docs/changelog/zudo-sg/<version>.mdx
git commit -m "chore(release): @takazudo/zudo-sg v<version>"
git push origin main
BUMP_SHA=$(git rev-parse HEAD)Step 6: Wait for CI on the bump commit
gh run list --branch main --limit 5 --json name,status,conclusion,headShaPoll until every run at headSha == $BUMP_SHA reports conclusion: success. If CI fails, fix, commit, push, and re-watch — refresh BUMP_SHA to the commit whose CI actually passed before tagging:
BUMP_SHA=$(git rev-parse HEAD) # only after CI on THIS commit is greenDo not advance to the tag push until CI on the bump commit is green.
Step 7: Push the tag (triggers the publish)
git tag "v<version>" "$BUMP_SHA"
git push origin "v<version>"This fires .. Do NOT ask "push the tag now?" — the Step 2 confirmation already authorized this.
Step 8: Watch the publish workflow
Match the run by its head commit, not headBranch (often empty for tag events):
PUBLISH_RUN=""
for i in $(seq 1 12); do
PUBLISH_RUN=$(gh run list --workflow publish-zudo-sg.yml --limit 15 \
--json databaseId,headSha \
-q "[.[] | select(.headSha==\"$BUMP_SHA\")][0].databaseId")
[ -n "$PUBLISH_RUN" ] && break
sleep 5
done
if [ -z "$PUBLISH_RUN" ]; then
echo "ERROR: could not find the publish-zudo-sg.yml run for v<version> (commit $BUMP_SHA)." >&2
exit 1
fi
gh run watch "$PUBLISH_RUN" --exit-statusIf it fails, surface the failing logs (gh run view "$PUBLISH_RUN" --log-failed) and stop — do NOT create the GitHub Release. See Failure Recovery.
Step 9: Create the GitHub Release
The publish succeeded. Extract the changelog body (everything after the frontmatter) as release notes:
awk 'f; /^---$/{c++; if(c==2) f=1}' doc/src/content/docs/changelog/zudo-sg/<version>.mdx > /tmp/zudo-sg-release-notes.md
gh release create "v<version>" --verify-tag --title "@takazudo/zudo-sg <version>" \
--notes-file /tmp/zudo-sg-release-notes.mdNo --prerelease flag ever applies — this package has no prerelease channel.
Step 10: Verify + report, then STOP
npm view "@takazudo/zudo-sg@<version>" version
npm dist-tag ls @takazudo/zudo-sg<version> should show under latest. There is no next dist-tag to check for staleness — this package never publishes one. Print a final report (published version, npm package URL https:, the publish workflow run URL, the GitHub Release URL), then STOP.
Cancelling a release
Use this for / or a mid-release problem. The tag push (Step 7) is the irreversible boundary.
The tag has NOT been pushed yet (before Step 7)
Delete a local, unpushed tag if one was minted by mistake:
git tag -d v<version>.Decide whether to undo the bump commit:
git rev-list --count <BUMP_SHA>..HEAD0(bump is still HEAD) -> revert it (one commit reverts the version bump,CHANGELOG.md, and the changelog MDX page together):git revert --no-edit <BUMP_SHA> git push origin main>0(buried under later commits) -> leave it. The stale version number is harmless; the next release simply supersedes it.
The tag HAS been pushed (Step 7 done)
Treat the version as live. Do NOT delete the remote tag and do NOT attempt to re-publish that version. If the publish failed, recover by cutting a new version — see Failure Recovery. If it succeeded and needs retracting, that's a manual npm unpublish / npm deprecate decision for the user, outside this skill.
Failure Recovery
Build/test/pack failure (Step 4) — stop and report. Do not commit. Fix and re-run. Nothing reached the remote.
CI fails on the bump commit (Step 6) — fix, commit, push, re-watch. Do not push the tag until CI is green.
Wrong version proposed — caught at the Step 2 gate. If already committed but the tag was NOT pushed, use Cancelling a release to revert and re-run.
Publish workflow fails after the tag was pushed (Step 8) — the tag exists on the remote but the npm publish did not complete. Inspect
gh run view "$PUBLISH_RUN" --log-failed.Transient (registry hiccup, runner eviction) — re-run the same workflow:
gh run rerun "$PUBLISH_RUN". The version was never published, so a clean re-run can still succeed under the same tag.Needs a code fix — the tag must move to a new commit; npm will not accept the same version twice:
git push origin :refs/tags/v<version> git tag -d v<version>Fix the code, then re-run
/— resume detection picks the un-tagged bump back up.l- make- release
OTP /
EOTP/ 2FA error in the publish step —NPM_TOKENis not an Automation-type token. Regenerate it as Automation at npmjs.com, update the repo secret (gh secret set NPM_TOKEN), then recover per the "code change" path above.
See packages/ for the same recovery paths without the skill's step numbering — useful when recovering by hand.