agentsclimarketplace

Release bully

Skill dynamik-dev/bully/.claude/skills/release-bully

Use when cutting a new bully release — bumping the plugin version so Claude Code users get an update notification. Triggers include "cut a release", "ship a new version", "release vX.Y.Z", "bump the version", or "publish an update to bully".From its SKILL.md

Install
npx -y skills add dynamik-dev/bully --skill release-bully

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 14 stars14 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.2 KB, as published. Nobody here has run it

Release bully

Overview

Bully is a Claude Code plugin. Claude Code detects updates by comparing the version field in the marketplace repo against the user's installed version. A release = bump SemVer everywhere, rewrite the changelog, commit, tag, push, create a GitHub release. Do not skip steps — inconsistent version fields break update detection silently.

When to use

  • User asks to cut a release, ship a version, or publish an update.
  • Version bumps in plugin.json, marketplace.json, or pyproject.toml — all three must move together.

Do not use for: dependency bumps, internal refactors, or any change that doesn't warrant a new published version.

Version fields (all four must match)

FilePathField
.claude-plugin/plugin.jsonrootversion
.claude-plugin/marketplace.jsonrootmetadata.version
.claude-plugin/marketplace.jsonfirst plugin entryplugins[0].version
pyproject.toml[project] tableversion
src/bully/__init__.pytop of fileBULLY_VERSION = "..." (stamped into session_init telemetry records)

Drift between these is the most common release bug. Grep to verify after editing:

grep -RnE '"version"|^version|^BULLY_VERSION' .claude-plugin/ pyproject.toml src/bully/__init__.py

All five lines must show the new version.

Workflow

1. Pre-flight

git status                    # working tree must be clean
git rev-parse --abbrev-ref HEAD  # must be main
git fetch origin && git status   # must be up to date with origin/main
bash scripts/lint.sh          # ruff + shellcheck + pytest + dogfood must pass

If any check fails, stop. Do not release from a dirty or failing tree.

2. Decide the bump

Look at commits since the last tag:

LAST=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
git log ${LAST:+$LAST..}HEAD --oneline

Apply SemVer:

  • Patch (0.1.0 → 0.1.1): bug fixes, docs, internal refactor, no behavior change.
  • Minor (0.1.0 → 0.2.0): new features, new rules, new skills, added CLI flags — backwards-compatible.
  • Major (0.1.0 → 1.0.0): schema changes to .bully.yml, removed/renamed rules, breaking hook contract, changed exit codes. Schema bumps for .bully.yml (e.g. schema_version: 1 → 2) are always major.

If the commit list mixes categories, pick the highest-impact bump. If unsure, ask the user.

3. Edit the five version fields

Use Edit (not sed) to update each file. Exact new string in every case: X.Y.Z (no leading v).

For marketplace.json, there are two occurrences — bump both.

For src/bully/__init__.py, update the BULLY_VERSION = "X.Y.Z" constant near the top. This value gets stamped into every session_init telemetry record, so it must match the released version exactly.

Then re-lock so uv.lock records the new project version:

uv lock

The editable bully package's version in uv.lock is derived from pyproject.toml, so bumping pyproject.toml without re-locking leaves the lock stale. CI runs uv sync --locked --all-extras, which hard-fails on a stale lock ("The lockfile at uv.lock needs to be updated, but --locked was provided"). The re-locked uv.lock must be part of the release commit.

4. Rewrite CHANGELOG.md

Target format (Keep a Changelog + SemVer, matching the existing file):

## [Unreleased]
### Planned
See docs/plan.md for the active improvement plan.

## [X.Y.Z] - YYYY-MM-DD
### Added
- ...
### Changed
- ...
### Fixed
- ...

## [0.1.0] - 2026-04-16
...

Steps:

  1. Move whatever was under ## [Unreleased] into a new ## [X.Y.Z] - YYYY-MM-DD section (today's date, ISO).
  2. Use Added / Changed / Fixed / Removed / Security subheads as applicable. Drop empty ones.
  3. Reset ## [Unreleased] to just the ### Planned placeholder.
  4. Entries should describe user-visible change and motivation — read them as if they were release notes, because they are.

If the [Unreleased] section is empty or placeholder-only, derive entries from git log ${LAST:+$LAST..}HEAD --oneline and confirm with the user.

5. Verify before committing

grep -RnE '"version"|^version|^BULLY_VERSION' .claude-plugin/ pyproject.toml src/bully/__init__.py
git diff

All five version lines must show X.Y.Z. Diff should touch only the five version fields, uv.lock (the re-lock from §3), and CHANGELOG.md. If anything else changed, abort.

6. Commit, tag, push

git add .claude-plugin/plugin.json .claude-plugin/marketplace.json pyproject.toml src/bully/__init__.py uv.lock CHANGELOG.md
git commit -m "Release v0.2.0"

Set the version once, then reuse it so nothing drifts:

V=0.2.0   # no leading v
awk -v ver="$V" 'BEGIN{p="^## \\[" ver "\\]"} $0~p{f=1;next} /^## \[/{f=0} f' CHANGELOG.md > /tmp/bully-release-notes.md
test -s /tmp/bully-release-notes.md || { echo "empty release notes — CHANGELOG section missing"; exit 1; }

git tag -a "v$V" -F /tmp/bully-release-notes.md
git push origin main
git push origin "v$V"

7. GitHub release

gh release create "v$V" --title "v$V" --notes-file /tmp/bully-release-notes.md

If gh is not authenticated, stop and tell the user to run gh auth login — do not try to paper over auth errors.

8. Announce result

Report: version bumped, tag pushed, release URL (from gh release create output). Note that Claude Code users running /plugin update bully will now pick up the new version.

Common mistakes

MistakeFix
Only bumped plugin.json, not marketplace.jsonRe-run the grep in §5 — all five lines must match.
Tagged before pushing commitPush the commit first, then the tag — otherwise remote has an orphan tag.
Used a lightweight tag (git tag v0.2.0)Use -a ... -F ... for an annotated tag with release notes.
gh release create before git push origin <tag>Push the tag first; gh release create needs it on the remote.
Picked patch when a breaking change landedRe-read commits. If users need to edit their .bully.yml or re-wire hooks, it's major.
Committed with uncommitted unrelated changesStash them first. The release commit must be version + changelog only.
Bumped the version but forgot uv lockCI's uv sync --locked fails on the stale lock. Re-lock (§3) and include uv.lock in the release commit.

Abort criteria

Stop mid-flow if any of these happen — do not try to "just fix it forward":

  • Lint or tests fail during pre-flight.
  • Four version fields don't all match after edits.
  • git diff shows unexpected files.
  • git push rejects (someone else pushed; pull/rebase and restart from §1).
  • gh release create fails with anything other than "release already exists".

When you stop, leave the tree in whatever state the failure produced and surface the error to the user. Do not git reset --hard — they may want to salvage work.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.