refactor(skills): mandate metadata.version on every skill's frontmatter

Only 12 of 39 skills carried metadata.version, and adoption tracked
which plugin a skill lived in rather than any stated rule: core,
gitea and lint were consistent adopters, bin and kyberforge were
consistent non-adopters, git was split with one outlier. There was
no documented convention, and skill-author's own bump logic was
already written as if presence were conditional.

metadata.version is now required on every skill. The 19 skills here
that never carried one (bin, kyberforge, gitea-files) are seeded at
1.0.0, not 0.1.0 -- that value stays reserved for a skill's actual
creation point under skill-author's existing convention. The
skill-frontmatter pre-commit hook now fails a SKILL.md missing the
field, the same class of failure as a missing name/description.

Full rationale in the new ADR. The git-plugin skills that also need
this field follow in the next commit, bundled with issue #113's rtk
normalization since both touch the same files.

Refs: #127
ADR: 0022
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeH8SCbcrCAQrtymkNuhKP
This commit is contained in:
2026-09-07 20:36:24 +00:00
parent 598a7c326a
commit 60be7b3232
22 changed files with 115 additions and 6 deletions

View File

@@ -259,8 +259,18 @@ repos:
- |
for f in "$@"; do
if [[ -f "$f" ]]; then
if ! grep -q "^name:" "$f" || ! grep -q "^description:" "$f"; then
echo "ERROR: $f is missing required frontmatter fields (name: and description:)"
missing=""
if ! grep -q "^name:" "$f"; then
missing="${missing}name: "
fi
if ! grep -q "^description:" "$f"; then
missing="${missing}description: "
fi
if ! grep -A10 "^metadata:" "$f" | grep -q " version:"; then
missing="${missing}metadata.version "
fi
if [[ -n "$missing" ]]; then
echo "ERROR: $f is missing required frontmatter fields (${missing})"
exit 1
fi
fi

View File

@@ -0,0 +1,70 @@
# 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`**, not `0.1.0`. `0.1.0` is
`skill-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 with `0.1.0` would understate that, and there is
no real history to justify seeding higher than a first stable release. `1.0.0` marks "versioned as
of this retrofit," `0.1.0` keeps 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.
- **`skill-frontmatter`'s pre-commit hook gains the check.** It already fails a SKILL.md missing
`name:` or `description:`; a missing `metadata.version` is 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"`. `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.

View File

@@ -4,6 +4,8 @@ disable-model-invocation: true
description: >
Ultra-compressed output mode that drops articles, filler and pleasantries while
keeping technical substance exact, cutting token usage by roughly 75%.
metadata:
version: "1.0.0"
---
Respond terse like smart caveman. All technical substance stay. Only fluff die.

View File

@@ -4,6 +4,8 @@ description: >
Use when the user says "diagnose this" or "debug this", reports something
broken, throwing, or failing, or says something got slow. Not filing or
triaging a reported bug -> `triage`. Not test-first feature work -> `tdd`.
metadata:
version: "1.0.0"
---
# Diagnose

View File

@@ -5,6 +5,8 @@ description: >
relentless interview — one question at a time, down each branch of the
decision tree. Not a plan to challenge against `CONTEXT.md` and ADRs ->
`grill-with-docs`.
metadata:
version: "1.0.0"
---
Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.

View File

@@ -4,6 +4,8 @@ description: >
Use when a plan should be stress-tested against the project's domain model —
the interview challenges terms against `CONTEXT.md` and writes decisions into
it and into ADRs as they land. Not a plain interview -> `grill-me`.
metadata:
version: "1.0.0"
---
<what-to-do>

View File

@@ -6,6 +6,8 @@ description: >
testable and AI-navigable — deepening opportunities that turn shallow modules
into deep ones, informed by `CONTEXT.md` and `docs/adr/`. Not debugging a
failure -> `diagnose`.
metadata:
version: "1.0.0"
---
# Improve Codebase Architecture

View File

@@ -5,6 +5,8 @@ description: >
a data model, state machine or business logic, or to mock up a UI in several
variations. Not production code -> `tdd`. Not talking a design through ->
`grill-me`.
metadata:
version: "1.0.0"
---
# Prototype

View File

@@ -6,6 +6,7 @@ description: >-
documentation written from existing code or specs -> `write-docs`. Not a bug
or incident -> `diagnose`.
metadata:
version: "1.0.0"
category: research
allowed-tools:
- Grep

View File

@@ -4,6 +4,8 @@ description: >
Use when the user wants a feature built or a bug fixed test-first, in a strict
red-green-refactor loop, one behaviour at a time. Not diagnosing an existing
bug -> `diagnose`. Not throwaway exploratory code -> `prototype`.
metadata:
version: "1.0.0"
---
# Test-Driven Development

View File

@@ -4,6 +4,8 @@ description: >
Use when the user wants an issue created, triaged, or moved through the
tracker's triage states, or an issue prepared for an AFK agent. Not debugging
the bug itself -> `diagnose`. Not fleshing out a design -> `grill-with-docs`.
metadata:
version: "1.0.0"
---
# Triage

View File

@@ -6,10 +6,10 @@ description: >
module", "create docs for this feature", "write a README for this". Not an ADR
or other decision record -> `grill-with-docs`. Not an external tool researched
from its docs -> `research`.
version: "1.0"
updated: 2026-05-17
when: invoked by explicit trigger ("write docs for X", "document this module", "create docs for this feature") or implicit request to produce technical documentation from code or spec
metadata:
version: "1.0"
category: implement
source:
- repo: anthropics/skills

View File

@@ -2,6 +2,8 @@
name: zoom-out
description: Tell the agent to zoom out and give broader context or a higher-level perspective. Use when you're unfamiliar with a section of code or need to understand how it fits into the bigger picture.
disable-model-invocation: true
metadata:
version: "1.0.0"
---
I don't know this area of code well. Go up a layer of abstraction. Give me a map of all the relevant modules and callers, using the project's domain glossary vocabulary.

View File

@@ -11,6 +11,7 @@ compatibility: Requires the Gitea MCP server configured with a token scoped to a
is not actually required for any of this domain's five tools.
metadata:
version: "1.0.0"
category: gitea
source_keys:
- gitea-mcp-repo

View File

@@ -7,6 +7,7 @@ description: >
directory -> skill-audit.
allowed-tools: Bash Read
metadata:
version: "1.0.0"
category: factory
source_keys:
- context7-websites-code-claude

View File

@@ -6,6 +6,7 @@ description: >
Not read-only review -> `agent-audit`. Not skills -> `skill-author`.
allowed-tools: Bash Read Write Edit
metadata:
version: "1.0.0"
category: factory
source_keys:
- context7-websites-code-claude

View File

@@ -6,6 +6,7 @@ description: >
authoring, publishing, auditing, or dependency installation for an apm
package -> `apm-workflow`.
metadata:
version: "1.0.0"
category: apm
source_keys:
- context7-microsoft-apm

View File

@@ -5,6 +5,7 @@ description: >
the dependencies it declares, or an apm marketplace — even when the user does
not say "apm". Not the apm binary or an agent runtime -> `apm-install`.
metadata:
version: "1.0.0"
category: apm
source_keys:
- context7-microsoft-apm

View File

@@ -8,6 +8,7 @@ description: >
already named — invoke `skill-author`, `agent-author` or `apm-workflow`
directly.
metadata:
version: "1.0.0"
category: factory
source_keys:
- claude-code-subagents-docs

View File

@@ -7,6 +7,7 @@ description: >
skill-author.
allowed-tools: Bash Read
metadata:
version: "1.0.0"
category: factory
source_keys:
- agentskills-home

View File

@@ -6,6 +6,7 @@ description: >
Not read-only review -> `skill-audit`. Not agent files -> `agent-author`.
allowed-tools: Bash Read Write Edit
metadata:
version: "1.0.0"
category: factory
source_keys:
- agentskills-home
@@ -56,6 +57,6 @@ Gates `/skill-audit` enforces in both flows:
Run `/skill-audit` on the resolved skill directory; resolve every FAIL before reporting done. It checks name-to-directory match, placeholders, both size budgets, boundary-target resolution and script hygiene — do not hand-check those. Hand-check the one thing it misses: an empty body reports `PASS SKILL.md body word count 0 (ADR-0020 target: 600)`, so confirm at least one non-empty section exists.
With `metadata.version` present, bump the **minor** version on create (new skills start at `0.1.0`) and the **patch** version on improve.
Bump `metadata.version`: the **minor** version on create (new skills start at `0.1.0`) and the **patch** version on improve.
**Commit verification.** Inside a git worktree: once the audit is clean, run `git add` and `git commit` — do not stop at staging. Re-run `git log --oneline -1` and confirm the hash changed from Step 1's. A non-empty `git diff --stat` is not proof: staged-but-uncommitted work is part of no commit and is silently lost if the tree is cleaned up. Report done only once the hash has changed. Outside a worktree (a skill under `~/.claude/skills/`, say) nothing is committable — report done on a clean audit, naming that as the reason.

View File

@@ -101,8 +101,10 @@ plain sentence and `disable-model-invocation: true` instead.
- `license` — include when distributing the skill externally
- `compatibility` — include if the skill requires specific tools, runtimes, or network access
(max 500 characters)
- `metadata` — key-value map; use `author`, `version`, `category`; add `source_keys` now (Step 6)
if research sources are in context
- `metadata` — key-value map. `version` is **required** on every skill (ADR-0022), seeded at
`"1.0.0"` for a retrofitted skill with no prior version and at `"0.1.0"` for a newly created
skill; `author` and `category` stay optional; add `source_keys` now (Step 6) if research sources
are in context
- `allowed-tools` — space-separated pre-approved tools; reduces permission prompts (experimental —
support varies by client)
- `disable-model-invocation` — hand-invoked skills only