From genvid-dev
Cut a ROUTINE release of a package that ALREADY publishes to npmjs.com via the shared genvid-public-ci publish.yml OIDC recipe — bump, commit, tag, and trigger the publish workflow. The TAG push is what triggers publishing; there is no manual npm publish step. Reach for it whenever someone wants to ship a new version of an npm package that is already wired for OIDC publishing. Trigger on requests like "release the package", "cut a new version", "ship 0.2.0", "ship a patch", "bump and tag", "publish the new version to npm", or "push the release tag". It owns the whole job — state assessment and classification, version decision, the version bump commit, CHANGELOG move, tag creation, CI gate, and publish-run hand-off. Do NOT use it for: one-time publish setup where publish.yml is absent or not the v*.*.* OIDC recipe (use publish-npm-package instead; it owns that setup); releasing a marketplace plugin (use release-plugin); publishing to a private or internal registry (Azure Artifacts, GitHub Packages, etc.); or non-release work like adding a feature without shipping a version.
How this skill is triggered — by the user, by Claude, or both
Slash command
/genvid-dev:release-npm-packageThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill cuts a **routine** release of a package that already publishes to
This skill cuts a routine release of a package that already publishes to
npmjs.com via the shared genvid-holdings/genvid-public-ci GitHub Actions
publish.yml recipe. "Routine" means the OIDC wiring is already in place — the
package has a .github/workflows/publish.yml triggered on v*.*.* tags with
id-token: write and no stored npm token.
The tag push — not the branch push — is the publish trigger. There is no
manual npm publish step; the skill's job is to get the in-repo git work right
(version bump, commit, tag), push the tag, and hand off watching the resulting
workflow run. A branch push alone does nothing to npm.
Use it when a package is already wired for OIDC publishing and a new version needs shipping. Signals: "release the package", "cut a version", "ship 0.2.0", "push the release tag", "bump and tag".
It is not for:
publish.yml absent, not in the v*..-triggered
OIDC shape, or the package name was never published. Use publish-npm-package.release-plugin.Before doing anything else, assert that .github/workflows/publish.yml exists
and matches the genvid-public-ci OIDC shape: triggered on push: tags: v*.*.*, has id-token: write, and has no step that reads an npm token
secret.
Fetch the canonical template to compare shape (trigger + OIDC block only — not a byte diff):
gh api repos/genvid-holdings/genvid-public-ci/contents/templates/publish.yml \
--jq .content | base64 -d
If publish.yml is absent or does not match this shape → STOP. Tell the
user this is a setup task, not a routine release, and redirect to
publish-npm-package.
A correct release keeps these facts consistent — the npm release triangle:
vX.Y.Z and the tag string minus its leading v equals
package.json version at the tagged commit. Assert this BEFORE pushing
the tag — catching a mismatch here costs nothing; catching it after costs a
version number.package.json version, and both
occurrences in package-lock.json (version at root and
packages[""].version). All three must carry the same value.vX.Y.Z is not an
existing tag. A half-failed prior publish → bump to next patch, never retry
the same version.main/types/exports stay top-level (not moved into
publishConfig). Run npm pack and inspect the packed manifest only if the
commit range touched package.json main/types/exports/files/
publishConfig or tsconfig — otherwise skip and say so.CHANGELOG.md, it must have a dated [X.Y.Z] section
for the version being released before the tag is pushed.Never reason about release state from a local checkout you haven't synced. Start every run with:
git fetch origin --tags --prune
You were probably invoked from the just-merged feature branch. Releasing
right after a PR merges is the common case, so the working checkout is often the
feature branch — which a squash-merge may have already deleted on the remote
(git status shows [gone]). Get onto an up-to-date default branch before
classifying: git checkout <default> && git merge --ff-only origin/<default>.
Otherwise Phase 3 prepares the release on the wrong ref.
Then gather these signals:
git rev-parse origin/<default>; local tip: git rev-parse HEAD; working tree: git status -sb.package.json version: git show origin/<default>:package.json | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>console.log(JSON.parse(d).version))".v* tag: git for-each-ref --sort=-creatordate --count=1 --format='%(refname:short)' 'refs/tags/v*'; whether it is on the default
branch: git merge-base --is-ancestor <tag> origin/<default>.npm view <name> versions --json; latest: npm view <name> version.CHANGELOG.md has an [Unreleased] section.Classify into exactly one state and echo it to the user:
| State | How to recognize it | Action |
|---|---|---|
| needs-bump | Clean, up to date; remote package.json version == latest tag (and published on npm) | Normal path: Phase 2 picks next version, Phase 3 bumps + commits, Phase 5 tags |
| version-already-landed | Remote package.json version is ahead of the latest v* tag AND that version is not yet published on npm | SKIP Phase 3; confirm 3 manifest spots carry the bumped version; Phase 5 tags the existing commit |
| local-stale-ff | Working tree clean; local is behind origin/<default> with no divergent commits (fast-forwardable) | Not a broken release. Offer git merge --ff-only origin/<default>; re-run Phase 1 |
| first-release | No prior v* tags AND (name unpublished OR current version unpublished) | Skip prior-tag diffs; default lightweight tag + note it. BUT if the name was never published at all → STOP, redirect to publish-npm-package |
| already-published | Target version already in npm view <name> versions --json OR already has a matching vX.Y.Z tag | REFUSE. Tell user to pick the next free version. A half-failed prior publish → bump to next patch |
Refusal guard: before accepting any X.Y.Z, assert that vX.Y.Z is not an
existing tag and X.Y.Z is not in npm view <name> versions --json. State
the chosen version explicitly.
Version decision (in priority order):
Explicit override wins. If the user named a version, validate it against the refusal guard, then use it.
Infer from the commit range:
git log --oneline <last-tag>..origin/<default>
New feature → minor. Fixes or internal changes → patch. Breaking change → major.
0.x advisory. At 0.x a breaking public-API change (removing or renaming an export from the entry point) takes a minor bump, not a major — surface it, confirm with the user, never auto-decide a 0.x major.
In version-already-landed: read the version from the merged
package.json; do not infer.
SKIP this entire phase in the version-already-landed state.
Steps:
Bump package.json version. On a confirmed single-package repo (no
workspaces field in package.json, no pnpm-workspace.yaml), run:
npm version <bump-type-or-explicit-version> --no-git-tag-version
--no-git-tag-version prevents npm from committing or tagging. Re-read
package.json after to confirm all three manifest spots carry the new
version.
Workspace guard: if workspaces is present in package.json or
pnpm-workspace.yaml exists, do NOT run npm version — fall back to
manually editing the three spots in the root manifest and ask the user to
confirm member manifests. Do not auto-traverse workspace members.
CHANGELOG. If the package has a CHANGELOG.md with an [Unreleased]
section, move its content into a new dated ## [X.Y.Z] - <today> section
(Keep a Changelog format), leaving an empty ## [Unreleased] above it.
Validate. If commands.validate is present in .genvid-agent.json, run
it now.
Release commit. Show the diff; collect a single review confirmation. Read
the consuming repo's CLAUDE.md for commit format; default to
chore: Release X.Y.Z. Stage package.json, package-lock.json, and
CHANGELOG.md (if modified).
Present ONE "here's the plan, proceed?" gate covering all non-irreversible steps. The tag push gets its own separate hard confirm.
No .github/workflows/ files → report "no CI configured, skipping" and
continue.
PR was opened (protected-branch path) → gh pr checks <pr> must be green
before tagging.
Otherwise check the release commit directly:
gh api repos/<org>/<repo>/commits/<sha>/check-runs \
--jq '.check_runs[] | {name, status, conclusion}'
Require every concluded run to be success, neutral, or skipped. Treat
"no checks reported" as pass-with-warning, not failure. Tag only after the
release commit's checks are green.
A red default branch is a stop sign; absence of CI is not.
git for-each-ref --sort=-creatordate --count=1 \
--format='%(refname:short)' 'refs/tags/v*'
Then check whether the most recent v* tag is annotated or lightweight:
git cat-file -t <tag> # "tag" → annotated, "commit" → lightweight
No prior v* tags → default to lightweight and note it. Echo the detected
convention to the user.
Land the release commit on the default branch.
git push origin <default>.create-pr skill, and
tag the squash-merge commit after the PR lands.CI gate (Phase 4, on the landed commit).
Assert tag == version BEFORE tagging. Confirm the tag string minus v
equals package.json version at the tip of origin/<default>.
Create and push the tag. This step gets its own hard confirm.
# Annotated (if that is the detected convention):
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.Z
# Lightweight (if that is the detected convention):
git tag vX.Y.Z
git push origin vX.Y.Z
Push the branch FIRST, then the tag. The tag push — not the branch push —
triggers publish.yml.
Watch the publish run and verify.
gh run list --workflow=publish.yml --limit=5 # find the triggered run
gh run view <run-id> # monitor progress
npm view <package-name> version # confirm after the run
Do NOT block-poll the async publish. Report the gh run command and the
npm view verification command; the user can run them to track progress.
Optional packaging verification. Run npm pack and inspect the packed
manifest only if the commit range touched package.json main/types/
exports/files/publishConfig or tsconfig. Otherwise skip and say so.
origin is the most common false
"broken release". Phase 1 fetches and classifies before judging anything.package.json version is the most common release error. Catching it before
the tag push costs nothing; catching it after costs a version number.npm version --no-git-tag-version only on confirmed single-package repos.
Running npm version in a workspace repo bumps only the root manifest and
produces a wrong or misleading state. The workspace guard keeps the skill safe
in monorepo setups.publishConfig overrides for main/types/
exports are not applied by npm 11.x; packaging verification catches this
before it reaches consumers.Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub genvid-holdings/claude-code-marketplace --plugin genvid-dev