
Skill Standardization
- 210 installs
- 40 repo stars
- Updated August 5, 2026
- akillness/oh-my-skills
Author or refactor Claude Code skills so they follow a shared structure, naming, triggers, and quality bar before publishing them to teammates or open-source collections.
About
Skill-standardization guides creation and cleanup of Claude Code skills in oh-my-skills, enforcing shared structure, triggers, and documentation so agent instructions remain portable, reviewable, and safe to reuse across repositories and contributors.
- Enforces consistent skill layout and metadata
- Aligns triggers, scope, and naming conventions
- Improves maintainability of shared skill libraries
- Reduces duplicate or conflicting agent instructions
- Supports reviewable skill authoring workflows
Skill Standardization by the numbers
- 210 all-time installs (skills.sh)
- Ranked #176 of 779 Skill Development skills by installs in the Skillselion catalog
- Data as of Aug 6, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akillness/oh-my-skills --skill skill-standardizationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 210 |
|---|---|
| repo stars | ★ 40 |
| Last updated | August 5, 2026 |
| Repository | akillness/oh-my-skills ↗ |
What it does
Author or refactor Claude Code skills so they follow a shared structure, naming, triggers, and quality bar before publishing them to teammates or open-source collections.
Files
Skill Standardization
When to use this skill
- Creating a new
SKILL.mdfrom scratch - Auditing an existing skill for Agent Skills spec compliance
- Repairing weak trigger descriptions or stale route-outs
- Canonicalizing duplicate skills into a default skill plus compatibility alias
- Validating repo-level discovery/catalog drift after add/rename/remove/alias work
- Reviewing
SKILL.toon/SKILL.compact.mddrift after material rewrites
Instructions
Step 1: Pick the smallest working mode
Use references/working-modes.md and pick exactly one primary mode: 1. validate or repair one existing skill 2. create or rewrite a SKILL.md 3. canonicalize duplicates 4. structural catalog / discovery sync 5. derived-surface sync after a material rewrite
Do not start with a giant blended pass if one of those modes answers the job.
Step 2: Run the truthful validator command for the current working directory
For jeo-skills repo-root maintenance loops, use:
bash .agent-skills/skill-standardization/scripts/validate_skill.sh .agent-skills/<skill-name>If you are running from inside .agent-skills/skill-standardization, use the skill-local form from references/working-modes.md instead. Do not copy bare scripts/... commands into repo-root runbooks unless the current cwd really makes them valid.
The validator checks:
- required frontmatter (
name,description) - directory-safe naming
- description length and imperative trigger phrasing
- recommended sections
- file-length warning
Repo-specific validator nuance:
validate_skill.shis expected to accept valid multilinedescription: >blocks that contain ordinary apostrophes or shell-like text. If it still fails on prose-level quoting, treat that as a validator bug to repair, not as a reason to weaken the skill description.- Prefer fixing the validator or adding a regression case before rewriting natural language just to appease shell parsing.
Step 3: Fix or write the primary SKILL.md
When creating or rewriting the skill:
- keep the description trigger-oriented and specific about when to use the skill
- make the default job-to-be-done obvious before listing edge cases
- keep the front door routing-first; move slower-changing examples/checklists/templates to
references/when they start to crowd the main skill - keep
SKILL.mdunder 500 lines and preferably much smaller when the skill is a high-frequency router
Use the compact template and legacy heading map in references/working-modes.md instead of copying a bloated starter by hand.
Step 4: Improve description quality before polishing anything else
A weak description means the skill never activates. Use this pattern:
description: >
[What it does — list specific operations.]
Use when [trigger conditions]. Even if the user does not explicitly
mention [domain keyword] — also triggers on: [synonym list].Description rules: 1. use imperative phrasing — "Use when..." 2. describe user intent, not internal implementation 3. include edge triggers and nearby synonyms 4. stay under 1024 characters 5. keep route-outs truthful when adjacent skills should win
Step 5: Add evals whenever the behavior changed materially
Create or refresh evals/evals.json when you add a skill, rewrite its trigger surface, or narrow it into an alias.
Good eval coverage includes:
- a normal prompt that should clearly trigger the skill
- a near-miss prompt that should route elsewhere
- a structural-change prompt that checks catalog/discovery sync when relevant
- compact/discovery-surface checks when
SKILL.toon/SKILL.compact.mdmatter in the repo
Keep assertions concrete and verifiable.
Step 6: Canonicalize duplicates instead of letting them compete
When two skills cover the same default job closely enough that their name + description metadata competes: 1. pick the canonical skill 2. sharpen the canonical description so it wins ordinary prompts 3. convert the overlapping skill into a narrow compatibility alias when old workflows or exact-name installs still depend on it 4. add evals for both sides 5. sync discovery surfaces in the same change so users do not see two false peers 6. if the alias includes support docs or examples that restate the canonical contract, refresh those alias-side docs too so they do not keep advertising the pre-ratchet packet shape
Hard deletion is usually a later step, not the first move.
Step 7: Run structural catalog sync after add / rename / remove / alias / major repositioning work
For repo-level drift in jeo-skills, use:
python3 .agent-skills/skill-standardization/scripts/validate_catalog_sync.py --repo-root /Users/jang_jennie/projects/jeo-skillsPortable form:
python3 .agent-skills/skill-standardization/scripts/validate_catalog_sync.py --repo-root /path/to/repoTreat the catalog validator as a guardrail, not the whole job. It checks membership/count/path drift, but it does not replace manual review of derived discovery wording or compact freshness.
Step 8: Sync derived discovery surfaces when the rewrite was material
If SKILL.md changed the job-to-be-done, route-outs, or supported use-cases materially, review and refresh as needed:
- skill-local
SKILL.toon/SKILL.compact.md - alias-side support docs or checklists that mirror the canonical contract
.agent-skills/skills.json.agent-skills/skills.toonREADME.mdREADME.ko.mdsetup-all-skills-prompt.md
If one of those surfaces does not need a change, be able to explain why.
Step 9: Do a residue scan before calling the pass done
After support-heavy or structural rewrites, search for obvious residue:
- stale filenames or paths
- removed command examples
- old canonical-vs-alias wording
- compact/discovery text that still advertises the old job
Keep the pass only if the result is more truthful and transferable than the baseline.
Available scripts
bash .agent-skills/skill-standardization/scripts/validate_skill.sh <skill-dir>— validate one skillbash .agent-skills/skill-standardization/scripts/validate_skill.sh --all .agent-skills/— batch validationpython3 .agent-skills/skill-standardization/scripts/validate_catalog_sync.py --repo-root /path/to/repo— compare live folders against catalog/discovery surfacesbash .agent-skills/skill-standardization/scripts/regression_folded_description_quotes.sh [repo-root]— regression-check folded descriptions with apostrophes / shell-like text
Examples
Example 1: Validate one skill from repo root
bash .agent-skills/skill-standardization/scripts/validate_skill.sh .agent-skills/my-skillExample 2: Batch validate the whole repo
bash .agent-skills/skill-standardization/scripts/validate_skill.sh --all .agent-skills/Example 3: Check catalog/discovery sync after a rename or alias change
python3 .agent-skills/skill-standardization/scripts/validate_catalog_sync.py --repo-root /path/to/repoUse this after adding, renaming, removing, or canonicalizing a skill so stale manifest entries, wrong inventory counts, and forgotten derived-surface updates do not linger.
Example 4: Fix common frontmatter issues
# WRONG
metadata:
tags: [tag1, tag2]
platforms: Claude
# CORRECT
metadata:
tags: tag1, tag2
allowed-tools: Bash Read WriteBest practices
1. Description quality first — weak descriptions mean the skill never activates 2. Shrink high-frequency routers — move slower-changing detail to references/ before the front door turns into a handbook 3. Use truthful command paths — match repo-root vs skill-local execution context explicitly 4. Keep scripts non-interactive and structured — prefer flags and machine-readable output 5. Add evals before publishing — cover should-trigger, should-not-trigger, and structural-change cases 6. Treat compact/discovery files as derived artifacts — refresh them after material rewrites or document why not 7. Canonicalize duplicates instead of multiplying peers — prefer one default skill plus a compatibility alias 8. Sync alias-side support when the canonical contract changes — if the alias has checklists, references, or examples that restate the old packet shape, update them in the same pass so the alias does not quietly preserve stale guidance 9. Run residue scans after structural rewrites — passing validators can still leave stale filenames, commands, or discovery copy behind
References
{
"skill_name": "skill-standardization",
"evals": [
{
"id": 1,
"prompt": "Check if my SKILL.md file follows the Agent Skills specification format. The file is at .agent-skills/my-skill/SKILL.md",
"expected_output": "A validation report listing any issues with frontmatter fields, name format, description length, imperative trigger phrasing, and recommended sections.",
"assertions": [
"The output checks for the required 'name' field",
"The output checks for the required 'description' field",
"The output verifies the name follows lowercase hyphenated format",
"The output checks description length is under 1024 characters",
"The output checks for recommended sections such as When to use this skill, Instructions, and Examples"
]
},
{
"id": 2,
"prompt": "Create a new SKILL.md for a git-workflow skill that handles committing, branching, and PR creation",
"expected_output": "A properly formatted SKILL.md with correct frontmatter, a trigger-oriented description, and the standard sections.",
"assertions": [
"The output includes YAML frontmatter between --- delimiters",
"The frontmatter contains a 'name' field set to 'git-workflow'",
"The frontmatter contains a 'description' field",
"The description uses imperative phrasing containing 'Use when' or similar",
"The description is under 1024 characters",
"The output includes ## When to use this skill section",
"The output includes ## Instructions section",
"The output includes ## Examples section"
]
},
{
"id": 3,
"prompt": "My skill description is 'Helps with CSV files.' — improve it so the skill triggers more reliably",
"expected_output": "An improved description with imperative phrasing, specific operations, trigger keywords, and route-aware wording under 1024 characters.",
"assertions": [
"The improved description contains 'Use when' or similar imperative phrasing",
"The improved description lists specific operations instead of generic help wording",
"The improved description is under 1024 characters",
"The improved description includes trigger keywords or synonyms"
]
},
{
"id": 4,
"prompt": "Validate all the skills in the .agent-skills directory and give me a summary of which ones have issues",
"expected_output": "A per-skill validation report showing errors and warnings for each skill, with a total summary.",
"assertions": [
"The output covers multiple skills",
"The output distinguishes between errors and warnings",
"The output includes a total or summary count"
]
},
{
"id": 5,
"prompt": "We renamed one skill, removed another, and added a new game skill. Check whether skills.json and the README/setup inventory are still in sync with the .agent-skills folders.",
"expected_output": "A response that validates catalog/discovery surfaces, compares live folders against skills.json and README/setup counts, and identifies stale or missing entries.",
"assertions": [
"The output checks live skill folders against skills.json",
"The output checks README or setup inventory counts after structural changes",
"The output flags stale manifest-only names or missing live skills",
"The output treats catalog sync as part of the standardization workflow"
]
},
{
"id": 6,
"prompt": "I rewrote a skill's SKILL.md description and route-outs, but I also ship SKILL.toon for runtime discovery. What should I validate before I call the standardization pass done?",
"expected_output": "A response that treats SKILL.toon / SKILL.compact.md as derived discovery surfaces, checks whether compact variants need refresh after material trigger-surface changes, and keeps catalog/runtime sync in scope.",
"assertions": [
"The output mentions refreshing or reviewing SKILL.toon or SKILL.compact.md after material rewrites",
"The output distinguishes compact-variant sync from only checking skills.json and README/setup counts",
"The output keeps runtime discovery or query/list tooling in scope",
"The output frames compact files as derived artifacts, not independent source of truth"
]
},
{
"id": 7,
"prompt": "I'm at the repo root of jeo-skills. What exact command should I run to validate .agent-skills/skill-standardization, and what still needs manual review after the scripts pass?",
"expected_output": "A response that gives the truthful repo-root validator command, distinguishes repo-root from skill-local execution, and lists manual review items such as compact-surface freshness or stale discovery copy.",
"assertions": [
"The output uses the nested repo-root path .agent-skills/skill-standardization/scripts/validate_skill.sh instead of bare scripts/validate_skill.sh",
"The output distinguishes repo-root execution from skill-local execution or notes the working-directory dependency",
"The output names at least one manual review item beyond the scripts, such as SKILL.toon freshness, manifest command residue, or README/setup discovery wording"
]
},
{
"id": 8,
"prompt": "A valid SKILL.md uses `description: >` and says `ClawTeam's operator guide` in the folded description. Validate it without telling me to rewrite the prose just to appease shell parsing.",
"expected_output": "A response that treats folded descriptions with apostrophes as valid input, keeps the validator bug/repair path in scope if parsing fails, and avoids author-side wording downgrades as the default fix.",
"assertions": [
"The output accepts ordinary apostrophes inside valid folded descriptions as something the validator should handle",
"The output frames shell-quote failures as validator bugs or regression cases to repair, not as a reason to rewrite natural language",
"The output keeps repo-root validator usage in scope when explaining the validation path"
]
},
{
"id": 9,
"prompt": "I tightened the canonical skill and refreshed skills.json plus README/setup, but the compatibility alias still has a checklist and reference packet. What else must I review before calling the standardization pass done?",
"expected_output": "A response that treats alias-side support docs/checklists/examples as derived surfaces whenever they mirror the canonical contract, not just top-level discovery files.",
"assertions": [
"The output explicitly tells the maintainer to review alias-side support docs, checklists, or examples when a compatibility alias mirrors the canonical packet",
"The output distinguishes alias-side support sync from only refreshing skills.json, README/setup, or SKILL.toon",
"The output frames stale alias support docs as a real residue risk even if catalog validators pass"
]
}
]
}
Catalog Sync Checklist
Use this checklist whenever a skill is added, renamed, removed, or materially repositioned.
Source of truth
1. Start from the live filesystem: every installable skill should have ./.agent-skills/<name>/SKILL.md. 2. Treat catalog/index artifacts as derived surfaces, not independent truth. 3. When a rename creates a canonical skill + compatibility alias, make sure both folder names and frontmatter names still match their directories.
Surfaces to verify
.agent-skills/skills.jsonREADME.mdREADME.ko.mdsetup-all-skills-prompt.md- token-optimized discovery variants such as
SKILL.toonorSKILL.compact.mdwhen the repo/runtime ships them - any query or list tooling that loads the catalog (for this repo,
skill-query-handler.py)
What to check
1. Catalog membership
- every live skill folder appears in
skills.json - removed/renamed skills are not left behind as stale manifest entries
- category groupings reflect the live repo structure closely enough that users can find specialist lanes
2. Path and naming integrity
- manifest
namematches folder name - manifest
pathpoints to the liveSKILL.md - compatibility aliases are labeled as aliases instead of equal peers
3. Discovery copy
- README and setup prompt counts match the live folder count
- inventories include newly added specialist clusters when they materially affect discovery
- setup prompts do not advertise removed skills
4. Compact discovery variants
- if
SKILL.mdchanged the trigger surface or supported use-cases materially, refreshSKILL.toon/SKILL.compact.md - compact variants should preserve the current default job-to-be-done and major route-outs, not stale legacy wording
- if no compact variant update was needed, be able to explain why the rewrite was non-material for runtime discovery
5. Runtime behavior
- query/list tooling should still find live skills even if the manifest is stale
- manifest metadata can enrich search terms, but it should not hide filesystem skills
Keep / revert rule
- Keep changes that reduce silent drift and improve discovery for multiple future runs.
- Revert changes that only patch one count/string while leaving runtime discovery brittle.
Working Modes
Use the smallest mode that solves the maintenance problem.
Mode 1 — Validate or repair one existing skill
Best for frontmatter, section, trigger-description, and size hygiene on a single folder.
Repo-root commands (jeo-skills workflow)
bash .agent-skills/skill-standardization/scripts/validate_skill.sh .agent-skills/<skill-name>Skill-local alternative
cd .agent-skills/skill-standardization
bash scripts/validate_skill.sh ../<skill-name>What this validator covers
- required frontmatter (
name,description) - name format and directory match
- description length and imperative trigger phrasing
- quote-safe folded-description extraction for ordinary prose (apostrophes, backticks, shell-like text)
- recommended sections
- file-length warning
What you still review manually
- whether route-outs are truthful
- whether examples match the repo's actual workflow
- whether
SKILL.toon/SKILL.compact.mdneed refresh after a material rewrite - whether a new parser edge case deserves a durable regression check such as
scripts/regression_folded_description_quotes.sh
Mode 2 — Create or rewrite a SKILL.md
Use when there is no good SKILL.md yet or the current one is too weak to keep.
Compact starter template
---
name: skill-name
description: >
[What it does and specific operations it handles.]
Use when [trigger conditions]. Triggers on: [keyword list].
allowed-tools: Bash Read Write Edit Glob Grep
metadata:
tags: tag1, tag2, tag3
version: "1.0"
---
# Skill Title
## When to use this skill
- Scenario 1
- Scenario 2
## Instructions
### Step 1: [Action]
### Step 2: [Action]
## Examples
### Example 1: [Scenario]
## Best practices
1. Practice 1
## References
- [Link](url)Legacy heading map
| Legacy heading | Standard heading |
|---|---|
## Purpose | ## When to use this skill |
## When to Use | ## When to use this skill |
## Procedure | ## Instructions |
## Best Practices | ## Best practices |
## Reference | ## References |
## Output Format | ## Output format |
Mode 3 — Canonicalize duplicates
Use when two skills compete for the same default job.
Keep the canonical skill focused
- sharpen the canonical description so it wins ordinary prompts
- narrow the legacy/exact-name skill into a compatibility alias
- add evals that prove canonical-vs-alias behavior
- sync discovery surfaces in the same pass
- if the alias has support docs, examples, or checklists that restate the canonical packet, refresh those alias-side docs too
Mode 4 — Structural catalog / discovery sync
Use after add / rename / remove / alias / major repositioning work.
Repo-root commands (jeo-skills workflow)
python3 .agent-skills/skill-standardization/scripts/validate_catalog_sync.py --repo-root /Users/jang_jennie/projects/jeo-skillsPortable form
python3 .agent-skills/skill-standardization/scripts/validate_catalog_sync.py --repo-root /path/to/repoWhat the catalog validator covers
- live skill folders vs
skills.json - manifest path existence
- recorded skill count
- README / README.ko / setup prompt count surfaces
What you still review manually
SKILL.toon/SKILL.compact.mdfreshness after material rewrites- alias wording and discovery copy quality
- alias-side support docs/checklists/examples that may still mirror the pre-ratchet canonical packet
- stale manifest command examples or other support-file residue
- runtime/query tooling behavior when a change affects discovery semantics
Mode 5 — Derived-surface sync
Use when SKILL.md changed the trigger surface, route-outs, or supported use-cases materially.
Check these surfaces together:
- skill-local
SKILL.toon/SKILL.compact.md - alias-side support docs/checklists/examples when a compatibility alias mirrors the canonical contract
.agent-skills/skills.json.agent-skills/skills.toonREADME.mdREADME.ko.mdsetup-all-skills-prompt.md
If you leave a discovery surface unchanged, be able to explain why the rewrite was non-material for that surface.
#!/usr/bin/env bash
set -euo pipefail
repo_root="${1:-/Users/jang_jennie/projects/jeo-skills}"
validator="$repo_root/.agent-skills/skill-standardization/scripts/validate_skill.sh"
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
mkdir -p "$tmpdir/problem-skill"
cat > "$tmpdir/problem-skill/SKILL.md" <<'EOF'
---
name: problem-skill
description: >
Validate a skill when the ClawTeam's operator guide includes shell-like text and route-outs.
allowed-tools: Bash Read Write
---
# Problem Skill
## When to use this skill
- Demo.
## Instructions
- Demo.
## Examples
- Demo.
## Best practices
1. Demo.
## References
- Demo.
EOF
bash "$validator" "$tmpdir/problem-skill"
#!/usr/bin/env python3
"""Validate that shipped skill catalog surfaces stay in sync with the live filesystem."""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
COUNT_PATTERNS = {
'README.md': [
(r'(\d+) local skill folders', 'local skill folder count'),
(r'← (\d+) skill folders', 'tree snippet skill folder count'),
],
'README.ko.md': [
(r'(\d+)개 로컬 스킬 폴더', 'local skill folder count'),
(r'← (\d+) skill folders', 'tree snippet skill folder count'),
],
'setup-all-skills-prompt.md': [
(r'Install ALL (\d+) skills', 'install-all count'),
(r'Skill Inventory \((\d+) skills\)', 'inventory count'),
],
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description='Validate skill catalog / docs sync against live skill folders.')
parser.add_argument('--repo-root', default=Path(__file__).resolve().parents[3], type=Path)
parser.add_argument('--json', action='store_true', help='Emit machine-readable JSON')
return parser.parse_args()
def live_skills(skills_root: Path) -> list[str]:
return sorted(p.parent.name for p in skills_root.glob('*/SKILL.md'))
def load_manifest(path: Path) -> dict:
if not path.exists():
return {}
return json.loads(path.read_text(encoding='utf-8'))
def manifest_names(data: dict) -> list[str]:
return sorted(entry.get('name') for entry in data.get('skills', []) if isinstance(entry, dict) and entry.get('name'))
def check_counts(repo_root: Path, live_count: int) -> list[dict]:
findings = []
for rel, patterns in COUNT_PATTERNS.items():
path = repo_root / rel
if not path.exists():
findings.append({'level': 'warn', 'surface': rel, 'message': 'file missing'})
continue
text = path.read_text(encoding='utf-8')
for pattern, label in patterns:
for match in re.finditer(pattern, text):
value = int(match.group(1))
if value != live_count:
findings.append({
'level': 'error',
'surface': rel,
'message': f'{label} = {value}, expected {live_count}',
})
return findings
def main() -> int:
args = parse_args()
repo_root = args.repo_root.expanduser().resolve()
skills_root = repo_root / '.agent-skills'
manifest_path = skills_root / 'skills.json'
live = live_skills(skills_root)
live_set = set(live)
data = load_manifest(manifest_path)
manifest = manifest_names(data)
manifest_set = set(manifest)
findings = []
missing = sorted(live_set - manifest_set)
extras = sorted(manifest_set - live_set)
if missing:
findings.append({'level': 'error', 'surface': 'skills.json', 'message': f'missing live skills: {missing}'})
if extras:
findings.append({'level': 'error', 'surface': 'skills.json', 'message': f'stale manifest-only skills: {extras}'})
declared_count = data.get('skill_count')
if declared_count is not None and declared_count != len(live):
findings.append({'level': 'error', 'surface': 'skills.json', 'message': f'skill_count={declared_count}, expected {len(live)}'})
for entry in data.get('skills', []):
if not isinstance(entry, dict) or not entry.get('name'):
continue
name = entry['name']
path_value = entry.get('path')
if path_value:
target = skills_root / path_value
if name in live_set and not target.exists():
findings.append({'level': 'warn', 'surface': 'skills.json', 'message': f'path mismatch for {name}: {path_value}'})
findings.extend(check_counts(repo_root, len(live)))
summary = {
'live_skill_count': len(live),
'manifest_skill_count': len(manifest),
'missing_from_manifest': missing,
'manifest_only': extras,
'findings': findings,
}
if args.json:
print(json.dumps(summary, indent=2, ensure_ascii=False))
else:
print(f'Live skills: {len(live)}')
print(f'Manifest skills: {len(manifest)}')
if missing:
print(f'Missing from manifest: {", ".join(missing)}')
if extras:
print(f'Manifest-only entries: {", ".join(extras)}')
if findings:
print('\nFindings:')
for item in findings:
print(f"- [{item['level']}] {item['surface']}: {item['message']}")
else:
print('\nNo drift detected.')
return 1 if any(item['level'] == 'error' for item in findings) else 0
if __name__ == '__main__':
raise SystemExit(main())
#!/usr/bin/env bash
# validate_skill.sh — Validates a SKILL.md against the Agent Skills specification
# Usage:
# validate_skill.sh <skill-directory> # validate one skill
# validate_skill.sh --all <skills-dir> # validate all skills in directory
# validate_skill.sh --help # show usage
set -euo pipefail
ERRORS=0
WARNINGS=0
TOTAL_ERRORS=0
TOTAL_WARNINGS=0
usage() {
cat <<EOF
Usage: validate_skill.sh [--all] <path>
validate_skill.sh my-skill/ Validate a single skill directory
validate_skill.sh --all .agent-skills/ Validate all skill directories
Checks performed:
- Required frontmatter fields: name, description
- name format: lowercase, no consecutive hyphens, matches directory name
- description length: 1-1024 characters
- description phrasing: warns if missing imperative trigger language
- allowed-tools format: space-delimited (not YAML list)
- Recommended sections: When to use, Instructions, Examples, Best practices, References
- File length: warns if over 500 lines
Exit codes:
0 = no errors (warnings may exist)
1 = one or more errors found
EOF
}
check() {
local status="$1" msg="$2"
if [[ "$status" == "ok" ]]; then
echo " ✓ $msg"
elif [[ "$status" == "warn" ]]; then
echo " ⚠ $msg"
((WARNINGS++)) || true
else
echo " ✗ $msg"
((ERRORS++)) || true
fi
}
extract_fm_field() {
local content="$1" field="$2"
# Handle both single-line and block scalar (>) values
# Use || true to prevent set -e from triggering when grep finds no match
echo "$content" | grep -E "^${field}:" | head -1 | sed "s/^${field}: *//" | tr -d '"' | sed 's/^>//' || true
}
validate_skill() {
local skill_dir="$1"
local skill_md="${skill_dir%/}/SKILL.md"
ERRORS=0
WARNINGS=0
if [[ ! -f "$skill_md" ]]; then
echo "✗ No SKILL.md found in $skill_dir"
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
return
fi
echo "Validating: $skill_md"
# Extract frontmatter block
local in_fm=0 fm_content="" body_started=0 line_count=0
while IFS= read -r line; do
((line_count++)) || true
if [[ "$line" == "---" && $in_fm -eq 0 && $body_started -eq 0 ]]; then
in_fm=1
elif [[ "$line" == "---" && $in_fm -eq 1 ]]; then
in_fm=0
body_started=1
elif [[ $in_fm -eq 1 ]]; then
fm_content+="$line"$'\n'
fi
done < "$skill_md"
if [[ -z "$fm_content" ]]; then
check "error" "No YAML frontmatter found (expected --- ... --- block)"
echo ""
echo " Issues: ${ERRORS} errors, ${WARNINGS} warnings"
TOTAL_ERRORS=$((TOTAL_ERRORS + ERRORS))
TOTAL_WARNINGS=$((TOTAL_WARNINGS + WARNINGS))
return
fi
# --- Check: name field ---
local name
name=$(extract_fm_field "$fm_content" "name") || true
if [[ -n "$name" ]]; then
check "ok" "Required field: name = '$name'"
# Format check: lowercase, alphanumeric + hyphens
if echo "$name" | grep -qE '^[a-z0-9]([a-z0-9-]*[a-z0-9])?$'; then
if echo "$name" | grep -q -- '--'; then
check "error" "Name contains consecutive hyphens: '$name'"
else
check "ok" "Name format: valid (lowercase alphanumeric + hyphens)"
fi
else
check "error" "Name format: invalid characters or leading/trailing hyphen: '$name'"
fi
# Length check
if [[ ${#name} -gt 64 ]]; then
check "error" "Name length: ${#name} chars (max 64)"
fi
# Directory name match
local dir_name
dir_name=$(basename "${skill_dir%/}")
if [[ "$name" == "$dir_name" ]]; then
check "ok" "Name matches directory: '$name'"
else
check "warn" "Name/directory mismatch: name='$name' vs dir='$dir_name'"
fi
else
check "error" "Required field 'name' is missing"
fi
# --- Check: description field ---
local desc
desc=$(extract_fm_field "$fm_content" "description") || true
# For block scalars, also collect continuation lines.
# Important: do not use xargs for trimming here. xargs performs shell-like
# quote parsing and can abort on ordinary apostrophes in valid prose such as
# "ClawTeam's" with `xargs: unterminated quote`.
if [[ -z "$desc" ]] || [[ "$desc" == ">" ]] || [[ "$desc" == "|" ]]; then
desc=$(python3 - "$skill_md" <<'PY'
import re
import sys
from pathlib import Path
text = Path(sys.argv[1]).read_text(encoding='utf-8')
match = re.search(r'^description:\s*[>|]\s*\n((?: .*\n?)*)', text, re.M)
if not match:
print('')
raise SystemExit(0)
parts = []
for raw_line in match.group(1).splitlines():
line = raw_line[2:] if raw_line.startswith(' ') else raw_line.lstrip()
stripped = line.strip()
if stripped:
parts.append(stripped)
print(' '.join(parts))
PY
)
fi
if [[ -n "$desc" ]]; then
check "ok" "Required field: description present"
local desc_len=${#desc}
if [[ $desc_len -gt 1024 ]]; then
check "error" "Description length: ${desc_len} chars (max 1024)"
elif [[ $desc_len -lt 20 ]]; then
check "warn" "Description seems too short (${desc_len} chars): '$desc'"
else
check "ok" "Description length: ${desc_len} chars (OK)"
fi
# Phrasing check
if echo "$desc" | grep -qiE "(use when|use this skill when|triggers on|use for)"; then
check "ok" "Description has imperative trigger phrasing"
else
check "warn" "Description may lack imperative phrasing — consider adding 'Use when...' or 'Triggers on:'"
fi
else
check "error" "Required field 'description' is missing or empty"
fi
# --- Check: allowed-tools format ---
local tools_line
tools_line=$(echo "$fm_content" | grep -E "^allowed-tools:" | head -1 || true)
if [[ -n "$tools_line" ]]; then
local tools_val
tools_val=$(echo "$tools_line" | sed 's/^allowed-tools: *//')
if echo "$tools_val" | grep -qE '^\['; then
check "warn" "allowed-tools uses YAML list syntax — spec requires space-delimited string"
else
check "ok" "allowed-tools format: space-delimited (OK)"
fi
fi
# --- Check: compatibility field length ---
local compat
compat=$(extract_fm_field "$fm_content" "compatibility") || true
if [[ -n "$compat" && ${#compat} -gt 500 ]]; then
check "warn" "compatibility field: ${#compat} chars (max 500)"
fi
# --- Check: recommended sections ---
for section in "When to use this skill" "Instructions" "Examples" "Best practices" "References"; do
if grep -q "^## ${section}" "$skill_md"; then
check "ok" "Recommended section: $section"
else
check "warn" "Missing recommended section: ## ${section}"
fi
done
# --- Check: file length ---
if [[ $line_count -gt 500 ]]; then
check "warn" "File length: ${line_count} lines (recommended max: 500 — move details to references/)"
else
check "ok" "File length: ${line_count} lines (OK)"
fi
echo ""
echo " Issues: ${ERRORS} errors, ${WARNINGS} warnings"
echo ""
TOTAL_ERRORS=$((TOTAL_ERRORS + ERRORS))
TOTAL_WARNINGS=$((TOTAL_WARNINGS + WARNINGS))
}
main() {
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
if [[ "${1:-}" == "--all" ]]; then
local skills_dir="${2:-.}"
local found=0
for dir in "${skills_dir%/}"/*/; do
if [[ -f "${dir}SKILL.md" ]]; then
found=1
validate_skill "$dir"
fi
done
if [[ $found -eq 0 ]]; then
echo "No skill directories (containing SKILL.md) found in: $skills_dir"
exit 1
fi
echo "========================================"
echo "Total: ${TOTAL_ERRORS} errors, ${TOTAL_WARNINGS} warnings"
[[ $TOTAL_ERRORS -eq 0 ]] && exit 0 || exit 1
else
local skill_dir="${1:-.}"
validate_skill "$skill_dir"
[[ $TOTAL_ERRORS -eq 0 ]] && exit 0 || exit 1
fi
}
main "$@"
N:skill-standardization
D:Standardize and validate SKILL.md files against the Agent Skills specification. Use when creating or rewriting a skill, auditing spec compliance, sharpening trigger descriptions, canonicalizing overlapping skills into a canonical skill plus compatibility alias, or checking whether derived discovery surfaces (`skills.json`, README/setup inventories, `SKILL.toon`, `SKILL.compact.md`) still match the live skill folders. In repo-root maintenance loops, prefer truthful validator commands instead of bare `scripts/...` examples.
G:skill-management standardization validation agentskills-spec automation deduplication catalog-sync compact-drift canonical-skill
U[9]:
Create a new SKILL.md from scratch
Audit an existing skill for spec compliance
Repair weak trigger descriptions or stale route-outs
Canonicalize duplicates into a default skill plus compatibility alias
Validate repo-level discovery/catalog drift after add/rename/remove/alias work
Review SKILL.toon / SKILL.compact.md drift after material rewrites
Review alias-side support docs/checklists/examples when a compatibility alias mirrors the canonical contract
Use repo-root validator commands truthfully in maintenance loops
Do residue scans for stale command/path/discovery copy
S[5]{n,action}:
1,Pick the smallest working mode from references/working-modes.md
2,Run the truthful validator command for the current working directory
3,Fix or write the primary SKILL.md and its description/evals
4,Run catalog sync after structural changes and refresh derived surfaces, including alias-side support packets, when the rewrite was material
5,Do a residue scan before calling the pass done