Skip to content

Commit ae12ba2

Browse files
committed
ci: enforce per-skill SKILL.md version bump on content change (#4)
The existing validate-version job catches forgotten plugin.json bumps, but says nothing when a specific skill's content changes without that skill's own metadata.version moving. Add a second step in the same job that: 1. Diffs against the PR base for paths under skills/<name>/** 2. For each affected skill, parses metadata.version out of SKILL.md on base and head 3. Fails unless the head version is strictly greater (semver-sorted) Skips skills that are new on the branch or were deleted, and fails loud if metadata.version is not semver. Moves BASE_SHA to job-level env so both steps share it. AGENTS.md updated to document the expanded check and the resulting expectation that skill content changes also bump SKILL.md metadata.version.
1 parent 8017901 commit ae12ba2

2 files changed

Lines changed: 45 additions & 5 deletions

File tree

β€Ž.github/workflows/ci.ymlβ€Ž

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ jobs:
1212
runs-on: ubuntu-latest
1313
permissions:
1414
contents: read
15+
env:
16+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
1517

1618
steps:
1719
- name: Checkout repository
@@ -20,8 +22,6 @@ jobs:
2022
fetch-depth: 0
2123

2224
- name: Verify plugin.json#version bumped if skills/ or .claude-plugin/ changed
23-
env:
24-
BASE_SHA: ${{ github.event.pull_request.base.sha }}
2525
run: |
2626
base=$(git show "$BASE_SHA":.claude-plugin/plugin.json | jq -r .version)
2727
head=$(jq -r .version .claude-plugin/plugin.json)
@@ -42,7 +42,47 @@ jobs:
4242
fi
4343
fi
4444
45-
echo "βœ… version check passed"
45+
echo "βœ… plugin version check passed"
46+
47+
- name: Verify SKILL.md#version bumped if skill content changed
48+
if: always()
49+
run: |
50+
changed_skills=$(git diff --name-only "$BASE_SHA"...HEAD -- 'skills/*/**' \
51+
| awk -F/ 'NF>=3 {print $2}' | sort -u)
52+
if [ -z "$changed_skills" ]; then
53+
echo "βœ… No skill content changed; per-skill version bump not required."
54+
exit 0
55+
fi
56+
extract_version() {
57+
awk '/^---$/{c++; next} c==1' | yq -r '.metadata.version // ""'
58+
}
59+
fail=0
60+
for skill in $changed_skills; do
61+
head_file="skills/$skill/SKILL.md"
62+
if [ ! -f "$head_file" ]; then
63+
echo "ℹ️ $skill: SKILL.md missing on HEAD (skill deleted); skipping."
64+
continue
65+
fi
66+
base_content=$(git show "$BASE_SHA:$head_file" 2>/dev/null || true)
67+
if [ -z "$base_content" ]; then
68+
echo "ℹ️ $skill: new skill on this branch; skipping bump check."
69+
continue
70+
fi
71+
head_v=$(extract_version < "$head_file")
72+
base_v=$(printf '%s\n' "$base_content" | extract_version)
73+
if [[ ! "$head_v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
74+
echo "❌ $skill: SKILL.md metadata.version is not semver: '$head_v'"
75+
fail=1
76+
continue
77+
fi
78+
if [ "$head_v" = "$base_v" ] || ! printf '%s\n%s\n' "$base_v" "$head_v" | sort -V -C; then
79+
echo "❌ $skill: content changed but SKILL.md metadata.version did not strictly increase ($base_v β†’ $head_v). Bump the patch version for bug fixes."
80+
fail=1
81+
continue
82+
fi
83+
echo "βœ… $skill: $base_v β†’ $head_v"
84+
done
85+
exit $fail
4686
4787
validate-spec:
4888
runs-on: ubuntu-latest

β€ŽAGENTS.mdβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ Each skill follows the Agent Skills specification:
119119

120120
`.github/workflows/ci.yml` runs on every PR and main push with five jobs:
121121

122-
- **`validate-version`** (PR only): Fails the PR if anything under `skills/` or `.claude-plugin/` changed without bumping `.claude-plugin/plugin.json#version`. Prevents shipping silent updates.
122+
- **`validate-version`** (PR only): Two checks. (1) Fails if anything under `skills/` or `.claude-plugin/` changed without bumping `.claude-plugin/plugin.json#version`. (2) For each skill whose content changed under `skills/<name>/**`, fails if the skill's own `SKILL.md` `metadata.version` did not strictly increase. Prevents shipping silent updates at both the plugin and individual-skill level.
123123
- **`validate-spec`**: Validates each `SKILL.md` against the Agent Skills spec using `skills-ref validate` (from `agentskills/agentskills`).
124124
- **`validate-publish`**: Runs `gh skill publish --dry-run` to check publishability before main merges.
125125
- **`install-local`**: Installs all skills from the local working tree via `npx skills add "$GITHUB_WORKSPACE" --yes` and verifies each skill landed in `.agents/skills/`.
@@ -138,7 +138,7 @@ Three places carry version numbers, but only one matters operationally.
138138

139139
Use semver: patch for fixes/wording, minor for new skills or pattern additions, major when removing or renaming a skill.
140140

141-
**Individual `SKILL.md#version`** (e.g. humanizer 1.6.0) β€” independent of the plugin version. Track skill-internal evolution there. The plugin version doesn't have to follow the highest skill version β€” they're separate trackers, like Apollo's pattern (apollo-skills plugin at 1.2.x while internal skills are at various 1.x).
141+
**Individual `SKILL.md#version`** (e.g. humanizer 1.6.0) β€” independent of the plugin version. Track skill-internal evolution there. The plugin version doesn't have to follow the highest skill version β€” they're separate trackers, like Apollo's pattern (apollo-skills plugin at 1.2.x while internal skills are at various 1.x). When a PR modifies any file under `skills/<name>/`, the `validate-version` job requires that skill's `metadata.version` to strictly increase (semver patch is enough for bug fixes).
142142

143143
**`marketplace.json#metadata.version` and `marketplace.json#plugins[0].version`** β€” keep aligned with `plugin.json#version` for tidiness, but no automation reads them today. CI does not enforce sync.
144144

0 commit comments

Comments
Β (0)