l-make-release
Orchestrate a create-zudo-doc release: bump versions, fill changelog, validate, commit, push, wait for CI, tag all three lockstep packages, create a GitHub release for each, and publish them to npm. A...
/l-make-release
Orchestrate the full create-zudo-doc release sequence end-to-end: bump → changelog → validate → commit → release PR → CI (A2 executed) → merge → tag → create three GitHub releases (one each for @takazudo/zudo-doc-history-server, @takazudo/zudo-doc, and create-zudo-doc, all in lockstep at the same version) → publish all three to npm. The publish step fires each package's CI publish workflow and is irreversible.
Autonomy — runs to a published release by default
Default behavior is fully autonomous: this skill takes the release all the way to published on npm without asking the user for permission at any gate (not at the version proposal, not at tagging, not at publish). The user invoked / to get a release — deliver one.
--confirm — when this flag is passed, insert a single confirmation gate right before the irreversible npm publish (Step 9): show the version + the three packages + one line summarizing each package-specific note and ask "Correct — publish now?". Only publish on an affirmative answer; otherwise stop with the drafts left in place. --confirm does NOT add prompts anywhere else — the rest of the flow stays autonomous.
(Everything before Step 9 — bump, changelog, commit, push, tag, create drafts — is reversible or local, so it always runs without asking regardless of --confirm.)
Why a new skill — not editing /zudo-doc-version-bump
/ is dual-purpose: it is also shipped to downstream scaffolded projects via create-zudo-doc. Modifying it risks breaking that downstream contract. This skill wraps and extends the same release flow for the create-zudo-doc package specifically:
Bumps both root
package.jsonandpackages/in lockstep (the existing skill only bumps the root).create- zudo- doc/ package. json Accepts prerelease versions (
1.0.0-next.1,1.0.0-beta.2,1.0.0-rc.1) whichscripts/rejects due to its strict semver regex.version- bump. sh Drives the release all the way to published on npm (three lockstep packages) by default — each GitHub release publish fires that package's CI
npm publishworkflow. Pass--confirmto gate the publish; otherwise it is autonomous.
The existing / skill is not modified by this flow.
Version scheme
The release script auto-derives the next version from the current one, or accepts explicit bump modes and version strings:
| Current version | Mode / arg | Result |
|---|---|---|
X.Y.Z (stable) | (no arg / auto) | X.Y.(Z+1) |
X.Y.Z-next.N | (no arg / auto) | X.Y.Z (graduate) |
| any | major | (X+1).0.0 |
| any | minor | X.(Y+1).0 |
| any | patch | X.Y.(Z+1) |
X.Y.Z (stable) | next | X.(Y+1).0-next.1 |
X.Y.Z-next.N | next | X.Y.Z-next.(N+1) |
X.Y.Z-next.N | stable | X.Y.Z |
| already stable | stable | (error) |
| any | <semver> | use exactly that |
Scheme B (pre-1.0): the 0.x mainline ships clean 0.MINOR.PATCH — no -next suffix. major / minor / patch and the auto default all produce a clean version (a breaking 0.x change rides a minor bump). The next keyword is the opt-in prerelease escape hatch (deliberate previews / the 1.0.0-beta run-up); auto from an in-flight prerelease graduates it to the clean X.Y.Z.
Dry path (compute-only, no mutations)
# Test auto-derive from a specific version without touching package.json:
DRY=1 FROM=0.2.0-next.9 ./scripts/release-create-zudo-doc.sh
# → next version: 0.2.0 / pin string: ^0.2.0 (auto graduates a prerelease to clean)
DRY=1 FROM=0.2.0 ./scripts/release-create-zudo-doc.sh
# → next version: 0.2.1 / pin string: ^0.2.1 (auto from a stable = patch bump)
DRY=1 FROM=0.2.0 ./scripts/release-create-zudo-doc.sh minor
# → next version: 0.3.0 / pin string: ^0.3.0 (breaking 0.x change = minor bump)latest dist-tag (Scheme B — no bootstrap needed)
Under Scheme B the 0.x mainline ships clean X.Y.Z versions, which the publish workflow routes to latest automatically — there is no dual-tag probe to seed and no bootstrap step in the normal flow. scripts/ is retained only as a one-time remediation helper for a stranded latest (see RELEASE.md → "release-bootstrap-latest.mjs"). The standing fix for a stale latest is simply to ship a clean version.
Preconditions
Verify ALL of the following before proceeding. If any check fails, stop and tell the user.
Current branch is
mainWorking tree is clean (
git status --porcelainreturns empty)At least one
v*tag exists (git tag -l 'v*'). If none, ask the user to create the initial tag first (e.g.git tag v0.0.1 && git push --tags).
Find the latest version tag:
git tag -l 'v*' --sort=-v:refname | head -1Step 1 — Propose the new version
If the user passed an explicit version string (0.2.0, 1.0.0-next.1), use it directly. If the user passed a bump mode keyword (major, minor, patch, next, stable), pass it to the script. Otherwise:
git log <last-tag>..HEAD --onelineUse the conventional-commit prefix to propose the version, then separately classify each user-facing change by the published package(s) it affects. During 0.x (Scheme B — see "Version scheme") the major stays at 0 and a breaking change rides a minor bump, NOT a jump to 1.0.0:
Breaking changes (
feat!,BREAKING CHANGE) →minorbump (0.2→0.3)Everything else (
feat:,fix:, …) →patchbump
(Post-1.0, switch to standard SemVer: breaking → major, feat: → minor, otherwise → patch. The deliberate jump to 1.0.0 itself is always an explicit major / <semver> arg.)
The version category is not the release-note destination. Inspect each commit and its diff and assign its user-facing effect to one or more of these package owners:
| Changelog slug | Published package | Owns |
|---|---|---|
doc-history-server | @takazudo/zudo-doc-history-server | History server API, CLI, and runtime behavior |
zudo-doc | @takazudo/zudo-doc | Framework, integrations, plugins, and public package behavior |
create-zudo-doc | create-zudo-doc | Generator CLI and the projects it emits |
Duplicate a user-facing change into every affected package note when it spans two or three packages. Do not replace it with a vague shared summary.
Omit repository/showcase documentation, tests, CI, and maintenance changes when they do not affect a published package's users.
A lockstep package with no user-facing change still gets its own localized no-change entry in Step 3; all three packages are always released.
For an opt-in preview, propose the next keyword (or an explicit -next.N). To preview the computed version without touching any files, use the dry path:
DRY=1 ./scripts/release-create-zudo-doc.sh [<version>|<mode>]Announce the proposed version and proceed — do NOT wait for confirmation (the version is derived deterministically and everything through Step 8 is reversible). The single confirmation gate, if --confirm was passed, comes at Step 9 before the irreversible npm publish.
Step 2 — Run the release script
./scripts/release-create-zudo-doc.sh [<version>|<mode>]This script (sibling to version-bump.sh, does NOT modify it):
Computes the next version from the arg or auto-derives it (see "Version scheme" above)
Validates the version format (accepts prerelease suffixes)
Bumps
package.jsonat the rootBumps
packages/create- zudo- doc/ package. json Bumps
packages/(W4A — #1732)zudo- doc/ package. json Bumps
packages/(W4A — #1732)doc- history- server/ package. json Rewrites the
@takazudo/zudo-docand@takazudo/zudo-doc-history-serverpins inscaffold.tsto^<new-version>— including prerelease versions (e.g.^0.2.0-next.1) so a fresh downstream scaffold resolves the packages being releasedScaffolds one entry for each package under
src/content/ docs/ changelog/ {doc- history- server,zudo- doc,create- zudo- doc}/ <NEW_ VERSION>. mdx Scaffolds the matching three Japanese entries under
src/content/ docs- ja/ changelog/ {doc- history- server,zudo- doc,create- zudo- doc}/ <NEW_ VERSION>. mdx
Step 3 — Fill in the changelog
Replace the placeholder content in all six MDX files with the package-owned changes from Step 1. Each English/Japanese pair describes only its package. A cross-cutting change appears in each affected pair; repo/showcase/docs/CI-only work with no published package effect appears in none.
English (all three package slugs)
Apply this shape to:
src/content/ docs/ changelog/ doc- history- server/ <NEW_ VERSION>. mdx src/content/ docs/ changelog/ zudo- doc/ <NEW_ VERSION>. mdx src/content/ docs/ changelog/ create- zudo- doc/ <NEW_ VERSION>. mdx
---
title: <NEW_VERSION>
description: Release notes for <NEW_VERSION>.
sidebar_position: <value from script>
---
Released: <YYYY-MM-DD>
### Breaking Changes ← omit section if empty
- Description (commit-hash)
### Features ← omit section if empty
- Description (commit-hash)
### Bug Fixes ← omit section if empty
- Description (commit-hash)
### Other Changes ← omit section if empty
- Description (commit-hash)Japanese (all three package slugs)
Create the corresponding files under src/ and mirror the English package-specific content in Japanese. Sections: ### 破壊的変更, ### 機能, ### バグ修正, ### その他の変更. Use リリース日: <YYYY-MM-DD>.
When a package has no user-facing package change, replace all placeholder sections with exactly one bullet after the release date:
# English
- No package-specific changes.
# Japanese
- パッケージ固有の変更はありません。Do not omit an unchanged package entry: lockstep releases require three truthful notes.
Step 4 — Validate
Regenerate all three package Markdown changelogs from the completed English entries:
pnpm gen:changelogConfirm that packages/, packages/, and packages/ contain the new version and reflect their respective package note before running the full gate:
B4PUSH_SKIP_PIN_PUBLISHED=1 pnpm b4pushFix any failures and recommit before proceeding. Do not tag until b4push is fully green.
Do NOT bump the
@takazudo/zudo-doc-history-serverpeer floor during a minor or patch release — and not after publishing either. The release script intentionally leavespackages/zudo- doc/ package. json peerDependencies["@takazudo/zudo-doc-history-server"]alone: that floor is a minimum-supported-version declaration, not a lockstep mirror, and a minor or patch release preserves it unchanged. The pin-parity guard uses satisfies-semantics here, so a lag of any size (e.g. floor^5.17.2at release5.25.0) passes and only emits a non-fatal advisory — the lag is expected and is not a defect. Do not "fix" that advisory.A major release is the one exception. At a major bump the old floor no longer includes the root version, so
pnpm check:pin-parityerrors on it. Raise the floor and itsapprovedBaselineinscripts/by hand tocheck- pin- parity. mjs ^<new major>.0.0— after Step 2's script run, before this step'spnpm b4push, in the same release commit. If you runpnpm check:scaffold-pin-freshnessby hand during that window it reports the history-server peer aspeer-range-excludes-latestuntil the 6a publishes in Step 9 land on npm — contract-sanctioned, and it closes on publish. That gate is not a b4push step, and the publish workflow's Safeguard 4/5 runs after the 6a publishes, so neither goes red. One exception: Safeguard 4/5 runs ondry_runtoo, so a manualpublish-create-zudo-doc.ymldry run fired inside the window fails on this finding — dry-run after the 6a publishes, not before.The full contract, including the only reasons the floor may ever move, lives in RELEASE.md → "First-party peer floor (publish-lag)"; it is deliberately not restated here.
Step 5 — Commit
Stage and commit all changed files:
git add package.json \
packages/create-zudo-doc/package.json \
packages/zudo-doc/package.json \
packages/doc-history-server/package.json \
packages/create-zudo-doc/src/scaffold.ts \
src/content/docs/changelog/doc-history-server/<NEW_VERSION>.mdx \
src/content/docs/changelog/zudo-doc/<NEW_VERSION>.mdx \
src/content/docs/changelog/create-zudo-doc/<NEW_VERSION>.mdx \
src/content/docs-ja/changelog/doc-history-server/<NEW_VERSION>.mdx \
src/content/docs-ja/changelog/zudo-doc/<NEW_VERSION>.mdx \
src/content/docs-ja/changelog/create-zudo-doc/<NEW_VERSION>.mdx \
packages/doc-history-server/CHANGELOG.md \
packages/zudo-doc/CHANGELOG.md \
packages/create-zudo-doc/CHANGELOG.md
# Add any formatting fixes from b4push:
git diff --name-only | xargs -r git add
git switch -c release/<NEW_VERSION>
git commit -m "chore: bump create-zudo-doc to v<NEW_VERSION>"Step 6 — Release PR, then wait for CI
A direct push to main runs no A2 job, so the release commit goes through a PR to main (as #4524 did). See RELEASE.md, "Release-candidate CI: A2 must execute, not just pass".
git push -u origin release/<NEW_VERSION>
gh pr create --base main --title "release: v<NEW_VERSION>" --body "<summary>"Follow the evidence procedure in RELEASE.md, "Release-candidate CI: A2 must execute, not just pass", before merging or tagging:
Freeze and record the release candidate head SHA/tree and the successful PR run's base SHA. Require that run's
head_shato equal the candidate. Record its URL, ID, attempt, required checks, A2 job ID/URL and same-run/attempt diagnostics artifact ID/digest. Preserve the artifact before expiry.Confirm
A2 decision: RUNandA2 execution: EXECUTED and passed, a real test-step duration and passing vitest output. Read the actual checkout SHA from the diagnostic manifest'ssourceHeadand job checkout log, resolve its Git tree, and verify its parents identify the recorded base/candidate. Default checkout tests the synthetic PR merge; the run'shead_shaalone does not identify the tested tree.If the candidate/base changes the integration before merge, synchronize and rerun current PR checks with A2 executed. Re-freeze the evidence; never substitute an old green run. Repair failures on the still-open PR. Rebaseline only with fresh hosted causal evidence and review, preserving strict normalization.
Merge under the repository's normal policy, then freeze the accepted main integration SHA from the PR merge result as
RELEASE_INTEGRATION_SHA. Require its tree to equal the actual A2 checkout tree and verify it belongs to main's accepted history, using the commands inRELEASE.md. This integration SHA, not the release branch head, is the canonical tag target for every package.Enumerate all applicable main workflows triggered at that exact integration SHA, including Production Deploy, and record successful runs/attempts and job conclusions. Filter by the full SHA;
gh run list --branch main --limit 1is insufficient. Pending, missing, failed or cancelled applicable runs block tags. Do not manually dispatch a deployment as verification.
If an unchanged transient main CI failure occurs, inspect its failed logs and rerun at that same recorded SHA, retaining the successful attempt. After merge, any source repair requires a new corrective PR against refreshed main, fresh PR checks with A2 executed, and an accepted corrected integration with matching tree and successful applicable main workflows. A tested/accepted tree mismatch also stops tagging and requires that corrective PR evidence chain. Pushing fixes to the already-merged release branch is not acceptance. Keep the intended release contents/version in the corrected integration and update the frozen tag target.
Do not create any of the three tags until this entire chain is verified. If main advances later, retain the verified frozen integration target; never silently tag the new main tip or HEAD.
Step 7 — Tag all three packages (lockstep)
Use the full RELEASE_INTEGRATION_SHA recorded and verified in Step 6 for all three tags. Confirm its tree equality and exact-SHA main workflow evidence before the first tag; do not resolve a moving ref at tag time.
The release script bumps all four package.json versions in lockstep, and create-zudo-doc's generated scaffold pins @takazudo/zudo-doc@^<ver> and @takazudo/zudo-doc-history-server@^<ver>. So all three publishable packages MUST be released at the same version every time — if you tag/publish only create-zudo-doc, a fresh downstream scaffold's pnpm install 404s on the unpublished @takazudo/* pin. Always create all three tags (the old "only if its source changed" guidance is wrong for the lockstep model):
git tag zudo-doc-history-server-<NEW_VERSION> "$RELEASE_INTEGRATION_SHA"
git tag zudo-doc-v<NEW_VERSION> "$RELEASE_INTEGRATION_SHA"
git tag v<NEW_VERSION> "$RELEASE_INTEGRATION_SHA"
git push origin \
zudo-doc-history-server-<NEW_VERSION> \
zudo-doc-v<NEW_VERSION> \
v<NEW_VERSION>The three tag namespaces are distinct so each fires exactly one publish workflow: zudo-doc-history-server-*, zudo-doc-v*, and the bare v* (create-zudo-doc).
Step 8 — Create three DRAFT GitHub releases (one per package)
Extract each package's English changelog body independently (strip the YAML frontmatter), then create a DRAFT release for each of the three tags. Never reuse one shared notes value across tags. Title each with the package name + bare version to match the existing release history — NOT the raw tag (create-zudo-doc 0.2.0-next.4, not v0.2.0-next.4). Mark every prerelease (-next / -beta / -rc) with --prerelease:
| Tag | --title (package + bare version) |
|---|---|
zudo-doc-history-server-<NEW_VERSION> | @takazudo/zudo-doc-history-server <NEW_VERSION> |
zudo-doc-v<NEW_VERSION> | @takazudo/zudo-doc <NEW_VERSION> |
v<NEW_VERSION> | create-zudo-doc <NEW_VERSION> |
HISTORY_NOTES=$(awk 'BEGIN{f=0} /^---$/{f++; next} f>=2' \
src/content/docs/changelog/doc-history-server/<NEW_VERSION>.mdx)
ZUDO_DOC_NOTES=$(awk 'BEGIN{f=0} /^---$/{f++; next} f>=2' \
src/content/docs/changelog/zudo-doc/<NEW_VERSION>.mdx)
CREATE_NOTES=$(awk 'BEGIN{f=0} /^---$/{f++; next} f>=2' \
src/content/docs/changelog/create-zudo-doc/<NEW_VERSION>.mdx)
# --prerelease for any version containing a hyphen (next/beta/rc); empty for stable.
PRE=""; case "<NEW_VERSION>" in *-*) PRE="--prerelease";; esac
gh release create "zudo-doc-history-server-<NEW_VERSION>" \
--title "@takazudo/zudo-doc-history-server <NEW_VERSION>" \
--notes "$HISTORY_NOTES" --draft $PRE
gh release create "zudo-doc-v<NEW_VERSION>" \
--title "@takazudo/zudo-doc <NEW_VERSION>" \
--notes "$ZUDO_DOC_NOTES" --draft $PRE
gh release create "v<NEW_VERSION>" \
--title "create-zudo-doc <NEW_VERSION>" \
--notes "$CREATE_NOTES" --draft $PREThe --draft flag is critical on all three. A draft release does NOT trigger its publish workflow. Before proceeding, inspect the three drafts and verify each body matches the package and tag in the table; an unchanged package must show its explicit no-change entry rather than another package's narrative.
Step 9 — Publish the three releases to npm (autonomous by default)
This is the only irreversible step (npm's unpublish lock kicks in after 72 h). Flipping a GitHub release from draft → published (gh release edit <tag>
--draft=false) fires that package's publish-*.yml workflow, which runs the actual npm publish.
--confirm gate (only when the flag was passed): before publishing, show the user what is about to ship and ask once. Default (no --confirm) skips this and publishes immediately.
About to publish <NEW_VERSION> to npm — IRREVERSIBLE:
1. @takazudo/zudo-doc-history-server — <one-line package-note summary>
2. @takazudo/zudo-doc — <one-line package-note summary>
3. create-zudo-doc — <one-line package-note summary>
Correct — publish now? (yes / no) Proceed only on an affirmative answer; otherwise STOP and leave the three drafts in place for the user to publish later.
Publish in dependency order, one at a time, verifying each is live on npm before the next. create-zudo-doc's scaffold pins the other two at <NEW_VERSION>, so an out-of-order publish 404s a fresh downstream install. For each package:
gh release edit <tag> --draft=false --prerelease(drop--prereleasefor a stable version — but the npm dist-tag is derived from the tag name regardless: hyphen →next, no hyphen →latest).Watch the triggered workflow run to success (a background watcher on the newest
release-event run of that workflow file works well). If it fails, STOP and report — do NOT publish the next package.Confirm the version is live: poll
npm view <pkg>@<NEW_VERSION> versionuntil it returns, before moving to the next package.
# Order: history-server → zudo-doc → create-zudo-doc.
# Per package: flip draft→published, watch publish-*.yml to success, verify on npm.
# 1) @takazudo/zudo-doc-history-server
gh release edit "zudo-doc-history-server-<NEW_VERSION>" --draft=false --prerelease
# watch publish-zudo-doc-history-server.yml run to success, then:
until npm view @takazudo/zudo-doc-history-server@<NEW_VERSION> version 2>/dev/null; do sleep 10; done
# 2) @takazudo/zudo-doc
gh release edit "zudo-doc-v<NEW_VERSION>" --draft=false --prerelease
# watch publish-zudo-doc.yml run to success, then:
until npm view @takazudo/zudo-doc@<NEW_VERSION> version 2>/dev/null; do sleep 10; done
# 3) create-zudo-doc (LAST — its pins now resolve)
gh release edit "v<NEW_VERSION>" --draft=false --prerelease
# watch publish-create-zudo-doc.yml run to success, then:
until npm view create-zudo-doc@<NEW_VERSION> version 2>/dev/null; do sleep 10; doneStep 10 — Report
Confirm all three are live and report the final state:
for p in @takazudo/zudo-doc-history-server @takazudo/zudo-doc create-zudo-doc; do
npm view "$p" dist-tags --json
doneEach should show <NEW_VERSION> under latest for a clean 0.x release (the normal Scheme B path), or under next for an opt-in prerelease (with latest left untouched). Report the three release URLs and the published versions. The skill ends here — the release is live on npm.
(Optional but recommended for confidence: a fresh-scaffold smoke — pnpm dlx create-zudo-doc@<NEW_VERSION> <dir> --yes ... then pnpm install — proves every published pin resolves from npm. See the prior release session for the pattern.)
dist-tag behavior: latest and next
Each package is published to npm with a dist-tag determined by the version string. CI reads the tag name and selects the tag automatically — the maintainer does NOT specify --tag manually. Publishing the DRAFT is the only irreversible step.
| Tag pattern | npm dist-tag | Who resolves it |
|---|---|---|
v1.2.3 | latest | pnpm install create-zudo-doc |
v1.2.3-next.1 | next | pnpm install create-zudo-doc@next |
v1.2.3-beta.2 | next | pnpm install create-zudo-doc@next |
v1.2.3-rc.3 | next | pnpm install create-zudo-doc@next |
Clean versions (no -) publish under latest; prereleases (any tag containing a -) publish under next. There is no dual-tag probe — see RELEASE.md. Under Scheme B the 0.x mainline ships clean 0.MINOR.PATCH, so each release moves latest to the newest build and a tagless npm install / pnpm dlx always gets it. -next is reserved for opt-in previews and the 1.0.0-beta run-up.
next is removed on stable graduation. Each publish workflow runs an extra step after a clean X.Y.Z publish that removes the next dist-tag (npm dist-tag rm <pkg> next). This prevents @next from silently resolving to a stale prerelease once the line graduates. The step is idempotent and non-fatal. See RELEASE.md — "One-time manual cleanup" — for the manual runbook that clears the currently-stale next tag (frozen at 0.2.0-next.9 as of #2121).
Files involved (pin sources)
The release script writes to ALL of these in one pass:
| File | Role |
|---|---|
scripts/ | Version bump + changelog scaffold (run in Step 2) |
. | CI publish workflow for create-zudo-doc (fires on v* draft publish) |
. | CI publish workflow for @takazudo/zudo-doc (fires on zudo-doc-v* draft publish) — W4A (#1732) |
. | CI publish workflow for @takazudo/zudo-doc-history-server (fires on zudo-doc-history-server-* draft publish) — W4A (#1732) |
package.json (root) | Root version (kept in lockstep) |
packages/ | Generator package version (must match the v<X.Y.Z> git tag) |
packages/ | zudo-doc framework package version (must match the zudo-doc-v<X.Y.Z> git tag) — W4A (#1732) |
packages/ | doc-history-server package version (must match the zudo-doc-history-server-<X.Y.Z> git tag) — W4A (#1732) |
packages/ | @takazudo/zudo-doc: ^<version> pin in the generated downstream package.json; bumped in lockstep with zudo-doc by the release script — W4A (#1732) |
Publish ORDER matters (W4A — #1732)
Step 9 publishes the three releases in this order automatically (the CREATE order in Steps 7–8 doesn't matter; the PUBLISH order does). This section is the rationale and the manual fallback. create-zudo-doc's generated package.json pins @takazudo/zudo-doc: ^<version> and @takazudo/zudo-doc-history-server: ^<version>, so those two must be live on npm before create-zudo-doc is published — otherwise a fresh scaffold's pnpm install 404s on the unpublished pin.
All three are released at the same version every time (lockstep — the release script bumps all four package.json files together), so this is always all three, never a subset. The order (Step 9 follows it; replicate it if publishing by hand), waiting for each to appear on npm before the next:
Publish
@takazudo/(tagzudo- doc- history- server <X. Y. Z> zudo-doc-history-server-<X.Y.Z>).Publish
@takazudo/(tagzudo- doc <X. Y. Z> zudo-doc-v<X.Y.Z>).After #1 and #2 are live on npm, publish
create-zudo-doc <X.Y.Z>(tagv<X.Y.Z>).
Each draft-publish fires its own dedicated workflow (publish-zudo-doc-history-server.yml, publish-zudo-doc.yml, publish-create-zudo-doc.yml; concurrency groups are distinct). Verify each on npm with npm view <pkg>@<X.Y.Z> version before publishing the next.