feat: add marketplace-architect skill with Bash scripts and evals
Adds a new skill for creating, managing, and adopting plugins across Claude Code and GitHub Copilot CLI marketplaces. Includes three Bash scripts (inventory, gen_manifests, validate), three reference docs (cross-compat, claude-code, copilot-cli), a test harness with 18 passing tests, and an eval.yaml. Also adds the `marketplace` category to CATEGORIES.md and commits the plugin marketplace architecture research doc that informed the skill design. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
62
.agents/evals/marketplace/marketplace-architect/eval.yaml
Normal file
62
.agents/evals/marketplace/marketplace-architect/eval.yaml
Normal file
@@ -0,0 +1,62 @@
|
||||
skill_name: marketplace-architect
|
||||
|
||||
trigger_tests:
|
||||
- id: explicit-trigger-refactor
|
||||
name: "Explicit trigger — refactor repo into marketplace"
|
||||
query: "turn this repo into a plugin marketplace"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-trigger-team-sharing
|
||||
name: "Implicit trigger — team distribution without saying marketplace"
|
||||
query: "how do I share my skills with my team so they can install them"
|
||||
should_trigger: true
|
||||
|
||||
- id: negative-trigger-new-skill
|
||||
name: "Negative trigger — new skill authoring belongs to write-skill"
|
||||
query: "write me a new skill for code review"
|
||||
should_trigger: false
|
||||
|
||||
output_tests:
|
||||
- id: deterministic-cross-compat-loaded
|
||||
name: "Deterministic — cross-compat reference loaded before tool-specific recommendations"
|
||||
type: deterministic
|
||||
prompt: "audit this repo and recommend how to convert it into a plugin marketplace for both Claude Code and Copilot CLI"
|
||||
expected_output: >
|
||||
The skill reads references/cross-compat.md early in its response and demonstrates
|
||||
awareness of the Claude Code / Copilot CLI format differences — specifically that
|
||||
manifest paths differ (.claude-plugin/ vs .github/plugin/), agent files differ
|
||||
(.md vs .agent.md), and hooks placement differs — before recommending any layout.
|
||||
assertions:
|
||||
- "Output references or acknowledges the divergence between Claude Code and Copilot CLI manifest paths before proposing a directory layout"
|
||||
- "Output does not recommend a single shared plugin.json location without noting the two-location requirement (.claude-plugin/ and plugin root)"
|
||||
- "Output does not write or propose writing any files before presenting a plan"
|
||||
|
||||
- id: deterministic-gate-a-blocks-writes
|
||||
name: "Deterministic — Gate A plan presented and approval requested before any writes"
|
||||
type: deterministic
|
||||
prompt: "help me migrate my skills and agents into a plugin marketplace layout"
|
||||
expected_output: >
|
||||
The skill produces a migration plan (checklist of old path → new path, one row per
|
||||
file) and explicitly asks the user for approval before proceeding to generate any
|
||||
manifest files. No plugin.json or marketplace.json is written or shown as written output.
|
||||
assertions:
|
||||
- "Output contains a migration plan or checklist with at least one file-move entry in the form 'old path → new path'"
|
||||
- "Output explicitly asks the user to approve or confirm the plan before proceeding"
|
||||
- "Output does not contain a complete plugin.json or marketplace.json file unless the user has already said 'yes' or equivalent in the prompt"
|
||||
|
||||
- id: llm-rubric-adopt-plugin
|
||||
name: "LLM-rubric — adopt external plugin covers all required checks without premature writes"
|
||||
type: llm-rubric
|
||||
prompt: "I want to add this plugin to my marketplace: https://github.com/example/my-tool-plugin"
|
||||
expected_output: >
|
||||
The skill fetches and inspects the plugin source, classifies what assets it contains
|
||||
(skills, agents, hooks, MCP servers), checks whether any existing plugin in the
|
||||
marketplace has a naming conflict, evaluates cross-tool compatibility against the
|
||||
divergence table, and summarises what would be added to marketplace.json — all without
|
||||
writing any files and without proceeding past Gate A without explicit user approval.
|
||||
assertions:
|
||||
- "Response identifies what asset types the external plugin contains (skills, agents, hooks, and/or MCP servers)"
|
||||
- "Response checks or asks about naming conflicts with existing plugins in the marketplace"
|
||||
- "Response notes at least one Claude Code vs Copilot CLI compatibility consideration for the adopted plugin"
|
||||
- "Response summarises the proposed change to marketplace.json without writing the file"
|
||||
- "Response asks for user approval (Gate A) before any file is created or modified"
|
||||
21
.agents/skills/marketplace-architect/META.md
Normal file
21
.agents/skills/marketplace-architect/META.md
Normal 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
|
||||
```
|
||||
101
.agents/skills/marketplace-architect/SKILL.md
Normal file
101
.agents/skills/marketplace-architect/SKILL.md
Normal 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>
|
||||
169
.agents/skills/marketplace-architect/references/claude-code.md
Normal file
169
.agents/skills/marketplace-architect/references/claude-code.md
Normal 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.
|
||||
143
.agents/skills/marketplace-architect/references/copilot-cli.md
Normal file
143
.agents/skills/marketplace-architect/references/copilot-cli.md
Normal 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) |
|
||||
@@ -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.
|
||||
194
.agents/skills/marketplace-architect/scripts/gen_manifests.sh
Executable file
194
.agents/skills/marketplace-architect/scripts/gen_manifests.sh
Executable 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
|
||||
121
.agents/skills/marketplace-architect/scripts/inventory.sh
Executable file
121
.agents/skills/marketplace-architect/scripts/inventory.sh
Executable 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" ]]; 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
|
||||
264
.agents/skills/marketplace-architect/scripts/validate.sh
Executable file
264
.agents/skills/marketplace-architect/scripts/validate.sh
Executable file
@@ -0,0 +1,264 @@
|
||||
#!/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
|
||||
if [[ -f "$claude_manifest" && -f "$root_manifest" ]]; then
|
||||
if ! diff <(jq -S . "$claude_manifest") <(jq -S . "$root_manifest") >/dev/null 2>&1; then
|
||||
warn "$pd: .claude-plugin/plugin.json and plugin.json differ — keep them in sync"
|
||||
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 ─────────────────────────────────────────────────
|
||||
|
||||
MARKETPLACE_JSON=""
|
||||
for mp in "$ROOT/.claude-plugin/marketplace.json" "$ROOT/.github/plugin/marketplace.json"; do
|
||||
if [[ -f "$mp" ]]; then
|
||||
MARKETPLACE_JSON="$mp"
|
||||
validate_marketplace_json "$mp"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$MARKETPLACE_JSON" ]]; then
|
||||
warn "No marketplace.json found. Expected at .claude-plugin/marketplace.json"
|
||||
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
|
||||
194
.agents/skills/marketplace-architect/tests/test_scripts.sh
Executable file
194
.agents/skills/marketplace-architect/tests/test_scripts.sh
Executable file
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPTS_DIR="$(cd "$(dirname "$0")/../scripts" && pwd)"
|
||||
PASS=0; FAIL=0
|
||||
# Use += to avoid ((var++)) returning 0 exit code when var was 0 under set -e
|
||||
|
||||
# ── helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
tmpdir() { mktemp -d; }
|
||||
|
||||
assert_contains() {
|
||||
local label="$1" expected="$2" actual="$3"
|
||||
if echo "$actual" | grep -qF "$expected"; then
|
||||
echo " PASS: $label"
|
||||
PASS=$((PASS+1))
|
||||
else
|
||||
echo " FAIL: $label"
|
||||
echo " expected to contain: $expected"
|
||||
echo " got: $(echo "$actual" | head -5)"
|
||||
FAIL=$((FAIL+1))
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local label="$1" unexpected="$2" actual="$3"
|
||||
if echo "$actual" | grep -qF "$unexpected"; then
|
||||
echo " FAIL: $label"
|
||||
echo " expected NOT to contain: $unexpected"
|
||||
FAIL=$((FAIL+1))
|
||||
else
|
||||
echo " PASS: $label"
|
||||
PASS=$((PASS+1))
|
||||
fi
|
||||
}
|
||||
|
||||
assert_exit() {
|
||||
local label="$1" expected="$2" actual="$3"
|
||||
if [[ "$actual" -eq "$expected" ]]; then
|
||||
echo " PASS: $label"
|
||||
PASS=$((PASS+1))
|
||||
else
|
||||
echo " FAIL: $label"
|
||||
echo " expected exit $expected, got $actual"
|
||||
FAIL=$((FAIL+1))
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_exists() {
|
||||
local label="$1" path="$2"
|
||||
if [[ -f "$path" ]]; then
|
||||
echo " PASS: $label"
|
||||
PASS=$((PASS+1))
|
||||
else
|
||||
echo " FAIL: $label"
|
||||
echo " file not found: $path"
|
||||
FAIL=$((FAIL+1))
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_absent() {
|
||||
local label="$1" path="$2"
|
||||
if [[ ! -e "$path" ]]; then
|
||||
echo " PASS: $label"
|
||||
PASS=$((PASS+1))
|
||||
else
|
||||
echo " FAIL: $label"
|
||||
echo " file should not exist: $path"
|
||||
FAIL=$((FAIL+1))
|
||||
fi
|
||||
}
|
||||
|
||||
# ── inventory.sh tests ───────────────────────────────────────────────────────
|
||||
|
||||
echo "=== inventory.sh ==="
|
||||
|
||||
# 1. SKILL.md classified as skill
|
||||
t=$(tmpdir)
|
||||
mkdir -p "$t/skills/my-skill"
|
||||
echo "---" > "$t/skills/my-skill/SKILL.md"
|
||||
out=$("$SCRIPTS_DIR/inventory.sh" "$t")
|
||||
assert_contains "SKILL.md classified as skill" "skill" "$out"
|
||||
rm -rf "$t"
|
||||
|
||||
# 2. agents/foo.agent.md classified as agent-copilot
|
||||
t=$(tmpdir)
|
||||
mkdir -p "$t/agents"
|
||||
touch "$t/agents/my-agent.agent.md"
|
||||
out=$("$SCRIPTS_DIR/inventory.sh" "$t")
|
||||
assert_contains "agents/*.agent.md classified as agent-copilot" "agent-copilot" "$out"
|
||||
rm -rf "$t"
|
||||
|
||||
# 3. ../ in file content produces cross-reference warning
|
||||
t=$(tmpdir)
|
||||
mkdir -p "$t/skills/my-skill"
|
||||
printf -- '---\ndescription: test\n---\nSee ../shared/file.md\n' > "$t/skills/my-skill/SKILL.md"
|
||||
out=$("$SCRIPTS_DIR/inventory.sh" "$t")
|
||||
assert_contains "../ cross-reference warning emitted" "Cross-reference" "$out"
|
||||
assert_contains "../ path shown in warning" "../shared/file.md" "$out"
|
||||
rm -rf "$t"
|
||||
|
||||
# ── gen_manifests.sh tests ───────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "=== gen_manifests.sh ==="
|
||||
|
||||
# 4. Without --write, no files are created
|
||||
t=$(tmpdir)
|
||||
mkdir -p "$t/plugins/my-plugin/skills/hello"
|
||||
echo "---" > "$t/plugins/my-plugin/skills/hello/SKILL.md"
|
||||
"$SCRIPTS_DIR/gen_manifests.sh" "$t" --marketplace-name my-marketplace >/dev/null
|
||||
assert_file_absent "dry run: .claude-plugin/marketplace.json not written" "$t/.claude-plugin/marketplace.json"
|
||||
assert_file_absent "dry run: plugin.json not written" "$t/plugins/my-plugin/plugin.json"
|
||||
rm -rf "$t"
|
||||
|
||||
# 5. With --write, creates plugin.json in both locations
|
||||
t=$(tmpdir)
|
||||
mkdir -p "$t/plugins/my-plugin/skills/hello"
|
||||
echo "---" > "$t/plugins/my-plugin/skills/hello/SKILL.md"
|
||||
"$SCRIPTS_DIR/gen_manifests.sh" "$t" --marketplace-name my-marketplace --write >/dev/null
|
||||
assert_file_exists "--write: plugin root plugin.json created" "$t/plugins/my-plugin/plugin.json"
|
||||
assert_file_exists "--write: .claude-plugin/plugin.json created" "$t/plugins/my-plugin/.claude-plugin/plugin.json"
|
||||
rm -rf "$t"
|
||||
|
||||
# 6. Reserved name exits 1
|
||||
t=$(tmpdir)
|
||||
mkdir -p "$t/plugins/my-plugin"
|
||||
set +e
|
||||
out=$("$SCRIPTS_DIR/gen_manifests.sh" "$t" --marketplace-name claude-plugins-official 2>&1)
|
||||
code=$?
|
||||
set -e
|
||||
assert_exit "reserved name: exit 1" 1 "$code"
|
||||
assert_contains "reserved name: error message" "reserved" "$out"
|
||||
rm -rf "$t"
|
||||
|
||||
# ── validate.sh tests ────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "=== validate.sh ==="
|
||||
|
||||
# 7. Invalid JSON exits 1
|
||||
t=$(tmpdir)
|
||||
mkdir -p "$t/.claude-plugin"
|
||||
echo "not json" > "$t/.claude-plugin/marketplace.json"
|
||||
set +e
|
||||
out=$("$SCRIPTS_DIR/validate.sh" "$t" 2>&1)
|
||||
code=$?
|
||||
set -e
|
||||
assert_exit "invalid JSON: exit 1" 1 "$code"
|
||||
assert_contains "invalid JSON: error message" "ERROR" "$out"
|
||||
rm -rf "$t"
|
||||
|
||||
# 8. Reserved marketplace name exits 1
|
||||
t=$(tmpdir)
|
||||
mkdir -p "$t/.claude-plugin"
|
||||
printf '{"name":"claude-plugins-official","plugins":[]}\n' > "$t/.claude-plugin/marketplace.json"
|
||||
set +e
|
||||
out=$("$SCRIPTS_DIR/validate.sh" "$t" 2>&1)
|
||||
code=$?
|
||||
set -e
|
||||
assert_exit "reserved marketplace name: exit 1" 1 "$code"
|
||||
assert_contains "reserved marketplace name: error message" "reserved" "$out"
|
||||
rm -rf "$t"
|
||||
|
||||
# 9. ../ in plugin file exits 1
|
||||
t=$(tmpdir)
|
||||
mkdir -p "$t/.claude-plugin" "$t/plugins/my-plugin/skills/hello"
|
||||
printf '{"name":"my-marketplace","plugins":[{"name":"my-plugin","source":"./plugins/my-plugin"}]}\n' \
|
||||
> "$t/.claude-plugin/marketplace.json"
|
||||
printf -- '---\ndescription: test\n---\nSee ../shared.md\n' \
|
||||
> "$t/plugins/my-plugin/skills/hello/SKILL.md"
|
||||
set +e
|
||||
out=$("$SCRIPTS_DIR/validate.sh" "$t" 2>&1)
|
||||
code=$?
|
||||
set -e
|
||||
assert_exit "..// in plugin: exit 1" 1 "$code"
|
||||
assert_contains "..// in plugin: error message" "../" "$out"
|
||||
rm -rf "$t"
|
||||
|
||||
# 10. No marketplace.json warns but exits 0
|
||||
t=$(tmpdir)
|
||||
set +e
|
||||
out=$("$SCRIPTS_DIR/validate.sh" "$t" 2>&1)
|
||||
code=$?
|
||||
set -e
|
||||
assert_exit "no marketplace.json: exit 0" 0 "$code"
|
||||
assert_contains "no marketplace.json: warning emitted" "WARN" "$out"
|
||||
rm -rf "$t"
|
||||
|
||||
# ── summary ──────────────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
[[ $FAIL -eq 0 ]]
|
||||
@@ -12,4 +12,5 @@
|
||||
| `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 |
|
||||
|
||||
334
docs/research/plugin-marketplace-architecture.md
Normal file
334
docs/research/plugin-marketplace-architecture.md
Normal file
@@ -0,0 +1,334 @@
|
||||
# Plugin Marketplace Architecture
|
||||
|
||||
Reference for refactoring this repo of skills/agents/hooks/prompts into a single Git-based
|
||||
plugin marketplace installable by **Claude Code** and **GitHub Copilot CLI**, and for building
|
||||
the `marketplace-architect` skill that automates the migration.
|
||||
|
||||
> **Provenance & staleness:** Verified against the official Claude Code plugin docs
|
||||
> (`code.claude.com/docs`) and GitHub Copilot CLI plugin docs (`docs.github.com`) as of
|
||||
> **June 2026**. Both ecosystems are moving fast; re-verify the divergence table before a
|
||||
> big migration. One item below (Copilot reading `.claude-plugin/plugin.json` per-plugin) is
|
||||
> **explicitly unverified** — see the flagged note. Don't treat that part as settled.
|
||||
|
||||
---
|
||||
|
||||
## 1. Core model (this part is correct and stable)
|
||||
|
||||
- The **Git repository is the marketplace.** No backend, registry API, database, SaaS, or MCP
|
||||
server is required. A marketplace is just a manifest file that lists plugins and where to find them.
|
||||
- A **plugin is the deployable unit.** Each plugin bundles one or more of: skills, agents, hooks,
|
||||
prompts/commands (flat `.md`), MCP servers, and — Claude Code only — output styles, LSP servers,
|
||||
background monitors, a `bin/` on PATH, and default `settings.json`. "Workflows" from the handoff
|
||||
aren't a distinct file type; express them as a skill that orchestrates steps, or a command.
|
||||
- **Organize by user outcome, not file type.** `startup-cto/` and `security-reviewer/`, not
|
||||
`all-skills/` and `all-agents/`.
|
||||
- **Aim for ~10–20 opinionated plugins**, not 50 tiny ones. (This is a usability judgment, not a
|
||||
hard rule from either vendor — but it's sound. Many skills can live inside one plugin.) The
|
||||
handoff's suggested set, as a starting shape: `startup-cto`, `system-architect`,
|
||||
`security-reviewer`, `product-manager`, `growth-marketer`, `developer-relations`,
|
||||
`technical-writer`, `research-analyst`.
|
||||
|
||||
Everything below is where the original handoff was either wrong or incomplete.
|
||||
|
||||
---
|
||||
|
||||
## 2. The two tools are convergent, NOT identical
|
||||
|
||||
This is the single most important correction. The handoff assumed "write once, run both."
|
||||
In reality the formats overlap heavily but diverge in specific, breaking ways. **Skills are the
|
||||
portable core; manifests and agents are where they split.**
|
||||
|
||||
**Recommended stance:** make **Claude Code the source of truth** (it's the stricter, more
|
||||
fully-specified format) and treat "loads in Copilot CLI" as a **tested checklist item per plugin**,
|
||||
not an assumption. Encode the Copilot deltas (manifest location, `.agent.md` naming) explicitly in
|
||||
the architect skill rather than pretending the two are the same. This is more honest than "write
|
||||
once, run both" and stops surprises at install time.
|
||||
|
||||
### Divergence table (ground truth)
|
||||
|
||||
| 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 also `.github/plugin/`. |
|
||||
| Plugin manifest path | `.claude-plugin/plugin.json` (required; **only** plugin.json goes in this dir) | `plugin.json` at **plugin root** | ⚠️ See flagged note — may need it in **both** locations |
|
||||
| Skills | `skills/<name>/SKILL.md` | `skills/<name>/SKILL.md` | ✅ Identical — lean on these |
|
||||
| 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 | Claude validator + custom 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>` | from a registered marketplace by plugin name; **`@marketplace` suffix not confirmed** — `update`/`uninstall` take a bare `<name>`, so don't assume Claude's `@marketplace` form | ⚠️ Verify the Copilot install string before documenting it |
|
||||
| Local install (dev) | `claude --plugin-dir ./plugin` | `copilot plugin install ./plugin` | Tool-specific |
|
||||
|
||||
> ⚠️ **FLAGGED / UNVERIFIED — test this by hand before committing to a layout.**
|
||||
> Copilot's docs explicitly confirm it falls back to reading the **marketplace** manifest from
|
||||
> `.claude-plugin/`. They do **not** confirm the same fallback for an individual plugin's
|
||||
> `plugin.json`; the Copilot docs only show `plugin.json` at the plugin root. Claude *requires*
|
||||
> it in `.claude-plugin/`. Until you verify, the pragmatic move is to **ship `plugin.json` in
|
||||
> both** `plugin-name/plugin.json` and `plugin-name/.claude-plugin/plugin.json` (identical
|
||||
> content), then drop whichever proves redundant. The architect skill should generate both and
|
||||
> note the duplication.
|
||||
|
||||
### `@<marketplace-name>` resolves to the manifest `name`, not the repo
|
||||
|
||||
`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 know they're separate things.
|
||||
|
||||
---
|
||||
|
||||
## 3. Canonical repo layout (cross-compatible)
|
||||
|
||||
```text
|
||||
repo-root/
|
||||
├── .claude-plugin/
|
||||
│ └── marketplace.json # both tools read here
|
||||
├── .github/plugin/
|
||||
│ └── marketplace.json # OPTIONAL: Copilot's canonical path (mirror of above)
|
||||
├── plugins/
|
||||
│ └── startup-cto/
|
||||
│ ├── plugin.json # Copilot root manifest ┐ ship both until
|
||||
│ ├── .claude-plugin/ # │ the §2 note 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 you ship native agents)
|
||||
│ ├── hooks/hooks.json # Claude
|
||||
│ ├── hooks.json # Copilot (if hooks used)
|
||||
│ ├── docs/
|
||||
│ └── README.md
|
||||
└── README.md
|
||||
```
|
||||
|
||||
If maintaining two manifest copies is annoying, generate the mirrors from one source in CI
|
||||
(see §7) rather than hand-editing both.
|
||||
|
||||
### Manifest shapes
|
||||
|
||||
`marketplace.json` (root):
|
||||
|
||||
```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": "..." },
|
||||
{ "name": "system-architect", "source": "./plugins/system-architect", "description": "..." }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`plugin.json` (keep minimal; add fields only when needed):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "startup-cto",
|
||||
"version": "1.0.0",
|
||||
"description": "Startup technical leadership toolkit",
|
||||
"author": { "name": "Your Name" }
|
||||
}
|
||||
```
|
||||
|
||||
Plugin names must be **kebab-case** (lowercase, digits, hyphens). Claude.ai's marketplace sync
|
||||
rejects anything else even though the local CLI may tolerate it.
|
||||
|
||||
---
|
||||
|
||||
## 4. Gotchas the original handoff omitted
|
||||
|
||||
These will cause real breakage during refactor. The architect skill must check for them.
|
||||
|
||||
1. **Plugins are copied to a cache on install.** A plugin **cannot** reference files outside its
|
||||
own directory (e.g. `../shared-utils`) — those files aren't copied. If your current repo shares
|
||||
helper files across skills/agents, that sharing breaks. Fix by **duplicating** the shared file
|
||||
into each plugin or using **symlinks**. Audit for cross-references before moving anything.
|
||||
|
||||
2. **Version-pinning footgun.** If `plugin.json` sets `"version"` and you don't bump it on a new
|
||||
release, existing users get **no update** — the cached copy is kept. Either bump every release,
|
||||
or **omit `version`** so the git commit SHA is used (every commit = new version). Don't set
|
||||
`version` in both `plugin.json` and the marketplace entry; the `plugin.json` value wins silently.
|
||||
|
||||
3. **Reserved marketplace names.** Claude blocks a set of names (`anthropic-*`, `claude-*`,
|
||||
`agent-skills`, and impersonators like `official-claude-plugins`). Validate against these.
|
||||
|
||||
4. **`commands/` ≠ `skills/` in Claude.** A flat `foo.md` is a legacy *command*; a
|
||||
`foo/SKILL.md` directory is a *skill*. Promote flat command files to skill directories during
|
||||
migration — don't treat them as interchangeable.
|
||||
|
||||
5. **Use `${CLAUDE_PLUGIN_ROOT}`** in Claude hook/MCP configs to reference in-plugin files, since
|
||||
the plugin runs from a cache path, not its repo location.
|
||||
|
||||
6. **Strict mode (Claude).** A marketplace plugin entry defaults to `strict: true` — `plugin.json`
|
||||
is authoritative and the entry can only supplement it. Set `strict: false` to make the
|
||||
marketplace entry the *entire* definition (it then declares `skills`/`agents`/`hooks`/`mcpServers`
|
||||
path arrays itself, and the plugin needs no `plugin.json`). Useful when the architect curates a
|
||||
plugin's exposed components differently from how the files are laid out. Don't mix the two — a
|
||||
`strict:false` entry plus a component-declaring `plugin.json` is a conflict and fails to load.
|
||||
|
||||
7. **Plugin sources beyond relative paths (Claude).** This repo uses `./plugins/x` relative sources
|
||||
(simplest for a monorepo). If you later split a plugin into its own repo, the `source` field also
|
||||
supports `github` (`owner/repo` + `ref`/`sha`), `git-subdir` (sparse clone of a monorepo path),
|
||||
`url` (any git host), and `npm` (published package). Copilot's docs only *show* relative-path
|
||||
sources — other source types aren't documented there, so don't rely on them cross-tool. Stay on
|
||||
relative paths unless you have a reason not to.
|
||||
|
||||
8. **Copilot declares component paths in `plugin.json` (Claude does it differently).** Copilot's
|
||||
`plugin.json` can carry `"skills": "skills/"`, `"agents": "agents/"`, `"hooks": "hooks.json"`,
|
||||
`"mcpServers": ".mcp.json"` fields that tell it where components live. Claude instead defaults to
|
||||
the standard dirs and only takes path overrides via the *marketplace entry* (see strict mode,
|
||||
#6). So the same `plugin.json` may need these path fields for Copilot but not for Claude — another
|
||||
reason the architect should generate per-tool manifests rather than one shared file.
|
||||
|
||||
---
|
||||
|
||||
## 5. The `marketplace-architect` skill spec
|
||||
|
||||
Build this skill **on the corrected spec above** — not on the original handoff, which would bake
|
||||
in the format errors. It's a Claude Code skill (`skills/marketplace-architect/SKILL.md`) following
|
||||
standard skill conventions: a tight SKILL.md body (<500 lines) plus bundled `references/` and
|
||||
`scripts/` loaded progressively.
|
||||
|
||||
### Frontmatter (description is the trigger — make it pushy)
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: marketplace-architect
|
||||
description: >
|
||||
Audits a repository of Claude Code / Copilot CLI skills, agents, hooks, and prompts and
|
||||
refactors it into an installable plugin marketplace. Use this whenever the user wants to
|
||||
organize loose skills/agents into plugins, define plugin boundaries, generate plugin.json
|
||||
or marketplace.json manifests, plan a migration to a plugin marketplace, validate plugin
|
||||
naming or detect duplicate capabilities, or set up cross-tool (Claude Code + GitHub Copilot
|
||||
CLI) distribution — even if they don't say the word "marketplace".
|
||||
---
|
||||
```
|
||||
|
||||
### Responsibilities (from the handoff, refined)
|
||||
|
||||
1. **Audit** — walk the repo; classify every asset as skill / command / agent / hook / prompt / MCP.
|
||||
2. **Detect cross-references** — flag any `../` or shared-file dependencies that break under caching (§4.1).
|
||||
3. **Recommend plugin boundaries** — group by outcome; warn on 50-tiny-plugins sprawl.
|
||||
4. **Prevent duplicate capabilities** — diff skill descriptions/agents for overlap before splitting.
|
||||
5. **Generate manifests** — emit `plugin.json` (both locations per §2 note) and `marketplace.json`
|
||||
(`.claude-plugin/`, optionally mirror to `.github/plugin/`).
|
||||
6. **Validate naming** — kebab-case, reserved names, unique plugin names, `@name` ↔ manifest `name`.
|
||||
7. **Produce a migration plan** — concrete file-move list (old path → new path) as a checklist.
|
||||
8. **Emit cross-tool deltas** — for each plugin, note what's needed for Copilot (`.agent.md`,
|
||||
root `plugin.json`) vs Claude.
|
||||
9. **Generate release notes + install docs** — per-plugin README with both `claude` and `copilot`
|
||||
install commands.
|
||||
|
||||
### Suggested bundled structure
|
||||
|
||||
```text
|
||||
skills/marketplace-architect/
|
||||
├── SKILL.md
|
||||
├── references/
|
||||
│ ├── claude-code.md # Claude paths, validate, version rules, reserved names
|
||||
│ ├── copilot-cli.md # Copilot paths, .agent.md, marketplace fallback
|
||||
│ └── cross-compat.md # the §2 divergence table — the heart of the skill
|
||||
└── scripts/
|
||||
├── inventory.py # scan repo → classify assets → emit a table
|
||||
├── gen_manifests.py # write plugin.json + marketplace.json (both layouts)
|
||||
└── validate.py # wraps `claude plugin validate .` + JSON/naming checks
|
||||
```
|
||||
|
||||
Put the §2 divergence table verbatim into `references/cross-compat.md` — that's the knowledge the
|
||||
skill exists to apply. Keep SKILL.md to the workflow (audit → boundaries → generate → validate)
|
||||
and point it at the reference files.
|
||||
|
||||
### Authoring notes
|
||||
|
||||
- Use **imperative** instructions ("Scan the repo", "Emit the manifest").
|
||||
- Explain *why* a rule matters (e.g. the cache constraint) rather than bare MUSTs — the model
|
||||
applies judgment better with rationale.
|
||||
- After drafting, run 2–3 realistic test prompts (e.g. "turn this repo into a marketplace",
|
||||
"which of these skills belong together?") and iterate.
|
||||
|
||||
---
|
||||
|
||||
## 6. Refactor playbook (corrected phases)
|
||||
|
||||
Run these *with* the architect skill once it exists; it automates 1–5.
|
||||
|
||||
1. **Inventory.** Classify every asset (skill/command/agent/hook/prompt/MCP). Record current path.
|
||||
2. **Detect breakage.** Find cross-references and shared files (§4.1). Decide duplicate vs symlink.
|
||||
3. **Draw boundaries.** Group by outcome into ~10–20 plugins. De-dupe overlapping capabilities.
|
||||
4. **Move files.**
|
||||
- `skills/react.md` (flat command) → `plugins/system-architect/skills/react/SKILL.md`
|
||||
- `agents/startup-founder.md` → `plugins/startup-cto/agents/startup-founder.md`
|
||||
(+ `startup-founder.agent.md` if shipping Copilot-native agents)
|
||||
5. **Generate manifests.** Per-plugin `plugin.json` (both locations); root `marketplace.json`.
|
||||
6. **Validate.** `claude plugin validate .` — fix every warning. Then test-install in both tools:
|
||||
- `claude --plugin-dir ./plugins/<x>` then `/plugin install <x>@<marketplace>`
|
||||
- `copilot plugin install ./plugins/<x>` then `copilot plugin list` / `/skills list` / `/agent`
|
||||
7. **Document.** Per-plugin README with both install commands; root README listing all plugins.
|
||||
|
||||
---
|
||||
|
||||
## 7. Future / roadmap (preserved from the handoff)
|
||||
|
||||
Three post-migration features the original handoff called for. Not needed for the first cut, but
|
||||
recorded here so the intent isn't lost.
|
||||
|
||||
### Marketplace website
|
||||
|
||||
Generate a docs site **directly from `marketplace.json`** — no separate content source. Iterate
|
||||
over the `plugins` array to produce a browsable catalog (one page per plugin from its
|
||||
`description`/`README.md`), publishable to something like `marketplace.example.com` via GitHub
|
||||
Pages. Because the manifest is the single source of truth, the site never drifts from what's
|
||||
installable.
|
||||
|
||||
### Plugin templates
|
||||
|
||||
Add a `templates/` directory so contributors never start from scratch:
|
||||
|
||||
```text
|
||||
templates/
|
||||
skill-plugin/ # plugin.json + skills/<name>/SKILL.md skeleton
|
||||
agent-plugin/ # plugin.json + agents/ skeleton (both .md and .agent.md)
|
||||
workflow-plugin/ # plugin.json + a multi-step orchestration skill
|
||||
```
|
||||
|
||||
Each ships the dual-manifest layout from §3 so new plugins are cross-tool by default.
|
||||
|
||||
### CI validation (the cross-tool catch)
|
||||
|
||||
A GitHub Action can gate PRs, but remember: **`claude plugin validate` covers the Claude side
|
||||
only.** There's no Copilot validator, so the Action needs custom JSON checks for the Copilot
|
||||
layout. Minimum checks:
|
||||
|
||||
- Valid JSON in every `marketplace.json` / `plugin.json`.
|
||||
- Each plugin's `SKILL.md` files have valid YAML frontmatter.
|
||||
- Unique plugin names; kebab-case; no reserved names.
|
||||
- Every `source` path resolves to an existing directory.
|
||||
- (If mirroring) `.claude-plugin/` and `.github/plugin/` manifests are in sync.
|
||||
- Version consistency (no conflicting `version` in plugin.json vs marketplace entry).
|
||||
|
||||
Fail the PR on any violation so the contributor flow stays self-service.
|
||||
|
||||
### Success criteria (the target end-state)
|
||||
|
||||
A new contributor should be able to, with **no manual registry edits, no backend, no MCP
|
||||
dependency**: (1) fork the repo, (2) create a plugin folder, (3) add `plugin.json`, (4) add
|
||||
skills/agents, (5) open a PR, (6) have the plugin appear in the marketplace automatically once
|
||||
merged. If a step requires hand-editing a central list, the automation isn't done.
|
||||
|
||||
---
|
||||
|
||||
## Open questions to resolve first
|
||||
|
||||
Two facts couldn't be confirmed from the published docs. Settle both with a one-skill test plugin
|
||||
before the architect generates layouts:
|
||||
|
||||
1. **Does Copilot CLI load a plugin whose `plugin.json` lives only in `.claude-plugin/`?** (Install
|
||||
the test plugin both ways.) The answer decides whether you ship one manifest or two. Copilot's
|
||||
docs put `plugin.json` at the plugin root; Claude requires `.claude-plugin/`.
|
||||
2. **What is Copilot's exact install-from-marketplace command?** The how-to pages don't show the
|
||||
literal string, and Copilot's `update`/`uninstall` take a bare plugin name — so it may *not* use
|
||||
Claude's `<name>@<marketplace>` form. Run `copilot plugin install --help` and confirm before
|
||||
putting the command in any README or the architect's generated docs.
|
||||
Reference in New Issue
Block a user