Skip to content
Development
Skill

/han-release

Cut a Han release: update CHANGELOG.md with the changes since the last release, bump and tag every plugin that changed as {plugin-name}--v{version} so a version-constrained dependency can resolve, and publish a GitHub release crediting every merged pull request and closed issue

From plugin
han
26345 skills25 agents
Install
$ npx -y skills add testdouble/han --skill han-release --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/han-release

Context preview

The summary Claude sees to decide when to auto-load this skill.

Cut a Han release: update CHANGELOG.md with the changes since the last release, bump and tag every plugin that changed as {plugin-name}--v{version} so a version-constrained dependency can resolve, and publish a GitHub release crediting every merged pull request and closed issue

SKILL.md

han-release.SKILL.md
name: han-release
description: >
  Cut a Han release: update CHANGELOG.md with the changes since the last release, bump and tag every plugin that changed
  as {plugin-name}--v{version} so a version-constrained dependency can resolve, and publish a GitHub release crediting
  every merged pull request and closed issue to the people behind it. Use when releasing, cutting a release, shipping a
  new Han version, publishing release notes, or tagging a version. Always stops for approval before creating any tag,
  because a pushed tag is never moved. Requires the gh CLI, jq, the claude CLI, and a clean git checkout. This is a
  repository-maintenance skill for the Han repo itself, not a general review or PR skill — use code-review for local
  review, post-code-review-to-pr to post a PR review, and update-pr-description for PR bodies.
argument-hint: "[pause before publishing] [draft] [optional release context]"
allowed-tools:
  Read, Edit, Write, Glob, Grep, Agent, Bash(git *), Bash(gh *), Bash(jq *), Bash(which *), Bash(grep *), Bash(sed *),
  Bash(head *), Bash(command claude *)

<!-- `AskUserQuestion` is deliberately absent from `allowed-tools`, and must stay absent. Listing it makes Claude Code's permission evaluator auto-approve the tool through its always-allow path and return empty answers without ever rendering the prompt, so every gate in this skill would silently pass. See han-plugin-builder/skills/guidance/references/skill-building-guidance/allowed-tools-AskUserQuestion.md. The tool still works unlisted; it prompts once for permission. -->

Pre-requisites

  • gh CLI: !`which gh 2>/dev/null || echo "not installed"`
  • jq: !`which jq 2>/dev/null || echo "not installed"`
  • claude CLI: !`which claude 2>/dev/null || echo "not installed"`
  • git repo: !`git rev-parse --is-inside-work-tree 2>/dev/null || echo NO`

**If `gh`, `jq`, or `claude` reads `not installed`, or this is not a git repo:** tell the operator which prerequisite is missing and that it must be installed/configured before `/han-release` can run, then **immediately stop**. The skill cannot proceed without all four.

The `claude` CLI is what creates the per-plugin tags in Step 10. Every invocation of it in this skill goes through the shell's `command` builtin (`command claude ...`), never a bare `claude`, because an operator's shell commonly wraps `claude` in a function or alias that blocks waiting for terminal input. The `which` probe above resolves the same way a bare call would, so it reports the wrapper's presence rather than the executable's; treat a non-empty result as "the tool is reachable" and let Step 10's first invocation be what proves it runs.

Project Context

  • repo: !`gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || git config --get remote.origin.url`
  • current branch: !`git branch --show-current 2>/dev/null || echo unknown`
  • default branch: !`git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##' || echo unknown`
  • working tree: !`git status --porcelain 2>/dev/null || echo NO`
  • parent plugin name: !`jq -r .name .claude-plugin/marketplace.json 2>/dev/null`
  • plugins (name source version):

!`jq -r '.plugins[] | "\(.name)\t\(.source)\t\(.version)"' .claude-plugin/marketplace.json 2>/dev/null`

  • latest parent tag: !`git fetch --tags --quiet >/dev/null 2>&1; git tag -l 'han--v*' --sort=-v:refname | head -n1`
  • latest suite tag: !`git tag -l 'v*.*.*' --sort=-v:refname | head -n1`
  • changelog head: !`grep -m1 '^## v' CHANGELOG.md 2>/dev/null`

The two tag probes are separate on purpose. A single pattern covering both namings returns the **wrong** tag: version sorting compares the whole refname, so `v4.6.0` sorts ahead of `han--v5.0.0` and the old suite tag wins on every release after the transition. The fetch runs on the first probe only; both read the same refreshed tag list.

`latest parent tag` carries the literal `han--v*` pattern, because a context-injection command is a fixed string and cannot interpolate `parent plugin name`. If `parent plugin name` is not `han`, redo the lookup in Step 2 with the actual name before using either value. Skipping that turns a renamed parent into an empty probe, which Step 2 would read as a first release and silently expand the changelog to the whole repository history.

Vocabulary used throughout this skill

  • **parent** — the meta-plugin whose name equals the marketplace `name` (`parent plugin name` above, normally `han`). It

has no skills or agents of its own; it exists to install the children via `dependencies`. The parent's own per-plugin tag is the one the GitHub release attaches to, so the release tag is `{parent plugin name}--v{parent target}`.

  • **children** — every other entry in `marketplace.json.plugins[]` (`han-core`, `han-github`, `han-reporting`, and any

future `han-*` plugin). Each child has its own version line, bumped independently of the others.

  • **baseline** of a plugin — its version at `prev` (the latest release tag). For the parent this is `prev#`. For a child

it is the version recorded in that child's `plugin.json` at `prev`; if the child did not exist at `prev`, it is a **new plugin** (see Step 3).

  • **current** of a plugin — the version in its working-tree `plugin.json`.
  • **target** of a plugin — the version being released for it. The release tag is

`{parent plugin name}--v{parent target}`.

  • **tag name** of a plugin — `{name}--v{target}`, for example `han--v5.0.0` and `han-core--v3.0.0`. Every plugin gets

one, and the parent's is what the GitHub release attaches to.

  • `prev` is the previous release's tag, resolved in Step 2 from the two tag probes. On the first release it is empty.
  • `prev#` is **the part of `prev` after its last `v`**. That rule is correct under both namings, which matters because

`prev` can be either shape:

  v4.6.0        -> prev# is 4.6.0
  han--v5.0.0   -> prev# is 5.0.0

Do not read `prev#` as "the number without the leading `v`". That

Read more
Ships withhan

Han is a suite of AI skills and agents for solo (or small-team) product engineers.

Get the whole plugin

Other skills on han.