Skip to content

Commit 2b7cb8e

Browse files
Add per-skill governance cards (skill cards) (#61)
1 parent c931312 commit 2b7cb8e

11 files changed

Lines changed: 254 additions & 6 deletions

File tree

.github/scripts/import_external_skills.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717
github.com URLs pinned to the imported commit, so the offline link
1818
checker doesn't flag them as missing local files. Links to files that
1919
were actually copied into the skill folder are left untouched.
20-
5. Updates `.claude-plugin/marketplace.json` with an entry per imported
20+
5. Synthesizes a minimal `skill-card.md` (Description, Owner, License)
21+
from the source metadata when the upstream copy doesn't already ship
22+
one, so the imported skill satisfies the card validation gate (see
23+
docs/skill-cards.md).
24+
6. Updates `.claude-plugin/marketplace.json` with an entry per imported
2125
skill (using the SKILL.md `description` as the marketplace blurb,
2226
unless the source declares an override).
23-
6. Removes any previously imported skill (one with a `.federated.json`)
27+
7. Removes any previously imported skill (one with a `.federated.json`)
2428
that is no longer listed in `.github/scripts/sources.yml`.
2529
2630
Usage:
@@ -53,6 +57,7 @@
5357
SKILLS_DIR = REPO_ROOT / "skills"
5458
CLAUDE_MARKETPLACE = REPO_ROOT / ".claude-plugin" / "marketplace.json"
5559
MARKER_FILENAME = ".federated.json"
60+
CARD_FILENAME = "skill-card.md"
5661

5762
FRONTMATTER_RE = re.compile(
5863
r"\A---\s*\n(?P<frontmatter>.*?)\n---\s*\n?(?P<body>.*)\Z",
@@ -363,6 +368,32 @@ def write_marker(
363368
)
364369

365370

371+
def write_card(skill_dir: Path, source: Source, description: str) -> None:
372+
"""Write a minimal skill-card.md unless the upstream copy shipped one.
373+
374+
Federated skills are copied wholesale (`copy_skill` does rmtree +
375+
copytree), so any card authored here would be wiped on re-import. When
376+
upstream doesn't provide a card, synthesize one from the source metadata
377+
so the imported skill still satisfies the card validation gate.
378+
"""
379+
card = skill_dir / CARD_FILENAME
380+
if card.exists():
381+
return
382+
owner_org = source.repo.split("/")[0]
383+
license_text = source.license or f"See [{source.repo}](https://github.com/{source.repo})"
384+
card.write_text(
385+
"# Skill Card\n\n"
386+
"## Description\n\n"
387+
f"{description}\n\n"
388+
"## Owner\n\n"
389+
f"{owner_org} (federated from "
390+
f"[{source.repo}](https://github.com/{source.repo}))\n\n"
391+
"## License\n\n"
392+
f"{license_text}\n",
393+
encoding="utf-8",
394+
)
395+
396+
366397
def update_marketplace(results: Iterable[ImportResult], dry_run: bool) -> bool:
367398
"""Sync `.claude-plugin/marketplace.json` with the imported skills.
368399
@@ -467,6 +498,7 @@ def import_source(
467498
if not dry_run:
468499
copy_skill(src_skill, dest_skill)
469500
write_marker(dest_skill, source, commit, relative_path)
501+
write_card(dest_skill, source, marketplace_description)
470502
rewrite_external_references(
471503
dest_skill,
472504
relative_path,

.github/scripts/validate_skills.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
substrings, and matches the directory name
1414
- `description` is a non-empty string <=1024 chars
1515
- SKILL.md body is <=500 lines
16+
- skill-card.md exists at the skill root and has non-empty
17+
`## Description`, `## Owner`, and `## License` sections
1618
1719
Also validates that `.claude-plugin/marketplace.json` is in sync with the
1820
skills on disk: every skill must have a marketplace entry, and every
@@ -61,6 +63,11 @@
6163
)
6264
RESERVED_NAME_SUBSTRINGS = ("anthropic", "claude")
6365

66+
# Per-skill governance card (see docs/skill-cards.md). Each section must be a
67+
# top-level `##` heading followed by some non-empty body text.
68+
CARD_FILENAME = "skill-card.md"
69+
REQUIRED_CARD_SECTIONS = ("Description", "Owner", "License")
70+
6471

6572
@dataclass
6673
class SkillReport:
@@ -102,6 +109,7 @@ def validate_skill(skill_dir: Path) -> SkillReport:
102109
_validate_name(frontmatter.get("name"), skill_dir.name, report)
103110
_validate_description(frontmatter.get("description"), report)
104111
_validate_body(match.group("body"), report)
112+
_validate_card(skill_dir, report)
105113
return report
106114

107115

@@ -157,6 +165,47 @@ def _validate_body(body: str, report: SkillReport) -> None:
157165
)
158166

159167

168+
def _validate_card(skill_dir: Path, report: SkillReport) -> None:
169+
"""Require a skill-card.md with non-empty Description, Owner, License."""
170+
card = skill_dir / CARD_FILENAME
171+
if not card.exists():
172+
report.errors.append(
173+
f"Missing {CARD_FILENAME} (governance card). See docs/skill-cards.md; "
174+
"it needs `## Description`, `## Owner`, and `## License` sections."
175+
)
176+
return
177+
178+
sections = _parse_card_sections(card.read_text(encoding="utf-8"))
179+
for name in REQUIRED_CARD_SECTIONS:
180+
body = sections.get(name.lower())
181+
if body is None:
182+
report.errors.append(f"{CARD_FILENAME} is missing a `## {name}` section.")
183+
elif not body.strip():
184+
report.errors.append(f"{CARD_FILENAME} `## {name}` section is empty.")
185+
186+
187+
def _parse_card_sections(text: str) -> dict[str, str]:
188+
"""Map each `##` heading (lowercased) to the text until the next heading."""
189+
sections: dict[str, str] = {}
190+
current: str | None = None
191+
buffer: list[str] = []
192+
193+
def flush() -> None:
194+
if current is not None:
195+
sections[current] = "\n".join(buffer).strip()
196+
197+
for line in text.splitlines():
198+
heading = re.match(r"^##\s+(?P<title>.+?)\s*$", line)
199+
if heading:
200+
flush()
201+
current = heading.group("title").lower()
202+
buffer = []
203+
elif current is not None:
204+
buffer.append(line)
205+
flush()
206+
return sections
207+
208+
160209
def discover_skills(root: Path) -> list[Path]:
161210
"""List skill directories under `root`, ignoring dotfiles."""
162211
if not root.exists():

CONTRIBUTING.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@ Best for cross-cutting skills that do not have a natural product home.
1717
1. Copy an existing skill folder under `skills/` as a starting point and rename it.
1818
2. Update the `SKILL.md` frontmatter so the `name` and `description` clearly explain *what* the skill does and *when* an agent should reach for it.
1919
3. Add the supporting scripts, templates, and reference docs your instructions point to. Keep skills focused: one well-scoped task per skill is better than one mega-skill.
20-
4. Register the skill in `.claude-plugin/marketplace.json` with a human-readable description (the marketplace description is for humans browsing the catalog; the `SKILL.md` description is what the agent uses for routing).
21-
5. Regenerate the Cursor manifest so it tracks the new skill:
20+
4. Add a `skill-card.md` at the skill root with `## Description`, `## Owner`, and `## License` sections. This is the skill's governance card; see [Skill cards](#skill-cards) and [docs/skill-cards.md](docs/skill-cards.md).
21+
5. Register the skill in `.claude-plugin/marketplace.json` with a human-readable description (the marketplace description is for humans browsing the catalog; the `SKILL.md` description is what the agent uses for routing).
22+
6. Regenerate the Cursor manifest so it tracks the new skill:
2223
```bash
2324
./.github/scripts/publish.sh # writes .cursor-plugin/marketplace.json
2425
```
25-
6. Validate the skill locally before pushing:
26+
7. Validate the skill locally before pushing:
2627
```bash
2728
./.github/scripts/check.sh # validates every SKILL.md and that manifests are in sync
2829
```
29-
7. Open a pull request. The `validate` GitHub Actions workflow runs `./.github/scripts/check.sh` and must pass before merge. See [Validating locally](#validating-locally) for the full set of enforced rules.
30+
8. Open a pull request. The `validate` GitHub Actions workflow runs `./.github/scripts/check.sh` and must pass before merge. See [Validating locally](#validating-locally) for the full set of enforced rules.
3031

3132
### Path B: Skills authored in a product repository
3233

@@ -114,6 +115,7 @@ Link from `SKILL.md` directly to reference files. Do not chain references throug
114115
```
115116
skill-name/
116117
SKILL.md # overview, quick start, links
118+
skill-card.md # governance card (Description, Owner, License)
117119
reference.md # full API / flag reference
118120
examples.md # worked examples
119121
scripts/ # executable utilities
@@ -153,6 +155,30 @@ Pre-made scripts beat generated code: more reliable, fewer tokens, consistent ac
153155
- **Make execution intent explicit.** Write *"Run `analyze.py`..."* (execute) or *"See `analyze.py` for the algorithm"* (read), never both.
154156
- **Use fully qualified MCP tool names.** `ServerName:tool_name`, e.g. `BigQuery:bigquery_schema`. Bare names fail when multiple servers are registered.
155157
158+
## Skill cards
159+
160+
Every skill ships a `skill-card.md` at its root: a short, human-facing governance record that tells a reviewer what the skill does, who owns it, and under what license it ships, without reading the source. It is not loaded by the agent.
161+
162+
The AMD card is intentionally minimal. Three required sections, each a `##` heading with non-empty body text:
163+
164+
```markdown
165+
# Skill Card
166+
167+
## Description
168+
169+
<one sentence: what the skill does, for whom>
170+
171+
## Owner
172+
173+
<team or org accountable for maintenance, e.g. AMD>
174+
175+
## License
176+
177+
<SPDX identifier or link, e.g. MIT>
178+
```
179+
180+
The validator fails any skill whose card is missing or whose required sections are absent or empty. For the full guide, examples, and how federated skills get cards, see [docs/skill-cards.md](docs/skill-cards.md).
181+
156182
## AMD-specific guidance
157183

158184
- **State prerequisites up front.** ROCm version, kernel version, GPU architecture (`gfx942`, `gfx90a`, `gfx1100`, ...), container image, driver branch.
@@ -172,6 +198,7 @@ Test the skill the way users will hit it:
172198

173199
- [ ] Description states the user's goal and includes likely trigger phrases
174200
- [ ] Description is third person and under 1024 characters
201+
- [ ] `skill-card.md` exists with non-empty Description, Owner, and License sections
175202
- [ ] Skill name is lowercase-with-hyphens and ties to the outcome
176203
- [ ] `SKILL.md` body is under 500 lines
177204
- [ ] Reference files are linked one level deep from `SKILL.md`
@@ -198,6 +225,7 @@ The validator checks every skill under `skills/` for:
198225
- `name`: lowercase-with-hyphens, ≤ 64 characters, no `anthropic` / `claude` substrings, matches the directory name
199226
- `description`: non-empty, ≤ 1024 characters
200227
- `SKILL.md` body: ≤ 500 lines
228+
- `skill-card.md`: present at the skill root with non-empty `## Description`, `## Owner`, and `## License` sections
201229

202230
It also checks the plugin manifests:
203231

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ A skill is a self-contained folder that bundles everything an agent needs to per
3939
skills/
4040
rocm-doctor/
4141
SKILL.md
42+
skill-card.md
4243
scripts/
4344
references/
4445
```
4546

4647
When an agent decides a skill is relevant (or you invoke it explicitly), it loads that `SKILL.md` and follows the instructions inside. Descriptions stay in context cheaply; the full body of a skill only loads when the task actually matches.
4748

49+
Every skill also ships a `skill-card.md`: a short, human-facing governance card (Description, Owner, License) that tells a reviewer what the skill is and who stands behind it without reading the source. See [docs/skill-cards.md](docs/skill-cards.md).
50+
4851
## Why a skill, not a doc?
4952

5053
Documentation describes an API surface: every flag, every option, neutral by design. A skill encodes the opinionated path: which flags, which container image, which `gfx` target, which environment variables, in what order. It captures the decisions a senior AMD engineer makes without thinking, in a form the agent can apply consistently across teams and repositories.
@@ -112,6 +115,7 @@ This repo also acts as an **incubator**: a skill can start under `skills/` to it
112115

113116
```
114117
skills/ # All skills the agent can load
118+
docs/ # Long-form documentation (e.g. skill-cards.md)
115119
.claude-plugin/ # Claude Code marketplace manifest
116120
.cursor-plugin/ # Cursor marketplace manifest
117121
plugin-metadata.json # Vendor-neutral identity/discovery metadata
@@ -166,3 +170,5 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for step-by-step instructions and the rul
166170
## License
167171

168172
Released under the MIT License. See [LICENSE](LICENSE) for details.
173+
174+
Copyright(C) 2026 Advanced Micro Devices, Inc. All rights reserved.

docs/skill-cards.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Skill cards
2+
3+
Every skill in this catalog ships a `skill-card.md` next to its `SKILL.md`. The card is a short, human-facing governance record: it tells a reviewer *what* the skill is, *who* owns it, and *under what license* it ships, without making them read the source first.
4+
5+
A `SKILL.md` is written for the agent (routing and instructions). A skill card is written for the people deciding whether to trust, install, or maintain the skill.
6+
7+
## Required sections
8+
9+
The AMD card is intentionally minimal. Three sections are required, each a top-level `##` heading with non-empty body text:
10+
11+
| Section | Question it answers |
12+
| --- | --- |
13+
| Description | What does this skill do, in one sentence? |
14+
| Owner | Who is accountable for maintaining it? |
15+
| License | What license governs its use and redistribution? |
16+
17+
The validator (`.github/scripts/validate_skills.py`) fails any skill whose card is missing or whose required sections are absent or empty.
18+
19+
## Template
20+
21+
Copy this into `skills/<your-skill>/skill-card.md`:
22+
23+
```markdown
24+
# Skill Card
25+
26+
## Description
27+
28+
<one sentence: what the skill does, for whom>
29+
30+
## Owner
31+
32+
<team or org accountable for maintenance, e.g. AMD>
33+
34+
## License
35+
36+
<SPDX identifier or link, e.g. MIT>
37+
```
38+
39+
## Writing a good Description
40+
41+
Keep it to one sentence that states the outcome, matching the marketplace blurb. Avoid restating internal mechanics (that belongs in `SKILL.md`).
42+
43+
```
44+
Good: Diagnose why ROCm, PyTorch, or llama.cpp isn't working on an AMD GPU
45+
and propose the next step.
46+
Bad: Runs a series of Python scripts that parse logs and apply regex rules.
47+
```
48+
49+
## Federated skills
50+
51+
Skills imported from a product repository (see [`.github/scripts/sources.yml`](../.github/scripts/sources.yml)) are vendored wholesale, so a card authored here would be overwritten on the next import. If upstream ships its own `skill-card.md`, it is kept as-is; otherwise the importer synthesizes a minimal card from the source metadata (description, owner repo, and license). To customize a federated skill's card, add a `skill-card.md` to the skill folder in the upstream repository.
52+
53+
## Out of scope (for now)
54+
55+
The card stays at Description, Owner, and License. Evaluation results, benchmark data, risk statements, and signing identifiers are not part of the AMD card today; sections can be added later without breaking the validation gate.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Skill Card
2+
3+
## Description
4+
5+
Inspect and tune the shared-vs-dedicated memory split (GTT / UMA Frame Buffer) on AMD Ryzen APUs so larger LLMs and image models fit on the iGPU.
6+
7+
## Owner
8+
9+
AMD
10+
11+
## License
12+
13+
MIT
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Skill Card
2+
3+
## Description
4+
5+
Integrate local AI into cloud LLM apps for offline support, better privacy, and lower API costs.
6+
7+
## Owner
8+
9+
AMD
10+
11+
## License
12+
13+
MIT

skills/local-ai-use/skill-card.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Skill Card
2+
3+
## Description
4+
5+
Route image generation, text-to-speech, and speech-to-text through a local AI Server to reduce token/cost usage.
6+
7+
## Owner
8+
9+
AMD
10+
11+
## License
12+
13+
MIT

skills/magpie/skill-card.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Skill Card
2+
3+
## Description
4+
5+
Perform GPU kernel correctness and performance evaluation and LLM inference benchmarking with Magpie (HIP/CUDA/PyTorch kernels, vLLM/SGLang benchmarks, TraceLens, and torch-trace gap analysis).
6+
7+
## Owner
8+
9+
AMD-AGI (federated from [AMD-AGI/Magpie](https://github.com/AMD-AGI/Magpie))
10+
11+
## License
12+
13+
MIT

skills/rocm-doctor/skill-card.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Skill Card
2+
3+
## Description
4+
5+
Diagnose why ROCm, PyTorch, or llama.cpp isn't working on an AMD GPU by matching the symptom against a fixed list of known misconfigurations and proposing the next step.
6+
7+
## Owner
8+
9+
AMD
10+
11+
## License
12+
13+
MIT

0 commit comments

Comments
 (0)