Skip to main content
ClaudeWave
Skill81 estrellas del repoactualizado today

han-release

>

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/testdouble/han /tmp/han-release && cp -r /tmp/han-release/.claude/skills/han-release ~/.claude/skills/han-release
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

## Pre-requisites

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

**If `gh` or `jq` is MISSING, 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 three.

## 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`
- default branch: !`git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##' || echo unknown`
- working tree: !`git status --porcelain`
- 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 release tag: !`git fetch --tags --quiet >/dev/null 2>&1; git tag -l 'v*.*.*' --sort=-v:refname | head -n1`
- changelog head: !`grep -m1 '^## v' CHANGELOG.md 2>/dev/null`

### 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 git tag tracks the parent's version, so the release tag is `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 `v{parent target}`.
- `prev` is the `latest release tag` (for example `v2.7.0`; the number without the leading `v` is `prev#`). On the first release `prev` is empty.
- Each plugin's source directory comes from the `source` field in `marketplace.json` (for example `./han.core`), so its `plugin.json` is `{source}/.claude-plugin/plugin.json`. Use `{source}` verbatim in every git command: the `./`-prefixed form works both after a `{ref}:` colon (`git show {prev}:{source}/...`) and as a pathspec (`git diff ... -- {source}/`). Do not strip the leading `./`.

## Step 1: Parse the invocation and check release safety

1. **Parse `$ARGUMENTS`** for two independent flags, then treat the remaining free text as optional release context that informs the changelog narrative:
   - `pause_before_publish` — true if the argument contains "pause", "review", or "confirm before publish" (case-insensitive). Default **false**.
   - `draft_release` — true if the argument contains "draft". Default **false**.
   - The leftover text (anything that is not those flag phrases) is `$release_context`, passed into the narrative dispatch in Step 5. May be empty.

2. **Working tree must be clean.** If `working tree` from Project Context is non-empty, there are uncommitted or untracked changes. Stop and tell the operator to commit or stash them first. Releasing an unknown working state is unsafe and a pushed tag is hard to reverse. This is a hard stop, not a pause gate.

3. **Branch note (non-blocking).** If `current branch` is not the `default branch`, do not stop — note in the Step 7 summary that the release is being cut from `current branch` and the tag will point at that branch's `HEAD`. The operator chose autonomous; surface the fact, do not block.

## Step 2: Determine previous version, commit range, and PR list

1. **`prev`** is `latest release tag`. If it is empty, this is the **first release**: there is no previous tag, the commit range is the full history, all compare links are omitted, and every plugin is treated as new (Step 3).

2. **Commit range.** With a previous tag: `${prev}..HEAD`. First release: the full history (`HEAD` with no range base).

3. **Nothing to release check.** Run `git log {range} --oneline`. If it is empty, there are no commits since `prev`. Stop and tell the operator there is nothing to release.

4. **Collect merged PRs in the range.** Extract PR numbers from both squash subjects and merge commits:

   ```
   git log {range} --pretty=%s%x00%b | grep -oE '#[0-9]+' | tr -d '#' | sort -un
   ```

   For each number `N`, run `gh pr view N --json number,title,author,url,mergedAt,state`. Keep only entries where `state` is `MERGED`. Sort the survivors by `mergedAt` ascending (newest merge last). This is `$pr_list`. The PR list is repo-wide and appears once per release; it is not split per plugin. Build the PR lines and the changelog bullets per [references/release-notes-format.md](./references/release-notes-format.md) and [references/changelog-rules.md](./references/changelog-rules.md).

5. **No-PR fallback.** If `$pr_list` is empty (local-only or squash history with no PR refs), record the notable commit subjects from `git log {range} --oneline` instead, and use the commits form documented in both reference files.

6. **Collect closed issues and their attribution.** For each merged PR `N` in `$pr_list`, find the issues that PR closed and credit everyone involved. This relates each closed issue to the fix that resolved it.

   - **Find the closed issues for the PR.** Take the issue numbers from `gh pr view N --json closingIssuesReferences --jq '[.closingIssuesReferences[]?.number]'` (the GitHub-tracked closing links). As a fallback for older PRs that linked via text, also scan the PR body and commit messages for GitHub closing keywords: `gh pr view N --json body,commits --jq '[.body, (.commits[].messageBody