feat: consolidate marketplace skills into kyberforge plugin

Moves create-plugin, marketplace-architect, write-skill, and write-eval
from canonical .agents/skills/ into plugins/kyberforge/skills/, along
with all bundled sub-files, evals, and the plugin-marketplace-architecture
research doc. Bundles templates/plugin/ into create-plugin/assets/plugin-template/
so the skill is self-contained after install-time caching. Removes
templates/plugin/ and docs/research/plugin-marketplace-architecture.md
from the repo root as they are now exclusively in the plugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 18:02:47 +00:00
parent 2287ddccbf
commit 280e98cb71
35 changed files with 7 additions and 7 deletions

View File

@@ -0,0 +1,13 @@
```yaml
version: "1.0"
updated: 2026-06-20
when: Invoked when the user wants to create a new plugin in the marketplace. Scaffolds the
directory structure from templates/plugin/, substitutes PLUGIN_NAME/PLUGIN_DESCRIPTION/
AUTHOR_NAME/AUTHOR_EMAIL/AUTHOR_URL placeholders, writes to plugins/<name>/, registers
the plugin in .claude-plugin/marketplace.json, runs claude plugin validate ., and hands
off to /marketplace-architect.
references:
- docs/research/plugin-marketplace-architecture.md
```

View File

@@ -0,0 +1,89 @@
---
name: create-plugin
description: >
Use when the user wants to create a new plugin in the marketplace — scaffold the directory
structure, generate plugin.json manifests for Claude Code and Copilot CLI, and register
the plugin in marketplace.json. Triggers: "create a new plugin", "add a plugin called X",
"scaffold a plugin", "new plugin for Y". Do NOT use when auditing or refactoring existing
plugins (use /marketplace-architect), authoring skill content inside a plugin (use
/write-skill), adopting an external plugin into the marketplace (use /marketplace-architect),
or validating existing manifests without creating anything (use /marketplace-architect).
metadata:
category: marketplace
---
<requirements>
## Required inputs
- **Plugin name** — kebab-case slug; ask if not stated. Validate: lowercase letters, digits, hyphens only; not already present in `plugins/` or `.claude-plugin/marketplace.json`; not a reserved name (`anthropic-*`, `claude-*`, `agent-skills`, `official-claude-plugins`).
- **Plugin description** — one sentence; ask if not stated.
- **Author name** — ask if not stated.
- **Author email** — ask if not stated; used in the Copilot root `plugin.json`.
- **Author URL** — ask if not stated; used in Claude's `.claude-plugin/plugin.json`.
## Constraints
- Load the plugin template from `assets/plugin-template/` bundled inside this skill (`${CLAUDE_PLUGIN_ROOT}/skills/create-plugin/assets/plugin-template/`). Stop if the path is missing — do not generate files from memory.
- Plugin name must be kebab-case and not a reserved name (`anthropic-*`, `claude-*`, `agent-skills`, `official-claude-plugins`) — halt and ask for a replacement before Gate A if violated.
- Do not write any file until Gate A (plan approval) and Gate B (file contents approval) are both explicitly confirmed.
- Replace all five placeholder markers — `PLUGIN_NAME`, `PLUGIN_DESCRIPTION`, `AUTHOR_NAME`, `AUTHOR_EMAIL`, `AUTHOR_URL` — in every copied file before Gate B review. No marker may appear in written output.
- Do not generate skill content — `skills/` is scaffolded as an empty directory with README only. Direct the user to `/write-skill` to add skills.
- Do not set `version` in both `plugin.json` and the marketplace entry — `plugin.json` wins silently and blocks updates for existing users.
- Update `.github/plugin/marketplace.json` only if it already exists — do not create it.
- Do not auto-invoke `/marketplace-architect` — hand off by name at the end; let the user trigger it.
</requirements>
<steps>
## Process
1. **Collect inputs.** Ask for plugin name, description, author name, author email, and author URL — one question at a time. Validate the plugin name: kebab-case format, not a reserved name, not already present in `plugins/` or `.claude-plugin/marketplace.json`. If any validation fails, stop and ask for a replacement before continuing.
2. **Check template.** Load the bundled template from `assets/plugin-template/` inside this skill (`${CLAUDE_PLUGIN_ROOT}/skills/create-plugin/assets/plugin-template/`). If the path is missing, stop and report it — do not proceed or generate files from memory.
3. **Gate A — plan review.** Present: the list of files that will be written (derived from `assets/plugin-template/` with `PLUGIN_NAME` substituted into filenames), the new `plugins/<name>/` directory path, the marketplace entry that will be added to `.claude-plugin/marketplace.json`, and whether `.github/plugin/marketplace.json` will also be updated. Wait for explicit approval — do not proceed on "looks good" or silence.
4. **Copy and substitute.** Copy `assets/plugin-template/` to `plugins/<name>/`. In every copied file, replace all occurrences of `PLUGIN_NAME`, `PLUGIN_DESCRIPTION`, `AUTHOR_NAME`, `AUTHOR_EMAIL`, and `AUTHOR_URL` with the collected values. Rename any file or directory whose name contains `PLUGIN_NAME`.
5. **Gate B — file contents review.** Show every file with its full substituted content. Wait for explicit approval — do not write until confirmed.
6. **Write files.** Write all substituted files to `plugins/<name>/`. Append the new plugin entry to `.claude-plugin/marketplace.json`. If `.github/plugin/marketplace.json` exists, append the same entry there.
7. **Validate.** Run `claude plugin validate .` from `plugins/<name>/`. Report all output inline — do not suppress warnings. If the command is unavailable, note it and suggest the user run it manually after local install.
8. **Hand off.** Print: "Plugin `<name>` created and registered in `marketplace.json`. Fill in skill and agent content, then run `/marketplace-architect` to audit the full marketplace."
## Output format
- `plugins/<name>/` — directory tree copied from `assets/plugin-template/` with all placeholders substituted
- `.claude-plugin/marketplace.json` — updated with the new plugin entry
- `.github/plugin/marketplace.json` — updated with the same entry, only if it already existed
- Inline validation report from `claude plugin validate .`
</steps>
<checks>
## Failure handling
- `assets/plugin-template/` not found at `${CLAUDE_PLUGIN_ROOT}/skills/create-plugin/assets/plugin-template/` — stop, report the path, do not generate from memory.
- Plugin name already exists in `plugins/` or `marketplace.json` — stop, report the conflict, ask for a different name.
- Reserved name detected — stop, report the name and the reserved list, ask for a replacement before continuing.
- `claude plugin validate .` unavailable — report that automated validation was skipped; suggest running it manually with `claude --plugin-dir ./plugins/<name>`.
## Self-check
- [ ] Plugin name validated: kebab-case, not reserved, not already present in `plugins/` or `marketplace.json`
- [ ] `assets/plugin-template/` verified to exist at `${CLAUDE_PLUGIN_ROOT}/skills/create-plugin/assets/plugin-template/` before any file generation
- [ ] Gate A presented with file list and marketplace entry — explicit approval received
- [ ] All five markers substituted in all files — none appear in written output
- [ ] Gate B presented with full substituted file contents — explicit approval received
- [ ] Files written only after Gate B approval
- [ ] `.github/plugin/marketplace.json` updated only if it already existed — not created
- [ ] `claude plugin validate .` run and results reported; or unavailability noted
- [ ] Hand-off message printed directing user to `/marketplace-architect`
- [ ] No skill content generated — `skills/` is empty with README only
</checks>

View File

@@ -0,0 +1,8 @@
{
"name": "PLUGIN_NAME",
"displayName": "PLUGIN_NAME",
"description": "PLUGIN_DESCRIPTION",
"author": { "name": "AUTHOR_NAME", "url": "AUTHOR_URL" },
"license": "MIT",
"keywords": []
}

View File

@@ -0,0 +1,3 @@
{
"mcpServers": {}
}

View File

@@ -0,0 +1,42 @@
# PLUGIN_NAME
PLUGIN_DESCRIPTION
## Install
**Claude Code:**
```bash
claude plugin marketplace add <owner>/<repo>
claude plugin install PLUGIN_NAME@<marketplace-name>
```
**GitHub Copilot CLI:**
```bash
copilot plugin marketplace add <owner>/<repo>
copilot plugin install PLUGIN_NAME
```
**Local (development):**
```bash
# Claude Code
claude --plugin-dir ./plugins/PLUGIN_NAME
# GitHub Copilot CLI
copilot plugin install ./plugins/PLUGIN_NAME
```
## Contents
| Component | Path | Description |
|---|---|---|
| Skills | `skills/` | Slash commands available after install |
| Agents | `agents/` | Role-based agents (`.md` for Claude, `.agent.md` for Copilot) |
| Hooks | `hooks/hooks.json` (Claude) / `hooks.json` (Copilot) | Event-triggered automation |
| MCP servers | `.mcp.json` | Model Context Protocol server definitions |
## Author
AUTHOR_NAME

View File

@@ -0,0 +1,13 @@
---
name: PLUGIN_NAME
description: PLUGIN_DESCRIPTION
tools:
- read_file
- list_directory
---
Replace this with your agent's system prompt and instructions.
This file is loaded by **GitHub Copilot CLI**. The `tools:` frontmatter field declares
which tools the agent can use — add or remove tools as needed.
For Claude Code, see `PLUGIN_NAME.md`.

View File

@@ -0,0 +1,8 @@
---
name: PLUGIN_NAME
description: PLUGIN_DESCRIPTION
---
Replace this with your agent's system prompt and instructions.
This file is loaded by **Claude Code**. For GitHub Copilot CLI, see `PLUGIN_NAME.agent.md`.

View File

@@ -0,0 +1,13 @@
# agents/
Agent definitions for this plugin. Each agent needs two files — one per tool:
| File | Tool | Notes |
|---|---|---|
| `<name>.md` | Claude Code | Frontmatter: `name`, `description`. No `tools:` field. |
| `<name>.agent.md` | GitHub Copilot CLI | Frontmatter: `name`, `description`, `tools:` (array of permitted tool names). |
The two files share the same system prompt body. Keep them in sync.
Rename `PLUGIN_NAME.md` and `PLUGIN_NAME.agent.md` to your agent's name (kebab-case).
Add additional agent pairs as needed — one pair per agent.

View File

@@ -0,0 +1,11 @@
# bin/
**Claude Code only.** Executables placed here are added to PATH when the plugin is installed.
Use this for CLI tools, helper scripts, or MCP server entry points bundled with the plugin.
Reference files in this directory from `.mcp.json` or hooks using `${CLAUDE_PLUGIN_ROOT}/bin/<file>`.
The `${CLAUDE_PLUGIN_ROOT}` variable resolves to the plugin's install cache path at runtime —
do not use relative paths from the repo root, as they will break after install.
GitHub Copilot CLI does not support `bin/`. If you add executables here, they are Claude Code only.

View File

@@ -0,0 +1,7 @@
# docs/
Plugin documentation. Place usage guides, reference material, and examples here.
This directory is not read automatically by either Claude Code or GitHub Copilot CLI.
Reference specific files from your skill bodies or agent prompts when needed
(e.g. `See references/usage.md for examples`).

View File

@@ -0,0 +1,3 @@
{
"hooks": []
}

View File

@@ -0,0 +1,27 @@
# hooks/
**Claude Code only.** Hook definitions that run in response to Claude Code events.
The `hooks.json` in this directory is read by Claude Code. Structure:
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "echo 'tool used'" }
]
}
]
}
}
```
Supported events: `PreToolUse`, `PostToolUse`, `Notification`, `Stop`.
Use `${CLAUDE_PLUGIN_ROOT}` to reference scripts inside this plugin — the plugin runs
from a cache path after install, not its original repo location.
For GitHub Copilot CLI hooks, see `hooks.json` at the plugin root.

View File

@@ -0,0 +1,3 @@
{
"hooks": {}
}

View File

@@ -0,0 +1,11 @@
{
"name": "PLUGIN_NAME",
"description": "PLUGIN_DESCRIPTION",
"author": { "name": "AUTHOR_NAME", "email": "AUTHOR_EMAIL" },
"license": "MIT",
"keywords": [],
"agents": "agents/",
"skills": ["skills/"],
"hooks": "hooks.json",
"mcpServers": ".mcp.json"
}

View File

@@ -0,0 +1,18 @@
# skills/
Skills for this plugin. Each skill lives in its own subdirectory:
```
skills/
<skill-name>/
SKILL.md # required — frontmatter + skill body
META.md # required — provenance and audit fields
references/ # optional — on-demand reference docs
scripts/ # optional — executable helper scripts
assets/ # optional — templates, data files, lookup tables
```
**Shared** — both Claude Code and GitHub Copilot CLI read `skills/<name>/SKILL.md`.
To add a skill, run `/write-skill` in a Claude Code session. Do not write SKILL.md by hand
without following the authoring standard — trigger descriptions and self-checks are required.

View File

@@ -0,0 +1,21 @@
```yaml
version: "1.0"
updated: 2026-06-20
when: >
Invoked when the user wants to create, manage, or maintain a plugin marketplace for
Claude Code and/or GitHub Copilot CLI. Covers four operations: (a) audit and refactor
a repository of skills/agents/hooks into a plugin marketplace layout, (b) adopt external
plugins/skills/agents from outside sources, (c) update and maintain an existing
marketplace.json and plugin manifests, (d) validate existing plugin manifests for naming,
structure, and cross-tool compatibility. Also triggered implicitly when the user asks
about distributing skills to a team, organizing loose skills into installable units, or
setting up cross-tool distribution — even without saying "marketplace".
references:
- https://code.claude.com/docs/en/plugins
- https://code.claude.com/docs/en/plugin-marketplaces
- https://code.claude.com/docs/en/plugins-reference
- https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/plugins-creating
- https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/plugins-marketplace
```

View File

@@ -0,0 +1,101 @@
---
name: marketplace-architect
description: >
Manages and maintains a plugin marketplace for Claude Code and GitHub Copilot CLI.
Use this whenever the user wants to: create or update a marketplace (marketplace.json,
plugin.json manifests), adopt plugins/skills/agents/hooks from external sources, evaluate
cross-tool compatibility between Claude Code and Copilot CLI, plan plugin groupings and
boundaries, refactor a repository into marketplace format, validate plugin naming, detect
duplicate capabilities, or generate per-plugin install docs — even if they don't use the
word "marketplace". Do NOT use when the user wants to author a new skill from scratch
(use write-skill), debug an existing skill (use diagnose), or run a direct plugin CLI
command (copilot plugin install, claude plugin list).
metadata:
category: marketplace
---
<requirements>
## Required inputs
- **Target operation** — what the user wants to do; inferred from request. If ambiguous, ask: audit/refactor, adopt an external plugin, update/maintain an existing marketplace, or validate manifests.
- **Repository path** — path to the repo to act on; defaults to current working directory if not stated.
- **Marketplace name** — kebab-case identifier (e.g. `my-ai-marketplace`); required only when generating a new `marketplace.json`. Infer from repo name if obvious, ask if not.
- **Plugin source** — URL, GitHub slug, or local path; required only when adopting an external plugin.
## Constraints
- Load `references/cross-compat.md` before any tool-specific decision — Claude Code and Copilot CLI diverge in ways that cause silent breakage at install time.
- Never write files until the user has approved the plan at Gate A and the specific file contents at Gate B — two separate explicit approvals required.
- If credential-shaped content is detected in any manifest field, halt and redirect to environment variable references (e.g. `$MY_TOKEN`) — do not generate the manifest.
- Produce cross-tool deltas and per-plugin READMEs only when explicitly requested — do not generate them automatically.
- Scripts in `scripts/` are loaded on demand by the step that needs them — never preloaded.
- Flag any `../` cross-references in the audited repo before recommending plugin boundaries — plugins cannot reference files outside their own directory after install-time caching.
- Plugin names must be kebab-case; validate against the reserved name list in `references/claude-code.md` before generating any manifest.
- Do not set `version` in both `plugin.json` and the marketplace entry — `plugin.json` wins silently and causes update failures.
</requirements>
<steps>
## Process
1. **Identify the operation.** Determine intent from the user's request — one of: (a) audit/refactor a repo into marketplace format, (b) adopt an external plugin/skill/agent, (c) maintain or update an existing marketplace, (d) validate existing manifests. Ask if the operation cannot be inferred.
2. **Load the compatibility reference.** Read `references/cross-compat.md` before any tool-specific decision. Claude Code and Copilot CLI diverge in manifest paths, agent file naming, and hooks layout — every recommendation depends on this table.
3. **Execute the operation phase.**
**(a) Audit/refactor:** Run `scripts/inventory.sh` against the repo to classify every asset (skill / command / agent / hook / prompt / MCP). Flag any `../` cross-references — these break under install-time caching. Recommend plugin groupings by user outcome (~10–20 plugins); warn if proposed count exceeds 20 or falls below 3. Diff skill descriptions for duplicate capabilities before finalising boundaries. Produce a concrete migration checklist: old path → new path, one row per file.
**(b) Adopt external plugin:** Fetch and inspect the plugin source. Classify included assets. Check for naming conflicts with existing plugins in the marketplace. Evaluate cross-tool compatibility using `references/cross-compat.md`. Summarise what will be added to `marketplace.json`.
**(c) Maintain/update:** Read current `marketplace.json` and all `plugin.json` files. Identify stale versions, reserved name violations, kebab-case violations, and `version` duplication between plugin.json and marketplace entry. Report findings as a prioritised fix list.
**(d) Validate:** Run `scripts/validate.sh` (wraps `claude plugin validate` plus custom JSON and naming checks). Report each violation with a recommended fix. Do not proceed to file writes until all errors are resolved.
4. **Gate A — plan review.** Present the full plan or fix list to the user. Wait for explicit approval before proceeding. Do not interpret silence or "looks good" as approval — require a direct "yes" or equivalent.
5. **Generate outputs.** After Gate A approval: for audit/refactor and adopt operations, run `scripts/gen_manifests.sh` to produce `plugin.json` (at both `.claude-plugin/plugin.json` and plugin root until the Copilot fallback is verified) and `marketplace.json` (at `.claude-plugin/marketplace.json`; optionally mirror to `.github/plugin/marketplace.json`). Read `references/claude-code.md` for Claude-specific path rules and `references/copilot-cli.md` for Copilot-specific requirements.
6. **Gate B — file write approval.** Show the user every file that will be written with its full contents. Wait for explicit approval per file or as a batch. Write nothing until approved.
7. **Validate post-write.** After writes complete, run `scripts/validate.sh` again. Report any remaining issues. Suggest local install test commands: `claude --plugin-dir ./plugins/<name>` and `copilot plugin install ./plugins/<name>`.
8. **Optional deliverables.** Only when the user explicitly asks: emit cross-tool delta notes (what each plugin needs for Copilot vs Claude Code) and per-plugin README with install commands for both tools.
## Output format
Files generated depend on operation:
- **Audit/refactor and adopt:** `plugin.json` (two locations per plugin until verified), `marketplace.json` (`.claude-plugin/`, optionally `.github/plugin/`), migration checklist as a markdown table
- **Maintain/update:** updated `marketplace.json` and affected `plugin.json` files
- **Validate:** report only — no file writes unless explicitly requested after review
- **Optional:** per-plugin `README.md` with both `claude` and `copilot` install commands
</steps>
<checks>
## Failure handling
- `scripts/inventory.sh` not found or fails — perform manual asset classification using Read and Bash find; note the fallback in output.
- `scripts/gen_manifests.sh` not found or fails — generate manifest JSON inline; flag that the output was not script-produced.
- `scripts/validate.sh` not found or `claude plugin validate` unavailable — run manual JSON schema and naming checks using `references/claude-code.md`; flag that automated validation was skipped.
- Plugin source unreachable (bad URL, private repo, missing path) — stop the adopt operation, report the error, ask the user to verify the source before retrying.
- Reserved name detected in proposed plugin or marketplace name — halt, report the name and the reserved list from `references/claude-code.md`, ask for a replacement before proceeding.
- Credential-shaped content detected in any manifest field — halt, do not generate the manifest, redirect to environment variable references.
## Self-check
- [ ] `references/cross-compat.md` loaded before any tool-specific recommendation was made
- [ ] Operation identified before any scanning or file reading began
- [ ] Gate A presented and explicit approval received before any manifest was generated
- [ ] Gate B presented with full file contents and explicit approval received before any file was written
- [ ] No credential-shaped content in any generated manifest field
- [ ] All plugin names validated as kebab-case and checked against reserved name list
- [ ] `version` field not set in both `plugin.json` and marketplace entry for the same plugin
- [ ] Scripts loaded on demand by step — not preloaded at skill invocation
- [ ] Cross-tool deltas and READMEs produced only if explicitly requested
- [ ] Post-write validation run and findings reported
</checks>

View File

@@ -0,0 +1,169 @@
# Claude Code Plugin Reference
Verified against code.claude.com/docs as of June 2026.
---
## Directory structure
```text
plugin-root/
├── .claude-plugin/
│ └── plugin.json # ONLY plugin.json goes here; all other dirs at plugin root
├── skills/ # skill directories: <name>/SKILL.md
├── commands/ # legacy flat .md files; promote to skills/ for new plugins
├── agents/ # agent definitions: <name>.md
├── hooks/
│ └── hooks.json
├── .mcp.json
├── .lsp.json
├── monitors/
│ └── monitors.json
├── bin/ # executables added to PATH while plugin is enabled
└── settings.json # default settings applied when plugin is enabled
```
A plugin that ships exactly one skill may place `SKILL.md` directly at the plugin root.
Use `skills/` for plugins that may grow beyond one skill.
---
## plugin.json schema
```json
{
"name": "my-plugin", // kebab-case, no spaces — also the skill namespace prefix
"displayName": "My Plugin", // human-readable; shown in UI (v2.1.143+)
"description": "What it does",
"version": "1.0.0", // OPTIONAL — omit to use git SHA per commit
"author": { "name": "Name", "url": "https://..." },
"homepage": "https://...",
"repository": "https://github.com/...",
"license": "MIT",
"keywords": [],
"defaultEnabled": true, // set false to install disabled (v2.1.154+)
"dependencies": [
{ "name": "other-plugin", "version": "~2.1.0" }
]
}
```
Only `name` is required. Add fields only when needed.
---
## marketplace.json schema
```json
{
"name": "my-ai-marketplace",
"owner": { "name": "Your Name", "email": "you@example.com" },
"description": "Description",
"version": "1.0.0",
"plugins": [
{
"name": "startup-cto",
"source": "./plugins/startup-cto",
"description": "...",
"strict": true
}
]
}
```
`description` and `version` are also accepted under a `metadata` key for backward compatibility.
---
## Plugin source types
| Type | Format | Notes |
|---|---|---|
| Relative path | `"./plugins/my-plugin"` | Must start with `./`. Resolved from marketplace root. Only works with git-hosted marketplaces, not URL-based. |
| GitHub | `{ "source": "github", "repo": "owner/repo", "ref": "main", "sha": "abc123" }` | sha pins exact commit; ref is branch/tag |
| URL / git | `{ "source": "url", "url": "https://...", "ref": "main" }` | also accepts `owner/repo` shorthand and SSH URLs |
| git-subdir | `{ "source": "git-subdir", "url": "...", "path": "packages/my-plugin" }` | sparse clone of a monorepo path |
| npm | `{ "source": "npm", "package": "@scope/pkg", "version": "^2.0.0", "registry": "https://..." }` | installed via npm install |
When both `ref` and `sha` are set, `sha` is the effective pin.
---
## Strict mode
Controls whether `plugin.json` is the authority for component definitions.
- **`strict: true`** (default) — plugin has its own `plugin.json`; marketplace entry can add extra skills/hooks on top.
- **`strict: false`** — marketplace entry is the entire definition; plugin needs no `plugin.json`. The entry declares `skills`, `agents`, `hooks`, `mcpServers` path arrays.
Do not use `strict: false` plus a component-declaring `plugin.json` — this is a conflict and fails to load.
---
## Reserved marketplace names
These names are blocked for third-party use:
`claude-code-marketplace`, `claude-code-plugins`, `claude-plugins-official`,
`claude-plugins-community`, `claude-community`, `anthropic-marketplace`,
`anthropic-plugins`, `agent-skills`, `anthropic-agent-skills`,
`knowledge-work-plugins`, `life-sciences`, `claude-for-legal`,
`claude-for-financial-services`, `financial-services-plugins`
Names that impersonate official marketplaces are also blocked (e.g. `official-claude-plugins`,
`anthropic-tools-v2`).
Plugin names must be kebab-case (lowercase, digits, hyphens). Claude.ai marketplace sync
rejects anything else even if the local CLI tolerates it.
---
## Version management
- If `version` is set in `plugin.json`, users receive updates only when you bump it.
- If `version` is omitted, git commit SHA is used — every commit is a new version.
- If `version` is set in both `plugin.json` and the marketplace entry, `plugin.json` wins silently.
- **Recommendation:** omit `version` unless you need explicit release gates.
---
## Environment variables
- **`${CLAUDE_PLUGIN_ROOT}`** — absolute path to the plugin's installation directory. Use in hook commands and MCP/LSP configs for all in-plugin file references. This path changes on update.
- **`${CLAUDE_PLUGIN_DATA}`** — persistent directory for plugin state that survives updates. Use for `node_modules`, generated code, caches.
---
## Validation and CLI commands
```bash
# Validate plugin structure and manifest
claude plugin validate ./my-plugin
claude plugin validate ./my-plugin --strict # treat warnings as errors
# Install/manage
claude plugin install <name>@<marketplace>
claude plugin update <name>@<marketplace>
claude plugin uninstall <name>
claude plugin list
claude plugin enable <name>
claude plugin disable <name>
# Marketplace
claude plugin marketplace add owner/repo
claude plugin marketplace update
# Development
claude --plugin-dir ./my-plugin # load without installing
claude --plugin-dir ./my-plugin.zip # load from zip (v2.1.128+)
claude plugin init my-tool # scaffold a skills-dir plugin
```
---
## Key gotchas
1. **Plugins are copied to cache on install.** Cannot reference `../shared-utils` — those files are not copied. Duplicate shared files into each plugin or use symlinks.
2. **`commands/` ≠ `skills/`.** Flat `foo.md` is a legacy command; `foo/SKILL.md` is a skill. Promote flat commands to skill directories during migration.
3. **Only `plugin.json` in `.claude-plugin/`.** Skills, agents, hooks, and other directories must be at the plugin root, not inside `.claude-plugin/`.
4. **Plugin names are skill namespace prefixes.** `name: my-plugin` means skills invoke as `/my-plugin:skill-name`.
5. **`defaultEnabled: false` requires v2.1.154+.** Earlier versions ignore it and enable on install.

View File

@@ -0,0 +1,143 @@
# GitHub Copilot CLI Plugin Reference
Verified against docs.github.com as of June 2026.
---
## Directory structure
```text
plugin-root/
├── plugin.json # at plugin root (NOT in .claude-plugin/)
├── skills/ # skill directories: <name>/SKILL.md (same as Claude Code)
├── agents/ # agent files: <name>.agent.md (differs from Claude Code)
├── hooks.json # at plugin root (differs from Claude Code: hooks/hooks.json)
└── .mcp.json # at plugin root (same as Claude Code)
```
---
## plugin.json schema (Copilot)
```json
{
"name": "my-plugin",
"description": "What it does",
"version": "1.0.0",
"author": { "name": "Name", "email": "you@example.com" },
"license": "MIT",
"keywords": [],
"agents": "agents/",
"skills": ["skills/"],
"hooks": "hooks.json",
"mcpServers": ".mcp.json"
}
```
Key difference from Claude Code: Copilot expects component path declarations inside
`plugin.json` (`"skills": "skills/"`, `"agents": "agents/"`, etc.). Claude Code instead
defaults to standard dirs and takes path overrides only via the marketplace entry.
This means the same `plugin.json` may need these fields for Copilot but not for Claude.
---
## marketplace.json schema (Copilot)
```json
{
"name": "my-ai-marketplace",
"owner": { "name": "Your Name", "email": "you@example.com" },
"metadata": { "description": "Agents, skills and workflows", "version": "1.0.0" },
"plugins": [
{
"name": "startup-cto",
"source": "./plugins/startup-cto",
"description": "...",
"version": "1.0.0"
}
]
}
```
Copilot's primary marketplace manifest path is `.github/plugin/marketplace.json`.
It also reads `.claude-plugin/marketplace.json` as a fallback.
Relative `source` paths: `./x` and `x` are both valid (Claude requires `./`).
---
## Agent file format
Copilot agents use `.agent.md` extension with frontmatter:
```markdown
---
name: my-agent
description: What this agent does
tools:
- read_file
- run_command
---
Agent instructions here.
```
Claude Code agents use `.md` extension without the `.agent.md` suffix.
If shipping agents for both tools, create both files:
- `agents/my-agent.md` — Claude Code
- `agents/my-agent.agent.md` — Copilot CLI
---
## CLI commands
```bash
# Install plugin locally (development)
copilot plugin install ./my-plugin
# List installed plugins
copilot plugin list
# In interactive mode
/plugin list
/skills list
/agent
# Reload after changes
/reload-plugins
# Uninstall (uses bare plugin name, not @marketplace form)
copilot plugin uninstall <name>
# Marketplace
copilot plugin marketplace add owner/repo
```
> ⚠️ **UNVERIFIED: Copilot marketplace install command.**
> The `update`/`uninstall` commands take a bare `<name>`. Whether install from a marketplace
> uses `<name>@<marketplace>` (Claude Code's form) or a bare `<name>` is not confirmed in docs.
> Run `copilot plugin install --help` before documenting the install command anywhere.
---
## Validation
Copilot has no documented `plugin validate` command. For Copilot-side validation, run manual checks:
- Valid JSON in `plugin.json` and `marketplace.json`
- Required fields: `name`, `description`
- Unique plugin names across marketplace
- Kebab-case plugin names
- All `source` paths resolve to existing directories
- `.agent.md` files have valid YAML frontmatter with `name`, `description`, `tools`
---
## Key differences from Claude Code (summary)
| What | Claude Code | Copilot CLI |
|---|---|---|
| Plugin manifest location | `.claude-plugin/plugin.json` | `plugin.json` at plugin root |
| Agent files | `agents/<name>.md` | `agents/<name>.agent.md` |
| Hooks file | `hooks/hooks.json` | `hooks.json` at plugin root |
| Component paths | Declared in marketplace entry | Declared in `plugin.json` |
| Validate command | `claude plugin validate` | None — manual checks only |
| Relative source `./` | Required | Optional (`x` also valid) |

View File

@@ -0,0 +1,79 @@
# Cross-Tool Compatibility Reference
Claude Code and GitHub Copilot CLI share the plugin concept but diverge in specific, breaking
ways. **Skills are the portable core. Manifests and agents are where they split.**
Make Claude Code the source of truth — it is the stricter, more fully specified format.
Treat "loads in Copilot CLI" as a tested checklist item per plugin, not an assumption.
---
## Divergence table
| Concern | Claude Code | GitHub Copilot CLI | Portable choice |
|---|---|---|---|
| Marketplace manifest path | `.claude-plugin/marketplace.json` (required) | `.github/plugin/marketplace.json` (primary); also reads `.claude-plugin/` | Put it in `.claude-plugin/` — both read it. Optionally mirror to `.github/plugin/`. |
| Plugin manifest path | `.claude-plugin/plugin.json` (required; only `plugin.json` goes in this dir) | `plugin.json` at **plugin root** | Ship in both locations until verified — see ⚠️ below |
| Skills | `skills/<name>/SKILL.md` | `skills/<name>/SKILL.md` | ✅ Identical |
| Agents | `agents/<name>.md` | `agents/<name>.agent.md` (frontmatter incl. `tools:`) | Diverges — keep portable logic in skills; ship per-tool agent files only when needed |
| Hooks | `hooks/hooks.json` | `hooks.json` at plugin root | Diverges; declare paths in manifest to be safe |
| MCP servers | `.mcp.json` at plugin root | `.mcp.json` at plugin root | ✅ Same |
| Relative `source` | must start with `./` | `./x` and `x` both valid | Always use `./` — valid for both |
| Validate command | `claude plugin validate .` (or `/plugin validate .`) | none documented | Run Claude validator + manual JSON checks for Copilot |
| Install marketplace | `claude plugin marketplace add owner/repo` | `copilot plugin marketplace add owner/repo` | Same shape |
| Install plugin | `claude plugin install <name>@<marketplace-name>` | install by plugin name; `@marketplace` suffix unconfirmed | ⚠️ Verify Copilot install string before documenting |
| Local install (dev) | `claude --plugin-dir ./plugin` | `copilot plugin install ./plugin` | Tool-specific |
| Component paths in plugin.json | Claude defaults to standard dirs; path overrides via marketplace entry only | `"skills": "skills/"`, `"agents": "agents/"`, etc. in plugin.json | Generate per-tool manifests rather than one shared file |
> ⚠️ **UNVERIFIED — test before committing to a layout.**
> Copilot docs confirm it reads the **marketplace** manifest from `.claude-plugin/`. They do NOT
> confirm the same fallback for a plugin's `plugin.json`. Copilot docs show `plugin.json` at plugin
> root; Claude requires it in `.claude-plugin/`. Until verified: ship `plugin.json` in BOTH
> `plugin-name/plugin.json` and `plugin-name/.claude-plugin/plugin.json` (identical content), then
> drop whichever proves redundant.
---
## `@<marketplace-name>` resolution
`claude plugin install startup-cto@my-ai-marketplace` requires the marketplace manifest's
top-level `name` field to be exactly `my-ai-marketplace`. It is **not** the GitHub repo name.
Keep them aligned to avoid confusion, but they are separate fields.
---
## Canonical cross-compatible repo layout
```text
repo-root/
├── .claude-plugin/
│ └── marketplace.json # both tools read here
├── .github/plugin/
│ └── marketplace.json # OPTIONAL: Copilot canonical path (mirror)
├── plugins/
│ └── startup-cto/
│ ├── plugin.json # Copilot root manifest ┐ ship both until
│ ├── .claude-plugin/ # │ the note above is
│ │ └── plugin.json # Claude manifest ┘ verified
│ ├── skills/
│ │ ├── fundraising/SKILL.md
│ │ └── hiring/SKILL.md
│ ├── agents/
│ │ ├── startup-cto.md # Claude
│ │ └── startup-cto.agent.md # Copilot (only if shipping native agents)
│ ├── hooks/hooks.json # Claude
│ ├── hooks.json # Copilot (if hooks used)
│ └── README.md
└── README.md
```
---
## Open questions to resolve before generating layouts
1. **Does Copilot CLI load a plugin whose `plugin.json` lives only in `.claude-plugin/`?**
Install a test plugin both ways. The answer decides whether to ship one manifest or two.
2. **What is Copilot's exact install-from-marketplace command?**
Run `copilot plugin install --help`. The `update`/`uninstall` commands take a bare plugin
name — the `@marketplace` form may not apply.

View File

@@ -0,0 +1,194 @@
#!/usr/bin/env bash
# Generate plugin.json (in both locations) and marketplace.json.
# Dry-run by default; pass --write to apply.
#
# Usage:
# gen_manifests.sh <repo-root> --marketplace-name <name> [options]
#
# Options:
# --marketplace-name <name> kebab-case marketplace identifier (required)
# --author "Name <email>" author string (default: "Unknown <unknown@example.com>")
# --plugins-dir <dir> subdir containing plugin folders (default: plugins)
# --mirror-github also write to .github/plugin/marketplace.json
# --write apply changes (default is dry run)
set -euo pipefail
RESERVED_NAMES="claude-code-marketplace claude-code-plugins claude-plugins-official
claude-plugins-community claude-community anthropic-marketplace anthropic-plugins
agent-skills anthropic-agent-skills knowledge-work-plugins life-sciences
claude-for-legal claude-for-financial-services financial-services-plugins"
RESERVED_PATTERNS="official-claude anthropic-tools claude-official"
is_kebab_case() {
[[ "$1" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]
}
is_reserved() {
local name="$1"
for n in $RESERVED_NAMES; do
[[ "$name" == "$n" ]] && return 0
done
for p in $RESERVED_PATTERNS; do
[[ "$name" == "$p"* ]] && return 0
done
return 1
}
validate_name() {
local name="$1" context="$2"
local ok=true
if ! is_kebab_case "$name"; then
echo "ERROR: $context: name '$name' is not kebab-case (lowercase, digits, hyphens only)." >&2
ok=false
fi
if is_reserved "$name"; then
echo "ERROR: $context: name '$name' is reserved for official Anthropic use." >&2
ok=false
fi
[[ "$ok" == "true" ]]
}
write_json() {
local path="$1" content="$2" dry_run="$3"
if [[ "$dry_run" == "true" ]]; then
echo ""
echo "--- $path (dry run) ---"
echo "$content"
else
mkdir -p "$(dirname "$path")"
echo "$content" > "$path"
echo " Written: $path"
fi
}
# ── parse args ───────────────────────────────────────────────────────────────
ROOT=""
MARKETPLACE_NAME=""
AUTHOR="Unknown <unknown@example.com>"
PLUGINS_DIR="plugins"
MIRROR_GITHUB=false
WRITE=false
while [[ $# -gt 0 ]]; do
case "$1" in
--marketplace-name) MARKETPLACE_NAME="$2"; shift 2 ;;
--author) AUTHOR="$2"; shift 2 ;;
--plugins-dir) PLUGINS_DIR="$2"; shift 2 ;;
--mirror-github) MIRROR_GITHUB=true; shift ;;
--write) WRITE=true; shift ;;
-*) echo "Unknown option: $1" >&2; exit 1 ;;
*) ROOT="$1"; shift ;;
esac
done
if [[ -z "$ROOT" || -z "$MARKETPLACE_NAME" ]]; then
echo "Usage: gen_manifests.sh <repo-root> --marketplace-name <name> [--write]" >&2
exit 1
fi
ROOT="$(cd "$ROOT" && pwd)"
DRY_RUN=$( [[ "$WRITE" == "true" ]] && echo "false" || echo "true" )
[[ "$DRY_RUN" == "true" ]] && echo "DRY RUN — pass --write to apply changes"
# Parse author
AUTHOR_NAME="${AUTHOR%% <*}"
AUTHOR_EMAIL=""
if [[ "$AUTHOR" =~ \<(.+)\> ]]; then
AUTHOR_EMAIL="${BASH_REMATCH[1]}"
fi
# Validate marketplace name
validate_name "$MARKETPLACE_NAME" "marketplace" || exit 1
# Discover plugins
PLUGINS_PATH="$ROOT/$PLUGINS_DIR"
if [[ ! -d "$PLUGINS_PATH" ]]; then
echo "No plugins directory found at $PLUGINS_PATH" >&2
exit 1
fi
mapfile -t PLUGIN_DIRS < <(find "$PLUGINS_PATH" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | sort)
if [[ ${#PLUGIN_DIRS[@]} -eq 0 ]]; then
echo "No plugin directories found in $PLUGINS_PATH" >&2
exit 1
fi
echo "Found ${#PLUGIN_DIRS[@]} plugin(s)"
# Build plugins array for marketplace.json
PLUGINS_JSON="[]"
for pd in "${PLUGIN_DIRS[@]}"; do
pname="$(basename "$pd")"
validate_name "$pname" "plugin '$pname'" || exit 1
# Read existing plugin.json if present
existing_claude="$pd/.claude-plugin/plugin.json"
existing_root="$pd/plugin.json"
existing_desc="Plugin: $pname"
existing_name="$pname"
for existing in "$existing_claude" "$existing_root"; do
if [[ -f "$existing" ]] && jq -e . "$existing" >/dev/null 2>&1; then
d=$(jq -r '.description // empty' "$existing")
n=$(jq -r '.name // empty' "$existing")
[[ -n "$d" ]] && existing_desc="$d"
[[ -n "$n" ]] && existing_name="$n"
break
fi
done
# Warn on version
for existing in "$existing_claude" "$existing_root"; do
if [[ -f "$existing" ]] && jq -e '.version' "$existing" >/dev/null 2>&1; then
echo " WARNING: plugin '$pname' sets version in plugin.json. Do not also set it in the marketplace entry — plugin.json wins silently."
break
fi
done
# Build plugin.json
plugin_json=$(jq -n \
--arg name "$existing_name" \
--arg desc "$existing_desc" \
--arg aname "$AUTHOR_NAME" \
--arg aemail "$AUTHOR_EMAIL" \
'{name: $name, description: $desc, author: {name: $aname, email: $aemail}}')
write_json "$pd/plugin.json" "$plugin_json" "$DRY_RUN"
write_json "$pd/.claude-plugin/plugin.json" "$plugin_json" "$DRY_RUN"
source="./$PLUGINS_DIR/$pname"
PLUGINS_JSON=$(echo "$PLUGINS_JSON" | jq \
--arg name "$existing_name" \
--arg src "$source" \
--arg desc "$existing_desc" \
'. + [{name: $name, source: $src, description: $desc}]')
done
# Build marketplace.json
marketplace_json=$(jq -n \
--arg name "$MARKETPLACE_NAME" \
--arg aname "$AUTHOR_NAME" \
--arg aemail "$AUTHOR_EMAIL" \
--arg desc "$MARKETPLACE_NAME plugin marketplace" \
--argjson plugins "$PLUGINS_JSON" \
'{name: $name, owner: {name: $aname, email: $aemail}, description: $desc, plugins: $plugins}')
write_json "$ROOT/.claude-plugin/marketplace.json" "$marketplace_json" "$DRY_RUN"
if [[ "$MIRROR_GITHUB" == "true" ]]; then
write_json "$ROOT/.github/plugin/marketplace.json" "$marketplace_json" "$DRY_RUN"
fi
if [[ "$DRY_RUN" == "true" ]]; then
echo ""
echo "--- End dry run. Pass --write to apply. ---"
else
echo ""
echo "Done. Run scripts/validate.sh to verify."
fi

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env bash
# Scan a repository and classify every asset as skill/command/agent/hook/prompt/MCP.
# Outputs a markdown table of findings plus a list of cross-reference warnings.
#
# Usage: inventory.sh <repo-path>
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "Usage: inventory.sh <repo-path>" >&2
exit 1
fi
ROOT="$(cd "$1" && pwd)"
if [[ ! -d "$ROOT" ]]; then
echo "Error: $ROOT is not a directory" >&2
exit 1
fi
# ── classify assets ──────────────────────────────────────────────────────────
declare -a ROWS=()
declare -a CROSS_REFS=()
while IFS= read -r -d '' path; do
rel="${path#"$ROOT/"}"
name="$(basename "$path")"
dir="$(dirname "$rel")"
parent="$(basename "$dir")"
# Skip hidden dirs except .claude-plugin and .github
skip=false
IFS='/' read -ra parts <<< "$dir"
for part in "${parts[@]}"; do
if [[ "$part" == .* && "$part" != ".claude-plugin" && "$part" != ".github" && "$part" != ".agents" ]]; then
skip=true; break
fi
done
$skip && continue
asset_type=""
case "$name" in
SKILL.md) asset_type="skill" ;;
hooks.json) asset_type="hook" ;;
.mcp.json) asset_type="mcp" ;;
.lsp.json) asset_type="lsp" ;;
plugin.json) asset_type="manifest-plugin" ;;
marketplace.json) asset_type="manifest-marketplace" ;;
*.agent.md) asset_type="agent-copilot" ;;
*.md)
if [[ "$parent" == "agents" ]]; then
asset_type="agent-claude"
elif [[ "$parent" == "commands" ]]; then
asset_type="command"
elif [[ "$rel" != *"/skills/"* && "$rel" != *"/commands/"* && "$rel" != *"/agents/"* ]]; then
asset_type="prompt"
fi
;;
esac
[[ -n "$asset_type" ]] && ROWS+=("$asset_type|$rel")
# Check for cross-references in text files
case "$name" in *.md|*.json|*.sh)
if grep -q '\.\.\/' "$path" 2>/dev/null; then
while IFS= read -r line; do
lineno="${line%%:*}"
content="${line#*:}"
CROSS_REFS+=("$rel:$lineno: $content")
done < <(grep -n '\.\.\/' "$path" 2>/dev/null | head -20)
fi
;;
esac
done < <(find "$ROOT" -type f -print0 | sort -z)
# ── report ───────────────────────────────────────────────────────────────────
echo "# Asset Inventory: $ROOT"
echo ""
echo "## Assets"
echo ""
echo "| Type | Path |"
echo "|---|---|"
for row in "${ROWS[@]+"${ROWS[@]}"}"; do
type="${row%%|*}"
path="${row#*|}"
echo "| \`$type\` | \`$path\` |"
done | sort
total="${#ROWS[@]}"
echo ""
echo "**Total: $total assets**"
echo ""
# Summary by type
echo "## Summary by type"
echo ""
for row in "${ROWS[@]+"${ROWS[@]}"}"; do
echo "${row%%|*}"
done | sort | uniq -c | while read -r count type; do
echo "- \`$type\`: $count"
done
# Cross-reference warnings
echo ""
if [[ ${#CROSS_REFS[@]} -gt 0 ]]; then
echo "## ⚠️ Cross-reference warnings (${#CROSS_REFS[@]} found)"
echo ""
echo "These \`../\` references will break after install-time caching:"
echo ""
for ref in "${CROSS_REFS[@]}"; do
echo "- \`$ref\`"
done
else
echo "## Cross-references"
echo ""
echo "No \`../\` cross-references found. Safe to proceed with plugin boundaries."
fi

View File

@@ -0,0 +1,332 @@
#!/usr/bin/env bash
# Validate plugin marketplace manifests for Claude Code and GitHub Copilot CLI.
# Wraps `claude plugin validate` (Claude-side) and runs manual checks (Copilot-side).
#
# Usage:
# validate.sh <repo-root>
# validate.sh <repo-root> --plugin plugins/my-plugin
set -euo pipefail
RESERVED_NAMES="claude-code-marketplace claude-code-plugins claude-plugins-official
claude-plugins-community claude-community anthropic-marketplace anthropic-plugins
agent-skills anthropic-agent-skills knowledge-work-plugins life-sciences
claude-for-legal claude-for-financial-services financial-services-plugins"
RESERVED_PATTERNS="official-claude anthropic-tools claude-official"
ERRORS=0
WARNINGS=0
error() { echo "ERROR: $1"; ((ERRORS++)) || true; }
warn() { echo "WARN: $1"; ((WARNINGS++)) || true; }
is_kebab_case() { [[ "$1" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; }
is_reserved() {
local name="$1"
for n in $RESERVED_NAMES; do [[ "$name" == "$n" ]] && return 0; done
for p in $RESERVED_PATTERNS; do [[ "$name" == "$p"* ]] && return 0; done
return 1
}
validate_name() {
local name="$1" context="$2"
[[ -z "$name" ]] && { error "$context: name is missing or empty"; return 0; }
is_kebab_case "$name" || error "$context: name '$name' is not kebab-case"
if is_reserved "$name"; then
error "$context: name '$name' is reserved for official Anthropic use"
fi
return 0
}
valid_json() {
local path="$1"
if ! jq -e . "$path" >/dev/null 2>&1; then
error "Invalid JSON in $path"
return 1
fi
return 0
}
validate_marketplace_json() {
local path="$1"
[[ -f "$path" ]] || return 0
valid_json "$path" || return 0
local name
name=$(jq -r '.name // empty' "$path")
[[ -z "$name" ]] && error "$path: 'name' field is required" || validate_name "$name" "$path"
local plugins_type
plugins_type=$(jq -r 'if .plugins | type == "array" then "ok" else "bad" end' "$path")
if [[ "$plugins_type" != "ok" ]]; then
error "$path: 'plugins' must be an array"
return 0
fi
# Check each plugin entry
local seen_names=()
while IFS= read -r pname; do
# Duplicate check
for seen in "${seen_names[@]+"${seen_names[@]}"}"; do
if [[ "$seen" == "$pname" ]]; then error "$path: duplicate plugin name '$pname'"; fi
done
seen_names+=("$pname")
validate_name "$pname" "$path plugin '$pname'"
# Source path check
local src
src=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .source // empty' "$path")
if [[ -n "$src" && "$src" != ./* && "$src" != "github" && "$src" != "npm" && "$src" != "url" && "$src" != "git-subdir" ]]; then
warn "$path plugin '$pname': relative source '$src' should start with './' for Claude Code compatibility"
fi
# Version duplication warning
local has_ver
has_ver=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .version // empty' "$path")
if [[ -n "$has_ver" ]]; then
warn "$path plugin '$pname': version set in marketplace entry. If also set in plugin.json, plugin.json wins silently."
fi
done < <(jq -r '.plugins[].name // empty' "$path")
return 0
}
validate_plugin_json() {
local path="$1" marketplace_json="${2:-}"
[[ -f "$path" ]] || return 0
valid_json "$path" || return 0
local name
name=$(jq -r '.name // empty' "$path")
if [[ -z "$name" ]]; then
warn "$path: 'name' field missing (plugin dir name will be used)"
else
validate_name "$name" "$path"
# Version duplication check
if [[ -n "$marketplace_json" && -f "$marketplace_json" ]]; then
local pver mver
pver=$(jq -r '.version // empty' "$path")
mver=$(jq -r --arg n "$name" '.plugins[]? | select(.name==$n) | .version // empty' "$marketplace_json")
if [[ -n "$pver" && -n "$mver" ]]; then
error "$path: version '$pver' set in both plugin.json and marketplace entry — plugin.json wins silently. Remove one."
fi
fi
fi
return 0
}
validate_skill_md() {
local path="$1"
local content
content=$(cat "$path")
if [[ "$content" != ---* ]]; then
warn "$path: SKILL.md has no YAML frontmatter"
return
fi
if ! echo "$content" | awk 'NR>1 && /^---/' | grep -q '^---'; then
error "$path: SKILL.md frontmatter not closed"
return
fi
if ! echo "$content" | awk '/^---/{n++; if(n==2) exit} n==1' | grep -q 'description:'; then
warn "$path: SKILL.md frontmatter missing 'description' field"
fi
}
validate_plugin_dir() {
local pd="$1" marketplace_json="${2:-}"
local claude_manifest="$pd/.claude-plugin/plugin.json"
local root_manifest="$pd/plugin.json"
if [[ ! -f "$claude_manifest" && ! -f "$root_manifest" ]]; then
warn "$pd: no plugin.json found (will auto-discover components)"
else
validate_plugin_json "$claude_manifest" "$marketplace_json"
validate_plugin_json "$root_manifest" "$marketplace_json"
# Sync check — shared identity fields must match; component path fields legitimately diverge
if [[ -f "$claude_manifest" && -f "$root_manifest" ]]; then
local field cv rv
for field in name description version license; do
cv=$(jq -r ".$field // empty" "$claude_manifest")
rv=$(jq -r ".$field // empty" "$root_manifest")
if [[ ( -n "$cv" || -n "$rv" ) && "$cv" != "$rv" ]]; then
error "$pd: '$field' differs between .claude-plugin/plugin.json ('$cv') and plugin.json ('$rv')"
fi
done
cv=$(jq -r '.author.name // empty' "$claude_manifest")
rv=$(jq -r '.author.name // empty' "$root_manifest")
if [[ ( -n "$cv" || -n "$rv" ) && "$cv" != "$rv" ]]; then
error "$pd: 'author.name' differs between .claude-plugin/plugin.json ('$cv') and plugin.json ('$rv')"
fi
cv=$(jq -r '.keywords // [] | sort | join(",")' "$claude_manifest")
rv=$(jq -r '.keywords // [] | sort | join(",")' "$root_manifest")
if [[ "$cv" != "$rv" ]]; then
error "$pd: 'keywords' differs between .claude-plugin/plugin.json and plugin.json"
fi
fi
fi
# Components must not be inside .claude-plugin/
for bad_dir in skills agents hooks commands; do
if [[ -d "$pd/.claude-plugin/$bad_dir" ]]; then
error "$pd/.claude-plugin/$bad_dir: only plugin.json belongs in .claude-plugin/; move $bad_dir/ to plugin root"
fi
done
# Validate SKILL.md files
while IFS= read -r -d '' skill_md; do
validate_skill_md "$skill_md"
done < <(find "$pd" -name "SKILL.md" -print0 2>/dev/null)
# Cross-reference check
while IFS= read -r -d '' f; do
if grep -q '\.\.\/' "$f" 2>/dev/null; then
local rel="${f#"$pd/"}"
error "$rel: contains '../' reference — plugins cannot access files outside their directory after caching"
fi
done < <(find "$pd" \( -name "*.md" -o -name "*.json" \) -print0 2>/dev/null)
}
run_claude_validate() {
local path="$1"
if command -v claude >/dev/null 2>&1; then
if ! claude plugin validate "$path" 2>&1; then
error "claude plugin validate failed for $path"
fi
else
warn "'claude' CLI not found — skipping claude plugin validate"
fi
}
# ── parse args ────────────────────────────────────────────────────────────────
ROOT=""
PLUGIN_ONLY=""
while [[ $# -gt 0 ]]; do
case "$1" in
--plugin) PLUGIN_ONLY="$2"; shift 2 ;;
-*) echo "Unknown option: $1" >&2; exit 1 ;;
*) ROOT="$1"; shift ;;
esac
done
if [[ -z "$ROOT" ]]; then
echo "Usage: validate.sh <repo-root> [--plugin <path>]" >&2
exit 1
fi
ROOT="$(cd "$ROOT" && pwd)"
# ── validate marketplace.json ─────────────────────────────────────────────────
CLAUDE_MARKETPLACE="$ROOT/.claude-plugin/marketplace.json"
COPILOT_MARKETPLACE="$ROOT/.github/plugin/marketplace.json"
MARKETPLACE_JSON=""
for mp in "$CLAUDE_MARKETPLACE" "$COPILOT_MARKETPLACE"; do
if [[ -f "$mp" ]]; then
[[ -z "$MARKETPLACE_JSON" ]] && MARKETPLACE_JSON="$mp"
validate_marketplace_json "$mp"
fi
done
if [[ -z "$MARKETPLACE_JSON" ]]; then
warn "No marketplace.json found. Expected at .claude-plugin/marketplace.json"
fi
# Marketplace sync check — shared identity fields must match; description/version
# legitimately differ in structure (Claude: top-level; Copilot: under metadata)
if [[ -f "$CLAUDE_MARKETPLACE" && -f "$COPILOT_MARKETPLACE" ]]; then
cm_val=$(jq -r '.name // empty' "$CLAUDE_MARKETPLACE")
cp_val=$(jq -r '.name // empty' "$COPILOT_MARKETPLACE")
if [[ "$cm_val" != "$cp_val" ]]; then
error "marketplace: 'name' differs — .claude-plugin ('$cm_val') vs .github/plugin ('$cp_val')"
fi
cm_val=$(jq -r '.owner.name // empty' "$CLAUDE_MARKETPLACE")
cp_val=$(jq -r '.owner.name // empty' "$COPILOT_MARKETPLACE")
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
error "marketplace: 'owner.name' differs — '$cm_val' vs '$cp_val'"
fi
# description: Claude top-level, Copilot under metadata — compare values regardless of path
cm_val=$(jq -r '.description // .metadata.description // empty' "$CLAUDE_MARKETPLACE")
cp_val=$(jq -r '.metadata.description // .description // empty' "$COPILOT_MARKETPLACE")
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
error "marketplace: description differs between .claude-plugin/marketplace.json and .github/plugin/marketplace.json"
fi
# version: same structural divergence as description
cm_val=$(jq -r '.version // .metadata.version // empty' "$CLAUDE_MARKETPLACE")
cp_val=$(jq -r '.metadata.version // .version // empty' "$COPILOT_MARKETPLACE")
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
error "marketplace: version differs — '$cm_val' vs '$cp_val'"
fi
# Plugin catalog must be identical across both files
cm_plugins=$(jq -r '.plugins[].name' "$CLAUDE_MARKETPLACE" 2>/dev/null | sort)
cp_plugins=$(jq -r '.plugins[].name' "$COPILOT_MARKETPLACE" 2>/dev/null | sort)
if [[ "$cm_plugins" != "$cp_plugins" ]]; then
error "marketplace: plugin lists differ between .claude-plugin/marketplace.json and .github/plugin/marketplace.json"
else
while IFS= read -r pname; do
[[ -z "$pname" ]] && continue
cm_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .source // empty' "$CLAUDE_MARKETPLACE")
cp_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .source // empty' "$COPILOT_MARKETPLACE")
if [[ "$cm_val" != "$cp_val" ]]; then
error "marketplace plugin '$pname': source differs — '$cm_val' vs '$cp_val'"
fi
cm_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .description // empty' "$CLAUDE_MARKETPLACE")
cp_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .description // empty' "$COPILOT_MARKETPLACE")
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
error "marketplace plugin '$pname': description differs between the two marketplace.json files"
fi
done <<< "$cm_plugins"
fi
fi
# ── validate plugins ──────────────────────────────────────────────────────────
if [[ -n "$PLUGIN_ONLY" ]]; then
validate_plugin_dir "$(cd "$PLUGIN_ONLY" && pwd)" "$MARKETPLACE_JSON"
run_claude_validate "$(cd "$PLUGIN_ONLY" && pwd)"
else
plugins_path="$ROOT/plugins"
if [[ -d "$plugins_path" ]]; then
while IFS= read -r -d '' pd; do
validate_plugin_dir "$pd" "$MARKETPLACE_JSON"
run_claude_validate "$pd"
done < <(find "$plugins_path" -mindepth 1 -maxdepth 1 -type d ! -name '.*' -print0 | sort -z)
else
warn "No plugins/ directory found at $ROOT"
fi
fi
# ── check source paths resolve ────────────────────────────────────────────────
if [[ -n "$MARKETPLACE_JSON" ]]; then
while IFS= read -r src; do
[[ "$src" != ./* ]] && continue
src_path="$ROOT/${src#./}"
if [[ ! -d "$src_path" ]]; then error "Marketplace source path '$src' does not exist at $src_path"; fi
done < <(jq -r '.plugins[]?.source | strings' "$MARKETPLACE_JSON" 2>/dev/null)
fi
# ── report ────────────────────────────────────────────────────────────────────
echo ""
if [[ $ERRORS -eq 0 && $WARNINGS -eq 0 ]]; then
echo "✓ All checks passed."
exit 0
elif [[ $ERRORS -eq 0 ]]; then
echo "Passed with $WARNINGS warning(s)."
exit 0
else
echo "Failed. Fix $ERRORS error(s) before proceeding."
exit 1
fi

View File

@@ -0,0 +1,141 @@
---
name: write-eval
description: Write or generate an eval.yaml test file for a skill. Use when the user wants to create evals, add test coverage, or says "write evals for this skill", "create eval.yaml for X", or "add tests for this skill". Do NOT use when the user wants to run existing evals, write unit tests for code, or debug test failures.
version: "1.0"
updated: 2026-05-17
when: invoked by explicit trigger ("write evals for this skill", "create eval.yaml for X") or implicit request for skill test coverage
metadata:
category: factory
source:
- repo: agentskills/agentskills
commit: 2d3e01f590f68bee2cb76a3200823e93b2cc9eaa
files:
- docs/skill-creation/evaluating-skills.mdx # evals schema, two-section workspace layout, assertion quality guidelines
updated: 2026-05-17
- repo: darkrishabh/agent-skills-eval
commit: b60eebe3c6edaa917a284e13b9b0e9fa00f1c957
files:
- src/types.ts # AgentSkillsEval interface — string-slug id, name field, prompt/expected_output/assertions structure
- examples/basic-skill/evals/evals.json # concrete schema example
updated: 2026-05-17
- repo: bmad-code-org/BMAD-METHOD
commit: 71136bc6af77cbf507d3768494311d5b6ca95cc5
files:
- evals/bmm-skills/bmad-product-brief/triggers.json # trigger classification dataset, should_trigger boolean pattern
- evals/bmm-skills/bmad-product-brief/evals.json # output test structure, boundary-enforcement negative test pattern
updated: 2026-05-17
- repo: mattpocock/skills
commit: e74f0061bb67222181640effa98c675bdb2fdaa7
files:
- skills/engineering/tdd/SKILL.md # behavioral test philosophy: test observable outputs through public interfaces
updated: 2026-05-17
references:
- https://agentskills.io/skill-creation/evaluating-skills
---
## Role
You are a test architect producing eval.yaml files that verify AI skill trigger behaviour and output quality.
## When to use / When not to use
**Use when:**
- User explicitly requests evals: "write evals for this skill", "create eval.yaml for X", "add tests for this skill"
- A new or refactored skill needs an eval file
- Existing eval coverage needs to be extended with additional test cases
**Do not use when:**
- User wants to run or execute existing evals
- User wants to write unit tests for application code (not a skill eval)
- User asks to debug or analyse failing eval results
- User asks to review or compare eval output
## Required inputs
- Target skill name — explicit or unambiguous from session context
- Target skill's SKILL.md — must be readable at `.agents/skills/<skill-name>/SKILL.md`
- Target skill's `metadata.category` — used to derive the output path
## Constraints
- Output path: `.agents/evals/<category>/<skill-name>/eval.yaml` — nested by category, not flat
- Every eval.yaml must contain all five required test types: ≥1 explicit trigger, ≥1 implicit trigger, ≥1 negative trigger, ≥2 deterministic output, ≥1 LLM-rubric quality
- Assertions must be specific and verifiable — "The output contains a trigger_tests section" not "The output is good"
- Assertions must be provider-agnostic — no tool-call assertions, no assumptions about the underlying model or runtime
- Show the test plan and wait for confirmation before writing any file
- On re-run (eval.yaml already exists): merge — classify proposed cases as NEW / IDENTICAL / CONFLICT; surface conflicts for human resolution before writing; do not silently overwrite
- Body ≤500 lines
## Process
1. **Identify the target skill.** If not explicit in the invocation, infer from session context. If ambiguous, ask before proceeding.
2. **Read the target SKILL.md** at `.agents/skills/<skill-name>/SKILL.md`. Extract:
- `name`, `metadata.category` (for output path)
- `description` (trigger description — source for explicit and implicit trigger test queries)
- When / when not criteria (source for negative trigger test queries)
- Required inputs and output format (source for deterministic output assertions)
3. **Check for an existing eval.yaml** at `.agents/evals/<category>/<skill-name>/eval.yaml`.
- If it exists: read it and record all existing test IDs.
4. **Propose test cases** — one minimum per required type:
**trigger_tests** — classify each query by whether the skill should activate:
- ≥1 explicit trigger: a query using the skill's exact trigger phrase
- ≥1 implicit trigger: a query describing the task without the trigger phrase; derive from the skill's purpose and use cases
- ≥1 negative trigger: a query for an adjacent task the skill must NOT activate on; derive from the skill's when-not criteria; choose a case with surface similarity to the trigger
**output_tests** — test what the skill produces:
- ≥2 deterministic: assert on observable, machine-checkable properties of the output — required sections present, correct file path, schema compliance. Write as specific string conditions a reader could verify without inference.
- ≥1 LLM-rubric: holistic quality assertions — conditions a judge evaluates from the full output. Test qualities that deterministic checks cannot capture: realism of trigger queries, specificity of assertions, boundary case coverage.
For all assertions: write as verifiable conditions, not value judgements. Test boundary cases, not only happy paths. A good assertion survives internal refactoring of the skill.
5. **Classify proposed cases if an existing eval.yaml was found:**
- **NEW** — ID not in existing file; safe to append
- **IDENTICAL** — ID exists, content matches exactly; skip silently
- **CONFLICT** — ID exists, content differs; display existing vs proposed side-by-side
6. **Present the full test plan.** Show each proposed case with its classification label (NEW / IDENTICAL / CONFLICT). For CONFLICT cases, ask the user to choose: keep existing, use proposed, or skip. Wait for confirmation before writing.
7. **Write eval.yaml.** Append NEW cases to the existing file (or write the full structure for a new file). Apply CONFLICT resolutions as chosen. Skip IDENTICAL cases.
## Output format
```yaml
skill_name: <name>
trigger_tests:
- id: <string-slug> # e.g. explicit-trigger-basic
name: <display label> # human-readable, e.g. "Explicit trigger — basic invocation"
query: <exact user input text>
should_trigger: true # true for explicit and implicit; false for negative
output_tests:
- id: <string-slug>
name: <display label>
type: deterministic # or llm-rubric
prompt: <user input to the skill>
expected_output: <prose description of ideal output>
assertions:
- <specific, verifiable condition string>
```
## Failure handling
- **Target SKILL.md not found:** stop, report the path searched, do not guess or generate content from the skill name alone
- **`metadata.category` absent from SKILL.md:** ask for the category before computing the output path
- **All proposed cases conflict with existing file:** report the full conflict summary, wait for explicit direction — do not auto-resolve
- **Proposed test count below minimums:** flag which type is short before presenting the plan; do not proceed with a deficient eval
## Self-check
Verify before writing:
- [ ] All five test types present — ≥1 explicit, ≥1 implicit, ≥1 negative trigger; ≥2 deterministic, ≥1 LLM-rubric output
- [ ] trigger_tests: at least one `should_trigger: true` and at least one `should_trigger: false`
- [ ] All assertions are specific and verifiable — no vague quality claims
- [ ] Output path matches `.agents/evals/<category>/<skill-name>/eval.yaml`
- [ ] Test plan was presented and confirmed before the file was written
- [ ] CONFLICT cases were surfaced to the user and not silently resolved

View File

@@ -0,0 +1,16 @@
# Skill Categories
| Category | Scope |
|---|---|
| `design` | grill-me, grill-with-docs, to-prd, prototype, architecture-review |
| `plan` | to-issues, triage |
| `implement` | tdd, diagnose, implement-feature, refactor, write-docs |
| `test` | write-tests, generate-test-data, review-test-coverage |
| `review` | improve-codebase-architecture, code-review, security-review, pr-description, changelog-entry |
| `deploy` | write-ci-pipeline, write-deployment-config, write-ai-review-workflow, deployment-checklist |
| `operate` | write-runbook, incident-diagnosis, post-mortem, inspect-deployment |
| `iac` | write-ansible-role, write-terraform-module, write-k8s-manifest, write-docker-compose, proxmox-vm-spec, iac-security-review, write-molecule-test |
| `cross-cutting` | zoom-out, caveman, session-handoff, governance-check, git-guardrails, git-commit-message |
| `factory` | write-skill, write-adr, write-workflow, write-eval, validate-skill, upgrade-skill, write-issue-spec |
| `marketplace` | marketplace-architect — plugin and skill distribution tooling for Claude Code / GitHub Copilot CLI |
| `roles` | architect, developer, reviewer, security, qa, ops — Chunk 5 |

View File

@@ -0,0 +1,27 @@
```yaml
version: "1.0" # increment on meaningful changes to the skill
updated: YYYY-MM-DD # ISO date of last update
# when: describes when this skill is loaded — the full trigger context.
# More detail than the description field; not used for routing.
when: <describe the invocation conditions here>
# source: tracks content you ADOPTED from an upstream repo.
# Adopt = you read someone else's code or docs and incorporated text or logic directly.
# Omit this field entirely if the skill is self-authored — absence means original work.
# Present only when content was actually taken, tracked at commit-level for upgrade reviews.
source:
- repo: org/repo-name # GitHub slug — no URL, slug is stable and searchable
commit: <full SHA> # exact commit reviewed at time of adoption
files:
- path/to/file.md # inline comment: what was taken from this file
- path/to/other.md # inline comment: what was taken from this file
updated: YYYY-MM-DD # date this source entry was last reviewed
# references: tracks content you CITED but did not adopt verbatim.
# Cite = you read it and it informed the skill, but nothing was copied or adapted.
# Examples: a spec you followed, a paper that shaped the approach, external documentation.
# Distinct from source: source = took content; references = informed by content.
references:
- https://example.com/relevant-doc
```

View File

@@ -0,0 +1,16 @@
```yaml
version: "1.5"
updated: 2026-05-26
# when: describes when this skill is loaded — the full trigger context.
# More detail than the description field; not used for routing.
when: invoked by explicit trigger ("write a new skill for X", "create a SKILL.md that does Y") or implicit request to author a skill file or convert an existing placeholder to the canonical authoring standard
# source: omitted — self-authored original; no upstream content adopted
# Absence of source means self-authored. If content is adopted from upstream,
# add a source entry per the META-TEMPLATE.md schema.
references:
- https://agentskills.io/specification.md
- https://agentskills.io/skill-creation/optimizing-descriptions
```

View File

@@ -0,0 +1,93 @@
---
name: <skill-name>
# description: routing-only field — loaded at startup for every skill scan to decide whether
# to activate this skill. Write in imperative phrasing ("Use when X", not "This skill does X").
# Must cover: (1) what the skill does, (2) when to invoke it, (3) negative triggers — what
# adjacent tasks must NOT activate it. No behavioral or role framing; that belongs in the body.
# Max 1024 characters. The `when:` detail that lived here previously now lives in META.md.
# Example: "Use when the user wants to create a new SKILL.md file or convert a placeholder to
# canonical format. Do NOT use when updating an existing well-formed skill — use upgrade-skill."
description: <trigger description>
metadata:
category: <category — see CATEGORIES.md>
# allowed-tools: <add only when the skill has a narrow, well-defined tool surface; omit otherwise>
# model: sonnet | opus | haiku — Claude Code extension; overrides session model for this skill's turn.
# Omit to inherit the active session model. Factory §9 routing: haiku=formatting/classification,
# sonnet=most coding/review, opus=adversarial/complex reasoning.
---
<requirements>
## Required inputs
<!-- List each required input as a bullet: name, what it is, how the agent obtains it.
Negative trigger cases are NOT listed here — the agent proposes them during trigger testing.
Example:
- **Skill name** — kebab-case slug; inferred from user description if not stated explicitly, ask if ambiguous
- **Existing SKILL.md path** — for placeholder conversions only; read before writing -->
- **<Input name>** — <description; how obtained>
## Constraints
<!-- One rule per bullet. State the boundary condition inline. Plain English, no jargon.
Do not include a constraint about body section structure — the template enforces that.
Example:
- Frontmatter has three fields only: `name`, `description`, and `metadata.category` — add `allowed-tools` only when the skill has a narrow, well-defined tool surface
- Body ≤500 lines — content that explains rather than directs belongs in sub-files, not the body
- Sub-files use three spec-defined optional directories: `scripts/` (executable code), `references/` (on-demand docs), `assets/` (templates, data files, lookup tables). File references must be one level deep. Wire each sub-file with an explicit step instruction (e.g. "See references/lookup.md for error codes") — without wiring, the file is never loaded -->
- <constraint>
</requirements>
<steps>
## Process
<!-- Numbered steps with a bold action label. Short, direct sentences — state what to do and
what happens as a result. Call out hard gates explicitly (steps that block all progress
until satisfied). No preamble, no meta-commentary about the steps themselves.
Example:
1. **Scan for overlap.** Check `.agents/skills/` for skills with similar purpose or trigger phrases. If overlap is found, surface it and wait for explicit direction — do not continue.
2. **Grill.** Run a focused grill to reach shared understanding of: skill name, category, purpose, and use cases. One question at a time, with a recommendation for each. -->
1. **<Step name>.** <what to do and what happens as a result>
## Output format
<!-- Describe the files or artifacts produced. Include paths and how they are created
(copy-fill from template, generated, etc.). State the template used for structured file output.
Example:
Two files produced for every skill, plus optional sub-files if the skill requires them:
- `SKILL.md` — copy-filled from `SKILL-TEMPLATE.md` at `.agents/skills/<name>/SKILL.md`
- `META.md` — copy-filled from `META-TEMPLATE.md` at `.agents/skills/<name>/META.md`
- `scripts/`, `references/`, or `assets/` — created only when needed; each file wired with an explicit step instruction -->
<description of output>
</steps>
<checks>
## Failure handling
<!-- One bullet per failure mode. Lean — no overlap with constraints or process.
Format: condition — action.
Example:
- Template file missing — stop, report the path searched, do not write from memory
- `write-eval` fails or is unavailable — flag, do not mark the skill complete -->
- <failure condition> — <what to do>
## Self-check
<!-- Verifiable checklist the agent runs before declaring the skill complete.
Each item must be checkable, not aspirational.
Example:
- [ ] Overlap check completed before any content was written
- [ ] Trigger description tested against all three cases — all passed before body content was written -->
- [ ] <check>
</checks>

View File

@@ -0,0 +1,92 @@
---
name: write-skill
description: Use when the user wants to author a new skill file or convert an existing placeholder to the canonical authoring standard. Triggers: "write a new skill for X", "create a SKILL.md that does Y", "build a skill to handle Z". Do NOT use when fixing or updating an existing well-formed skill (use upgrade-skill), running existing evals (use write-eval), refactoring application code, or writing documentation for non-skill artifacts.
metadata:
category: factory
model: sonnet
---
<requirements>
## Required inputs
- **Skill name** — kebab-case slug; inferred from user description if not stated explicitly, ask if ambiguous
- **Category** — from the category table in `CATEGORIES.md`; ask if unclear
- **Purpose + use cases** — what the skill does and what tasks it handles; source for the trigger description
- **For placeholder conversions:** existing SKILL.md path — read before writing
Negative trigger cases are NOT a required input. The agent proposes them based on the skill's purpose and adjacent skills found during the overlap scan. The user confirms or refines before trigger testing begins.
## Constraints
- Write two files for every skill: `SKILL.md` at `.agents/skills/<name>/SKILL.md` and `META.md` alongside it
- Frontmatter required fields: `name`, `description`, `metadata.category` — add `allowed-tools` only when the skill has a narrow, well-defined tool surface; add `model:` only when the skill's task complexity warrants a specific model tier (see SKILL-TEMPLATE.md for routing guidance)
- Keep the body under 500 lines — content that explains rather than directs belongs in sub-files, not the body
- Sub-files use three spec-defined optional directories: `scripts/` (executable code), `references/` (on-demand docs), `assets/` (templates, data files, lookup tables); additional files (e.g. `META.md`) are valid at the skill root. File references must be one level deep — no nested chains. Wire each sub-file with an explicit instruction in the step that needs it (e.g. `"See references/lookup.md for error codes"`) — without a wiring instruction the file is never loaded
- Use XML tags only when the body has three or more logical sections and exceeds 500 tokens — default to plain prose
- Test the trigger description against all three cases — explicit, implicit, negative — before writing any body content. Hard gate: a failed case means revise and retest, not proceed
- Check for overlapping skills in `.agents/skills/` before writing anything — if overlap is found, surface it and wait for direction
- For placeholder conversions: read the existing SKILL.md first and remove all stale or outdated content
</requirements>
<steps>
## Process
1. **Scan for overlap.** Check for skills with similar purpose or trigger phrases. If overlap is found, surface it and wait for explicit direction — do not continue.
2. **Grill.** Run a focused grill with the /grill-me skill to reach shared understanding of: skill name, category, purpose, and use cases. One question at a time, with a recommendation for each.
3. **Conflict check.** Spawn a sub-agent: read `docs/ai-constitution.md`, `docs/research/ai-coding-factory/ai-coding-factory-principles.md`, and `docs/notes/factory-integration-decisions.md`, then check the agreed skill purpose and design against all three. Where a factory principle is superseded by an integration decision, the decision takes precedence — do not flag it as a conflict. Return a numbered list of genuine unresolved tensions, or confirm none found. An empty list is a valid result. Hard gate: resolve any findings before proceeding.
4. **Write and test the trigger description.** Using the agreed name, category, purpose, and use cases from the grill, draft `description:`. Propose negative trigger cases based on the skill's purpose and adjacent skills — get explicit user confirmation before running tests. Test all three cases and show per-case PASS/FAIL. A failed case means revise and retest — do not proceed.
5. **Walk through each section.** For each section in `SKILL-TEMPLATE.md`: propose content, state where it comes from, present alternatives if they exist. Wait for explicit human confirmation before moving to the next section.
6. **Copy both templates.** Copy `SKILL-TEMPLATE.md` to `.agents/skills/<name>/SKILL.md`. Copy `META-TEMPLATE.md` to `.agents/skills/<name>/META.md`. Do not modify content yet — copy first, fill second.
7. **Fill both files.** Fill in the copied `SKILL.md` with confirmed section content. Fill in the copied `META.md` with version, updated date, when, source (if applicable), and references (if applicable).
8. **Invoke `write-eval`.** Do not mark the skill complete without an eval file.
9. **Run self-check.** Work through every item in the Self-check section below. Do not proceed until all items pass.
10. **Prompt for HITL.** Ask the user to open a fresh session, trigger the skill, and confirm output before committing.
## Output format
Two files produced for every skill, plus optional sub-files if the skill requires them:
- `SKILL.md` — copy-filled from `SKILL-TEMPLATE.md` at `.agents/skills/<name>/SKILL.md`
- `META.md` — copy-filled from `META-TEMPLATE.md` at `.agents/skills/<name>/META.md`
- `scripts/`, `references/`, or `assets/` — created only when needed; each file wired with an explicit step instruction
For placeholder conversions, `SKILL.md` replaces the existing file entirely — no partial edits.
</steps>
<checks>
## Failure handling
- Template file missing — stop, report the path searched, do not write from memory
- Existing SKILL.md not found for a placeholder conversion — stop, report the path searched
- `write-eval` fails or is unavailable — flag, do not mark the skill complete
## Self-check
- [ ] Overlap check completed before any content was written
- [ ] Conflict check sub-agent ran against constitution and factory principles — findings resolved before any writing began
- [ ] Trigger description tested against all three cases — all passed before body content was written
- [ ] Negative trigger cases confirmed by user before testing
- [ ] Each section confirmed explicitly by user before SKILL.md was written
- [ ] SKILL.md copy-filled from `SKILL-TEMPLATE.md` at correct path
- [ ] `META.md` copy-filled from `META-TEMPLATE.md` at correct path
- [ ] Frontmatter contains `name`, `description`, and `metadata.category`; optional `allowed-tools` and `model:` only where justified
- [ ] Body is under 500 lines
- [ ] If sub-files exist: placed in correct directory type (`scripts/`, `references/`, or `assets/`) and wired with an explicit instruction in the relevant step
- [ ] For placeholder conversions: existing files read, all stale content removed, old directory deleted if renamed
- [ ] `write-eval` invoked — eval file exists at correct path, covers trigger cases (explicit, implicit, negative) and at least one output case
</checks>