check-skill-version-bump fails a push when a skill directory changed against its merge-base with main (tests/ excluded) without a strictly higher metadata.version than main. New, renamed and deleted skills are exempt; every plugin is covered. Recorded as a dated section in ADR-0022 and documented in gates.md. Patch-bumps the 17 skills that changed on this branch without a bump, so the branch passes its own gate. Simplification audit finding 33. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7.0 KiB
Every skill's metadata.version is mandatory, not a per-plugin option
Status: accepted (2026-09-07).
Context
metadata.version is optional SKILL.md frontmatter (create.md's "Optional frontmatter" list:
"uncomment and fill in, or remove entirely"). skill-author's own bump logic was written
conditionally — "with metadata.version present, bump the minor version on create... and the
patch version on improve" — which only makes sense if presence is a real per-skill choice.
Adoption never followed a rule; it followed the plugin. Of 39 skills, 12 carry a version:
| Plugin | Has it | Total |
|---|---|---|
core |
3 | 3 |
gitea |
6 | 7 |
lint |
2 | 2 |
git |
1 | 9 |
bin |
0 | 11 |
kyberforge |
0 | 7 |
core, gitea and lint are consistent adopters (gitea-files the one gap); bin and
kyberforge are consistent non-adopters; git has one outlier (git-commits, versioned for no
plugin-specific reason found on inspection — no comment, no cross-reference, nothing distinguishing
it from its eight siblings). Issue #127 raised this as an undocumented split: two house norms
coexisting with no stated rule for which applies where, the same class of defect as an unstated
rtk/bare-git convention (#113) found in the same audit pass.
Decision
Every skill's frontmatter carries metadata.version. It is no longer optional, and no longer a
per-plugin choice.
- The 27 skills that never carried one are seeded at
1.0.0, not0.1.0.0.1.0isskill-author's existing new-skill starting point, chosen for a skill with no revision history to its name yet. These 27 have all been through the ADR-0020 retrofit and repeated audit passes without ever tracking a version; crediting them with0.1.0would understate that, and there is no real history to justify seeding higher than a first stable release.1.0.0marks "versioned as of this retrofit,"0.1.0keeps meaning "created and never yet revised." - New skills still start at
0.1.0.skill-author's create/improve bump convention is unchanged; only the presence of the field stops being conditional. - The one outlier in the other direction,
git-commits, keeps its existing value (0.1.3) — it already had real tracked history under the old conditional rule, and this decision does not reset skills that were already compliant. bin/write-docs's top-levelversion:moves intometadata:, normalized to1.0.0. It is the one skill that carried a version outside themetadata:block, which is why the table above countsbinas 0 — a top-levelversion:is notmetadata.version, and nothing reads it. #127 raised it alongside the split because "does a skill carry a version" and "where does it live" are the same question. Its value (1.0) is not semver and carries no more real history than the 27 unversioned skills, so it is relocated and reset to the same1.0.0seed rather than preserved likegit-commits's tracked0.1.3.skill-frontmatter's pre-commit hook gains the check. It already fails a SKILL.md missingname:ordescription:; a missingmetadata.versionis now the same class of failure, not a style nit an audit might or might not catch.
Considered options
Leave it per-plugin, document the split. This was the initial framing of #127 and is coherent —
core/gitea/lint keep it, bin/kyberforge don't, two outliers get normalized to match their
plugin. Rejected on reconsideration: a rule that says "some plugins track this and some don't" is
strictly harder to state, audit and onboard against than "every skill does," for a field whose entire
job is answering "did this change since I last read it" — a question with the same shape everywhere
it's asked, not one that varies by plugin domain.
Drop the field corpus-wide. Rejected: skill-author already depends on it to decide whether a
create/improve pass owes a bump, so the 12 skills carrying it are not tracking dead weight — removing
it discards real revision signal for no gain.
Consequences
27 SKILL.md files gain metadata.version: "1.0.0", and a 28th — bin/write-docs — reaches the same
value by relocating its top-level version: "1.0" into metadata:. skill-author's create.md
moves the field from "Optional frontmatter" to the required list, citing this ADR. skill-author's
own SKILL.md drops the "with metadata.version present" conditional in its bump-rule line, since
presence is no longer in question. .pre-commit-config.yaml's skill-frontmatter hook is extended
to require the field, closing the gap #113 and #118 both named in the same audit pass: a stated rule
with nothing enforcing it drifts the same way an unstated one does.
Amendment (2026-09-16) — the bump is enforced at push, not only required to exist
Context. Making the field mandatory did not make it move. The only thing that bumped it was
skill-author Step 4, so every hand edit and every trim pass skipped the bump: on
docs/simplification-audit, 17 of the 40 skill directories that changed against main carried
the same metadata.version as main, and gitea alone sat at six different values. Both
validators checked presence and semver shape, never movement, so the field could not answer the
question this ADR gives it — "did this change since I last read it". (Simplification audit
finding 33.)
Decision. scripts/check-skill-version-bump.sh runs as a pre-push hook on every push. For each
skill directory under plugins/*/.apm/skills/ that differs between the pushed ref and its
merge-base with main, ignoring tests/, the pushed metadata.version must be strictly greater
than main's.
- Baseline is the merge-base with
main, not the previous commit. Readers only ever seemain— installs resolve against the default branch (ADR-0018) — so one bump per branch is what the field owes them. A per-commit check would bump a skill once per commit and inflate the number past meaning. - "Greater", not "exactly one patch higher". A second
skill-authorpass on the same branch that bumps again still passes. tests/is excluded. No agent loads it; a fixture-only change does not change the skill.- Skills absent from either side are exempt. New, renamed and merged skills start fresh under the rules above; deleted skills have nothing to check.
- Every plugin is covered,
binincluded, and the gate is repo-local — it is not exported through.pre-commit-hooks.yaml.
Considered options. Declare the field advisory — cheapest, but concedes the field cannot do
its job. Drop the field — rejected by this ADR already, and costlier now. Check at commit
time against HEAD — rejected for the inflation above.
Consequences. The 17 unbumped skills took a patch bump in the commit that added the gate.
Like check-release-needed, the gate only fires on a local git push through pre-commit: a merge
made with Gitea's merge button runs no local hooks and is not checked. A typo fix in a skill now
costs a version bump; that is the rule working, not noise.