
Skill Creator
- 7.6k installs
- 18 repo stars
- Updated July 27, 2026
- starchild-ai-agent/official-skills
skill-creator is an agent skill that scaffolds new skills with valid frontmatter, directory layout, and progressive-disclosure SKILL.md structure.
About
The skill-creator skill scaffolds new reusable agent skills with valid frontmatter, directory layout, and starter SKILL.md files. Core principles emphasize concise SKILL.md bodies because context window space is shared with system prompts and conversation history. Progressive disclosure uses three levels: name and description always in available_skills, full SKILL.md loaded on activation, and scripts references assets loaded on demand. Degrees of freedom guide instruction specificity from high freedom text guidance through medium pseudocode to low freedom executable scripts for fragile operations. Anatomy covers SKILL.md plus optional scripts for executable code, references for on-demand docs, and assets for templates not loaded into context. Creation steps include understanding the capability, choosing directories, writing frontmatter with strong trigger descriptions, drafting lean body content, and adding references or scripts as needed. Use when building new skills, wrapping APIs, or scaffolding reusable agent workflows.
- Progressive disclosure: metadata, body on activation, references on demand.
- Concise SKILL.md under 500 lines with heavy content in references/.
- scripts/ for fragile executable operations never loaded into context.
- Degrees of freedom from text guidance to executable script patterns.
- Frontmatter description must be a strong activation trigger.
Skill Creator by the numbers
- 7,592 all-time installs (skills.sh)
- +67 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #16 of 826 Skill Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
skill-creator capabilities & compatibility
- Capabilities
- skill directory scaffolding · frontmatter and trigger description authoring · progressive disclosure structure design · scripts references assets layout guidance · degrees of freedom instruction calibration
- Use cases
- orchestration · documentation
What skill-creator says it does
Scaffold new skills with valid frontmatter, directory layout, and a starter SKILL.md.
npx skills add https://github.com/starchild-ai-agent/official-skills --skill skill-creatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 7.6k |
|---|---|
| repo stars | ★ 18 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | starchild-ai-agent/official-skills ↗ |
How do I create a new agent skill with correct frontmatter, folder layout, and lean progressive-disclosure documentation?
Scaffold new agent skills with valid frontmatter, directory layout, progressive disclosure, and starter SKILL.md content.
Who is it for?
Developers authoring new agent skills or wrapping APIs into reusable workflow packages.
Skip if: Skip when you only need to invoke existing skills without creating new skill packages.
When should I use this skill?
User asks to create a new skill, scaffold an API helper, or start a reusable agent workflow package.
What you get
A scaffolded skill directory with SKILL.md, optional scripts, references, and assets following best practices.
- SKILL.md template
- skill directory structure
By the numbers
- Enforces a 64-character maximum skill name length
- Supports 3 allowed resource directory types: scripts, references, assets
Files
Core Principles
Concise is key. The context window is a shared resource between the system prompt, skills, conversation history, and your reasoning. Every line in a SKILL.md competes with everything else. Only add what you don't already know — don't document tool parameters visible in the system prompt, don't prescribe step-by-step workflows for things you can figure out. Focus on domain knowledge, interpretation guides, decision frameworks, and gotchas.
Progressive disclosure. Skills load in three levels: 1. Always in context — name, emoji, and description appear in <available_skills> in every conversation. This is how you decide which skill to activate. The description must be a strong trigger. 2. On activation — the full SKILL.md body is loaded via read_file when you decide the skill is relevant. This is where workflow, guidelines, and decision trees live. 3. On demand — scripts/, references/, and assets/ are only loaded when explicitly needed. Heavy content goes here, not in the body.
This means: keep the SKILL.md body lean (< 500 lines). Put detailed API docs in references/. Put automation in scripts/. The body should be what you need to start working, not an encyclopedia.
Degrees of freedom. Match instruction specificity to task fragility:
- High freedom (text guidance) — When multiple approaches are valid. Write natural language explaining WHAT and WHY, not step-by-step HOW. Example: "Check funding rates and social sentiment to gauge market mood."
- Medium freedom (pseudocode + params) — When a preferred pattern exists but details can vary. Describe the approach with key parameters. Example: "Use RSI with period 14, buy below 30, sell above 70."
- Low freedom (scripts in
scripts/) — When operations are fragile, require exact syntax, or are repetitive boilerplate. Put the code in standalone scripts that get executed, not loaded into context. Example: Chart rendering with exact color codes and API calls.
Default assumption: you are already smart. Only add context you don't already have.
Anatomy of a Skill
my-skill/
├── SKILL.md # Required: Frontmatter + instructions
├── scripts/ # Optional: Executable code (low freedom)
│ └── render.py # Run via bash, not loaded into context
├── references/ # Optional: Docs loaded on demand (medium freedom)
│ └── api-guide.md # Loaded via read_file when needed
└── assets/ # Optional: Templates, images, data files
└── template.json # NOT loaded into context, used in outputWhen to use each:
| Directory | Loaded into context? | Use for |
|---|---|---|
| SKILL.md body | On activation | Core workflow, decision trees, gotchas |
scripts/ | Never (executed) | Fragile operations, exact syntax, boilerplate |
references/ | On demand | Detailed API docs, long guides, lookup tables |
assets/ | Never | Templates, images, data files used in output |
Creating a Skill
Step 1: Understand the Request
Before scaffolding, understand what you're building:
- What capability? API integration, workflow automation, knowledge domain?
- What triggers it? When should the agent activate this skill? (This becomes the description.)
- What freedom level? Can the agent improvise, or does it need exact scripts?
- What dependencies? API keys, binaries, Python packages?
Examples:
- "I want to generate charts" → charting skill with scripts (low freedom rendering)
- "Help me think about trading strategies" → knowledge skill (high freedom, conversational)
- "Integrate with Binance API" → API skill with env requirements and reference docs
Step 2: Scaffold
Use the init script:
python skills/skill-creator/scripts/init_skill.py my-new-skill --path ./workspace/skillsWith resource directories:
python skills/skill-creator/scripts/init_skill.py api-helper --path ./workspace/skills --resources scripts,referencesWith example files:
python skills/skill-creator/scripts/init_skill.py my-skill --path ./workspace/skills --resources scripts --examplesStep 3: Plan Reusable Contents
Before writing, decide what goes where:
- SKILL.md body: Core instructions the agent needs every time this skill activates. Decision trees, interpretation guides, "when to do X vs Y" logic.
- scripts/: Any code that must run exactly as written — API calls with specific auth, rendering with exact formats, data processing pipelines.
- references/: Detailed docs the agent might need occasionally — full API endpoint lists, schema definitions, troubleshooting guides.
- assets/: Output templates, images, config files that the agent copies/modifies for output.
Step 4: Write the SKILL.md
Plan the content first — frontmatter trigger, body structure, freedom level. Then:
1. Frontmatter — Update description (CRITICAL trigger), add requirements, set emoji 2. Body — Write for the agent, not the user. Short paragraphs over bullet walls. Opinions over hedging.
Design patterns for the body:
- Workflow-based — Step-by-step process (charting: fetch data → configure chart → render → serve)
- Task-based — Organized by what the user might ask (trading: "analyze a coin" / "compare strategies" / "check sentiment")
- Reference/guidelines — Rules and frameworks (strategy: core truths, conversation style, when to pull data)
- Capabilities-based — Organized by what the skill can do (market-data: price tools / derivatives tools / social tools)
Step 5: Create / Update via skill_manage
`skill_manage` is the primary workflow — it validates frontmatter, runs a security scan, and auto-reloads the cache. Do NOT use write_file as the main path.
Creating a new skill:
skill_manage(action="create", name="my-skill", content="---\nname: my-skill\n...")Patching an existing skill (preferred for targeted changes):
# Always read_file first to get exact whitespace/content
skill_manage(action="patch", name="my-skill", old_string="exact old text", new_string="new text")Full rewrite of existing skill:
skill_manage(action="edit", name="my-skill", content="---\nname: my-skill\n...")⚠️ Known gotchas:
createerrors if skill already exists → useeditorpatchinstead.edit/patcherrors if skill does NOT exist → usecreatefirst.patchrequires exactold_stringmatch (whitespace included) → alwaysread_filebefore patching.execute()must accept**kwargs— if you seeunexpected keyword argument 'action', it's a bug in the tool implementation (fix:def execute(self, **kwargs)).
Fallback only — if skill_manage is unavailable, use write_file + skill_refresh() manually.
Step 6: Validate
python skills/skill-creator/scripts/validate_skill.py ./workspace/skills/my-new-skillAfter skill_manage, validate is optional (auto-reloaded), but run it to catch schema issues early.
Frontmatter Format
The frontmatter uses metadata.starchild for Star Child-specific fields:
---
name: skill-name
version: 1.0.0
description: "What this skill does. Use when [specific trigger scenarios]."
metadata:
starchild:
emoji: "🔧"
skillKey: skill-name
requires:
env: [API_KEY_NAME]
bins: [python]
anyBins: [curl, wget]
install:
- kind: pip
package: pandas
- kind: apt
package: curl
bins: [curl]
user-invocable: true
disable-model-invocation: false
---Field reference:
| Field | Location | Required | Purpose |
|---|---|---|---|
name | top-level | Yes | Skill identifier (lowercase hyphen-case) |
version | top-level | Yes | Semantic version (e.g. 1.0.0). Required for publishing. Always include. |
description | top-level | Yes | Trigger text — when should the agent use this? |
emoji | metadata.starchild | No | Display emoji |
skillKey | metadata.starchild | No | Dedup key |
requires.env | metadata.starchild | No | Required env vars |
requires.bins | metadata.starchild | No | Required binaries (ALL must exist) |
requires.anyBins | metadata.starchild | No | Required binaries (ANY one) |
install | metadata.starchild | No | How to install deps (pip, apt, npm, etc.) |
user-invocable | top-level | No | Can user trigger via /command (default: true) |
disable-model-invocation | top-level | No | Hide from <available_skills> (default: false) |
On-Chain Skills — Wallet Policy Prerequisite
If the skill involves any on-chain operations (sending transactions, token approvals, swaps, bridging, signing, deposits, withdrawals, smart contract interactions), add a Prerequisites section near the top of the SKILL.md:
## Prerequisites — Wallet Policy
Before executing any [operation], the wallet policy must be active.
Load the **wallet-policy** skill and propose the standard wildcard
policy (deny key export + allow `*`). This covers all [skill-name]
operations across all chains.This ensures the agent proposes a wallet policy before attempting any transaction. Without it, the first transaction will fail with a policy violation.
What NOT to Include
- README.md — The SKILL.md IS the readme. Don't duplicate.
- CHANGELOG.md — Skills aren't versioned packages.
- Docs the agent already has — Don't repeat tool descriptions from the system prompt.
- Step-by-step for simple tasks — The agent can figure out "read a file then process it."
- Generic programming advice — "Use error handling" is noise. Specific gotchas are signal.
Best Practices
1. Description is the trigger. This is how the agent decides to activate your skill. Include "Use when..." with specific scenarios. Bad: "Trading utilities." Good: "Test trading strategies against real historical data. Use when a strategy needs validation or before committing to a trade approach."
2. Write for the agent, not the user. The skill is instructions for the AI. Use direct language: "You generate charts" not "This skill can be used to generate charts."
3. Scripts execute without loading. Good for large automation. The agent reads the script only when it needs to customize, keeping context clean.
4. Don't duplicate the system prompt. The agent already sees tool names and descriptions. Focus on knowledge it doesn't have: interpretation guides, decision trees, domain-specific gotchas.
5. Request credentials last. Design the skill first, then ask the user for API keys.
6. Always validate before refreshing — run validate_skill.py to catch issues early.
#!/usr/bin/env python3
"""
Skill Initializer - Creates a new skill from template
Usage:
init_skill.py <skill-name> --path <path> [--resources scripts,references,assets] [--examples]
Examples:
python init_skill.py my-new-skill --path ./workspace/skills
python init_skill.py api-helper --path ./workspace/skills --resources scripts,references
python init_skill.py custom-skill --path ./workspace/skills --resources scripts --examples
"""
import argparse
import re
import sys
from pathlib import Path
MAX_SKILL_NAME_LENGTH = 64
ALLOWED_RESOURCES = {"scripts", "references", "assets"}
SKILL_TEMPLATE = """---
name: {skill_name}
description: "[TODO: What this skill does. Use when <specific trigger scenarios>.]"
metadata:
starchild:
emoji:
skillKey: {skill_name}
requires:
env: []
bins: []
user-invocable: true
---
# {skill_title}
[TODO: 1-2 sentences in direct voice. "You do X" not "This skill does X."
Explain what capability this gives the agent and why it matters.]
## Structuring This Skill
Pick the pattern that fits best, then delete this section:
- **Workflow-based** — Step-by-step process (fetch data -> process -> render -> output)
- **Task-based** — Organized by user request ("analyze X" / "compare Y" / "generate Z")
- **Reference/guidelines** — Rules, decision frameworks, core truths
- **Capabilities-based** — Organized by what the skill can do (tool group A / tool group B)
## [TODO: Main Section]
[TODO: Core instructions the agent needs every time this skill activates.
Focus on knowledge the agent doesn't already have:
- Domain-specific interpretation guides
- Decision trees ("when X, do Y; when Z, do W")
- Gotchas and edge cases
- Key parameters and thresholds]
## Resources
[TODO: Document scripts/, references/, assets/ if used.
Delete this section if no resource directories exist.]
"""
EXAMPLE_SCRIPT = '''#!/usr/bin/env python3
"""
{skill_title} - Helper Script
Usage:
python scripts/example.py --input <path> [--output <path>]
This script handles [TODO: describe what this automates].
Scripts are for low-freedom operations: fragile API calls, exact rendering,
repetitive boilerplate. The agent executes these via bash, not by reading
them into context.
"""
import argparse
import json
import sys
def main():
parser = argparse.ArgumentParser(description="{skill_title} helper")
parser.add_argument("--input", required=True, help="Input file path")
parser.add_argument("--output", default=None, help="Output file path (default: stdout)")
args = parser.parse_args()
# TODO: Replace with actual implementation
print(f"Processing: {{args.input}}")
result = {{"status": "ok", "input": args.input}}
if args.output:
with open(args.output, "w") as f:
json.dump(result, f, indent=2)
print(f"Output written to: {{args.output}}")
else:
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
'''
EXAMPLE_REFERENCE = """# {skill_title} — Reference Documentation
This reference is loaded on demand via `read_file` when the agent needs
detailed information beyond what's in the main SKILL.md.
## When to Load This Reference
- Agent needs full API endpoint details
- Agent needs to look up error codes or schema definitions
- Complex multi-step process requires detailed walkthrough
## API Reference
### Authentication
[TODO: How to authenticate — headers, tokens, env vars]
### Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/example` | GET | [TODO: Description] |
| `/api/example` | POST | [TODO: Description] |
### Error Codes
| Code | Meaning | Recovery |
|------|---------|----------|
| 401 | Invalid API key | Check env var is set |
| 429 | Rate limited | Wait and retry (backoff) |
## Troubleshooting
[TODO: Common issues and how to resolve them]
"""
EXAMPLE_ASSET = """{{
"name": "{skill_name}",
"version": "1.0",
"description": "Template asset for {skill_title}",
"TODO": "Replace this with actual asset content (templates, config, data files). Assets are NOT loaded into context — they're used in output generation."
}}
"""
def normalize_skill_name(skill_name: str) -> str:
"""Normalize skill name to lowercase hyphen-case."""
normalized = skill_name.strip().lower()
normalized = re.sub(r"[^a-z0-9]+", "-", normalized)
normalized = normalized.strip("-")
normalized = re.sub(r"-{2,}", "-", normalized)
return normalized
def title_case_skill_name(skill_name: str) -> str:
"""Convert hyphenated skill name to Title Case."""
return " ".join(word.capitalize() for word in skill_name.split("-"))
def parse_resources(raw_resources: str) -> list:
if not raw_resources:
return []
resources = [item.strip() for item in raw_resources.split(",") if item.strip()]
invalid = [item for item in resources if item not in ALLOWED_RESOURCES]
if invalid:
print(f"[ERROR] Unknown resource type(s): {', '.join(invalid)}")
print(f" Allowed: {', '.join(sorted(ALLOWED_RESOURCES))}")
sys.exit(1)
return list(dict.fromkeys(resources)) # dedupe while preserving order
def create_resource_dirs(skill_dir: Path, skill_name: str, skill_title: str,
resources: list, include_examples: bool):
for resource in resources:
resource_dir = skill_dir / resource
resource_dir.mkdir(exist_ok=True)
if resource == "scripts":
if include_examples:
example_script = resource_dir / "example.py"
example_script.write_text(EXAMPLE_SCRIPT.format(
skill_name=skill_name, skill_title=skill_title))
example_script.chmod(0o755)
print(f"[OK] Created {resource}/example.py")
else:
print(f"[OK] Created {resource}/")
elif resource == "references":
if include_examples:
example_ref = resource_dir / "reference.md"
example_ref.write_text(EXAMPLE_REFERENCE.format(
skill_name=skill_name, skill_title=skill_title))
print(f"[OK] Created {resource}/reference.md")
else:
print(f"[OK] Created {resource}/")
elif resource == "assets":
if include_examples:
example_asset = resource_dir / "template.json"
example_asset.write_text(EXAMPLE_ASSET.format(
skill_name=skill_name, skill_title=skill_title))
print(f"[OK] Created {resource}/template.json")
else:
print(f"[OK] Created {resource}/")
def init_skill(skill_name: str, path: str, resources: list, include_examples: bool) -> Path:
"""
Initialize a new skill directory with template SKILL.md.
Returns:
Path to created skill directory, or None if error
"""
skill_dir = Path(path).resolve() / skill_name
if skill_dir.exists():
print(f"[ERROR] Skill directory already exists: {skill_dir}")
return None
try:
skill_dir.mkdir(parents=True, exist_ok=False)
print(f"[OK] Created skill directory: {skill_dir}")
except Exception as e:
print(f"[ERROR] Error creating directory: {e}")
return None
# Create SKILL.md
skill_title = title_case_skill_name(skill_name)
skill_content = SKILL_TEMPLATE.format(skill_name=skill_name, skill_title=skill_title)
skill_md_path = skill_dir / "SKILL.md"
try:
skill_md_path.write_text(skill_content)
print("[OK] Created SKILL.md")
except Exception as e:
print(f"[ERROR] Error creating SKILL.md: {e}")
return None
# Create resource directories
if resources:
try:
create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples)
except Exception as e:
print(f"[ERROR] Error creating resource directories: {e}")
return None
print(f"\n[OK] Skill '{skill_name}' initialized at {skill_dir}")
print("\nNext steps:")
print("1. Edit SKILL.md — complete the TODOs, write a strong description")
print("2. Add resources to scripts/, references/, assets/ as needed")
print("3. Run validate_skill.py to check for issues")
print("4. Call skill_refresh() to make the skill available")
return skill_dir
def main():
parser = argparse.ArgumentParser(
description="Create a new skill directory with SKILL.md template."
)
parser.add_argument("skill_name", help="Skill name (normalized to hyphen-case)")
parser.add_argument("--path", required=True, help="Output directory for the skill")
parser.add_argument(
"--resources",
default="",
help="Comma-separated list: scripts,references,assets"
)
parser.add_argument(
"--examples",
action="store_true",
help="Create example files in resource directories"
)
args = parser.parse_args()
raw_name = args.skill_name
skill_name = normalize_skill_name(raw_name)
if not skill_name:
print("[ERROR] Skill name must include at least one letter or digit.")
sys.exit(1)
if len(skill_name) > MAX_SKILL_NAME_LENGTH:
print(f"[ERROR] Skill name too long ({len(skill_name)} > {MAX_SKILL_NAME_LENGTH})")
sys.exit(1)
if skill_name != raw_name:
print(f"Note: Normalized '{raw_name}' to '{skill_name}'")
resources = parse_resources(args.resources)
if args.examples and not resources:
print("[ERROR] --examples requires --resources to be set")
sys.exit(1)
print(f"Initializing skill: {skill_name}")
print(f" Location: {args.path}")
if resources:
print(f" Resources: {', '.join(resources)}")
print()
result = init_skill(skill_name, args.path, resources, args.examples)
sys.exit(0 if result else 1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Skill Validator - Validates a skill directory structure
Usage:
python validate_skill.py <path/to/skill-folder>
Example:
python validate_skill.py ./workspace/skills/my-skill
"""
import re
import sys
from pathlib import Path
# Try to import yaml, fallback to basic parsing if not available
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
def extract_frontmatter(content: str) -> tuple:
"""Extract YAML frontmatter from markdown content."""
if not content.startswith("---"):
return None, content
# Find the closing ---
end_match = re.search(r"\n---\n", content[3:])
if not end_match:
return None, content
frontmatter_text = content[3:end_match.start() + 3]
body = content[end_match.end() + 3:]
if HAS_YAML:
try:
frontmatter = yaml.safe_load(frontmatter_text)
except yaml.YAMLError as e:
return {"_error": str(e)}, body
else:
# Basic parsing without yaml library
frontmatter = {}
for line in frontmatter_text.split("\n"):
if ":" in line:
key, value = line.split(":", 1)
frontmatter[key.strip()] = value.strip()
return frontmatter, body
def lint_warnings(frontmatter: dict, body: str) -> list:
"""
Check for non-fatal issues and return warnings.
These are suggestions, not errors — the skill is still valid.
"""
warnings = []
# emoji at top level instead of inside metadata.starchild
if "emoji" in frontmatter:
meta = frontmatter.get("metadata")
has_starchild_emoji = (
isinstance(meta, dict)
and isinstance(meta.get("starchild"), dict)
and meta["starchild"].get("emoji")
)
if not has_starchild_emoji:
warnings.append(
"emoji is at top level — move it to metadata.starchild.emoji "
"so the loader picks it up correctly"
)
# requires at top level instead of inside metadata.starchild
if "requires" in frontmatter:
meta = frontmatter.get("metadata")
has_starchild_requires = (
isinstance(meta, dict)
and isinstance(meta.get("starchild"), dict)
and meta["starchild"].get("requires")
)
if not has_starchild_requires:
warnings.append(
"requires is at top level — move it to metadata.starchild.requires "
"so the loader picks it up correctly"
)
# any_bins used instead of anyBins (camelCase)
meta = frontmatter.get("metadata")
if isinstance(meta, dict):
sc = meta.get("starchild") or meta.get("openclaw")
if isinstance(sc, dict):
req = sc.get("requires")
if isinstance(req, dict) and "any_bins" in req:
warnings.append(
"requires.any_bins should be requires.anyBins (camelCase) "
"in metadata.starchild"
)
# Also check top-level requires for any_bins
top_req = frontmatter.get("requires")
if isinstance(top_req, dict) and "any_bins" in top_req:
warnings.append(
"requires.any_bins should be requires.anyBins (camelCase)"
)
# Description missing "Use when" trigger pattern
desc = frontmatter.get("description", "")
if desc and "[TODO" not in desc:
desc_lower = desc.lower()
if "use when" not in desc_lower and "use for" not in desc_lower:
warnings.append(
'description lacks a "Use when" trigger — consider adding '
'"Use when <specific scenarios>" to help the agent decide '
"when to activate this skill"
)
return warnings
def validate_skill(skill_path: Path) -> tuple:
"""
Validate a skill directory.
Returns:
(is_valid, message, warnings)
"""
skill_path = Path(skill_path).resolve()
# Check directory exists
if not skill_path.exists():
return False, f"Directory not found: {skill_path}", []
if not skill_path.is_dir():
return False, f"Not a directory: {skill_path}", []
# Check SKILL.md exists
skill_md = skill_path / "SKILL.md"
if not skill_md.exists():
return False, "SKILL.md not found", []
# Read and parse SKILL.md
try:
content = skill_md.read_text(encoding="utf-8")
except Exception as e:
return False, f"Error reading SKILL.md: {e}", []
# Extract frontmatter
frontmatter, body = extract_frontmatter(content)
if frontmatter is None:
return False, "SKILL.md must start with YAML frontmatter (---)", []
if "_error" in frontmatter:
return False, f"Invalid YAML frontmatter: {frontmatter['_error']}", []
# Check required fields
if not frontmatter.get("name"):
return False, "Frontmatter missing required field: name", []
if not frontmatter.get("description"):
return False, "Frontmatter missing required field: description", []
# Check description is not placeholder
desc = frontmatter.get("description", "")
if "[TODO" in desc or not desc.strip():
return False, "Description contains TODO or is empty - please complete it", []
# Check skill name format
name = frontmatter.get("name", "")
normalized = re.sub(r"[^a-z0-9-]", "", name.lower())
if name != normalized:
return False, f"Skill name should be lowercase hyphen-case: '{name}' -> '{normalized}'", []
# Check body has content
if len(body.strip()) < 50:
return False, "SKILL.md body is too short (< 50 chars)", []
# Collect lint warnings
warns = lint_warnings(frontmatter, body)
# Warn about TODOs in body
todo_count = body.count("[TODO")
if todo_count > 0:
return True, f"Valid (but has {todo_count} TODO items remaining)", warns
# Check line count
line_count = len(content.split("\n"))
if line_count > 500:
return True, f"Valid (but {line_count} lines - consider splitting to references/)", warns
return True, "Valid skill", warns
def main():
if len(sys.argv) < 2:
print("Usage: python validate_skill.py <path/to/skill-folder>")
print("\nExample:")
print(" python validate_skill.py ./workspace/skills/my-skill")
sys.exit(1)
skill_path = sys.argv[1]
print(f"Validating skill: {skill_path}\n")
is_valid, message, warnings = validate_skill(skill_path)
if is_valid:
print(f"[OK] {message}")
else:
print(f"[ERROR] {message}")
# Print warnings
for warn in warnings:
print(f"[WARN] {warn}")
sys.exit(0 if is_valid else 1)
if __name__ == "__main__":
main()
Related skills
FAQ
Where should detailed API docs go in a new skill?
In references/ for on-demand loading; keep SKILL.md body lean under 500 lines.
When should I use skill-creator?
When scaffolding a new skill with frontmatter, directory layout, and starter SKILL.md content.
Is Skill Creator safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.