Skip to content
Back to skills

Release

ASecurity

Use when releasing the craft plugin from main.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 6, 2026
ai-agentstypescriptrustshellreactgit

Works with

  • claude code

Security analysis

A100/100

Scanned November 6, 2026

npx -y skills add kalinichenko88/craft-skills --skill release --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Release?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Release
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kalinichenko88-release/badge)](https://www.skillsdirectory.com/skills/kalinichenko88-release)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: release
description: Use when releasing the craft plugin from main.
disable-model-invocation: true
argument-hint: "[major|minor|patch]"
---

# Release

A release is a `vX.Y.Z` tag on `main`, the same `version` in
`.codex-plugin/plugin.json`, a section in `CHANGELOG.md` and a GitHub release
with that section as its notes. Codex reinstalls the plugin when the `version`
field changes. Claude Code tracks commits instead, which is why
`.claude-plugin/plugin.json` carries no `version` — never add one.

This skill is the only place `version` and `CHANGELOG.md` change; pull requests
leave them alone. It releases what is already on `main`: merging a pull request
is a separate decision, never a step of the release.

Each command runs in a fresh shell, so `$last` and `$version` below stand for
values: write the real ones (`v0.1.0`, `0.2.0`) into every later command.

1. **Start from `main`.** Run `git fetch origin --tags`. Stop and say why if the
   branch is not `main` or the tree is dirty. Behind `origin/main`: `git merge
   --ff-only origin/main`. Stop if that fails, or if `HEAD` is still not
   `origin/main` — local commits the remote does not have.
2. **Read what is unreleased.** `last=$(git describe --tags --abbrev=0 --match 'v[0-9]*')`, then
   `git log --format='%h %s' "$last"..HEAD`.
   A log line is the short hash, a space and the subject; steps 3 and 4 read
   the subject. No commits since the tag: stop, there is nothing to release. If
   `jq -r .version .codex-plugin/plugin.json` is not `${last#v}`, stop: the field
   moved outside a release, and that has to be undone first. The same for
   `CHANGELOG.md`: if `grep -m1 '^## ' CHANGELOG.md` is not `## ${last#v} ...`,
   a section was written outside a release; stop.
3. **Pick the level.** `$ARGUMENTS` when it is `major`, `minor` or `patch`;
   empty, the highest that applies; anything else, stop and say it is not a
   level. A subject's type is everything before its first `:` —
   `feat(github)!` is type `feat`, scope `github`, breaking:

   | Level | When |
   | --- | --- |
   | major | a type ends in `!` |
   | minor | a type is `feat` |
   | patch | anything else, reverts included |

   Bump `${last#v}` by that level — a minor resets the patch, a major resets
   both. Show the subjects, the level and the new version, and wait for a yes.
4. **Write the changelog section.** Insert it into `CHANGELOG.md` above the
   previous version's `## ` heading, under a heading `## $version - YYYY-MM-DD`
   with today's date. One line per subject from step 2, in `git log` order,
   grouped by type under `### Added` (`feat`), `### Changed` (every other type)
   and `### Fixed` (`fix`), in that order; leave out a group with no lines. A
   line is the scope in parentheses, then the subject without its type:
   `feat(github): check the citations (#24)` becomes `- (github) check the
   citations (#24)`. Every line has a scope. A subject with no scope gets one
   from the files its commit changes, `git show --name-only --format= <hash>`.
   Each file gives the area it belongs to:

   | Files | Area |
   | --- | --- |
   | `skills/<name>/`, and `docs/<name>/` when `skills/<name>/` exists | the skill without `-conventions`: `typescript` |
   | `.claude/skills/<name>/` | `<name>`: `release` |
   | `scenarios/<name>/` | the skill `<name>`: `github` |
   | any other file in `scenarios/` | `scenarios` |
   | `rules.md` | `rules` |
   | `hooks/` | `hooks` |
   | `.github/workflows/` | `ci` |
   | `.codex-plugin/`, `.agents/` | `codex` |
   | `tests/<name>_eval.sh` | the areas of the skills whose use it counts: `tests/skill_load_eval.sh` gives `react` and `typescript` |
   | any other test, and a fixture of a test | the area of the code the test runs: `tests/rules_test.sh` runs `hooks/rules.sh`, so `hooks` |

   Any other file gives no area: the root `README.md`, `CHANGELOG.md`, the
   `Makefile`, `.gitattributes`, `.claude-plugin/`. A manifest's description
   that only lists a new skill gives no area too. The scope is the areas in
   alphabetical order, comma-separated: `(react, typescript)`. Four or more
   areas, or none, give `(plugin)`. Breaking subjects (`!`) get `(breaking)` at
   the end of the line.
5. **Bump and check.**
   `tmp=$(mktemp) && jq --arg v "$version" '.version = $v' .codex-plugin/plugin.json >"$tmp" && mv "$tmp" .codex-plugin/plugin.json`,
   then `make check`. If the check fails, restore both files and stop.
6. **Commit, tag and push together.**

   ```sh
   git commit -m "chore(release): bump the Codex version to $version" -- .codex-plugin/plugin.json CHANGELOG.md
   git tag -a "v$version" -m "v$version"
   git push --atomic origin main "v$version"
   ```

   `--atomic` lands the commit and the tag together or not at all. A rejected
   push means `main` moved: `git tag -d "v$version" && git reset --keep HEAD~1`,
   then start again at step 1: the reset drops the changelog section too, so
   step 4 writes it again. Never rebase or amend the release commit — the tag
   points at it.
7. **Publish the GitHub release** with the new section, heading left out, as
   its notes. One shell line, so the empty check guards the create. Nothing in
   this file may read as a dollar sign followed by a digit: Claude Code
   replaces those with the skill's arguments before the model reads it.

   ```sh
   notes=$(sed -n "/^## $version /,/^## /{/^## /d;p;}" CHANGELOG.md | sed '1{/^$/d;}') \
     && [ -n "$notes" ] && gh release create "v$version" --verify-tag --title "v$version" --notes "$notes"
   ```

   Empty notes mean the heading in step 4 does not read `## $version ` — fix
   the file, commit it on top and run this again. Without `--verify-tag`, a tag
   missing on the remote does not stop `gh`: it tags the default branch's HEAD
   and publishes the release there. With it, `gh` stops: run the push from step 6,
   then run this again. A failed `gh release create` leaves nothing to
   undo: fix the cause and run it again.
8. **Report** the new tag, the release URL and the update steps from the
   README's *Update* section, the trust in `/hooks` included.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in ai-agents →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.