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:
@@ -259,8 +259,18 @@ repos:
|
|||||||
- |
|
- |
|
||||||
for f in "$@"; do
|
for f in "$@"; do
|
||||||
if [[ -f "$f" ]]; then
|
if [[ -f "$f" ]]; then
|
||||||
if ! grep -q "^name:" "$f" || ! grep -q "^description:" "$f"; then
|
missing=""
|
||||||
echo "ERROR: $f is missing required frontmatter fields (name: and description:)"
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
70
docs/adr/0022-skill-metadata-version-is-mandatory.md
Normal file
70
docs/adr/0022-skill-metadata-version-is-mandatory.md
Normal 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.
|
||||||
@@ -4,6 +4,8 @@ disable-model-invocation: true
|
|||||||
description: >
|
description: >
|
||||||
Ultra-compressed output mode that drops articles, filler and pleasantries while
|
Ultra-compressed output mode that drops articles, filler and pleasantries while
|
||||||
keeping technical substance exact, cutting token usage by roughly 75%.
|
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.
|
Respond terse like smart caveman. All technical substance stay. Only fluff die.
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ description: >
|
|||||||
Use when the user says "diagnose this" or "debug this", reports something
|
Use when the user says "diagnose this" or "debug this", reports something
|
||||||
broken, throwing, or failing, or says something got slow. Not filing or
|
broken, throwing, or failing, or says something got slow. Not filing or
|
||||||
triaging a reported bug -> `triage`. Not test-first feature work -> `tdd`.
|
triaging a reported bug -> `triage`. Not test-first feature work -> `tdd`.
|
||||||
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Diagnose
|
# Diagnose
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ description: >
|
|||||||
relentless interview — one question at a time, down each branch of the
|
relentless interview — one question at a time, down each branch of the
|
||||||
decision tree. Not a plan to challenge against `CONTEXT.md` and ADRs ->
|
decision tree. Not a plan to challenge against `CONTEXT.md` and ADRs ->
|
||||||
`grill-with-docs`.
|
`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.
|
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.
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ description: >
|
|||||||
Use when a plan should be stress-tested against the project's domain model —
|
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
|
the interview challenges terms against `CONTEXT.md` and writes decisions into
|
||||||
it and into ADRs as they land. Not a plain interview -> `grill-me`.
|
it and into ADRs as they land. Not a plain interview -> `grill-me`.
|
||||||
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
---
|
---
|
||||||
|
|
||||||
<what-to-do>
|
<what-to-do>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ description: >
|
|||||||
testable and AI-navigable — deepening opportunities that turn shallow modules
|
testable and AI-navigable — deepening opportunities that turn shallow modules
|
||||||
into deep ones, informed by `CONTEXT.md` and `docs/adr/`. Not debugging a
|
into deep ones, informed by `CONTEXT.md` and `docs/adr/`. Not debugging a
|
||||||
failure -> `diagnose`.
|
failure -> `diagnose`.
|
||||||
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Improve Codebase Architecture
|
# Improve Codebase Architecture
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ description: >
|
|||||||
a data model, state machine or business logic, or to mock up a UI in several
|
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 ->
|
variations. Not production code -> `tdd`. Not talking a design through ->
|
||||||
`grill-me`.
|
`grill-me`.
|
||||||
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Prototype
|
# Prototype
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ description: >-
|
|||||||
documentation written from existing code or specs -> `write-docs`. Not a bug
|
documentation written from existing code or specs -> `write-docs`. Not a bug
|
||||||
or incident -> `diagnose`.
|
or incident -> `diagnose`.
|
||||||
metadata:
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
category: research
|
category: research
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Grep
|
- Grep
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ description: >
|
|||||||
Use when the user wants a feature built or a bug fixed test-first, in a strict
|
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
|
red-green-refactor loop, one behaviour at a time. Not diagnosing an existing
|
||||||
bug -> `diagnose`. Not throwaway exploratory code -> `prototype`.
|
bug -> `diagnose`. Not throwaway exploratory code -> `prototype`.
|
||||||
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Test-Driven Development
|
# Test-Driven Development
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ description: >
|
|||||||
Use when the user wants an issue created, triaged, or moved through the
|
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
|
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`.
|
the bug itself -> `diagnose`. Not fleshing out a design -> `grill-with-docs`.
|
||||||
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Triage
|
# Triage
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ description: >
|
|||||||
module", "create docs for this feature", "write a README for this". Not an ADR
|
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
|
or other decision record -> `grill-with-docs`. Not an external tool researched
|
||||||
from its docs -> `research`.
|
from its docs -> `research`.
|
||||||
version: "1.0"
|
|
||||||
updated: 2026-05-17
|
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
|
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:
|
metadata:
|
||||||
|
version: "1.0"
|
||||||
category: implement
|
category: implement
|
||||||
source:
|
source:
|
||||||
- repo: anthropics/skills
|
- repo: anthropics/skills
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
name: zoom-out
|
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.
|
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
|
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.
|
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.
|
||||||
|
|||||||
@@ -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.
|
is not actually required for any of this domain's five tools.
|
||||||
|
|
||||||
metadata:
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
category: gitea
|
category: gitea
|
||||||
source_keys:
|
source_keys:
|
||||||
- gitea-mcp-repo
|
- gitea-mcp-repo
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ description: >
|
|||||||
directory -> skill-audit.
|
directory -> skill-audit.
|
||||||
allowed-tools: Bash Read
|
allowed-tools: Bash Read
|
||||||
metadata:
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
category: factory
|
category: factory
|
||||||
source_keys:
|
source_keys:
|
||||||
- context7-websites-code-claude
|
- context7-websites-code-claude
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ description: >
|
|||||||
Not read-only review -> `agent-audit`. Not skills -> `skill-author`.
|
Not read-only review -> `agent-audit`. Not skills -> `skill-author`.
|
||||||
allowed-tools: Bash Read Write Edit
|
allowed-tools: Bash Read Write Edit
|
||||||
metadata:
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
category: factory
|
category: factory
|
||||||
source_keys:
|
source_keys:
|
||||||
- context7-websites-code-claude
|
- context7-websites-code-claude
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ description: >
|
|||||||
authoring, publishing, auditing, or dependency installation for an apm
|
authoring, publishing, auditing, or dependency installation for an apm
|
||||||
package -> `apm-workflow`.
|
package -> `apm-workflow`.
|
||||||
metadata:
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
category: apm
|
category: apm
|
||||||
source_keys:
|
source_keys:
|
||||||
- context7-microsoft-apm
|
- context7-microsoft-apm
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ description: >
|
|||||||
the dependencies it declares, or an apm marketplace — even when the user does
|
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`.
|
not say "apm". Not the apm binary or an agent runtime -> `apm-install`.
|
||||||
metadata:
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
category: apm
|
category: apm
|
||||||
source_keys:
|
source_keys:
|
||||||
- context7-microsoft-apm
|
- context7-microsoft-apm
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ description: >
|
|||||||
already named — invoke `skill-author`, `agent-author` or `apm-workflow`
|
already named — invoke `skill-author`, `agent-author` or `apm-workflow`
|
||||||
directly.
|
directly.
|
||||||
metadata:
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
category: factory
|
category: factory
|
||||||
source_keys:
|
source_keys:
|
||||||
- claude-code-subagents-docs
|
- claude-code-subagents-docs
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ description: >
|
|||||||
skill-author.
|
skill-author.
|
||||||
allowed-tools: Bash Read
|
allowed-tools: Bash Read
|
||||||
metadata:
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
category: factory
|
category: factory
|
||||||
source_keys:
|
source_keys:
|
||||||
- agentskills-home
|
- agentskills-home
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ description: >
|
|||||||
Not read-only review -> `skill-audit`. Not agent files -> `agent-author`.
|
Not read-only review -> `skill-audit`. Not agent files -> `agent-author`.
|
||||||
allowed-tools: Bash Read Write Edit
|
allowed-tools: Bash Read Write Edit
|
||||||
metadata:
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
category: factory
|
category: factory
|
||||||
source_keys:
|
source_keys:
|
||||||
- agentskills-home
|
- 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.
|
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.
|
**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.
|
||||||
|
|||||||
@@ -101,8 +101,10 @@ plain sentence and `disable-model-invocation: true` instead.
|
|||||||
- `license` — include when distributing the skill externally
|
- `license` — include when distributing the skill externally
|
||||||
- `compatibility` — include if the skill requires specific tools, runtimes, or network access
|
- `compatibility` — include if the skill requires specific tools, runtimes, or network access
|
||||||
(max 500 characters)
|
(max 500 characters)
|
||||||
- `metadata` — key-value map; use `author`, `version`, `category`; add `source_keys` now (Step 6)
|
- `metadata` — key-value map. `version` is **required** on every skill (ADR-0022), seeded at
|
||||||
if research sources are in context
|
`"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 —
|
- `allowed-tools` — space-separated pre-approved tools; reduces permission prompts (experimental —
|
||||||
support varies by client)
|
support varies by client)
|
||||||
- `disable-model-invocation` — hand-invoked skills only
|
- `disable-model-invocation` — hand-invoked skills only
|
||||||
|
|||||||
Reference in New Issue
Block a user