From 24084818c42062c90ed8a19bf46ec25da63fd57f Mon Sep 17 00:00:00 2001 From: Myasnikov Daniil Date: Thu, 14 May 2026 11:16:57 +0500 Subject: [PATCH] ci: require plugin version bump for any plugin change Adds a `Check plugin version bump` workflow that runs on PRs touching `agents/`, `hooks/`, `mcp/`, or `skills/`. For each `//` directory with changes in the PR, it asserts that `// .claude-plugin/plugin.json` `.version` is strictly greater (by `sort -V`) than the version on the base branch. New plugins (no manifest on base) are exempted. The check is hard-failed (`exit 1`) with a clear inline annotation pointing at the offending manifest. Documents the convention in `CLAUDE.md` so contributors see the expectation when modifying a plugin. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Myasnikov Daniil --- .../workflows/check-plugin-version-bump.yml | 84 +++++++++++++++++++ CLAUDE.md | 7 ++ 2 files changed, 91 insertions(+) create mode 100644 .github/workflows/check-plugin-version-bump.yml diff --git a/.github/workflows/check-plugin-version-bump.yml b/.github/workflows/check-plugin-version-bump.yml new file mode 100644 index 0000000..fc6458e --- /dev/null +++ b/.github/workflows/check-plugin-version-bump.yml @@ -0,0 +1,84 @@ +name: Check plugin version bump + +on: + pull_request: + paths: + - 'agents/**' + - 'hooks/**' + - 'mcp/**' + - 'skills/**' + +jobs: + check-version-bump: + name: Plugin version bumped + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Verify each modified plugin bumped its version + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + + # A plugin lives at // where is one of + # the directories scoped by `on.pull_request.paths` above, and its + # manifest is //.claude-plugin/plugin.json. + mapfile -t changed_files < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA") + + declare -A plugins=() + for f in "${changed_files[@]}"; do + case "$f" in + agents/*/*|hooks/*/*|mcp/*/*|skills/*/*) + category="${f%%/*}" + name="$(printf '%s' "$f" | cut -d/ -f2)" + plugins["$category/$name"]=1 + ;; + esac + done + + if [ ${#plugins[@]} -eq 0 ]; then + echo "No plugin files changed — nothing to check." + exit 0 + fi + + # head > base under `sort -V` (version sort). + version_gt() { + local v1="$1" v2="$2" + [ "$v1" != "$v2" ] \ + && [ "$(printf '%s\n%s\n' "$v1" "$v2" | sort -V | tail -n1)" = "$v1" ] + } + + failed=0 + for plugin_dir in "${!plugins[@]}"; do + manifest="$plugin_dir/.claude-plugin/plugin.json" + + head_version="$(git show "$HEAD_SHA:$manifest" 2>/dev/null | jq -r '.version // empty' 2>/dev/null || true)" + base_version="$(git show "$BASE_SHA:$manifest" 2>/dev/null | jq -r '.version // empty' 2>/dev/null || true)" + + if [ -z "$head_version" ]; then + echo "::error file=$manifest::missing or invalid .version in $manifest on HEAD" + failed=1 + continue + fi + + if [ -z "$base_version" ]; then + echo "✓ $plugin_dir: new plugin ($head_version) — skipping bump check" + continue + fi + + if [ "$head_version" = "$base_version" ]; then + echo "::error file=$manifest::$plugin_dir was modified but $manifest version is still $head_version — bump it (semver: patch for fixes, minor for additions, major for breaking changes)." + failed=1 + elif ! version_gt "$head_version" "$base_version"; then + echo "::error file=$manifest::$plugin_dir version went $base_version → $head_version (not greater); bump must increase the version." + failed=1 + else + echo "✓ $plugin_dir: $base_version → $head_version" + fi + done + + exit $failed diff --git a/CLAUDE.md b/CLAUDE.md index 2ea793e..371a478 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,3 +23,10 @@ Registry: `.claude-plugin/marketplace.json` 3. Add content files (SKILL.md, agent .md, .mcp.json, or hooks.json) 4. Register in `.claude-plugin/marketplace.json` 5. Update README.md + +## Modifying an Existing Plugin + +Any change under `agents//`, `hooks//`, `mcp//`, or `skills//` +must come with a bump to `.version` in that plugin's `.claude-plugin/plugin.json` +(semver: patch for fixes, minor for additions, major for breaking changes). +The `Check plugin version bump` GitHub Actions workflow enforces this on every PR.