Skip to content

Commit 7d941ef

Browse files
authored
feat: version bump validation (#16)
1 parent 21e6932 commit 7d941ef

12 files changed

Lines changed: 119 additions & 18 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env bash
2+
# Fail the PR when a changed publishable skill's package.json version was not
3+
# bumped. Pairs with publish-changed-skills.sh: that script silently skips an
4+
# unbumped version on merge, this one stops the unbumped change from landing.
5+
#
6+
# Reads one required env var:
7+
# BASE_REF - git ref to diff against (e.g. origin/main)
8+
#
9+
# Behaviour:
10+
# * For every skills/<name>/ directory the PR touches (ANY file inside the
11+
# folder counts), compares the version in HEAD's package.json against the
12+
# version in BASE_REF's package.json.
13+
# * Fails (and lists every offender) when the versions are equal. Bumping
14+
# the version is what ships a release - see CONTRIBUTING.md > Versioning.
15+
# * Skips brand-new skills (no package.json in BASE_REF) - there is no
16+
# previous version to compare against.
17+
# * Skips skill dirs that no longer have a package.json on HEAD - the spec
18+
# validator catches that as a separate error.
19+
# * Meta-skills under .github/skills/ are never published, so this check
20+
# does not apply to them.
21+
22+
set -uo pipefail
23+
24+
: "${BASE_REF:?BASE_REF is required}"
25+
26+
# Only the root skills/ tree is published; .github/skills/ holds meta-skills
27+
# that are validated on PRs but never shipped, so unbumped meta-skill edits
28+
# are fine.
29+
mapfile -t changed < <(
30+
git diff --name-only "$BASE_REF"...HEAD \
31+
| awk -F/ '$1 == "skills" && NF >= 2 { print "skills/" $2 }' \
32+
| sort -u
33+
)
34+
35+
if [ "${#changed[@]}" -eq 0 ]; then
36+
echo "No publishable skills changed; version bump check skipped."
37+
exit 0
38+
fi
39+
40+
failures=()
41+
for dir in "${changed[@]}"; do
42+
pkg="$dir/package.json"
43+
44+
if [ ! -f "$pkg" ]; then
45+
echo "skip $dir: no package.json on HEAD"
46+
continue
47+
fi
48+
49+
if ! git cat-file -e "$BASE_REF:$pkg" 2>/dev/null; then
50+
echo "skip $dir: new skill (no package.json in $BASE_REF)"
51+
continue
52+
fi
53+
54+
new_version=$(jq -r .version "$pkg")
55+
old_version=$(git show "$BASE_REF:$pkg" | jq -r .version)
56+
57+
if [ "$new_version" = "$old_version" ]; then
58+
echo "::error file=$pkg::skill '$dir' changed but version is still $new_version - run 'npm version patch|minor|major' inside $dir to bump it"
59+
failures+=("$dir (version $new_version unchanged)")
60+
else
61+
echo "ok $dir: $old_version -> $new_version"
62+
fi
63+
done
64+
65+
if [ "${#failures[@]}" -ne 0 ]; then
66+
echo
67+
echo "Version bump check failed for ${#failures[@]} skill(s):"
68+
for f in "${failures[@]}"; do
69+
echo " - $f"
70+
done
71+
echo
72+
echo "Bump the version in each skill's package.json (npm version patch|minor|major)."
73+
echo "See CONTRIBUTING.md > Versioning."
74+
exit 1
75+
fi
76+
77+
echo
78+
echo "All ${#changed[@]} changed skill(s) have version bumps."

.github/scripts/publish-changed-skills.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
# * Skips directories without a package.json (validated-only meta-skills,
1212
# or files outside any skill dir).
1313
# * Skips skill@version pairs already published to the registry.
14+
# * Publishes with `--tag latest` so the `latest` dist-tag always points at
15+
# the just-shipped version alongside the immutable version coordinate.
16+
# Consumers can then `npm install @bcgov/skill-<name>` (no version) to
17+
# track the most recent release.
1418
# * Keeps going after a per-skill failure and reports them all at the end,
1519
# then exits non-zero so the workflow turns red. (One bad skill should not
1620
# hide successful publishes of the others or stop them from running.)
@@ -48,8 +52,8 @@ for dir in "${changed[@]}"; do
4852
continue
4953
fi
5054

51-
echo "publishing $name@$version"
52-
if ! (cd "$dir" && npm publish); then
55+
echo "publishing $name@$version (tag: latest)"
56+
if ! (cd "$dir" && npm publish --tag latest); then
5357
echo "::error::failed to publish $name@$version from $dir"
5458
failures+=("$name@$version ($dir)")
5559
fi

.github/skills/skill-release/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,20 @@ description: Bumps a skill's version and ships it as an npm package when a contr
1919
1. Decide the change size: patch (fix/wording), minor (compatible capability), major (breaking).
2020
2. From the skill folder, run `npm version patch|minor|major` to bump `package.json`.
2121
3. Validate with `uv run python scripts/validate_skill.py skills/<name>/SKILL.md`.
22-
4. Open a PR; on merge to `main` the publish workflow reads `name` + `version` and runs `npm publish`.
22+
4. Open a PR; on merge to `main` the publish workflow reads `name` + `version` and runs `npm publish --tag latest`. The `--tag latest` keeps the `latest` dist-tag pointed at the version this run shipped, so consumers who `npm install @bcgov/skill-<name>` (no version) get the new release.
2323
5. Confirm the release: the workflow skips any version already published, so only a bumped version ships.
2424

2525
## Rules
2626
- Always bump the `version` in `package.json` for any release. (Why: the publish workflow skips already-published versions, so an unbumped merge ships nothing.)
2727
- Always choose the semver level by impact on consumers, not effort. (Why: a tiny but breaking change is still a major; consumers rely on semver ranges to upgrade safely.)
2828
- Never hand-edit a version to one that was already published. (Why: npm registry versions are immutable and the publish will fail.)
29+
- Never bypass the `--tag latest` behaviour for a normal forward release. (Why: the publish script ships every changed skill with `npm publish --tag latest`, which is what makes `npm install @bcgov/skill-<name>` resolve to the newest version. Backport publishes on an older line are the only case that wants a different dist-tag, and they belong in a follow-up `npm dist-tag` step rather than an edit to the publish script.)
2930

3031
## Examples
3132
- "I fixed a typo in the example skill, release it" → `npm version patch`, validate, open PR.
3233
- "This skill now supports a new lookup, ship it" → `npm version minor`, validate, open PR.
3334
- "Why didn't my merge publish anything?" → check whether `version` was bumped; an unchanged version is skipped by design.
35+
- "How do consumers pick up my release without pinning a version?" → `npm install @bcgov/skill-<name>` resolves to the `latest` dist-tag, which the publish workflow updates on every release.
3436

3537
## Edge Cases
3638
- If the user is unsure of the level → ask whether existing consumers would break; if yes it's major, if it only adds it's minor, otherwise patch.

.github/workflows/pr.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ name: PR
66
# 2. lint-tests — formats, lints, and unit-tests the tooling that produces
77
# skills (scripts/, tests/, pyproject.toml). Runs after fork-gate: if the
88
# generator is broken, validating skill output is meaningless.
9-
# 3. skills — validates the SKILL.md profiles the PR changed. Gated on
10-
# lint-tests passing.
9+
# 3. skills — validates the SKILL.md profiles the PR changed and
10+
# enforces that every changed publishable skill has its package.json
11+
# version bumped. Gated on lint-tests passing.
1112
# 4. results — always runs (`if: always()`), needs every other job, and
1213
# fails if any of them failed or was cancelled. This is the SINGLE
1314
# required status check on `main`, so we can add or rename jobs in this
@@ -88,6 +89,11 @@ jobs:
8889
- name: Validate changed skills
8990
run: uv run python scripts/validate_skill.py --base "origin/${{ github.base_ref }}"
9091

92+
- name: Check version bumps on changed skills
93+
env:
94+
BASE_REF: origin/${{ github.base_ref }}
95+
run: bash .github/scripts/check-version-bumps.sh
96+
9197
# 3. Aggregator. Always runs (even if upstream jobs failed or were cancelled)
9298
# and fails if any required job failed or was cancelled. Branch protection
9399
# only needs to require THIS one check — adding or renaming jobs above

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ public packages, so `NODE_AUTH_TOKEN` needs a credential with the
189189
npm install @bcgov/skill-<name>@0.1.0
190190
```
191191

192+
Or omit the version to track the `latest` dist-tag, which the publish
193+
workflow updates on every release:
194+
195+
```bash
196+
npm install @bcgov/skill-<name>
197+
```
198+
192199
It installs to `node_modules/@bcgov/skill-<name>/` with `SKILL.md` plus
193200
whatever else the skill ships, exactly as it lives in this repo. Point your
194201
agent's skill loader at that directory; the on-disk layout is preserved, so
@@ -249,8 +256,10 @@ When a PR merges to `main`, [`publish.yml`](.github/workflows/publish.yml):
249256
1. Re-validates every skill.
250257
2. Finds the skills the merge changed (via git diff).
251258
3. Reads `name` + `version` from each one's `package.json` and runs
252-
`npm publish`**unless that exact version is already published**, in
253-
which case it's skipped.
259+
`npm publish --tag latest`**unless that exact version is already
260+
published**, in which case it's skipped. The `--tag latest` keeps the
261+
`latest` dist-tag pointed at whatever this run shipped, so consumers who
262+
`npm install @bcgov/skill-<name>` (no version) get the most recent release.
254263

255264
Three things shape how this works:
256265

docs/_pages/architecture.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ <h2 id="publish-workflow">Publish workflow: <code>.github/workflows/publish.yml<
5252
<li>Install <code>uv</code> (third-party action, SHA-pinned per the policy below).</li>
5353
<li><strong>Re-validate every skill</strong> with <code>uv run python scripts/validate_skill.py --all</code>, the final check before any package bytes leave the repo.</li>
5454
<li>Set up Node with the registry pointed at <code>https://npm.pkg.github.com</code> and the <code>@bcgov</code> scope. (The exact Node version lives in the workflow file.)</li>
55-
<li>Run <a href="https://github.com/bcgov/agent-skills/blob/main/.github/scripts/publish-changed-skills.sh" target="_blank" rel="noopener"><code>.github/scripts/publish-changed-skills.sh</code></a>, which diffs <code>BEFORE_SHA</code> against <code>HEAD_SHA</code>, walks each changed skill, and runs <code>npm publish</code> with <code>NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}</code>.</li>
55+
<li>Run <a href="https://github.com/bcgov/agent-skills/blob/main/.github/scripts/publish-changed-skills.sh" target="_blank" rel="noopener"><code>.github/scripts/publish-changed-skills.sh</code></a>, which diffs <code>BEFORE_SHA</code> against <code>HEAD_SHA</code>, walks each changed skill, and runs <code>npm publish --tag latest</code> with <code>NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}</code>. The <code>--tag latest</code> keeps the <code>latest</code> dist-tag pointed at the just-shipped version, so <code>npm install @bcgov/skill-&lt;name&gt;</code> (no version) always pulls the most recent release.</li>
5656
</ol>
5757
</div>
5858

docs/_pages/consume.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ <h2 id="step-3-install">Step 3: Install</h2>
8787

8888
<pre><code>npm install @bcgov/skill-&lt;name&gt;@&lt;version&gt;</code></pre>
8989

90+
<p>Or omit the version to track the <code>latest</code> dist-tag, which the publish workflow updates on every release:</p>
91+
92+
<pre><code>npm install @bcgov/skill-&lt;name&gt;</code></pre>
93+
9094
<p>The package lands at <code>node_modules/@bcgov/skill-&lt;name&gt;/</code> with its <code>SKILL.md</code> plus any <code>references/</code>, <code>scripts/</code>, or <code>assets/</code> the skill ships. The on-disk layout matches the source repo exactly, so your agent's skills loader can point at the directory with no extra wiring.</p>
9195

9296
<div class="alert alert-success">

docs/_pages/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ <h2>The catalogue at a glance</h2>
7070
<div class="grid grid-2">
7171
<div class="card">
7272
<h3 style="margin-top:0;">Published skills</h3>
73-
<p>Published as versioned npm packages on <strong>GitHub Packages</strong> under the <code>@bcgov</code> scope. Install with <code>npm install @bcgov/skill-&lt;name&gt;@&lt;version&gt;</code>.</p>
73+
<p>Published as versioned npm packages on <strong>GitHub Packages</strong> under the <code>@bcgov</code> scope. Install with <code>npm install @bcgov/skill-&lt;name&gt;@&lt;version&gt;</code>, or drop the version to track the <code>latest</code> dist-tag the publish workflow updates on every release.</p>
7474
<p>The live list — every package and every version — lives on GitHub Packages itself, so the catalogue scales as more skills ship.</p>
7575
<p style="margin-top: 1rem;"><a href="https://github.com/orgs/bcgov/packages?repo_name=agent-skills" target="_blank" rel="noopener"><strong>Browse published packages →</strong></a> &nbsp;·&nbsp; <a href="catalog.html#published">Catalogue page</a></p>
7676
</div>

docs/architecture.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,8 +1035,6 @@
10351035
<main>
10361036
<!-- Breadcrumb: injected by search.js when arriving from a search result -->
10371037
<div id="search-breadcrumb" aria-label="Search breadcrumb"></div>
1038-
<!-- TITLE: Pipeline & Governance -->
1039-
<!-- NAV: architecture -->
10401038

10411039
<h1 id="pipeline-governance">Pipeline &amp; Governance</h1>
10421040

@@ -1089,7 +1087,7 @@ <h2 id="publish-workflow">Publish workflow: <code>.github/workflows/publish.yml<
10891087
<li>Install <code>uv</code> (third-party action, SHA-pinned per the policy below).</li>
10901088
<li><strong>Re-validate every skill</strong> with <code>uv run python scripts/validate_skill.py --all</code>, the final check before any package bytes leave the repo.</li>
10911089
<li>Set up Node with the registry pointed at <code>https://npm.pkg.github.com</code> and the <code>@bcgov</code> scope. (The exact Node version lives in the workflow file.)</li>
1092-
<li>Run <a href="https://github.com/bcgov/agent-skills/blob/main/.github/scripts/publish-changed-skills.sh" target="_blank" rel="noopener"><code>.github/scripts/publish-changed-skills.sh</code></a>, which diffs <code>BEFORE_SHA</code> against <code>HEAD_SHA</code>, walks each changed skill, and runs <code>npm publish</code> with <code>NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}</code>.</li>
1090+
<li>Run <a href="https://github.com/bcgov/agent-skills/blob/main/.github/scripts/publish-changed-skills.sh" target="_blank" rel="noopener"><code>.github/scripts/publish-changed-skills.sh</code></a>, which diffs <code>BEFORE_SHA</code> against <code>HEAD_SHA</code>, walks each changed skill, and runs <code>npm publish --tag latest</code> with <code>NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}</code>. The <code>--tag latest</code> keeps the <code>latest</code> dist-tag pointed at the just-shipped version, so <code>npm install @bcgov/skill-&lt;name&gt;</code> (no version) always pulls the most recent release.</li>
10931091
</ol>
10941092
</div>
10951093

docs/assets/search-index.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)