From f9868a50df81c43e72181a73a86a70d32e0d39b4 Mon Sep 17 00:00:00 2001 From: fanhuanjie Date: Sun, 5 Jul 2026 21:15:10 +0800 Subject: [PATCH] feat(i18n): add skill-creator skill description translations Add localized descriptions for the skill-creator skill across all supported languages (en, es, fr, ja, ru, zh, zht) and register it in the builtin skill list. --- packages/opencode/src/cli/cmd/tui/i18n/en.ts | 1 + packages/opencode/src/cli/cmd/tui/i18n/es.ts | 1 + packages/opencode/src/cli/cmd/tui/i18n/fr.ts | 1 + packages/opencode/src/cli/cmd/tui/i18n/ja.ts | 1 + packages/opencode/src/cli/cmd/tui/i18n/ru.ts | 1 + .../opencode/src/cli/cmd/tui/i18n/skill.ts | 1 + packages/opencode/src/cli/cmd/tui/i18n/zh.ts | 1 + packages/opencode/src/cli/cmd/tui/i18n/zht.ts | 1 + .../builtin/.bundle/skill-creator/SKILL.md | 148 ++++++++++++++++++ .../skill-creator/references/frontmatter.md | 97 ++++++++++++ .../skill-creator/references/patterns.md | 114 ++++++++++++++ .../skill-creator/references/testing.md | 73 +++++++++ .../skill-creator/scripts/validate_skill.py | 146 +++++++++++++++++ 13 files changed, 586 insertions(+) create mode 100644 packages/opencode/src/skill/builtin/.bundle/skill-creator/SKILL.md create mode 100644 packages/opencode/src/skill/builtin/.bundle/skill-creator/references/frontmatter.md create mode 100644 packages/opencode/src/skill/builtin/.bundle/skill-creator/references/patterns.md create mode 100644 packages/opencode/src/skill/builtin/.bundle/skill-creator/references/testing.md create mode 100644 packages/opencode/src/skill/builtin/.bundle/skill-creator/scripts/validate_skill.py diff --git a/packages/opencode/src/cli/cmd/tui/i18n/en.ts b/packages/opencode/src/cli/cmd/tui/i18n/en.ts index dd343c70f..38a363e7d 100644 --- a/packages/opencode/src/cli/cmd/tui/i18n/en.ts +++ b/packages/opencode/src/cli/cmd/tui/i18n/en.ts @@ -207,6 +207,7 @@ export const dict: Record = { "tui.skill.loop.description": "Schedule a prompt to run on a recurring interval", "tui.skill.html-to-video-pipeline.description": "Short-video magic — make short videos with HTML", "tui.skill.arxiv.description": "Search, cite, download, and track arXiv papers", + "tui.skill.skill-creator.description": "Create, review, and improve agent skills", // Language switching "tui.command.language.switch.title": "Switch language", diff --git a/packages/opencode/src/cli/cmd/tui/i18n/es.ts b/packages/opencode/src/cli/cmd/tui/i18n/es.ts index 510e45a53..058635ced 100644 --- a/packages/opencode/src/cli/cmd/tui/i18n/es.ts +++ b/packages/opencode/src/cli/cmd/tui/i18n/es.ts @@ -283,6 +283,7 @@ export const dict = { "tui.skill.loop.description": "Programar un prompt para ejecutarse en un intervalo recurrente", "tui.skill.html-to-video-pipeline.description": "El arma definitiva para vídeos cortos — crea vídeos cortos con HTML", "tui.skill.arxiv.description": "Busca, cita, descarga y sigue artículos de arXiv", + "tui.skill.skill-creator.description": "Crea, revisa y mejora skills de agente", // Language switching "tui.command.language.switch.title": "Cambiar idioma", diff --git a/packages/opencode/src/cli/cmd/tui/i18n/fr.ts b/packages/opencode/src/cli/cmd/tui/i18n/fr.ts index ef3558282..67810f666 100644 --- a/packages/opencode/src/cli/cmd/tui/i18n/fr.ts +++ b/packages/opencode/src/cli/cmd/tui/i18n/fr.ts @@ -271,6 +271,7 @@ export const dict = { "tui.skill.loop.description": "Planifier l'exécution récurrente d'un prompt", "tui.skill.html-to-video-pipeline.description": "L'arme ultime pour vidéos courtes — créez des vidéos courtes avec du HTML", "tui.skill.arxiv.description": "Rechercher, citer, télécharger et suivre des articles arXiv", + "tui.skill.skill-creator.description": "Créer, réviser et améliorer des skills d'agent", // Language switching "tui.command.language.switch.title": "Changer de langue", diff --git a/packages/opencode/src/cli/cmd/tui/i18n/ja.ts b/packages/opencode/src/cli/cmd/tui/i18n/ja.ts index 01fea4a22..e7d344db5 100644 --- a/packages/opencode/src/cli/cmd/tui/i18n/ja.ts +++ b/packages/opencode/src/cli/cmd/tui/i18n/ja.ts @@ -220,6 +220,7 @@ export const dict = { "tui.skill.loop.description": "プロンプトを一定間隔で繰り返し実行するようスケジュール", "tui.skill.html-to-video-pipeline.description": "ショート動画の神ツール - HTML でショート動画を制作", "tui.skill.arxiv.description": "arXiv 論文の検索・引用・ダウンロード・追跡", + "tui.skill.skill-creator.description": "エージェントスキルの作成・レビュー・改善", // Language switching "tui.command.language.switch.title": "言語を切り替え", diff --git a/packages/opencode/src/cli/cmd/tui/i18n/ru.ts b/packages/opencode/src/cli/cmd/tui/i18n/ru.ts index 110a77da7..bf20c11f1 100644 --- a/packages/opencode/src/cli/cmd/tui/i18n/ru.ts +++ b/packages/opencode/src/cli/cmd/tui/i18n/ru.ts @@ -286,6 +286,7 @@ export const dict = { "tui.skill.loop.description": "Запланировать запуск промпта с периодичностью", "tui.skill.html-to-video-pipeline.description": "Магический инструмент для коротких видео — создавайте короткие видео с помощью HTML", "tui.skill.arxiv.description": "Поиск, цитирование, загрузка и отслеживание статей arXiv", + "tui.skill.skill-creator.description": "Создание, проверка и улучшение skills агента", // Language switching "tui.command.language.switch.title": "Сменить язык", diff --git a/packages/opencode/src/cli/cmd/tui/i18n/skill.ts b/packages/opencode/src/cli/cmd/tui/i18n/skill.ts index 7d87c50f8..24c2cf3f6 100644 --- a/packages/opencode/src/cli/cmd/tui/i18n/skill.ts +++ b/packages/opencode/src/cli/cmd/tui/i18n/skill.ts @@ -9,6 +9,7 @@ const BUILTIN = new Set([ "loop", "html-to-video-pipeline", "arxiv", + "skill-creator", ]) export function skillDescription( diff --git a/packages/opencode/src/cli/cmd/tui/i18n/zh.ts b/packages/opencode/src/cli/cmd/tui/i18n/zh.ts index c1790e4b9..6c81b3914 100644 --- a/packages/opencode/src/cli/cmd/tui/i18n/zh.ts +++ b/packages/opencode/src/cli/cmd/tui/i18n/zh.ts @@ -196,6 +196,7 @@ export const dict = { "tui.skill.loop.description": "按固定周期循环运行提示词", "tui.skill.html-to-video-pipeline.description": "短视频神器 - 利用 HTML 制作短视频", "tui.skill.arxiv.description": "搜索、引用、下载与追踪 arXiv 论文", + "tui.skill.skill-creator.description": "创建、审查与改进 Agent 技能", // Language switching "tui.command.language.switch.title": "切换语言", diff --git a/packages/opencode/src/cli/cmd/tui/i18n/zht.ts b/packages/opencode/src/cli/cmd/tui/i18n/zht.ts index 47459139d..6ce66f194 100644 --- a/packages/opencode/src/cli/cmd/tui/i18n/zht.ts +++ b/packages/opencode/src/cli/cmd/tui/i18n/zht.ts @@ -196,6 +196,7 @@ export const dict = { "tui.skill.loop.description": "依固定週期循環執行提示詞", "tui.skill.html-to-video-pipeline.description": "短影片神器 - 利用 HTML 製作短影片", "tui.skill.arxiv.description": "搜尋、引用、下載與追蹤 arXiv 論文", + "tui.skill.skill-creator.description": "建立、審查與改進 Agent 技能", // Language switching "tui.command.language.switch.title": "切換語言", diff --git a/packages/opencode/src/skill/builtin/.bundle/skill-creator/SKILL.md b/packages/opencode/src/skill/builtin/.bundle/skill-creator/SKILL.md new file mode 100644 index 000000000..2c3ad8b46 --- /dev/null +++ b/packages/opencode/src/skill/builtin/.bundle/skill-creator/SKILL.md @@ -0,0 +1,148 @@ +--- +name: skill-creator +description: "Interactive guide for creating, reviewing, and improving agent skills (SKILL.md folders). Use when the user wants to build a new skill ('create a skill', 'make a skill for X', 'write a SKILL.md', 'turn this workflow into a skill'), review or improve an existing skill, fix a skill that never triggers or triggers too often, or validate a skill folder before sharing it. Do NOT use for general prompt writing, MCP server development, or editing arbitrary markdown files." +version: 1.0.0 +license: MIT +platforms: [linux, macos, windows] +--- + +# Skill Creator + +A skill is a folder that teaches an agent how to handle a specific task or workflow: + +``` +your-skill-name/ +├── SKILL.md # Required — YAML frontmatter + Markdown instructions +├── scripts/ # Optional — executable code (Python, Bash, ...) +├── references/ # Optional — docs loaded only when needed +└── assets/ # Optional — templates, fonts, icons used in output +``` + +Skills rely on **progressive disclosure**: the frontmatter is always in context (so it decides *when* the skill loads), the SKILL.md body loads when relevant, and linked files load only on demand. Keep each level as small as it can be. + +## Workflow: Creating a New Skill + +### Step 1: Define 2-3 concrete use cases + +Before writing anything, pin down with the user: + +- What does the user want to accomplish? (outcome, not feature) +- What triggers it? Collect literal phrases users would say. +- What steps does the workflow require, in order? +- Which tools are needed (built-in, scripts, MCP servers)? +- What domain knowledge or best practices must be embedded? + +Write each use case as: **Trigger → Steps → Result**. If the user is vague, propose use cases and confirm rather than guessing silently. + +Identify the category — it shapes the structure: + +1. **Document & asset creation** — embed style guides, templates, quality checklists. +2. **Workflow automation** — step-by-step process with validation gates. +3. **MCP enhancement** — orchestrate MCP tool calls in sequence with domain expertise. + +### Step 2: Plan the folder structure + +- Folder name: kebab-case only (`my-skill` — no spaces, capitals, or underscores) and it should match the frontmatter `name`. +- `SKILL.md` must be named exactly that, case-sensitive. +- Never put a `README.md` inside the skill folder. +- Keep SKILL.md under ~5,000 words; move detail to `references/` and link to it. +- For critical validations, prefer a bundled script over prose — code is deterministic, language interpretation isn't. + +### Step 3: Write the frontmatter + +The frontmatter is the single most important part — it alone decides whether the skill ever loads. + +```yaml +--- +name: your-skill-name +description: [What it does] + [When to use it, with literal trigger phrases] + [negative triggers if needed] +--- +``` + +Rules (hard requirements): + +- `description` MUST state both WHAT the skill does and WHEN to use it, under 1024 characters. +- Include specific phrases users would actually say, and file types if relevant. +- No XML angle brackets anywhere in frontmatter (it is injected into the system prompt). +- `name` must not use reserved prefixes ("claude", "anthropic"). + +Weak: `description: Helps with projects.` +Strong: `description: Manages Linear sprint workflows including planning, task creation, and status tracking. Use when the user mentions "sprint", "Linear tasks", or asks to "create tickets".` + +For all optional fields (`license`, `compatibility`, `metadata`, `allowed-tools`) and more good/bad examples, read `references/frontmatter.md`. + +### Step 4: Write the instructions + +Recommended body structure: + +```markdown +# Skill Name + +## Instructions +### Step 1: [First major step] +Exact commands / tool calls, with expected output described. + +## Examples +User says X → actions → result. + +## Troubleshooting +Error → cause → fix. +``` + +Best practices: + +- Be specific and actionable: give exact commands with flags and expected output, not vibes ("validate the data"). +- Put critical instructions at the top; use `## Important` headers for must-not-skip rules. +- Include error handling for the failures users will actually hit. +- Reference bundled resources explicitly ("Before writing queries, read the API-patterns file in references/"). +- Number steps that must happen in order; state data dependencies between steps. + +For proven structural patterns (sequential orchestration, multi-MCP coordination, iterative refinement, context-aware tool selection, domain-specific intelligence), read `references/patterns.md`. + +### Step 5: Validate + +Run the bundled validator on the skill folder: + +```bash +python scripts/validate_skill.py /path/to/your-skill-name +``` + +It checks naming, frontmatter format and length, forbidden content, missing linked files, and body size. Fix every ERROR; treat WARNINGs as review prompts. Expected output on success: `PASS` with 0 errors. + +### Step 6: Test and iterate + +Iterate on a single challenging task until it succeeds, then extract the winning approach into the skill — this gives faster signal than broad testing. Then cover: + +1. **Triggering**: obvious phrasing loads it, paraphrases load it, unrelated queries don't. +2. **Function**: outputs correct, tool calls succeed, edge cases handled. +3. **Baseline comparison**: fewer corrections / tool calls / tokens than without the skill. + +Debugging trick: ask the agent "When would you use the [name] skill?" — it will paraphrase the description back; fix what's missing. + +Full test-case templates and iteration signals are in `references/testing.md`. + +## Workflow: Reviewing an Existing Skill + +When asked to review or improve a skill: + +1. Read its SKILL.md and run `python scripts/validate_skill.py `. +2. Diagnose against the common failure modes: + - **Never triggers** → description too generic or missing user-facing trigger phrases. Rewrite with literal phrases and keywords. + - **Triggers too often** → add negative triggers ("Do NOT use for...") and narrow the scope. + - **Loads but instructions ignored** → instructions too verbose, buried, or ambiguous. Move critical rules to the top, replace prose validations with a script. + - **Slow / degraded responses** → SKILL.md too large; move detail into `references/`. +3. Propose concrete edits (before/after for the description), not general advice. +4. If the user brings failure examples from real sessions, encode the fix as an explicit instruction or troubleshooting entry — that is the highest-value iteration loop. + +## Quick Checklist + +Before delivering a skill, verify: + +- [ ] Folder is kebab-case and matches frontmatter `name` +- [ ] `SKILL.md` exact filename; no `README.md` inside the folder +- [ ] Frontmatter has `---` delimiters, `name`, and a WHAT+WHEN `description` under 1024 chars +- [ ] No XML angle brackets in frontmatter +- [ ] Instructions specific and actionable, with examples and error handling +- [ ] Every referenced `scripts/`, `references/`, `assets/` file actually exists +- [ ] `validate_skill.py` passes with 0 errors +- [ ] Triggering tested: fires on target phrasings, silent on unrelated ones diff --git a/packages/opencode/src/skill/builtin/.bundle/skill-creator/references/frontmatter.md b/packages/opencode/src/skill/builtin/.bundle/skill-creator/references/frontmatter.md new file mode 100644 index 000000000..c72fbcc7c --- /dev/null +++ b/packages/opencode/src/skill/builtin/.bundle/skill-creator/references/frontmatter.md @@ -0,0 +1,97 @@ +# YAML Frontmatter Reference + +The frontmatter is always loaded into the agent's system prompt. It is the first level of progressive disclosure and the only thing the agent sees when deciding whether to load the skill. + +## Required fields + +```yaml +--- +name: skill-name-in-kebab-case +description: What it does and when to use it. Include specific trigger phrases. +--- +``` + +### name + +- kebab-case only: `notion-project-setup` — no spaces, underscores, or capitals +- Should match the folder name +- Reserved: names containing "claude" or "anthropic" are rejected + +### description + +- MUST include both WHAT the skill does and WHEN to use it (trigger conditions) +- Under 1024 characters +- No XML angle brackets +- Include specific tasks/phrases users might say; mention file types if relevant + +Structure: `[What it does] + [When to use it] + [Key capabilities / negative triggers]` + +## Optional fields + +```yaml +license: MIT # for open-source skills +compatibility: Requires network access and Python 3.10+ # 1-500 chars, environment requirements +allowed-tools: "Bash(python:*) Bash(npm:*) WebFetch" # restrict tool access +metadata: # any custom key-value pairs + author: Company Name + version: 1.0.0 + mcp-server: server-name + category: productivity + tags: [project-management, automation] +``` + +## Security restrictions + +Frontmatter is injected into the system prompt, so: + +- No XML angle brackets anywhere +- Safe-YAML parsing only — no code execution +- No "claude"/"anthropic" in the name (reserved) + +## Description examples + +### Good + +```yaml +# Specific and actionable +description: Analyzes Figma design files and generates developer handoff + documentation. Use when user uploads .fig files, asks for "design specs", + "component documentation", or "design-to-code handoff". + +# Includes trigger phrases +description: Manages Linear project workflows including sprint planning, task + creation, and status tracking. Use when user mentions "sprint", "Linear + tasks", "project planning", or asks to "create tickets". + +# Clear value proposition + scope +description: End-to-end customer onboarding workflow for PayFlow. Handles + account creation, payment setup, and subscription management. Use when user + says "onboard new customer", "set up subscription", or "create PayFlow + account". +``` + +### Bad + +```yaml +# Too vague — will never trigger reliably +description: Helps with projects. + +# Missing triggers — the agent can't tell when to load it +description: Creates sophisticated multi-page documentation systems. + +# Too technical, no user-facing phrases +description: Implements the Project entity model with hierarchical relationships. +``` + +### Controlling over-triggering + +```yaml +# Negative triggers +description: Advanced data analysis for CSV files. Use for statistical + modeling, regression, clustering. Do NOT use for simple data exploration + (use data-viz skill instead). + +# Scope clarification +description: PayFlow payment processing for e-commerce. Use specifically for + online payment workflows, not for general financial queries. +``` diff --git a/packages/opencode/src/skill/builtin/.bundle/skill-creator/references/patterns.md b/packages/opencode/src/skill/builtin/.bundle/skill-creator/references/patterns.md new file mode 100644 index 000000000..93fb0f56e --- /dev/null +++ b/packages/opencode/src/skill/builtin/.bundle/skill-creator/references/patterns.md @@ -0,0 +1,114 @@ +# Skill Structure Patterns + +Proven approaches observed across real skills. Pick the framing first: + +- **Problem-first**: user describes an outcome ("set up a project workspace") → the skill orchestrates the right tools in the right sequence. +- **Tool-first**: user has tool access (an MCP server, a CLI) → the skill teaches optimal workflows and best practices for it. + +Most skills lean one direction; knowing which fits helps pick a pattern below. + +## Pattern 1: Sequential workflow orchestration + +Use when users need multi-step processes in a specific order. + +```markdown +## Workflow: Onboard New Customer + +### Step 1: Create Account +Call tool: `create_customer` — parameters: name, email, company + +### Step 2: Setup Payment +Call tool: `setup_payment_method`. Wait for: payment method verification. + +### Step 3: Create Subscription +Call tool: `create_subscription` — parameters: plan_id, customer_id (from Step 1) + +### Step 4: Send Welcome Email +Call tool: `send_email` — template: welcome_email_template +``` + +Key techniques: explicit step ordering, stated dependencies between steps, validation at each stage, rollback instructions for failures. + +## Pattern 2: Multi-MCP coordination + +Use when workflows span multiple services. + +```markdown +## Phase 1: Design Export (Figma MCP) +Export assets, generate specs, create asset manifest. + +## Phase 2: Asset Storage (Drive MCP) +Create project folder, upload assets, generate shareable links. + +## Phase 3: Task Creation (Linear MCP) +Create dev tasks, attach asset links, assign to engineering. + +## Phase 4: Notification (Slack MCP) +Post handoff summary with asset links and task references. +``` + +Key techniques: clear phase separation, explicit data passing between services, validation before advancing phases, centralized error handling. + +## Pattern 3: Iterative refinement + +Use when output quality improves with iteration. + +```markdown +## Initial Draft +Fetch data → generate first draft → save to temporary file. + +## Quality Check +Run `scripts/check_report.py`; identify missing sections, formatting +inconsistencies, data validation errors. + +## Refinement Loop +Address each issue → regenerate affected sections → re-validate → +repeat until the quality threshold is met. + +## Finalization +Apply final formatting, generate summary, save final version. +``` + +Key techniques: explicit quality criteria, validation scripts, a defined stopping condition (know when to stop iterating). + +## Pattern 4: Context-aware tool selection + +Use when the same outcome needs different tools depending on context. + +```markdown +## Decision Tree +1. Check file type and size +2. Choose storage: + - Large files (>10MB): cloud storage MCP + - Collaborative docs: Notion/Docs MCP + - Code files: GitHub MCP + - Temporary files: local storage +3. Execute, apply service-specific metadata, generate access link +4. Tell the user why that choice was made +``` + +Key techniques: clear decision criteria, fallback options, transparency about choices. + +## Pattern 5: Domain-specific intelligence + +Use when the skill adds specialized knowledge beyond tool access. + +```markdown +## Before Processing (Compliance Check) +Fetch transaction details → apply compliance rules (sanctions lists, +jurisdiction allowances, risk level) → document the decision. + +## Processing +IF compliance passed: process the payment with fraud checks. +ELSE: flag for review and create a compliance case. + +## Audit Trail +Log all checks, record decisions, generate audit report. +``` + +Key techniques: domain expertise embedded in the logic, compliance gates before action, comprehensive documentation. + +## Composability and portability + +- The agent can load multiple skills at once — never assume yours is the only capability available; delegate to other skills where they fit (e.g. "use a PDF-processing skill for the downloaded file"). +- Skills should work identically across surfaces (chat, CLI, API). Note environment requirements in the `compatibility` frontmatter field rather than assuming them. diff --git a/packages/opencode/src/skill/builtin/.bundle/skill-creator/references/testing.md b/packages/opencode/src/skill/builtin/.bundle/skill-creator/references/testing.md new file mode 100644 index 000000000..7f96d8a15 --- /dev/null +++ b/packages/opencode/src/skill/builtin/.bundle/skill-creator/references/testing.md @@ -0,0 +1,73 @@ +# Testing and Iteration + +Pick rigor to match the skill's audience: a personal skill needs manual spot-checks; one deployed to a whole org deserves a scripted test suite. + +**Pro tip**: iterate on a single challenging task until the agent succeeds, then extract the winning approach into the skill. This gives faster signal than broad testing. Expand to multiple cases only once the foundation works. + +## 1. Triggering tests + +Goal: the skill loads at the right times — and only then. + +Build a test suite like: + +``` +Should trigger: +- "Help me set up a new ProjectHub workspace" (obvious) +- "I need to create a project in ProjectHub" (paraphrase) +- "Initialize a ProjectHub project for Q4 planning" (paraphrase) + +Should NOT trigger: +- "What's the weather in San Francisco?" +- "Help me write Python code" +- "Create a spreadsheet" (unless the skill handles sheets) +``` + +Aim for triggering on ~90% of relevant queries. Debugging: ask the agent "When would you use the [name] skill?" — it will quote the description back; adjust based on what's missing. + +## 2. Functional tests + +Goal: the skill produces correct outputs. + +``` +Test: Create project with 5 tasks +Given: Project name "Q4 Planning", 5 task descriptions +When: Skill executes workflow +Then: Project created; 5 tasks with correct properties; + all tasks linked; no API errors +``` + +Cover: valid outputs, tool/API calls succeed, error handling works, edge cases. + +## 3. Performance comparison + +Goal: prove the skill beats the baseline. Run the same task with and without the skill; compare: + +``` +Without skill: 15 back-and-forth messages, 3 failed API calls, 12,000 tokens +With skill: 2 clarifying questions, 0 failed API calls, 6,000 tokens +``` + +Qualitative checks: does the user ever need to prompt for next steps? Do 3-5 runs of the same request produce structurally consistent output? Can a new user succeed on the first try? + +## Iteration signals + +Skills are living documents. Watch for: + +| Signal | Diagnosis | Fix | +|---|---|---| +| Skill doesn't load when it should; users invoke it manually | Under-triggering | Add detail, keywords, and literal trigger phrases to the description | +| Skill loads for irrelevant queries; users disable it | Over-triggering | Add negative triggers ("Do NOT use for..."), narrow the scope | +| Inconsistent results, failed calls, user corrections | Execution issues | Sharpen instructions, add error handling, replace prose validation with a script | +| Slow or degraded responses | Context bloat | Shrink SKILL.md, move detail to `references/` | + +When real sessions surface an edge case or failure, bring the transcript back and encode the fix directly: an explicit instruction, a troubleshooting entry, or a validation step. This is the highest-value iteration loop. + +## Common troubleshooting + +**"Could not find SKILL.md"** — file not named exactly `SKILL.md` (case-sensitive). + +**"Invalid frontmatter"** — missing `---` delimiters or malformed YAML (unclosed quotes are the usual culprit). + +**"Invalid skill name"** — spaces or capitals in `name`; use kebab-case. + +**Skill loads but instructions are ignored** — instructions too verbose or buried. Keep them concise, put critical rules at the top under `## Important`, and move detail to `references/`. For must-pass validations, bundle a script instead of relying on prose. diff --git a/packages/opencode/src/skill/builtin/.bundle/skill-creator/scripts/validate_skill.py b/packages/opencode/src/skill/builtin/.bundle/skill-creator/scripts/validate_skill.py new file mode 100644 index 000000000..04db9bbec --- /dev/null +++ b/packages/opencode/src/skill/builtin/.bundle/skill-creator/scripts/validate_skill.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""Validate an agent skill folder against the skill spec. + +Usage: python validate_skill.py /path/to/skill-folder + +Exit code 0 = PASS (warnings allowed), 1 = FAIL (errors found), 2 = usage error. +""" + +import os +import re +import sys + +KEBAB_RE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$") +RESERVED = ("claude", "anthropic") +MAX_DESCRIPTION = 1024 +MAX_BODY_WORDS = 5000 + +errors = [] +warnings = [] + + +def error(msg): + errors.append(msg) + + +def warn(msg): + warnings.append(msg) + + +def parse_frontmatter(text): + """Minimal YAML frontmatter parser: returns (fields, body) or (None, text).""" + if not text.startswith("---"): + return None, text + match = re.match(r"^---\s*\n(.*?)\n---\s*\n?", text, re.DOTALL) + if not match: + return None, text + fields = {} + current_key = None + for line in match.group(1).splitlines(): + if not line.strip() or line.lstrip().startswith("#"): + continue + if line.startswith((" ", "\t")): + if current_key: + fields[current_key] += " " + line.strip() + continue + if ":" not in line: + warn(f"frontmatter line has no key: {line!r}") + continue + key, _, value = line.partition(":") + current_key = key.strip() + fields[current_key] = value.strip().strip("\"'") + return fields, text[match.end():] + + +def main(): + if len(sys.argv) != 2: + print(__doc__.strip()) + return 2 + skill_dir = os.path.abspath(sys.argv[1]) + if not os.path.isdir(skill_dir): + print(f"ERROR: not a directory: {skill_dir}") + return 2 + + folder = os.path.basename(skill_dir) + entries = os.listdir(skill_dir) + + if not KEBAB_RE.match(folder): + error(f"folder name {folder!r} is not kebab-case (lowercase, digits, hyphens only)") + + # exact-case check works even on case-insensitive filesystems via listdir + if "SKILL.md" not in entries: + near = [e for e in entries if e.lower() == "skill.md"] + if near: + error(f"found {near[0]!r} — must be named exactly 'SKILL.md' (case-sensitive)") + else: + error("SKILL.md is missing") + report() + return 1 + + if any(e.lower() == "readme.md" for e in entries): + error("README.md must not be inside the skill folder (put docs in SKILL.md or references/)") + + with open(os.path.join(skill_dir, "SKILL.md"), encoding="utf-8") as f: + text = f.read() + + fields, body = parse_frontmatter(text) + if fields is None: + error("frontmatter missing or malformed: SKILL.md must start with '---' delimited YAML") + report() + return 1 + + fm_block = text.split("---")[1] if text.count("---") >= 2 else "" + if "<" in fm_block or ">" in fm_block: + error("frontmatter contains XML angle brackets (< >) — forbidden for security") + + name = fields.get("name", "") + if not name: + error("frontmatter is missing required field 'name'") + else: + if not KEBAB_RE.match(name): + error(f"name {name!r} is not kebab-case") + if name != folder: + warn(f"name {name!r} does not match folder name {folder!r}") + if any(word in name.lower() for word in RESERVED): + error(f"name {name!r} uses a reserved word ({'/'.join(RESERVED)})") + + description = fields.get("description", "") + if not description: + error("frontmatter is missing required field 'description'") + else: + if len(description) > MAX_DESCRIPTION: + error(f"description is {len(description)} chars (max {MAX_DESCRIPTION})") + lowered = description.lower() + if len(description) < 40: + warn(f"description is very short ({len(description)} chars) — likely too vague to trigger") + if not any(cue in lowered for cue in ("use when", "use this", "use for", "trigger", "use it when")): + warn("description has no obvious WHEN clause (e.g. 'Use when ...') — add trigger conditions") + + compat = fields.get("compatibility", "") + if compat and not (1 <= len(compat) <= 500): + error(f"compatibility must be 1-500 chars (got {len(compat)})") + + word_count = len(body.split()) + if word_count > MAX_BODY_WORDS: + warn(f"SKILL.md body is {word_count} words (recommended max {MAX_BODY_WORDS}) — move detail to references/") + + for match in re.finditer(r"(?:scripts|references|assets)/[\w./-]*\w", body): + rel = match.group(0) + if not os.path.exists(os.path.join(skill_dir, rel)): + warn(f"SKILL.md references {rel!r} but it does not exist in the skill folder") + + report() + return 1 if errors else 0 + + +def report(): + for msg in errors: + print(f"ERROR: {msg}") + for msg in warnings: + print(f"WARNING: {msg}") + verdict = "FAIL" if errors else "PASS" + print(f"{verdict}: {len(errors)} error(s), {len(warnings)} warning(s)") + + +if __name__ == "__main__": + sys.exit(main())