feat(kyberforge): add plugin-author and marketplace-author skills

## Why

Plugin and marketplace management had no governed authoring path. Creating or
updating a plugin required knowing the dual-manifest convention, version parity
rules, and directory skeleton by memory — nothing enforced consistency or guided
the process.

`/plugin-author` closes that gap by owning the full plugin scaffold lifecycle:
create, update, rename, and release. `/marketplace-author` handles the
marketplace-facing side: register, deregister, and update plugin entries in
`marketplace.json`.

ADR-0016 codifies the version parity convention (identical `version` in both
`plugin.json` and `.claude-plugin/plugin.json`) that `/plugin-author` now
enforces. The two plugin.json files in this repo are backfilled to comply
(keys also sorted to pass the pretty-format-json hook). CONTEXT.md gains
glossary entries for "plugin scaffold" and "version parity" so future agents
have shared vocabulary for these concepts.

## Implementation Notes

`/plugin-author` ships a `scripts/new-plugin.sh` scaffold script that generates
the directory skeleton and both manifests in one shot; the skill calls the script
rather than generating files ad hoc so the scaffold is reviewable and repeatable.

Version parity is an invariant, not a suggestion — the skill will fail loudly
on create/update if the two versions would diverge.

ADR: docs/adr/0016-plugin-version-parity.md
This commit is contained in:
2026-06-28 10:45:03 +00:00
parent 098fc7315e
commit 4d061bd199
18 changed files with 1159 additions and 13 deletions

View File

@@ -0,0 +1,40 @@
# plugin-author
Creates, updates, and releases plugin scaffolds for the holocron marketplace.
## What it does
Manages both manifests (`plugin.json` for Copilot CLI and `.claude-plugin/plugin.json` for Claude Code) in one pass. Three operations: create a new plugin scaffold with placeholder manifests and skeleton dirs; update configuration fields (shared fields updated in both manifests simultaneously); release a version with HITL gate before tagging.
Out of scope: plugin content (skills, agents, hooks, MCP servers inside those dirs) and `marketplace.json` entries.
## Before you start
Have ready: the plugin name (kebab-case) and the repo root path.
## Usage
```
/plugin-author
```
**Manual scaffold (human workflow):**
```bash
bash scripts/new-plugin.sh <plugin-name> <repo-root>
# Examples:
bash scripts/new-plugin.sh my-tools /root/ai-development
bash scripts/new-plugin.sh data-tools .
```
## Files
| File | Purpose |
|------|---------|
| `SKILL.md` | Skill instructions for agents |
| `scripts/new-plugin.sh` | Scaffolds both manifests and skeleton dirs for a new plugin |
| `references/manifest-fields.md` | All optional fields for both manifests beyond the scaffolded defaults |
| `references/sources.md` | Research provenance — sources that informed this skill |
| `scripts/README.md` | Directory meta-documentation for scripts/ |
| `references/README.md` | Directory meta-documentation for references/ |
| `tests/README.md` | Test dependency instructions and run command |

View File

@@ -0,0 +1,168 @@
---
name: plugin-author
description: >
Use when the user wants to create a new plugin scaffold ("create a plugin
for X", "new plugin called Y"), update plugin configuration ("change the
description", "add keyword", "bump version"), or release a plugin version
("release", "tag", "publish"). Manages both Claude Code
(.claude-plugin/plugin.json) and Copilot CLI (plugin.json) manifests in one
pass. Explicitly out of scope: plugin content (skills, agents, hooks, MCP
servers) and marketplace.json entries — use /marketplace-author for those.
allowed-tools: Bash Read Write Edit
metadata:
category: factory
source_keys:
- context7-websites-code-claude
- claude-code-plugins-docs
- github-cli-plugin-reference
- github-plugins-creating
- github-plugins-finding-installing
---
## Gotchas
- Both manifests must carry identical `version` values — version parity is a hard invariant (ADR-0016). Never update version in one manifest without updating the other in the same edit pass.
- `author.email` belongs in the Copilot manifest (`plugin.json`); `author.url` belongs in the CC manifest (`.claude-plugin/plugin.json`). Do not swap them.
- `claude plugin tag --push` is irreversible: it creates a git tag and pushes it to remote. Always present the HITL gate and wait for explicit confirmation before running it.
- `name` in both manifests must be kebab-case and must not use reserved prefixes: `anthropic-*`, `claude-*`, `agent-skills`, `official-claude-plugins`.
- Copilot manifest lookup order: `.plugin/plugin.json` → `plugin.json` → `.github/plugin/plugin.json` → `.claude-plugin/plugin.json`. The canonical location for the Copilot manifest in this repo is `plugin.json` at the plugin root.
- `displayName` is CC-only — do not add it to the Copilot manifest.
- `skills`, `agents`, `hooks`, `mcpServers` are Copilot-only fields — do not add them to the CC manifest.
## Route
Determine which flow before touching the filesystem. Read both manifest files if the plugin directory exists.
- **Plugin directory does not exist** → follow **CREATE flow**
- **Plugin directory exists + version/release intent** ("release", "tag", "bump", "publish", "version") → follow **RELEASE flow**
- **Plugin directory exists + field change intent** ("update description", "add keyword", "change author") → follow **UPDATE flow**
- **Ambiguous** → ask: "Did you mean to create a new plugin, update its configuration, or release a version?"
Validate runs automatically before tagging (in RELEASE flow) and can be invoked explicitly at any time.
## CREATE flow
### Prerequisites
Before touching the filesystem, confirm you have:
- [ ] Plugin name (kebab-case, e.g. `my-tools`)
- [ ] Repo root (absolute path or `.` for current directory)
If either is missing, stop and ask before proceeding.
### Step 1 — Scaffold
Run the scaffold script:
```bash
bash scripts/new-plugin.sh <name> <repo-root>
```
Examples:
```bash
bash scripts/new-plugin.sh my-tools /root/ai-development
bash scripts/new-plugin.sh data-tools .
```
The script creates under `<repo-root>/plugins/<name>/`:
- `plugin.json` — Copilot manifest with `FILL_IN_*` placeholders
- `.claude-plugin/plugin.json` — CC manifest with `FILL_IN_*` placeholders
- Empty skeleton directories: `skills/`, `agents/`, `hooks/`, `bin/`
Each file/dir is a no-op if it already exists.
### Step 2 — Fill in placeholders
Open both manifest files and replace every `FILL_IN_*` placeholder.
**Fields shared by both manifests** (must be identical in both):
- `name` — kebab-case plugin identifier (already set by script; verify it is correct)
- `description` — one or two sentences; what the plugin provides
- `version` — SemVer; defaults to `1.0.0`; must be identical in both manifests
- `author.name` — author display name
- `license` — SPDX identifier (default: `MIT`)
- `keywords` — search/discovery tags (default: `[]`)
**CC-only fields** (`.claude-plugin/plugin.json` only):
- `displayName` — human-readable name shown in plugin manager; capitalised form of `name`
- `author.url` — author URL (e.g. Gitea profile URL)
**Copilot-only fields** (`plugin.json` only):
- `author.email` — author email
- `skills`, `agents`, `hooks`, `mcpServers` — paths; defaults are already set by the script
### Step 3 — Validate
Check:
- [ ] `name` identical in both manifests, kebab-case, no reserved prefixes
- [ ] `description` identical in both manifests, non-empty
- [ ] `version` identical in both manifests (version parity — ADR-0016)
- [ ] `author.name` identical in both manifests
- [ ] `license` identical in both manifests
- [ ] No `FILL_IN_*` placeholders remain
- [ ] `displayName` present in CC manifest only
- [ ] `author.url` in CC manifest, `author.email` in Copilot manifest
## UPDATE flow
### Step 1 — Read both manifests
Read `plugins/<name>/plugin.json` and `plugins/<name>/.claude-plugin/plugin.json`. Identify the current field values.
### Step 2 — Classify each change
For every field the user wants to change:
| Change type | What to update |
|---|---|
| Shared field (`name`, `description`, `version`, `author.name`, `license`, `keywords`) | Both manifests in the same edit pass |
| CC-only (`displayName`, `author.url`) | `.claude-plugin/plugin.json` only |
| Copilot-only (`author.email`, `skills`, `agents`, `hooks`, `mcpServers`, `category`, `tags`, `extensions`) | `plugin.json` only |
Never update a shared field in one manifest without updating the other in the same pass.
### Step 3 — Announce and apply
State which fields change and which files are affected. Then apply. For `version` changes not part of a release, bump both manifests in the same edit.
### Step 4 — Validate
Re-run the validation checklist from CREATE flow Step 3 on both files.
## RELEASE flow
### Step 1 — Confirm version
If the user has not stated the new SemVer version, ask: "What version are you releasing?" Do not proceed until you have the version.
### Step 2 — Bump version in both manifests
Update `version` in both `plugin.json` and `.claude-plugin/plugin.json` in the same edit pass. Confirm they are identical after the edit.
### Step 3 — Validate
Run:
```bash
claude plugin validate plugins/<name>
```
Stop and report errors if validation fails. Do not proceed to tagging until validation passes.
### Step 4 — HITL gate
State exactly:
> "I will run `claude plugin tag --push` for plugin `<name>`, which will create git tag `<name>--v<version>` and push it to remote. This is irreversible. Confirm?"
Do not call the tool until the user explicitly confirms in the conversation.
### Step 5 — Tag and release
After explicit confirmation, run from the repo root:
```bash
claude plugin tag --push
```
Report the created tag name and confirm the push completed.

View File

@@ -0,0 +1,9 @@
# references/
## manifest-fields.md
Complete field reference for both plugin manifests. Covers all optional fields beyond the scaffolded defaults: field classification (shared / CC-only / Copilot-only), usage examples, and the version parity convention (ADR-0016). Loaded when a user asks to add a non-default field to either manifest.
## sources.md
Research provenance record for this skill. Lists the upstream research sources (claude-code-plugins and github-copilot-plugins research docs) that informed SKILL.md, the scaffold script, and the manifest-fields reference. Used by `skill-audit` to validate the provenance chain.

View File

@@ -0,0 +1,141 @@
---
topic: manifest-fields
source_keys:
- context7-websites-code-claude
- claude-code-plugins-docs
- github-cli-plugin-reference
- github-plugins-creating
- github-plugins-finding-installing
---
# Manifest Fields Reference
This document covers optional fields beyond the scaffolded defaults. Consult it when a user asks to add a non-default field to either manifest.
## Field Classification
| Field | Copilot `plugin.json` | CC `.claude-plugin/plugin.json` | Notes |
|---|---|---|---|
| `name` | Yes (shared) | Yes (shared) | Identical in both; kebab-case; max 64 chars (Copilot) |
| `description` | Yes (shared) | Yes (shared) | Identical in both; max 1024 chars (Copilot) |
| `version` | Yes (shared) | Yes (shared) | Identical in both; SemVer; version parity required (ADR-0016) |
| `author.name` | Yes (shared) | Yes (shared) | Identical in both |
| `license` | Yes (shared) | Yes (shared) | Identical in both; SPDX identifier |
| `keywords` | Yes (shared) | Yes (shared) | Identical in both; string array |
| `displayName` | No | Yes (CC-only) | Human-readable name shown in plugin manager |
| `author.url` | No | Yes (CC-only) | Author profile or homepage URL |
| `author.email` | Yes (Copilot-only) | No | Author contact email |
| `agents` | Yes (Copilot-only) | No | Path or array; default: `agents/` |
| `skills` | Yes (Copilot-only) | No | Path or array; default: `skills/` |
| `hooks` | Yes (Copilot-only) | No | Path to hooks config |
| `mcpServers` | Yes (Copilot-only) | No | Path or object for MCP server config |
| `category` | Yes (Copilot-only) | No | Marketplace category string |
| `tags` | Yes (Copilot-only) | No | Additional taxonomy tags (distinct from `keywords`) |
| `extensions` | Yes (Copilot-only) | No | Path, array, or `{ paths, exclusive: true }` to disable built-ins |
| `homepage` | Both (independent) | Both (independent) | Documentation URL; not required to be identical |
| `repository` | Both (independent) | Both (independent) | Source repo URL |
## Non-Default Optional Fields
### `homepage`
Documentation or project page URL. Shown in the plugin manager. Independent in each manifest — the two values do not need to match.
```json
// Copilot plugin.json
{ "homepage": "https://example.com/docs" }
// CC .claude-plugin/plugin.json
{ "homepage": "https://example.com/docs" }
```
### `repository`
Source repository URL. Independent in each manifest.
```json
{ "repository": "https://git.example.com/owner/repo" }
```
### `category` (Copilot-only)
Marketplace browsing category. Single string. Copilot manifest only.
```json
{ "category": "developer-tools" }
```
### `tags` (Copilot-only)
Additional taxonomy tags for Copilot marketplace browsing. Distinct from `keywords`.
```json
{ "tags": ["testing", "ci"] }
```
### `extensions` (Copilot-only)
Path to extension files, an array of paths, or an object. Use `{ "paths": [...], "exclusive": true }` to disable built-in extensions.
```json
{ "extensions": "extensions/" }
// or
{ "extensions": { "paths": ["extensions/"], "exclusive": true } }
```
### `lspServers`
Language Server Protocol configuration. Supported in both Copilot and CC manifests.
```json
{ "lspServers": ".lsp.json" }
```
### `outputStyles` (CC-only)
Path to output styles directory. Claude Code manifest only.
```json
{ "outputStyles": "styles/" }
```
### `experimental.themes` (CC-only)
Path to themes directory. Claude Code manifest only. Experimental — may change.
```json
{ "experimental": { "themes": "themes/" } }
```
### `experimental.monitors` (CC-only)
Path to `monitors.json`. Claude Code manifest only. Experimental.
```json
{ "experimental": { "monitors": "monitors.json" } }
```
### `dependencies` (CC-only)
Plugin dependencies. Each entry is a string (plugin name) or `{ "name": "<name>", "version": "<semver>" }`.
```json
{
"dependencies": [
"base-tools",
{ "name": "data-tools", "version": "^2.0.0" }
]
}
```
### `commands` (legacy, both)
Explicit list of `.md` command file paths. Deprecated in favour of `skills/`. Use `skills` instead for new plugins.
## Version Parity Convention (ADR-0016)
The `version` field must be present and identical in both manifests at all times. This is a hard invariant enforced by `/plugin-author` on every create, update, and release operation.
- If only the CC manifest had `version` before this convention was introduced, backfill the Copilot manifest immediately.
- Never change `version` in one manifest without changing it in the other in the same edit pass.
- The RELEASE flow bumps both manifests simultaneously before tagging.

View File

@@ -0,0 +1,50 @@
---
source_keys:
- context7-websites-code-claude
- claude-code-plugins-docs
- github-cli-plugin-reference
- github-plugins-creating
- github-plugins-finding-installing
---
# Sources
## context7-websites-code-claude
- **URL:** context7:/websites/code_claude
- **Research doc:** plugins/kyberforge/docs/research/docs/claude-code-plugins/sources.md
- **Description:** Official Claude Code documentation site indexed by Context7 — plugin manifest schema, marketplace JSON format, `claude plugin` CLI commands, validation, tagging
- **Contributing files:** SKILL.md, references/manifest-fields.md
- **Status:** `extracted`
## claude-code-plugins-docs
- **URL:** https://code.claude.com/docs/en/plugins
- **Research doc:** plugins/kyberforge/docs/research/docs/claude-code-plugins/sources.md
- **Description:** Official Claude Code plugin authoring guide — plugin structure, CC manifest fields (`displayName`, `author.url`, `version`, `outputStyles`, `experimental`), marketplace submission, `claude plugin tag`
- **Contributing files:** SKILL.md, references/manifest-fields.md
- **Status:** `extracted`
## github-cli-plugin-reference
- **URL:** https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-plugin-reference
- **Research doc:** plugins/kyberforge/docs/research/docs/github-copilot-plugins/sources.md
- **Description:** Full Copilot CLI plugin reference — `plugin.json` schema (all fields, types, constraints), marketplace.json schema, CLI commands, manifest lookup order
- **Contributing files:** SKILL.md, scripts/new-plugin.sh, references/manifest-fields.md
- **Status:** `extracted`
## github-plugins-creating
- **URL:** https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/plugins-creating
- **Research doc:** plugins/kyberforge/docs/research/docs/github-copilot-plugins/sources.md
- **Description:** How-to for creating Copilot CLI plugins — plugin structure, Copilot manifest fields, development lifecycle, hooks format, MCP config
- **Contributing files:** SKILL.md, scripts/new-plugin.sh
- **Status:** `extracted`
## github-plugins-finding-installing
- **URL:** https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/plugins-finding-and-installing
- **Research doc:** plugins/kyberforge/docs/research/docs/github-copilot-plugins/sources.md
- **Description:** Finding and installing Copilot CLI plugins — install spec formats, marketplace registration, `copilot plugin` CLI commands
- **Contributing files:** SKILL.md
- **Status:** `extracted`

View File

@@ -0,0 +1,11 @@
# scripts/
## new-plugin.sh
Scaffolds a new plugin directory with both manifests and empty skeleton dirs.
```
Usage: new-plugin.sh <plugin-name> <repo-root>
```
Creates `<repo-root>/plugins/<plugin-name>/` containing: `plugin.json` (Copilot manifest), `.claude-plugin/plugin.json` (CC manifest), and empty `skills/`, `agents/`, `hooks/`, `bin/` directories. Both manifest files carry `FILL_IN_*` placeholders for fields the user must supply. Each file and directory is a no-op if it already exists. Does not touch `marketplace.json`. See `--help` for full usage.

View File

@@ -0,0 +1,147 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<EOF
Usage: new-plugin.sh <plugin-name> <repo-root>
Scaffold a new plugin directory with both manifests and skeleton dirs.
Arguments:
plugin-name Kebab-case plugin identifier (e.g. my-tools, data-tools).
Must be lowercase letters, numbers, and hyphens only.
No leading, trailing, or consecutive hyphens.
repo-root Absolute or relative path to the repository root.
The plugin is created at <repo-root>/plugins/<plugin-name>/.
Created structure:
<repo-root>/plugins/<plugin-name>/
plugin.json Copilot CLI manifest (FILL_IN_* placeholders)
.claude-plugin/
plugin.json Claude Code manifest (FILL_IN_* placeholders)
skills/ Empty skeleton directory
agents/ Empty skeleton directory
hooks/ Empty skeleton directory
bin/ Empty skeleton directory
Each file and directory is a no-op if it already exists.
Does NOT touch marketplace.json.
Exit codes:
0 Files created or already existed (no-op)
1 Invalid arguments or missing root
EOF
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
if [[ $# -lt 2 ]]; then
echo "Error: plugin-name and repo-root are required." >&2
echo "" >&2
usage >&2
exit 1
fi
PLUGIN_NAME="$1"
REPO_ROOT="$2"
# Validate plugin name format
if ! echo "$PLUGIN_NAME" | grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$'; then
echo "Error: plugin-name must use lowercase letters, numbers, and hyphens only." >&2
echo " No leading, trailing, or consecutive hyphens." >&2
echo " Received: '$PLUGIN_NAME'" >&2
exit 1
fi
# Expand tilde
REPO_ROOT="${REPO_ROOT/#\~/$HOME}"
# Resolve to absolute path
REPO_ROOT="$(cd "$REPO_ROOT" 2>/dev/null && pwd)" || {
echo "Error: repo-root directory '$2' does not exist." >&2
exit 1
}
PLUGIN_DIR="$REPO_ROOT/plugins/$PLUGIN_NAME"
CC_DIR="$PLUGIN_DIR/.claude-plugin"
COPILOT_MANIFEST="$PLUGIN_DIR/plugin.json"
CC_MANIFEST="$CC_DIR/plugin.json"
# Create directory skeleton
created_any=false
create_dir_if_missing() {
local dir="$1"
if [[ -d "$dir" ]]; then
echo "Skipping directory '$dir' — already exists." >&2
else
mkdir -p "$dir"
echo "Created directory: $dir" >&2
created_any=true
fi
}
create_dir_if_missing "$PLUGIN_DIR"
create_dir_if_missing "$CC_DIR"
create_dir_if_missing "$PLUGIN_DIR/skills"
create_dir_if_missing "$PLUGIN_DIR/agents"
create_dir_if_missing "$PLUGIN_DIR/hooks"
create_dir_if_missing "$PLUGIN_DIR/bin"
# Create Copilot manifest (plugin.json)
if [[ -f "$COPILOT_MANIFEST" ]]; then
echo "Skipping '$COPILOT_MANIFEST' — already exists." >&2
else
cat > "$COPILOT_MANIFEST" <<COPILOT_JSON
{
"name": "$PLUGIN_NAME",
"description": "FILL_IN_DESCRIPTION",
"version": "1.0.0",
"author": { "name": "FILL_IN_AUTHOR_NAME", "email": "FILL_IN_AUTHOR_EMAIL" },
"license": "MIT",
"keywords": [],
"agents": "agents/",
"skills": ["skills/"],
"hooks": "hooks.json",
"mcpServers": ".mcp.json"
}
COPILOT_JSON
echo "Created: $COPILOT_MANIFEST" >&2
created_any=true
fi
# Create Claude Code manifest (.claude-plugin/plugin.json)
if [[ -f "$CC_MANIFEST" ]]; then
echo "Skipping '$CC_MANIFEST' — already exists." >&2
else
cat > "$CC_MANIFEST" <<CC_JSON
{
"name": "$PLUGIN_NAME",
"displayName": "FILL_IN_DISPLAY_NAME",
"description": "FILL_IN_DESCRIPTION",
"version": "1.0.0",
"author": { "name": "FILL_IN_AUTHOR_NAME", "url": "FILL_IN_AUTHOR_URL" },
"license": "MIT",
"keywords": []
}
CC_JSON
echo "Created: $CC_MANIFEST" >&2
created_any=true
fi
if [[ "$created_any" == false ]]; then
echo "All files already exist — nothing to do." >&2
else
echo "" >&2
echo "Plugin: $PLUGIN_NAME" >&2
echo "Location: $PLUGIN_DIR" >&2
echo "" >&2
echo "Next steps:" >&2
echo " 1. Fill in $COPILOT_MANIFEST — replace all FILL_IN_* placeholders" >&2
echo " 2. Fill in $CC_MANIFEST — replace all FILL_IN_* placeholders" >&2
echo " 3. Verify version is identical in both manifests (version parity — ADR-0016)" >&2
echo " 4. Add plugin content: skills in skills/, agents in agents/, etc." >&2
fi

View File

@@ -0,0 +1,33 @@
# tests/
Test files for scripts bundled with this skill.
## When to add tests
Add tests here when the skill has scripts in `scripts/` that are complex enough
to break silently — validators, parsers, generators, anything with branching
logic or edge cases. Test infrastructure (`.bats`, `*_test.*`, `test_*.sh`)
belongs here, not in `scripts/`.
## Dependencies
Tests require [bats-support](https://github.com/bats-core/bats-support) and
[bats-assert](https://github.com/bats-core/bats-assert). The test files load
helpers from the repo root's `tests/test_helper/`.
From the repo root:
```bash
git clone https://github.com/bats-core/bats-support tests/test_helper/bats-support
git clone https://github.com/bats-core/bats-assert tests/test_helper/bats-assert
```
Run all tests for this skill (from the repo root):
```bash
bats plugins/kyberforge/skills/plugin-author/tests/
```
## If no tests are needed
Delete this README and the `tests/` directory entirely.