Skip to main content
ClaudeWave
Skill1.5k repo starsupdated 3d ago

release-notes

Generate and publish concise, evidence-based notes in the body of the latest existing GitHub Release. Use only when the user explicitly invokes `$release-notes` or explicitly asks to update the latest existing GitHub Release body. Do not invoke for general release planning, changelog, tag, or version tasks.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/kenryu42/cc-safety-net /tmp/release-notes && cp -r /tmp/release-notes/.claude/skills/release-notes ~/.claude/skills/release-notes
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Release Notes

Update only the body of the latest existing GitHub Release. Treat the repository diff as the source of truth. Use pull requests and commits only as supporting evidence.

## Safety rules

- Use `gh` for all GitHub Release, pull request, issue, and API operations. Use `git fetch` only when required tag objects or history are missing.
- Do not create or delete a release.
- Do not change a tag, title, target, draft state, prerelease state, latest-release state, or other release metadata.
- Do not publish a draft.
- Do not use a new release as a fallback when no release exists.
- Do not infer release order from version text, semantic versions, or Git tag order.
- Stop when required evidence is unavailable. Do not publish partial or speculative notes.
- Treat the notes file as a full replacement for the current release body, not as text to append.

## 1. Verify prerequisites

Run all checks from the repository that the user wants to release:

1. Confirm that the current directory is in a Git worktree:

   ```sh
   git rev-parse --is-inside-work-tree
   ```

2. Confirm that `gh` is available:

   ```sh
   command -v gh
   ```

3. Confirm that `gh` can resolve and access the current GitHub repository. Save its URL and get the GitHub host from it:

   ```sh
   REPO_URL="$(gh repo view --json url --jq '.url')"
   GH_HOSTNAME="${REPO_URL#*://}"
   GH_HOSTNAME="${GH_HOSTNAME%%/*}"
   ```

4. Use the repository URL to get its GitHub host. Confirm the active account for that host:

   ```sh
   gh auth status --active --hostname "$GH_HOSTNAME"
   ```

Stop with a clear error if a check fails. Do not initialize a Git repository, change authentication, or select another repository as a fallback.

## 2. Select the release range

Use GitHub's descending release-list order as the release sequence. Include drafts and prereleases because they are existing releases:

```sh
gh release list --limit 2 --order desc \
  --json tagName,name,createdAt,publishedAt,isDraft,isPrerelease,isImmutable,isLatest
```

- Select the first item as the latest release.
- Select the second item as the previous release.
- If the list is empty, stop and state that no existing GitHub Release was found. Never create one.
- If there is one item, treat it as the project's first release.
- Do not re-sort the result. A draft can have no `publishedAt` value.
- Do not replace either tag with a Git tag that has no GitHub Release.

Set `LATEST_TAG` and, when present, `PREVIOUS_TAG` from this result. Quote both values in every command. Then load and record the latest release before any edit:

```sh
gh release view \
  --json tagName,name,body,isDraft,isPrerelease,isImmutable,publishedAt,targetCommitish,url \
  -- "$LATEST_TAG"
```

Confirm that the returned `tagName` is `LATEST_TAG`. Record `tagName`, `name`, `isDraft`, `isPrerelease`, `isImmutable`, and `targetCommitish` from this result. Also record `isLatest`, `createdAt`, and `publishedAt` from the release-list result. Use these values for the final safety check. If `isImmutable` is true, stop and report that GitHub does not permit the body update.

## 3. Resolve the tag history

Set full Git refs so that a tag that starts with `-` cannot become a command option:

```sh
LATEST_REF="refs/tags/$LATEST_TAG"
PREVIOUS_REF="refs/tags/$PREVIOUS_TAG"
```

Set `PREVIOUS_REF` only when `PREVIOUS_TAG` exists. Confirm that each selected release tag resolves to a local commit:

```sh
git rev-parse --verify "$LATEST_REF^{commit}"
git rev-parse --verify "$PREVIOUS_REF^{commit}"
```

Run the second command only when `PREVIOUS_TAG` exists. If a tag is missing, inspect the Git remotes, identify the remote for the same GitHub repository, and use `git fetch` to get the required tags. Do not assume that the remote is named `origin`. Stop if the remote is ambiguous or a release tag still does not resolve. Do not guess a replacement range.

Check for a shallow repository:

```sh
git rev-parse --is-shallow-repository
```

If the result is `true`, use the identified GitHub remote to fetch complete history and tags:

```sh
git fetch --unshallow --tags "$GITHUB_REMOTE"
```

Then run the shallow-repository check again. Continue only when the result is `false` and both required tag commits resolve. If the full history cannot be fetched, stop. Do not generate notes from an incomplete history.

When `PREVIOUS_TAG` exists, check whether it is an ancestor of `LATEST_TAG`:

```sh
git merge-base --is-ancestor "$PREVIOUS_REF^{commit}" "$LATEST_REF^{commit}"
```

A non-ancestor result is not an automatic failure. State it in the working notes, use the tree diff to identify the shipped-state change, and inspect both sides of the history so that the commit list does not cause a false claim.

## 4. Investigate all shipped changes

When a previous release exists, start with:

```sh
git log --date=short --format='%H%x09%ad%x09%s' "$PREVIOUS_REF..$LATEST_REF"
git diff --stat "$PREVIOUS_REF" "$LATEST_REF"
git diff --name-status "$PREVIOUS_REF" "$LATEST_REF"
git diff "$PREVIOUS_REF" "$LATEST_REF"
```

For non-linear history, also inspect:

```sh
git log --left-right --graph --oneline "$PREVIOUS_REF...$LATEST_REF"
```

For the first release, inspect all history reachable from the latest tag and compare its shipped tree with an empty tree:

```sh
git log --reverse --date=short --format='%H%x09%ad%x09%s' "$LATEST_REF"
EMPTY_TREE="$(git hash-object -t tree /dev/null)"
git diff --stat "$EMPTY_TREE" "$LATEST_REF"
git diff --name-status "$EMPTY_TREE" "$LATEST_REF"
git diff "$EMPTY_TREE" "$LATEST_REF"
```

Account for every changed path. Inspect the relevant diff hunks, not only the statistics or file names. For generated or binary files, inspect the source, manifest, configuration, or other evidence that explains their user impact. Do not include working-tree changes or commits after `LATEST_TAG`.

Use this evidence order:

1. Actual code and configuration diff.
2. Merged pull request title, body, changed files, and linked