Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Release

on:
push:
tags:
- "v*.*.*"

permissions:
contents: write

jobs:
release:
name: Create GitHub release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Derive version from tag
id: version
run: |
TAG="${GITHUB_REF_NAME}"
VERSION="${TAG#v}"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

- name: Verify tag matches plugin.json version
run: |
MANIFEST_VERSION="$(jq -r '.version' .claude-plugin/plugin.json)"
if [ "$MANIFEST_VERSION" != "${{ steps.version.outputs.version }}" ]; then
echo "::error::Tag ${{ steps.version.outputs.tag }} does not match plugin.json version ${MANIFEST_VERSION}. Bump plugin.json or retag."
exit 1
fi
echo "Manifest version ${MANIFEST_VERSION} matches tag."

- name: Extract release notes from CHANGELOG
id: notes
run: |
VERSION="${{ steps.version.outputs.version }}"
# Print the body of the [VERSION] section, stopping at the next ## heading.
awk -v ver="$VERSION" '
$0 ~ "^## \\[" ver "\\]" { capture = 1; next }
capture && /^## \[/ { exit }
capture { print }
' CHANGELOG.md > release-notes.md
if [ ! -s release-notes.md ]; then
echo "::error::No CHANGELOG section found for [${VERSION}]. Add a '## [${VERSION}] - <date>' entry before tagging."
exit 1
fi
echo "Extracted release notes for ${VERSION}:"
cat release-notes.md

- name: Create release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ steps.version.outputs.tag }}" \
--title "${{ steps.version.outputs.tag }}" \
--notes-file release-notes.md \
--latest
17 changes: 16 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,26 @@ The `main` branch is protected; all changes go through pull requests.

1. **Fork and branch.** Create a feature branch from `main`: `git checkout -b feat/<topic>` or `fix/<topic>`.
2. **Make your change.** Keep PRs focused on one concern. If you're touching hook scripts, follow the existing `set -euo pipefail` and library-sourcing pattern.
3. **Run the tests.** `bats tests/` from the repo root. All 69 tests must pass. Add tests for any new behavior.
3. **Run the tests.** `bats tests/` from the repo root. All tests must pass. Add tests for any new behavior.
4. **Update docs.** If you add or change a subagent, hook, or command, update the matching file under `docs/reference/`.
5. **Update CHANGELOG.md** under the `[Unreleased]` heading.
6. **Open a PR** with a clear title (`feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`) and a Summary + Test plan section.

## Releasing

Releases are cut by pushing a version tag. The `Release` workflow (`.github/workflows/release.yml`) does the rest.

1. **Land the version bump.** A merged PR should already have bumped `.claude-plugin/plugin.json` to the new `X.Y.Z` and moved its CHANGELOG entries from `[Unreleased]` into a `## [X.Y.Z] - <date>` section. Follow [SemVer](https://semver.org): MINOR for new backward-compatible features, PATCH for fixes.
2. **Tag `main` and push the tag:**
```bash
git checkout main && git pull
git tag -a vX.Y.Z -m "vX.Y.Z - <summary>"
git push origin vX.Y.Z
```
3. **The workflow takes over.** On a `v*.*.*` tag push it: verifies the tag matches `plugin.json` (fails loudly if not), extracts the `## [X.Y.Z]` section from `CHANGELOG.md` as the release notes, and creates the GitHub release marked `--latest`.

A merged PR alone does NOT publish a release: the marketplace serves released versions, so an untagged bump never reaches installs. Always push the tag.

## Local development

Install dependencies (macOS):
Expand Down
Loading