feat: consolidate marketplace skills into kyberforge plugin
Moves create-plugin, marketplace-architect, write-skill, and write-eval from canonical .agents/skills/ into plugins/kyberforge/skills/, along with all bundled sub-files, evals, and the plugin-marketplace-architecture research doc. Bundles templates/plugin/ into create-plugin/assets/plugin-template/ so the skill is self-contained after install-time caching. Removes templates/plugin/ and docs/research/plugin-marketplace-architecture.md from the repo root as they are now exclusively in the plugin. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
334
plugins/kyberforge/docs/plugin-marketplace-architecture.md
Normal file
334
plugins/kyberforge/docs/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.
|
||||
13
plugins/kyberforge/skills/create-plugin/META.md
Normal file
13
plugins/kyberforge/skills/create-plugin/META.md
Normal file
@@ -0,0 +1,13 @@
|
||||
```yaml
|
||||
version: "1.0"
|
||||
updated: 2026-06-20
|
||||
|
||||
when: Invoked when the user wants to create a new plugin in the marketplace. Scaffolds the
|
||||
directory structure from templates/plugin/, substitutes PLUGIN_NAME/PLUGIN_DESCRIPTION/
|
||||
AUTHOR_NAME/AUTHOR_EMAIL/AUTHOR_URL placeholders, writes to plugins/<name>/, registers
|
||||
the plugin in .claude-plugin/marketplace.json, runs claude plugin validate ., and hands
|
||||
off to /marketplace-architect.
|
||||
|
||||
references:
|
||||
- docs/research/plugin-marketplace-architecture.md
|
||||
```
|
||||
89
plugins/kyberforge/skills/create-plugin/SKILL.md
Normal file
89
plugins/kyberforge/skills/create-plugin/SKILL.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
name: create-plugin
|
||||
description: >
|
||||
Use when the user wants to create a new plugin in the marketplace — scaffold the directory
|
||||
structure, generate plugin.json manifests for Claude Code and Copilot CLI, and register
|
||||
the plugin in marketplace.json. Triggers: "create a new plugin", "add a plugin called X",
|
||||
"scaffold a plugin", "new plugin for Y". Do NOT use when auditing or refactoring existing
|
||||
plugins (use /marketplace-architect), authoring skill content inside a plugin (use
|
||||
/write-skill), adopting an external plugin into the marketplace (use /marketplace-architect),
|
||||
or validating existing manifests without creating anything (use /marketplace-architect).
|
||||
metadata:
|
||||
category: marketplace
|
||||
---
|
||||
|
||||
<requirements>
|
||||
|
||||
## Required inputs
|
||||
|
||||
- **Plugin name** — kebab-case slug; ask if not stated. Validate: lowercase letters, digits, hyphens only; not already present in `plugins/` or `.claude-plugin/marketplace.json`; not a reserved name (`anthropic-*`, `claude-*`, `agent-skills`, `official-claude-plugins`).
|
||||
- **Plugin description** — one sentence; ask if not stated.
|
||||
- **Author name** — ask if not stated.
|
||||
- **Author email** — ask if not stated; used in the Copilot root `plugin.json`.
|
||||
- **Author URL** — ask if not stated; used in Claude's `.claude-plugin/plugin.json`.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Load the plugin template from `assets/plugin-template/` bundled inside this skill (`${CLAUDE_PLUGIN_ROOT}/skills/create-plugin/assets/plugin-template/`). Stop if the path is missing — do not generate files from memory.
|
||||
- Plugin name must be kebab-case and not a reserved name (`anthropic-*`, `claude-*`, `agent-skills`, `official-claude-plugins`) — halt and ask for a replacement before Gate A if violated.
|
||||
- Do not write any file until Gate A (plan approval) and Gate B (file contents approval) are both explicitly confirmed.
|
||||
- Replace all five placeholder markers — `PLUGIN_NAME`, `PLUGIN_DESCRIPTION`, `AUTHOR_NAME`, `AUTHOR_EMAIL`, `AUTHOR_URL` — in every copied file before Gate B review. No marker may appear in written output.
|
||||
- Do not generate skill content — `skills/` is scaffolded as an empty directory with README only. Direct the user to `/write-skill` to add skills.
|
||||
- Do not set `version` in both `plugin.json` and the marketplace entry — `plugin.json` wins silently and blocks updates for existing users.
|
||||
- Update `.github/plugin/marketplace.json` only if it already exists — do not create it.
|
||||
- Do not auto-invoke `/marketplace-architect` — hand off by name at the end; let the user trigger it.
|
||||
|
||||
</requirements>
|
||||
|
||||
<steps>
|
||||
|
||||
## Process
|
||||
|
||||
1. **Collect inputs.** Ask for plugin name, description, author name, author email, and author URL — one question at a time. Validate the plugin name: kebab-case format, not a reserved name, not already present in `plugins/` or `.claude-plugin/marketplace.json`. If any validation fails, stop and ask for a replacement before continuing.
|
||||
|
||||
2. **Check template.** Load the bundled template from `assets/plugin-template/` inside this skill (`${CLAUDE_PLUGIN_ROOT}/skills/create-plugin/assets/plugin-template/`). If the path is missing, stop and report it — do not proceed or generate files from memory.
|
||||
|
||||
3. **Gate A — plan review.** Present: the list of files that will be written (derived from `assets/plugin-template/` with `PLUGIN_NAME` substituted into filenames), the new `plugins/<name>/` directory path, the marketplace entry that will be added to `.claude-plugin/marketplace.json`, and whether `.github/plugin/marketplace.json` will also be updated. Wait for explicit approval — do not proceed on "looks good" or silence.
|
||||
|
||||
4. **Copy and substitute.** Copy `assets/plugin-template/` to `plugins/<name>/`. In every copied file, replace all occurrences of `PLUGIN_NAME`, `PLUGIN_DESCRIPTION`, `AUTHOR_NAME`, `AUTHOR_EMAIL`, and `AUTHOR_URL` with the collected values. Rename any file or directory whose name contains `PLUGIN_NAME`.
|
||||
|
||||
5. **Gate B — file contents review.** Show every file with its full substituted content. Wait for explicit approval — do not write until confirmed.
|
||||
|
||||
6. **Write files.** Write all substituted files to `plugins/<name>/`. Append the new plugin entry to `.claude-plugin/marketplace.json`. If `.github/plugin/marketplace.json` exists, append the same entry there.
|
||||
|
||||
7. **Validate.** Run `claude plugin validate .` from `plugins/<name>/`. Report all output inline — do not suppress warnings. If the command is unavailable, note it and suggest the user run it manually after local install.
|
||||
|
||||
8. **Hand off.** Print: "Plugin `<name>` created and registered in `marketplace.json`. Fill in skill and agent content, then run `/marketplace-architect` to audit the full marketplace."
|
||||
|
||||
## Output format
|
||||
|
||||
- `plugins/<name>/` — directory tree copied from `assets/plugin-template/` with all placeholders substituted
|
||||
- `.claude-plugin/marketplace.json` — updated with the new plugin entry
|
||||
- `.github/plugin/marketplace.json` — updated with the same entry, only if it already existed
|
||||
- Inline validation report from `claude plugin validate .`
|
||||
|
||||
</steps>
|
||||
|
||||
<checks>
|
||||
|
||||
## Failure handling
|
||||
|
||||
- `assets/plugin-template/` not found at `${CLAUDE_PLUGIN_ROOT}/skills/create-plugin/assets/plugin-template/` — stop, report the path, do not generate from memory.
|
||||
- Plugin name already exists in `plugins/` or `marketplace.json` — stop, report the conflict, ask for a different name.
|
||||
- Reserved name detected — stop, report the name and the reserved list, ask for a replacement before continuing.
|
||||
- `claude plugin validate .` unavailable — report that automated validation was skipped; suggest running it manually with `claude --plugin-dir ./plugins/<name>`.
|
||||
|
||||
## Self-check
|
||||
|
||||
- [ ] Plugin name validated: kebab-case, not reserved, not already present in `plugins/` or `marketplace.json`
|
||||
- [ ] `assets/plugin-template/` verified to exist at `${CLAUDE_PLUGIN_ROOT}/skills/create-plugin/assets/plugin-template/` before any file generation
|
||||
- [ ] Gate A presented with file list and marketplace entry — explicit approval received
|
||||
- [ ] All five markers substituted in all files — none appear in written output
|
||||
- [ ] Gate B presented with full substituted file contents — explicit approval received
|
||||
- [ ] Files written only after Gate B approval
|
||||
- [ ] `.github/plugin/marketplace.json` updated only if it already existed — not created
|
||||
- [ ] `claude plugin validate .` run and results reported; or unavailability noted
|
||||
- [ ] Hand-off message printed directing user to `/marketplace-architect`
|
||||
- [ ] No skill content generated — `skills/` is empty with README only
|
||||
|
||||
</checks>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "PLUGIN_NAME",
|
||||
"displayName": "PLUGIN_NAME",
|
||||
"description": "PLUGIN_DESCRIPTION",
|
||||
"author": { "name": "AUTHOR_NAME", "url": "AUTHOR_URL" },
|
||||
"license": "MIT",
|
||||
"keywords": []
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"mcpServers": {}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# PLUGIN_NAME
|
||||
|
||||
PLUGIN_DESCRIPTION
|
||||
|
||||
## Install
|
||||
|
||||
**Claude Code:**
|
||||
|
||||
```bash
|
||||
claude plugin marketplace add <owner>/<repo>
|
||||
claude plugin install PLUGIN_NAME@<marketplace-name>
|
||||
```
|
||||
|
||||
**GitHub Copilot CLI:**
|
||||
|
||||
```bash
|
||||
copilot plugin marketplace add <owner>/<repo>
|
||||
copilot plugin install PLUGIN_NAME
|
||||
```
|
||||
|
||||
**Local (development):**
|
||||
|
||||
```bash
|
||||
# Claude Code
|
||||
claude --plugin-dir ./plugins/PLUGIN_NAME
|
||||
|
||||
# GitHub Copilot CLI
|
||||
copilot plugin install ./plugins/PLUGIN_NAME
|
||||
```
|
||||
|
||||
## Contents
|
||||
|
||||
| Component | Path | Description |
|
||||
|---|---|---|
|
||||
| Skills | `skills/` | Slash commands available after install |
|
||||
| Agents | `agents/` | Role-based agents (`.md` for Claude, `.agent.md` for Copilot) |
|
||||
| Hooks | `hooks/hooks.json` (Claude) / `hooks.json` (Copilot) | Event-triggered automation |
|
||||
| MCP servers | `.mcp.json` | Model Context Protocol server definitions |
|
||||
|
||||
## Author
|
||||
|
||||
AUTHOR_NAME
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: PLUGIN_NAME
|
||||
description: PLUGIN_DESCRIPTION
|
||||
tools:
|
||||
- read_file
|
||||
- list_directory
|
||||
---
|
||||
|
||||
Replace this with your agent's system prompt and instructions.
|
||||
|
||||
This file is loaded by **GitHub Copilot CLI**. The `tools:` frontmatter field declares
|
||||
which tools the agent can use — add or remove tools as needed.
|
||||
For Claude Code, see `PLUGIN_NAME.md`.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: PLUGIN_NAME
|
||||
description: PLUGIN_DESCRIPTION
|
||||
---
|
||||
|
||||
Replace this with your agent's system prompt and instructions.
|
||||
|
||||
This file is loaded by **Claude Code**. For GitHub Copilot CLI, see `PLUGIN_NAME.agent.md`.
|
||||
@@ -0,0 +1,13 @@
|
||||
# agents/
|
||||
|
||||
Agent definitions for this plugin. Each agent needs two files — one per tool:
|
||||
|
||||
| File | Tool | Notes |
|
||||
|---|---|---|
|
||||
| `<name>.md` | Claude Code | Frontmatter: `name`, `description`. No `tools:` field. |
|
||||
| `<name>.agent.md` | GitHub Copilot CLI | Frontmatter: `name`, `description`, `tools:` (array of permitted tool names). |
|
||||
|
||||
The two files share the same system prompt body. Keep them in sync.
|
||||
|
||||
Rename `PLUGIN_NAME.md` and `PLUGIN_NAME.agent.md` to your agent's name (kebab-case).
|
||||
Add additional agent pairs as needed — one pair per agent.
|
||||
@@ -0,0 +1,11 @@
|
||||
# bin/
|
||||
|
||||
**Claude Code only.** Executables placed here are added to PATH when the plugin is installed.
|
||||
|
||||
Use this for CLI tools, helper scripts, or MCP server entry points bundled with the plugin.
|
||||
|
||||
Reference files in this directory from `.mcp.json` or hooks using `${CLAUDE_PLUGIN_ROOT}/bin/<file>`.
|
||||
The `${CLAUDE_PLUGIN_ROOT}` variable resolves to the plugin's install cache path at runtime —
|
||||
do not use relative paths from the repo root, as they will break after install.
|
||||
|
||||
GitHub Copilot CLI does not support `bin/`. If you add executables here, they are Claude Code only.
|
||||
@@ -0,0 +1,7 @@
|
||||
# docs/
|
||||
|
||||
Plugin documentation. Place usage guides, reference material, and examples here.
|
||||
|
||||
This directory is not read automatically by either Claude Code or GitHub Copilot CLI.
|
||||
Reference specific files from your skill bodies or agent prompts when needed
|
||||
(e.g. `See references/usage.md for examples`).
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hooks": []
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# hooks/
|
||||
|
||||
**Claude Code only.** Hook definitions that run in response to Claude Code events.
|
||||
|
||||
The `hooks.json` in this directory is read by Claude Code. Structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "echo 'tool used'" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Supported events: `PreToolUse`, `PostToolUse`, `Notification`, `Stop`.
|
||||
|
||||
Use `${CLAUDE_PLUGIN_ROOT}` to reference scripts inside this plugin — the plugin runs
|
||||
from a cache path after install, not its original repo location.
|
||||
|
||||
For GitHub Copilot CLI hooks, see `hooks.json` at the plugin root.
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hooks": {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "PLUGIN_NAME",
|
||||
"description": "PLUGIN_DESCRIPTION",
|
||||
"author": { "name": "AUTHOR_NAME", "email": "AUTHOR_EMAIL" },
|
||||
"license": "MIT",
|
||||
"keywords": [],
|
||||
"agents": "agents/",
|
||||
"skills": ["skills/"],
|
||||
"hooks": "hooks.json",
|
||||
"mcpServers": ".mcp.json"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# skills/
|
||||
|
||||
Skills for this plugin. Each skill lives in its own subdirectory:
|
||||
|
||||
```
|
||||
skills/
|
||||
<skill-name>/
|
||||
SKILL.md # required — frontmatter + skill body
|
||||
META.md # required — provenance and audit fields
|
||||
references/ # optional — on-demand reference docs
|
||||
scripts/ # optional — executable helper scripts
|
||||
assets/ # optional — templates, data files, lookup tables
|
||||
```
|
||||
|
||||
**Shared** — both Claude Code and GitHub Copilot CLI read `skills/<name>/SKILL.md`.
|
||||
|
||||
To add a skill, run `/write-skill` in a Claude Code session. Do not write SKILL.md by hand
|
||||
without following the authoring standard — trigger descriptions and self-checks are required.
|
||||
21
plugins/kyberforge/skills/marketplace-architect/META.md
Normal file
21
plugins/kyberforge/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
plugins/kyberforge/skills/marketplace-architect/SKILL.md
Normal file
101
plugins/kyberforge/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>
|
||||
@@ -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.
|
||||
@@ -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
plugins/kyberforge/skills/marketplace-architect/scripts/gen_manifests.sh
Executable file
194
plugins/kyberforge/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
plugins/kyberforge/skills/marketplace-architect/scripts/inventory.sh
Executable file
121
plugins/kyberforge/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" && "$part" != ".agents" ]]; then
|
||||
skip=true; break
|
||||
fi
|
||||
done
|
||||
$skip && continue
|
||||
|
||||
asset_type=""
|
||||
|
||||
case "$name" in
|
||||
SKILL.md) asset_type="skill" ;;
|
||||
hooks.json) asset_type="hook" ;;
|
||||
.mcp.json) asset_type="mcp" ;;
|
||||
.lsp.json) asset_type="lsp" ;;
|
||||
plugin.json) asset_type="manifest-plugin" ;;
|
||||
marketplace.json) asset_type="manifest-marketplace" ;;
|
||||
*.agent.md) asset_type="agent-copilot" ;;
|
||||
*.md)
|
||||
if [[ "$parent" == "agents" ]]; then
|
||||
asset_type="agent-claude"
|
||||
elif [[ "$parent" == "commands" ]]; then
|
||||
asset_type="command"
|
||||
elif [[ "$rel" != *"/skills/"* && "$rel" != *"/commands/"* && "$rel" != *"/agents/"* ]]; then
|
||||
asset_type="prompt"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ -n "$asset_type" ]] && ROWS+=("$asset_type|$rel")
|
||||
|
||||
# Check for cross-references in text files
|
||||
case "$name" in *.md|*.json|*.sh)
|
||||
if grep -q '\.\.\/' "$path" 2>/dev/null; then
|
||||
while IFS= read -r line; do
|
||||
lineno="${line%%:*}"
|
||||
content="${line#*:}"
|
||||
CROSS_REFS+=("$rel:$lineno: $content")
|
||||
done < <(grep -n '\.\.\/' "$path" 2>/dev/null | head -20)
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
done < <(find "$ROOT" -type f -print0 | sort -z)
|
||||
|
||||
# ── report ───────────────────────────────────────────────────────────────────
|
||||
|
||||
echo "# Asset Inventory: $ROOT"
|
||||
echo ""
|
||||
echo "## Assets"
|
||||
echo ""
|
||||
echo "| Type | Path |"
|
||||
echo "|---|---|"
|
||||
for row in "${ROWS[@]+"${ROWS[@]}"}"; do
|
||||
type="${row%%|*}"
|
||||
path="${row#*|}"
|
||||
echo "| \`$type\` | \`$path\` |"
|
||||
done | sort
|
||||
|
||||
total="${#ROWS[@]}"
|
||||
echo ""
|
||||
echo "**Total: $total assets**"
|
||||
echo ""
|
||||
|
||||
# Summary by type
|
||||
echo "## Summary by type"
|
||||
echo ""
|
||||
for row in "${ROWS[@]+"${ROWS[@]}"}"; do
|
||||
echo "${row%%|*}"
|
||||
done | sort | uniq -c | while read -r count type; do
|
||||
echo "- \`$type\`: $count"
|
||||
done
|
||||
|
||||
# Cross-reference warnings
|
||||
echo ""
|
||||
if [[ ${#CROSS_REFS[@]} -gt 0 ]]; then
|
||||
echo "## ⚠️ Cross-reference warnings (${#CROSS_REFS[@]} found)"
|
||||
echo ""
|
||||
echo "These \`../\` references will break after install-time caching:"
|
||||
echo ""
|
||||
for ref in "${CROSS_REFS[@]}"; do
|
||||
echo "- \`$ref\`"
|
||||
done
|
||||
else
|
||||
echo "## Cross-references"
|
||||
echo ""
|
||||
echo "No \`../\` cross-references found. Safe to proceed with plugin boundaries."
|
||||
fi
|
||||
332
plugins/kyberforge/skills/marketplace-architect/scripts/validate.sh
Executable file
332
plugins/kyberforge/skills/marketplace-architect/scripts/validate.sh
Executable file
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env bash
|
||||
# Validate plugin marketplace manifests for Claude Code and GitHub Copilot CLI.
|
||||
# Wraps `claude plugin validate` (Claude-side) and runs manual checks (Copilot-side).
|
||||
#
|
||||
# Usage:
|
||||
# validate.sh <repo-root>
|
||||
# validate.sh <repo-root> --plugin plugins/my-plugin
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RESERVED_NAMES="claude-code-marketplace claude-code-plugins claude-plugins-official
|
||||
claude-plugins-community claude-community anthropic-marketplace anthropic-plugins
|
||||
agent-skills anthropic-agent-skills knowledge-work-plugins life-sciences
|
||||
claude-for-legal claude-for-financial-services financial-services-plugins"
|
||||
|
||||
RESERVED_PATTERNS="official-claude anthropic-tools claude-official"
|
||||
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
error() { echo "ERROR: $1"; ((ERRORS++)) || true; }
|
||||
warn() { echo "WARN: $1"; ((WARNINGS++)) || true; }
|
||||
|
||||
is_kebab_case() { [[ "$1" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; }
|
||||
|
||||
is_reserved() {
|
||||
local name="$1"
|
||||
for n in $RESERVED_NAMES; do [[ "$name" == "$n" ]] && return 0; done
|
||||
for p in $RESERVED_PATTERNS; do [[ "$name" == "$p"* ]] && return 0; done
|
||||
return 1
|
||||
}
|
||||
|
||||
validate_name() {
|
||||
local name="$1" context="$2"
|
||||
[[ -z "$name" ]] && { error "$context: name is missing or empty"; return 0; }
|
||||
is_kebab_case "$name" || error "$context: name '$name' is not kebab-case"
|
||||
if is_reserved "$name"; then
|
||||
error "$context: name '$name' is reserved for official Anthropic use"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
valid_json() {
|
||||
local path="$1"
|
||||
if ! jq -e . "$path" >/dev/null 2>&1; then
|
||||
error "Invalid JSON in $path"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_marketplace_json() {
|
||||
local path="$1"
|
||||
[[ -f "$path" ]] || return 0
|
||||
valid_json "$path" || return 0
|
||||
|
||||
local name
|
||||
name=$(jq -r '.name // empty' "$path")
|
||||
[[ -z "$name" ]] && error "$path: 'name' field is required" || validate_name "$name" "$path"
|
||||
|
||||
local plugins_type
|
||||
plugins_type=$(jq -r 'if .plugins | type == "array" then "ok" else "bad" end' "$path")
|
||||
if [[ "$plugins_type" != "ok" ]]; then
|
||||
error "$path: 'plugins' must be an array"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check each plugin entry
|
||||
local seen_names=()
|
||||
while IFS= read -r pname; do
|
||||
# Duplicate check
|
||||
for seen in "${seen_names[@]+"${seen_names[@]}"}"; do
|
||||
if [[ "$seen" == "$pname" ]]; then error "$path: duplicate plugin name '$pname'"; fi
|
||||
done
|
||||
seen_names+=("$pname")
|
||||
validate_name "$pname" "$path plugin '$pname'"
|
||||
|
||||
# Source path check
|
||||
local src
|
||||
src=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .source // empty' "$path")
|
||||
if [[ -n "$src" && "$src" != ./* && "$src" != "github" && "$src" != "npm" && "$src" != "url" && "$src" != "git-subdir" ]]; then
|
||||
warn "$path plugin '$pname': relative source '$src' should start with './' for Claude Code compatibility"
|
||||
fi
|
||||
|
||||
# Version duplication warning
|
||||
local has_ver
|
||||
has_ver=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .version // empty' "$path")
|
||||
if [[ -n "$has_ver" ]]; then
|
||||
warn "$path plugin '$pname': version set in marketplace entry. If also set in plugin.json, plugin.json wins silently."
|
||||
fi
|
||||
|
||||
done < <(jq -r '.plugins[].name // empty' "$path")
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_plugin_json() {
|
||||
local path="$1" marketplace_json="${2:-}"
|
||||
[[ -f "$path" ]] || return 0
|
||||
valid_json "$path" || return 0
|
||||
|
||||
local name
|
||||
name=$(jq -r '.name // empty' "$path")
|
||||
if [[ -z "$name" ]]; then
|
||||
warn "$path: 'name' field missing (plugin dir name will be used)"
|
||||
else
|
||||
validate_name "$name" "$path"
|
||||
|
||||
# Version duplication check
|
||||
if [[ -n "$marketplace_json" && -f "$marketplace_json" ]]; then
|
||||
local pver mver
|
||||
pver=$(jq -r '.version // empty' "$path")
|
||||
mver=$(jq -r --arg n "$name" '.plugins[]? | select(.name==$n) | .version // empty' "$marketplace_json")
|
||||
if [[ -n "$pver" && -n "$mver" ]]; then
|
||||
error "$path: version '$pver' set in both plugin.json and marketplace entry — plugin.json wins silently. Remove one."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_skill_md() {
|
||||
local path="$1"
|
||||
local content
|
||||
content=$(cat "$path")
|
||||
if [[ "$content" != ---* ]]; then
|
||||
warn "$path: SKILL.md has no YAML frontmatter"
|
||||
return
|
||||
fi
|
||||
if ! echo "$content" | awk 'NR>1 && /^---/' | grep -q '^---'; then
|
||||
error "$path: SKILL.md frontmatter not closed"
|
||||
return
|
||||
fi
|
||||
if ! echo "$content" | awk '/^---/{n++; if(n==2) exit} n==1' | grep -q 'description:'; then
|
||||
warn "$path: SKILL.md frontmatter missing 'description' field"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_plugin_dir() {
|
||||
local pd="$1" marketplace_json="${2:-}"
|
||||
|
||||
local claude_manifest="$pd/.claude-plugin/plugin.json"
|
||||
local root_manifest="$pd/plugin.json"
|
||||
|
||||
if [[ ! -f "$claude_manifest" && ! -f "$root_manifest" ]]; then
|
||||
warn "$pd: no plugin.json found (will auto-discover components)"
|
||||
else
|
||||
validate_plugin_json "$claude_manifest" "$marketplace_json"
|
||||
validate_plugin_json "$root_manifest" "$marketplace_json"
|
||||
|
||||
# Sync check — shared identity fields must match; component path fields legitimately diverge
|
||||
if [[ -f "$claude_manifest" && -f "$root_manifest" ]]; then
|
||||
local field cv rv
|
||||
for field in name description version license; do
|
||||
cv=$(jq -r ".$field // empty" "$claude_manifest")
|
||||
rv=$(jq -r ".$field // empty" "$root_manifest")
|
||||
if [[ ( -n "$cv" || -n "$rv" ) && "$cv" != "$rv" ]]; then
|
||||
error "$pd: '$field' differs between .claude-plugin/plugin.json ('$cv') and plugin.json ('$rv')"
|
||||
fi
|
||||
done
|
||||
cv=$(jq -r '.author.name // empty' "$claude_manifest")
|
||||
rv=$(jq -r '.author.name // empty' "$root_manifest")
|
||||
if [[ ( -n "$cv" || -n "$rv" ) && "$cv" != "$rv" ]]; then
|
||||
error "$pd: 'author.name' differs between .claude-plugin/plugin.json ('$cv') and plugin.json ('$rv')"
|
||||
fi
|
||||
cv=$(jq -r '.keywords // [] | sort | join(",")' "$claude_manifest")
|
||||
rv=$(jq -r '.keywords // [] | sort | join(",")' "$root_manifest")
|
||||
if [[ "$cv" != "$rv" ]]; then
|
||||
error "$pd: 'keywords' differs between .claude-plugin/plugin.json and plugin.json"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Components must not be inside .claude-plugin/
|
||||
for bad_dir in skills agents hooks commands; do
|
||||
if [[ -d "$pd/.claude-plugin/$bad_dir" ]]; then
|
||||
error "$pd/.claude-plugin/$bad_dir: only plugin.json belongs in .claude-plugin/; move $bad_dir/ to plugin root"
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate SKILL.md files
|
||||
while IFS= read -r -d '' skill_md; do
|
||||
validate_skill_md "$skill_md"
|
||||
done < <(find "$pd" -name "SKILL.md" -print0 2>/dev/null)
|
||||
|
||||
# Cross-reference check
|
||||
while IFS= read -r -d '' f; do
|
||||
if grep -q '\.\.\/' "$f" 2>/dev/null; then
|
||||
local rel="${f#"$pd/"}"
|
||||
error "$rel: contains '../' reference — plugins cannot access files outside their directory after caching"
|
||||
fi
|
||||
done < <(find "$pd" \( -name "*.md" -o -name "*.json" \) -print0 2>/dev/null)
|
||||
}
|
||||
|
||||
run_claude_validate() {
|
||||
local path="$1"
|
||||
if command -v claude >/dev/null 2>&1; then
|
||||
if ! claude plugin validate "$path" 2>&1; then
|
||||
error "claude plugin validate failed for $path"
|
||||
fi
|
||||
else
|
||||
warn "'claude' CLI not found — skipping claude plugin validate"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── parse args ────────────────────────────────────────────────────────────────
|
||||
|
||||
ROOT=""
|
||||
PLUGIN_ONLY=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--plugin) PLUGIN_ONLY="$2"; shift 2 ;;
|
||||
-*) echo "Unknown option: $1" >&2; exit 1 ;;
|
||||
*) ROOT="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$ROOT" ]]; then
|
||||
echo "Usage: validate.sh <repo-root> [--plugin <path>]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ROOT="$(cd "$ROOT" && pwd)"
|
||||
|
||||
# ── validate marketplace.json ─────────────────────────────────────────────────
|
||||
|
||||
CLAUDE_MARKETPLACE="$ROOT/.claude-plugin/marketplace.json"
|
||||
COPILOT_MARKETPLACE="$ROOT/.github/plugin/marketplace.json"
|
||||
MARKETPLACE_JSON=""
|
||||
|
||||
for mp in "$CLAUDE_MARKETPLACE" "$COPILOT_MARKETPLACE"; do
|
||||
if [[ -f "$mp" ]]; then
|
||||
[[ -z "$MARKETPLACE_JSON" ]] && MARKETPLACE_JSON="$mp"
|
||||
validate_marketplace_json "$mp"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$MARKETPLACE_JSON" ]]; then
|
||||
warn "No marketplace.json found. Expected at .claude-plugin/marketplace.json"
|
||||
fi
|
||||
|
||||
# Marketplace sync check — shared identity fields must match; description/version
|
||||
# legitimately differ in structure (Claude: top-level; Copilot: under metadata)
|
||||
if [[ -f "$CLAUDE_MARKETPLACE" && -f "$COPILOT_MARKETPLACE" ]]; then
|
||||
cm_val=$(jq -r '.name // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r '.name // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace: 'name' differs — .claude-plugin ('$cm_val') vs .github/plugin ('$cp_val')"
|
||||
fi
|
||||
|
||||
cm_val=$(jq -r '.owner.name // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r '.owner.name // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace: 'owner.name' differs — '$cm_val' vs '$cp_val'"
|
||||
fi
|
||||
|
||||
# description: Claude top-level, Copilot under metadata — compare values regardless of path
|
||||
cm_val=$(jq -r '.description // .metadata.description // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r '.metadata.description // .description // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace: description differs between .claude-plugin/marketplace.json and .github/plugin/marketplace.json"
|
||||
fi
|
||||
|
||||
# version: same structural divergence as description
|
||||
cm_val=$(jq -r '.version // .metadata.version // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r '.metadata.version // .version // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace: version differs — '$cm_val' vs '$cp_val'"
|
||||
fi
|
||||
|
||||
# Plugin catalog must be identical across both files
|
||||
cm_plugins=$(jq -r '.plugins[].name' "$CLAUDE_MARKETPLACE" 2>/dev/null | sort)
|
||||
cp_plugins=$(jq -r '.plugins[].name' "$COPILOT_MARKETPLACE" 2>/dev/null | sort)
|
||||
if [[ "$cm_plugins" != "$cp_plugins" ]]; then
|
||||
error "marketplace: plugin lists differ between .claude-plugin/marketplace.json and .github/plugin/marketplace.json"
|
||||
else
|
||||
while IFS= read -r pname; do
|
||||
[[ -z "$pname" ]] && continue
|
||||
cm_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .source // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .source // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace plugin '$pname': source differs — '$cm_val' vs '$cp_val'"
|
||||
fi
|
||||
cm_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .description // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .description // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace plugin '$pname': description differs between the two marketplace.json files"
|
||||
fi
|
||||
done <<< "$cm_plugins"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── validate plugins ──────────────────────────────────────────────────────────
|
||||
|
||||
if [[ -n "$PLUGIN_ONLY" ]]; then
|
||||
validate_plugin_dir "$(cd "$PLUGIN_ONLY" && pwd)" "$MARKETPLACE_JSON"
|
||||
run_claude_validate "$(cd "$PLUGIN_ONLY" && pwd)"
|
||||
else
|
||||
plugins_path="$ROOT/plugins"
|
||||
if [[ -d "$plugins_path" ]]; then
|
||||
while IFS= read -r -d '' pd; do
|
||||
validate_plugin_dir "$pd" "$MARKETPLACE_JSON"
|
||||
run_claude_validate "$pd"
|
||||
done < <(find "$plugins_path" -mindepth 1 -maxdepth 1 -type d ! -name '.*' -print0 | sort -z)
|
||||
else
|
||||
warn "No plugins/ directory found at $ROOT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── check source paths resolve ────────────────────────────────────────────────
|
||||
|
||||
if [[ -n "$MARKETPLACE_JSON" ]]; then
|
||||
while IFS= read -r src; do
|
||||
[[ "$src" != ./* ]] && continue
|
||||
src_path="$ROOT/${src#./}"
|
||||
if [[ ! -d "$src_path" ]]; then error "Marketplace source path '$src' does not exist at $src_path"; fi
|
||||
done < <(jq -r '.plugins[]?.source | strings' "$MARKETPLACE_JSON" 2>/dev/null)
|
||||
fi
|
||||
|
||||
# ── report ────────────────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
if [[ $ERRORS -eq 0 && $WARNINGS -eq 0 ]]; then
|
||||
echo "✓ All checks passed."
|
||||
exit 0
|
||||
elif [[ $ERRORS -eq 0 ]]; then
|
||||
echo "Passed with $WARNINGS warning(s)."
|
||||
exit 0
|
||||
else
|
||||
echo "Failed. Fix $ERRORS error(s) before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
141
plugins/kyberforge/skills/write-eval/SKILL.md
Normal file
141
plugins/kyberforge/skills/write-eval/SKILL.md
Normal file
@@ -0,0 +1,141 @@
|
||||
---
|
||||
name: write-eval
|
||||
description: Write or generate an eval.yaml test file for a skill. Use when the user wants to create evals, add test coverage, or says "write evals for this skill", "create eval.yaml for X", or "add tests for this skill". Do NOT use when the user wants to run existing evals, write unit tests for code, or debug test failures.
|
||||
version: "1.0"
|
||||
updated: 2026-05-17
|
||||
when: invoked by explicit trigger ("write evals for this skill", "create eval.yaml for X") or implicit request for skill test coverage
|
||||
metadata:
|
||||
category: factory
|
||||
source:
|
||||
- repo: agentskills/agentskills
|
||||
commit: 2d3e01f590f68bee2cb76a3200823e93b2cc9eaa
|
||||
files:
|
||||
- docs/skill-creation/evaluating-skills.mdx # evals schema, two-section workspace layout, assertion quality guidelines
|
||||
updated: 2026-05-17
|
||||
- repo: darkrishabh/agent-skills-eval
|
||||
commit: b60eebe3c6edaa917a284e13b9b0e9fa00f1c957
|
||||
files:
|
||||
- src/types.ts # AgentSkillsEval interface — string-slug id, name field, prompt/expected_output/assertions structure
|
||||
- examples/basic-skill/evals/evals.json # concrete schema example
|
||||
updated: 2026-05-17
|
||||
- repo: bmad-code-org/BMAD-METHOD
|
||||
commit: 71136bc6af77cbf507d3768494311d5b6ca95cc5
|
||||
files:
|
||||
- evals/bmm-skills/bmad-product-brief/triggers.json # trigger classification dataset, should_trigger boolean pattern
|
||||
- evals/bmm-skills/bmad-product-brief/evals.json # output test structure, boundary-enforcement negative test pattern
|
||||
updated: 2026-05-17
|
||||
- repo: mattpocock/skills
|
||||
commit: e74f0061bb67222181640effa98c675bdb2fdaa7
|
||||
files:
|
||||
- skills/engineering/tdd/SKILL.md # behavioral test philosophy: test observable outputs through public interfaces
|
||||
updated: 2026-05-17
|
||||
references:
|
||||
- https://agentskills.io/skill-creation/evaluating-skills
|
||||
---
|
||||
|
||||
## Role
|
||||
|
||||
You are a test architect producing eval.yaml files that verify AI skill trigger behaviour and output quality.
|
||||
|
||||
## When to use / When not to use
|
||||
|
||||
**Use when:**
|
||||
- User explicitly requests evals: "write evals for this skill", "create eval.yaml for X", "add tests for this skill"
|
||||
- A new or refactored skill needs an eval file
|
||||
- Existing eval coverage needs to be extended with additional test cases
|
||||
|
||||
**Do not use when:**
|
||||
- User wants to run or execute existing evals
|
||||
- User wants to write unit tests for application code (not a skill eval)
|
||||
- User asks to debug or analyse failing eval results
|
||||
- User asks to review or compare eval output
|
||||
|
||||
## Required inputs
|
||||
|
||||
- Target skill name — explicit or unambiguous from session context
|
||||
- Target skill's SKILL.md — must be readable at `.agents/skills/<skill-name>/SKILL.md`
|
||||
- Target skill's `metadata.category` — used to derive the output path
|
||||
|
||||
## Constraints
|
||||
|
||||
- Output path: `.agents/evals/<category>/<skill-name>/eval.yaml` — nested by category, not flat
|
||||
- Every eval.yaml must contain all five required test types: ≥1 explicit trigger, ≥1 implicit trigger, ≥1 negative trigger, ≥2 deterministic output, ≥1 LLM-rubric quality
|
||||
- Assertions must be specific and verifiable — "The output contains a trigger_tests section" not "The output is good"
|
||||
- Assertions must be provider-agnostic — no tool-call assertions, no assumptions about the underlying model or runtime
|
||||
- Show the test plan and wait for confirmation before writing any file
|
||||
- On re-run (eval.yaml already exists): merge — classify proposed cases as NEW / IDENTICAL / CONFLICT; surface conflicts for human resolution before writing; do not silently overwrite
|
||||
- Body ≤500 lines
|
||||
|
||||
## Process
|
||||
|
||||
1. **Identify the target skill.** If not explicit in the invocation, infer from session context. If ambiguous, ask before proceeding.
|
||||
|
||||
2. **Read the target SKILL.md** at `.agents/skills/<skill-name>/SKILL.md`. Extract:
|
||||
- `name`, `metadata.category` (for output path)
|
||||
- `description` (trigger description — source for explicit and implicit trigger test queries)
|
||||
- When / when not criteria (source for negative trigger test queries)
|
||||
- Required inputs and output format (source for deterministic output assertions)
|
||||
|
||||
3. **Check for an existing eval.yaml** at `.agents/evals/<category>/<skill-name>/eval.yaml`.
|
||||
- If it exists: read it and record all existing test IDs.
|
||||
|
||||
4. **Propose test cases** — one minimum per required type:
|
||||
|
||||
**trigger_tests** — classify each query by whether the skill should activate:
|
||||
- ≥1 explicit trigger: a query using the skill's exact trigger phrase
|
||||
- ≥1 implicit trigger: a query describing the task without the trigger phrase; derive from the skill's purpose and use cases
|
||||
- ≥1 negative trigger: a query for an adjacent task the skill must NOT activate on; derive from the skill's when-not criteria; choose a case with surface similarity to the trigger
|
||||
|
||||
**output_tests** — test what the skill produces:
|
||||
- ≥2 deterministic: assert on observable, machine-checkable properties of the output — required sections present, correct file path, schema compliance. Write as specific string conditions a reader could verify without inference.
|
||||
- ≥1 LLM-rubric: holistic quality assertions — conditions a judge evaluates from the full output. Test qualities that deterministic checks cannot capture: realism of trigger queries, specificity of assertions, boundary case coverage.
|
||||
|
||||
For all assertions: write as verifiable conditions, not value judgements. Test boundary cases, not only happy paths. A good assertion survives internal refactoring of the skill.
|
||||
|
||||
5. **Classify proposed cases if an existing eval.yaml was found:**
|
||||
- **NEW** — ID not in existing file; safe to append
|
||||
- **IDENTICAL** — ID exists, content matches exactly; skip silently
|
||||
- **CONFLICT** — ID exists, content differs; display existing vs proposed side-by-side
|
||||
|
||||
6. **Present the full test plan.** Show each proposed case with its classification label (NEW / IDENTICAL / CONFLICT). For CONFLICT cases, ask the user to choose: keep existing, use proposed, or skip. Wait for confirmation before writing.
|
||||
|
||||
7. **Write eval.yaml.** Append NEW cases to the existing file (or write the full structure for a new file). Apply CONFLICT resolutions as chosen. Skip IDENTICAL cases.
|
||||
|
||||
## Output format
|
||||
|
||||
```yaml
|
||||
skill_name: <name>
|
||||
|
||||
trigger_tests:
|
||||
- id: <string-slug> # e.g. explicit-trigger-basic
|
||||
name: <display label> # human-readable, e.g. "Explicit trigger — basic invocation"
|
||||
query: <exact user input text>
|
||||
should_trigger: true # true for explicit and implicit; false for negative
|
||||
|
||||
output_tests:
|
||||
- id: <string-slug>
|
||||
name: <display label>
|
||||
type: deterministic # or llm-rubric
|
||||
prompt: <user input to the skill>
|
||||
expected_output: <prose description of ideal output>
|
||||
assertions:
|
||||
- <specific, verifiable condition string>
|
||||
```
|
||||
|
||||
## Failure handling
|
||||
|
||||
- **Target SKILL.md not found:** stop, report the path searched, do not guess or generate content from the skill name alone
|
||||
- **`metadata.category` absent from SKILL.md:** ask for the category before computing the output path
|
||||
- **All proposed cases conflict with existing file:** report the full conflict summary, wait for explicit direction — do not auto-resolve
|
||||
- **Proposed test count below minimums:** flag which type is short before presenting the plan; do not proceed with a deficient eval
|
||||
|
||||
## Self-check
|
||||
|
||||
Verify before writing:
|
||||
|
||||
- [ ] All five test types present — ≥1 explicit, ≥1 implicit, ≥1 negative trigger; ≥2 deterministic, ≥1 LLM-rubric output
|
||||
- [ ] trigger_tests: at least one `should_trigger: true` and at least one `should_trigger: false`
|
||||
- [ ] All assertions are specific and verifiable — no vague quality claims
|
||||
- [ ] Output path matches `.agents/evals/<category>/<skill-name>/eval.yaml`
|
||||
- [ ] Test plan was presented and confirmed before the file was written
|
||||
- [ ] CONFLICT cases were surfaced to the user and not silently resolved
|
||||
16
plugins/kyberforge/skills/write-skill/CATEGORIES.md
Normal file
16
plugins/kyberforge/skills/write-skill/CATEGORIES.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Skill Categories
|
||||
|
||||
| Category | Scope |
|
||||
|---|---|
|
||||
| `design` | grill-me, grill-with-docs, to-prd, prototype, architecture-review |
|
||||
| `plan` | to-issues, triage |
|
||||
| `implement` | tdd, diagnose, implement-feature, refactor, write-docs |
|
||||
| `test` | write-tests, generate-test-data, review-test-coverage |
|
||||
| `review` | improve-codebase-architecture, code-review, security-review, pr-description, changelog-entry |
|
||||
| `deploy` | write-ci-pipeline, write-deployment-config, write-ai-review-workflow, deployment-checklist |
|
||||
| `operate` | write-runbook, incident-diagnosis, post-mortem, inspect-deployment |
|
||||
| `iac` | write-ansible-role, write-terraform-module, write-k8s-manifest, write-docker-compose, proxmox-vm-spec, iac-security-review, write-molecule-test |
|
||||
| `cross-cutting` | zoom-out, caveman, session-handoff, governance-check, git-guardrails, git-commit-message |
|
||||
| `factory` | write-skill, write-adr, write-workflow, write-eval, validate-skill, upgrade-skill, write-issue-spec |
|
||||
| `marketplace` | marketplace-architect — plugin and skill distribution tooling for Claude Code / GitHub Copilot CLI |
|
||||
| `roles` | architect, developer, reviewer, security, qa, ops — Chunk 5 |
|
||||
27
plugins/kyberforge/skills/write-skill/META-TEMPLATE.md
Normal file
27
plugins/kyberforge/skills/write-skill/META-TEMPLATE.md
Normal file
@@ -0,0 +1,27 @@
|
||||
```yaml
|
||||
version: "1.0" # increment on meaningful changes to the skill
|
||||
updated: YYYY-MM-DD # ISO date of last update
|
||||
|
||||
# when: describes when this skill is loaded — the full trigger context.
|
||||
# More detail than the description field; not used for routing.
|
||||
when: <describe the invocation conditions here>
|
||||
|
||||
# source: tracks content you ADOPTED from an upstream repo.
|
||||
# Adopt = you read someone else's code or docs and incorporated text or logic directly.
|
||||
# Omit this field entirely if the skill is self-authored — absence means original work.
|
||||
# Present only when content was actually taken, tracked at commit-level for upgrade reviews.
|
||||
source:
|
||||
- repo: org/repo-name # GitHub slug — no URL, slug is stable and searchable
|
||||
commit: <full SHA> # exact commit reviewed at time of adoption
|
||||
files:
|
||||
- path/to/file.md # inline comment: what was taken from this file
|
||||
- path/to/other.md # inline comment: what was taken from this file
|
||||
updated: YYYY-MM-DD # date this source entry was last reviewed
|
||||
|
||||
# references: tracks content you CITED but did not adopt verbatim.
|
||||
# Cite = you read it and it informed the skill, but nothing was copied or adapted.
|
||||
# Examples: a spec you followed, a paper that shaped the approach, external documentation.
|
||||
# Distinct from source: source = took content; references = informed by content.
|
||||
references:
|
||||
- https://example.com/relevant-doc
|
||||
```
|
||||
16
plugins/kyberforge/skills/write-skill/META.md
Normal file
16
plugins/kyberforge/skills/write-skill/META.md
Normal file
@@ -0,0 +1,16 @@
|
||||
```yaml
|
||||
version: "1.5"
|
||||
updated: 2026-05-26
|
||||
|
||||
# when: describes when this skill is loaded — the full trigger context.
|
||||
# More detail than the description field; not used for routing.
|
||||
when: invoked by explicit trigger ("write a new skill for X", "create a SKILL.md that does Y") or implicit request to author a skill file or convert an existing placeholder to the canonical authoring standard
|
||||
|
||||
# source: omitted — self-authored original; no upstream content adopted
|
||||
# Absence of source means self-authored. If content is adopted from upstream,
|
||||
# add a source entry per the META-TEMPLATE.md schema.
|
||||
|
||||
references:
|
||||
- https://agentskills.io/specification.md
|
||||
- https://agentskills.io/skill-creation/optimizing-descriptions
|
||||
```
|
||||
93
plugins/kyberforge/skills/write-skill/SKILL-TEMPLATE.md
Normal file
93
plugins/kyberforge/skills/write-skill/SKILL-TEMPLATE.md
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
name: <skill-name>
|
||||
# description: routing-only field — loaded at startup for every skill scan to decide whether
|
||||
# to activate this skill. Write in imperative phrasing ("Use when X", not "This skill does X").
|
||||
# Must cover: (1) what the skill does, (2) when to invoke it, (3) negative triggers — what
|
||||
# adjacent tasks must NOT activate it. No behavioral or role framing; that belongs in the body.
|
||||
# Max 1024 characters. The `when:` detail that lived here previously now lives in META.md.
|
||||
# Example: "Use when the user wants to create a new SKILL.md file or convert a placeholder to
|
||||
# canonical format. Do NOT use when updating an existing well-formed skill — use upgrade-skill."
|
||||
description: <trigger description>
|
||||
metadata:
|
||||
category: <category — see CATEGORIES.md>
|
||||
# allowed-tools: <add only when the skill has a narrow, well-defined tool surface; omit otherwise>
|
||||
# model: sonnet | opus | haiku — Claude Code extension; overrides session model for this skill's turn.
|
||||
# Omit to inherit the active session model. Factory §9 routing: haiku=formatting/classification,
|
||||
# sonnet=most coding/review, opus=adversarial/complex reasoning.
|
||||
---
|
||||
|
||||
<requirements>
|
||||
|
||||
## Required inputs
|
||||
|
||||
<!-- List each required input as a bullet: name, what it is, how the agent obtains it.
|
||||
Negative trigger cases are NOT listed here — the agent proposes them during trigger testing.
|
||||
Example:
|
||||
- **Skill name** — kebab-case slug; inferred from user description if not stated explicitly, ask if ambiguous
|
||||
- **Existing SKILL.md path** — for placeholder conversions only; read before writing -->
|
||||
|
||||
- **<Input name>** — <description; how obtained>
|
||||
|
||||
## Constraints
|
||||
|
||||
<!-- One rule per bullet. State the boundary condition inline. Plain English, no jargon.
|
||||
Do not include a constraint about body section structure — the template enforces that.
|
||||
Example:
|
||||
- Frontmatter has three fields only: `name`, `description`, and `metadata.category` — add `allowed-tools` only when the skill has a narrow, well-defined tool surface
|
||||
- Body ≤500 lines — content that explains rather than directs belongs in sub-files, not the body
|
||||
- Sub-files use three spec-defined optional directories: `scripts/` (executable code), `references/` (on-demand docs), `assets/` (templates, data files, lookup tables). File references must be one level deep. Wire each sub-file with an explicit step instruction (e.g. "See references/lookup.md for error codes") — without wiring, the file is never loaded -->
|
||||
|
||||
- <constraint>
|
||||
|
||||
</requirements>
|
||||
|
||||
<steps>
|
||||
|
||||
## Process
|
||||
|
||||
<!-- Numbered steps with a bold action label. Short, direct sentences — state what to do and
|
||||
what happens as a result. Call out hard gates explicitly (steps that block all progress
|
||||
until satisfied). No preamble, no meta-commentary about the steps themselves.
|
||||
Example:
|
||||
1. **Scan for overlap.** Check `.agents/skills/` for skills with similar purpose or trigger phrases. If overlap is found, surface it and wait for explicit direction — do not continue.
|
||||
2. **Grill.** Run a focused grill to reach shared understanding of: skill name, category, purpose, and use cases. One question at a time, with a recommendation for each. -->
|
||||
|
||||
1. **<Step name>.** <what to do and what happens as a result>
|
||||
|
||||
## Output format
|
||||
|
||||
<!-- Describe the files or artifacts produced. Include paths and how they are created
|
||||
(copy-fill from template, generated, etc.). State the template used for structured file output.
|
||||
Example:
|
||||
Two files produced for every skill, plus optional sub-files if the skill requires them:
|
||||
- `SKILL.md` — copy-filled from `SKILL-TEMPLATE.md` at `.agents/skills/<name>/SKILL.md`
|
||||
- `META.md` — copy-filled from `META-TEMPLATE.md` at `.agents/skills/<name>/META.md`
|
||||
- `scripts/`, `references/`, or `assets/` — created only when needed; each file wired with an explicit step instruction -->
|
||||
|
||||
<description of output>
|
||||
|
||||
</steps>
|
||||
|
||||
<checks>
|
||||
|
||||
## Failure handling
|
||||
|
||||
<!-- One bullet per failure mode. Lean — no overlap with constraints or process.
|
||||
Format: condition — action.
|
||||
Example:
|
||||
- Template file missing — stop, report the path searched, do not write from memory
|
||||
- `write-eval` fails or is unavailable — flag, do not mark the skill complete -->
|
||||
|
||||
- <failure condition> — <what to do>
|
||||
|
||||
## Self-check
|
||||
|
||||
<!-- Verifiable checklist the agent runs before declaring the skill complete.
|
||||
Each item must be checkable, not aspirational.
|
||||
Example:
|
||||
- [ ] Overlap check completed before any content was written
|
||||
- [ ] Trigger description tested against all three cases — all passed before body content was written -->
|
||||
|
||||
- [ ] <check>
|
||||
|
||||
</checks>
|
||||
92
plugins/kyberforge/skills/write-skill/SKILL.md
Normal file
92
plugins/kyberforge/skills/write-skill/SKILL.md
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
name: write-skill
|
||||
description: Use when the user wants to author a new skill file or convert an existing placeholder to the canonical authoring standard. Triggers: "write a new skill for X", "create a SKILL.md that does Y", "build a skill to handle Z". Do NOT use when fixing or updating an existing well-formed skill (use upgrade-skill), running existing evals (use write-eval), refactoring application code, or writing documentation for non-skill artifacts.
|
||||
metadata:
|
||||
category: factory
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
<requirements>
|
||||
|
||||
## Required inputs
|
||||
|
||||
- **Skill name** — kebab-case slug; inferred from user description if not stated explicitly, ask if ambiguous
|
||||
- **Category** — from the category table in `CATEGORIES.md`; ask if unclear
|
||||
- **Purpose + use cases** — what the skill does and what tasks it handles; source for the trigger description
|
||||
- **For placeholder conversions:** existing SKILL.md path — read before writing
|
||||
|
||||
Negative trigger cases are NOT a required input. The agent proposes them based on the skill's purpose and adjacent skills found during the overlap scan. The user confirms or refines before trigger testing begins.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Write two files for every skill: `SKILL.md` at `.agents/skills/<name>/SKILL.md` and `META.md` alongside it
|
||||
- Frontmatter required fields: `name`, `description`, `metadata.category` — add `allowed-tools` only when the skill has a narrow, well-defined tool surface; add `model:` only when the skill's task complexity warrants a specific model tier (see SKILL-TEMPLATE.md for routing guidance)
|
||||
- Keep the body under 500 lines — content that explains rather than directs belongs in sub-files, not the body
|
||||
- Sub-files use three spec-defined optional directories: `scripts/` (executable code), `references/` (on-demand docs), `assets/` (templates, data files, lookup tables); additional files (e.g. `META.md`) are valid at the skill root. File references must be one level deep — no nested chains. Wire each sub-file with an explicit instruction in the step that needs it (e.g. `"See references/lookup.md for error codes"`) — without a wiring instruction the file is never loaded
|
||||
- Use XML tags only when the body has three or more logical sections and exceeds 500 tokens — default to plain prose
|
||||
- Test the trigger description against all three cases — explicit, implicit, negative — before writing any body content. Hard gate: a failed case means revise and retest, not proceed
|
||||
- Check for overlapping skills in `.agents/skills/` before writing anything — if overlap is found, surface it and wait for direction
|
||||
- For placeholder conversions: read the existing SKILL.md first and remove all stale or outdated content
|
||||
|
||||
</requirements>
|
||||
|
||||
<steps>
|
||||
|
||||
## Process
|
||||
|
||||
1. **Scan for overlap.** Check for skills with similar purpose or trigger phrases. If overlap is found, surface it and wait for explicit direction — do not continue.
|
||||
|
||||
2. **Grill.** Run a focused grill with the /grill-me skill to reach shared understanding of: skill name, category, purpose, and use cases. One question at a time, with a recommendation for each.
|
||||
|
||||
3. **Conflict check.** Spawn a sub-agent: read `docs/ai-constitution.md`, `docs/research/ai-coding-factory/ai-coding-factory-principles.md`, and `docs/notes/factory-integration-decisions.md`, then check the agreed skill purpose and design against all three. Where a factory principle is superseded by an integration decision, the decision takes precedence — do not flag it as a conflict. Return a numbered list of genuine unresolved tensions, or confirm none found. An empty list is a valid result. Hard gate: resolve any findings before proceeding.
|
||||
|
||||
4. **Write and test the trigger description.** Using the agreed name, category, purpose, and use cases from the grill, draft `description:`. Propose negative trigger cases based on the skill's purpose and adjacent skills — get explicit user confirmation before running tests. Test all three cases and show per-case PASS/FAIL. A failed case means revise and retest — do not proceed.
|
||||
|
||||
5. **Walk through each section.** For each section in `SKILL-TEMPLATE.md`: propose content, state where it comes from, present alternatives if they exist. Wait for explicit human confirmation before moving to the next section.
|
||||
|
||||
6. **Copy both templates.** Copy `SKILL-TEMPLATE.md` to `.agents/skills/<name>/SKILL.md`. Copy `META-TEMPLATE.md` to `.agents/skills/<name>/META.md`. Do not modify content yet — copy first, fill second.
|
||||
|
||||
7. **Fill both files.** Fill in the copied `SKILL.md` with confirmed section content. Fill in the copied `META.md` with version, updated date, when, source (if applicable), and references (if applicable).
|
||||
|
||||
8. **Invoke `write-eval`.** Do not mark the skill complete without an eval file.
|
||||
|
||||
9. **Run self-check.** Work through every item in the Self-check section below. Do not proceed until all items pass.
|
||||
|
||||
10. **Prompt for HITL.** Ask the user to open a fresh session, trigger the skill, and confirm output before committing.
|
||||
|
||||
## Output format
|
||||
|
||||
Two files produced for every skill, plus optional sub-files if the skill requires them:
|
||||
|
||||
- `SKILL.md` — copy-filled from `SKILL-TEMPLATE.md` at `.agents/skills/<name>/SKILL.md`
|
||||
- `META.md` — copy-filled from `META-TEMPLATE.md` at `.agents/skills/<name>/META.md`
|
||||
- `scripts/`, `references/`, or `assets/` — created only when needed; each file wired with an explicit step instruction
|
||||
|
||||
For placeholder conversions, `SKILL.md` replaces the existing file entirely — no partial edits.
|
||||
|
||||
</steps>
|
||||
|
||||
<checks>
|
||||
|
||||
## Failure handling
|
||||
|
||||
- Template file missing — stop, report the path searched, do not write from memory
|
||||
- Existing SKILL.md not found for a placeholder conversion — stop, report the path searched
|
||||
- `write-eval` fails or is unavailable — flag, do not mark the skill complete
|
||||
|
||||
## Self-check
|
||||
|
||||
- [ ] Overlap check completed before any content was written
|
||||
- [ ] Conflict check sub-agent ran against constitution and factory principles — findings resolved before any writing began
|
||||
- [ ] Trigger description tested against all three cases — all passed before body content was written
|
||||
- [ ] Negative trigger cases confirmed by user before testing
|
||||
- [ ] Each section confirmed explicitly by user before SKILL.md was written
|
||||
- [ ] SKILL.md copy-filled from `SKILL-TEMPLATE.md` at correct path
|
||||
- [ ] `META.md` copy-filled from `META-TEMPLATE.md` at correct path
|
||||
- [ ] Frontmatter contains `name`, `description`, and `metadata.category`; optional `allowed-tools` and `model:` only where justified
|
||||
- [ ] Body is under 500 lines
|
||||
- [ ] If sub-files exist: placed in correct directory type (`scripts/`, `references/`, or `assets/`) and wired with an explicit instruction in the relevant step
|
||||
- [ ] For placeholder conversions: existing files read, all stale content removed, old directory deleted if renamed
|
||||
- [ ] `write-eval` invoked — eval file exists at correct path, covers trigger cases (explicit, implicit, negative) and at least one output case
|
||||
|
||||
</checks>
|
||||
118
plugins/kyberforge/tests/evals/create-plugin/eval.yaml
Normal file
118
plugins/kyberforge/tests/evals/create-plugin/eval.yaml
Normal file
@@ -0,0 +1,118 @@
|
||||
skill_name: plugin-create
|
||||
|
||||
trigger_tests:
|
||||
- id: explicit-create-named-plugin
|
||||
name: "Explicit trigger — create named plugin"
|
||||
query: "create a new plugin called security-tools"
|
||||
should_trigger: true
|
||||
|
||||
- id: explicit-scaffold-plugin
|
||||
name: "Explicit trigger — scaffold plugin"
|
||||
query: "scaffold a plugin called developer-tools"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-add-marketplace-plugin
|
||||
name: "Implicit trigger — add plugin to marketplace"
|
||||
query: "add a new plugin to the marketplace for developer tools"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-package-skills-into-plugin
|
||||
name: "Implicit trigger — package skills into a plugin"
|
||||
query: "I want to package up my skills into a distributable plugin"
|
||||
should_trigger: true
|
||||
|
||||
- id: negative-validate-marketplace
|
||||
name: "Negative trigger — validate marketplace.json (routes to /marketplace-architect)"
|
||||
query: "validate my marketplace.json"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-write-skill
|
||||
name: "Negative trigger — write a skill (routes to /write-skill)"
|
||||
query: "write a skill for code review"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-adopt-external-plugin
|
||||
name: "Negative trigger — adopt external plugin (routes to /marketplace-architect)"
|
||||
query: "adopt this external plugin into my marketplace"
|
||||
should_trigger: false
|
||||
|
||||
output_tests:
|
||||
- id: gate-a-shows-plan
|
||||
name: "Gate A presents file list and marketplace entry before writing"
|
||||
type: deterministic
|
||||
prompt: >
|
||||
Create a new plugin called marketplace-tools, description: 'Tools for managing the
|
||||
holocron marketplace', author: Jane Doe, email: jane@example.com,
|
||||
URL: https://github.com/jane
|
||||
expected_output: >
|
||||
The skill presents a plan listing all files that will be written under
|
||||
plugins/marketplace-tools/, shows the new marketplace.json entry, and
|
||||
explicitly asks for approval before writing any file.
|
||||
assertions:
|
||||
- "Output lists files to be written under plugins/marketplace-tools/"
|
||||
- "Output includes a marketplace.json entry with name 'marketplace-tools'"
|
||||
- "Output explicitly asks for approval and does not proceed without it"
|
||||
- "Output does not contain any written file confirmation before approval is given"
|
||||
|
||||
- id: reserved-name-rejected
|
||||
name: "Reserved plugin name is rejected before Gate A"
|
||||
type: deterministic
|
||||
prompt: "Create a new plugin called claude-tools"
|
||||
expected_output: >
|
||||
The skill halts before presenting Gate A, reports that 'claude-tools' matches
|
||||
the reserved name pattern 'claude-*', and asks the user to provide a different name.
|
||||
assertions:
|
||||
- "Output does not present Gate A or a file list"
|
||||
- "Output identifies 'claude-tools' as a reserved name"
|
||||
- "Output asks the user to provide a replacement name before continuing"
|
||||
|
||||
- id: no-skill-stub-generated
|
||||
name: "No skill stub generated — skills/ is empty with README only"
|
||||
type: deterministic
|
||||
prompt: >
|
||||
Create a new plugin called marketplace-tools, description: 'Tools for managing the
|
||||
holocron marketplace', author: Jane Doe, email: jane@example.com,
|
||||
URL: https://github.com/jane. Approve Gate A and Gate B.
|
||||
expected_output: >
|
||||
The plugin scaffold does not include any SKILL.md file. The skills/ directory
|
||||
is present with a README only. The skill directs the user to /write-skill
|
||||
to add skill content.
|
||||
assertions:
|
||||
- "Output does not include creation of any SKILL.md file"
|
||||
- "Output includes a skills/ directory entry with README.md only"
|
||||
- "Output mentions /write-skill for adding skill content"
|
||||
|
||||
- id: handoff-message-present
|
||||
name: "Hand-off message directs user to /marketplace-architect"
|
||||
type: deterministic
|
||||
prompt: >
|
||||
Create a new plugin called marketplace-tools, description: 'Tools for managing the
|
||||
holocron marketplace', author: Jane Doe, email: jane@example.com,
|
||||
URL: https://github.com/jane. Approve Gate A and Gate B.
|
||||
expected_output: >
|
||||
After writing files and running validation, the skill prints a hand-off message
|
||||
that names the created plugin and instructs the user to run /marketplace-architect.
|
||||
assertions:
|
||||
- "Output contains the string '/marketplace-architect'"
|
||||
- "Output includes the plugin name 'marketplace-tools' in the hand-off message"
|
||||
- "Output does not auto-invoke /marketplace-architect itself"
|
||||
|
||||
- id: full-flow-quality
|
||||
name: "Full flow — correct sequencing, substitution, and hand-off"
|
||||
type: llm-rubric
|
||||
prompt: >
|
||||
Create a new plugin called developer-tools with description 'Developer productivity
|
||||
toolkit', author: Alex Smith, email: alex@example.com, URL: https://github.com/alex.
|
||||
Approve Gate A and Gate B.
|
||||
expected_output: >
|
||||
The skill executes the full create-plugin flow: collects all five inputs one at a
|
||||
time, presents Gate A with a file plan, copies and substitutes the template, presents
|
||||
Gate B with full file contents, writes files after Gate B approval, updates
|
||||
marketplace.json, runs validation, and prints a hand-off message.
|
||||
assertions:
|
||||
- "All five inputs (plugin name, description, author name, email, URL) were collected individually before Gate A was presented"
|
||||
- "Gate A clearly listed all files to be written and the new marketplace.json entry, and waited for explicit approval"
|
||||
- "Gate B showed the full substituted content of every file before writing"
|
||||
- "None of the strings PLUGIN_NAME, PLUGIN_DESCRIPTION, AUTHOR_NAME, AUTHOR_EMAIL, or AUTHOR_URL appear in the written file output"
|
||||
- "The validation step either ran claude plugin validate . and reported output, or explicitly stated it was unavailable"
|
||||
- "The final message directed the user to run /marketplace-architect and included the plugin name 'developer-tools'"
|
||||
@@ -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"
|
||||
85
plugins/kyberforge/tests/evals/write-eval/eval.yaml
Normal file
85
plugins/kyberforge/tests/evals/write-eval/eval.yaml
Normal file
@@ -0,0 +1,85 @@
|
||||
skill_name: write-eval
|
||||
|
||||
trigger_tests:
|
||||
- id: explicit-trigger-write-evals
|
||||
name: "Explicit trigger — write evals"
|
||||
query: "write evals for this skill"
|
||||
should_trigger: true
|
||||
|
||||
- id: explicit-trigger-create-eval-yaml
|
||||
name: "Explicit trigger — create eval.yaml"
|
||||
query: "create eval.yaml for the tdd skill"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-trigger-test-coverage
|
||||
name: "Implicit trigger — test coverage request"
|
||||
query: "I need test coverage for the grill-me skill"
|
||||
should_trigger: true
|
||||
|
||||
- id: negative-trigger-run-evals
|
||||
name: "Negative — run evals (runner concern, not writer)"
|
||||
query: "run my evals"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-trigger-code-unit-tests
|
||||
name: "Negative — unit tests for application code"
|
||||
query: "write unit tests for my Python file"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-trigger-debug-failing-eval
|
||||
name: "Negative — debug failing eval"
|
||||
query: "my eval is failing, help me debug it"
|
||||
should_trigger: false
|
||||
|
||||
output_tests:
|
||||
- id: deterministic-correct-output-path
|
||||
name: "Deterministic — eval.yaml written to correct path"
|
||||
type: deterministic
|
||||
prompt: "write evals for the tdd skill"
|
||||
expected_output: "eval.yaml written to .agents/evals/implement/tdd/eval.yaml containing skill_name: tdd"
|
||||
assertions:
|
||||
- "Output references the path .agents/evals/implement/tdd/eval.yaml"
|
||||
- "Output file contains 'skill_name: tdd'"
|
||||
|
||||
- id: deterministic-all-five-types-present
|
||||
name: "Deterministic — eval.yaml contains all five required test types"
|
||||
type: deterministic
|
||||
prompt: "create eval.yaml for the grill-me skill"
|
||||
expected_output: "eval.yaml contains trigger_tests and output_tests sections with all five required test types represented"
|
||||
assertions:
|
||||
- "Output contains 'trigger_tests:'"
|
||||
- "Output contains 'output_tests:'"
|
||||
- "Output contains at least one entry with 'should_trigger: true'"
|
||||
- "Output contains at least one entry with 'should_trigger: false'"
|
||||
- "Output contains at least one entry with 'type: deterministic'"
|
||||
- "Output contains at least one entry with 'type: llm-rubric'"
|
||||
|
||||
- id: deterministic-plan-shown-before-write
|
||||
name: "Deterministic — test plan presented before file is written"
|
||||
type: deterministic
|
||||
prompt: "write evals for the diagnose skill"
|
||||
expected_output: "Skill presents each proposed test case with its id, type, and query before writing any file, then requests confirmation"
|
||||
assertions:
|
||||
- "Response presents each proposed test case individually — showing at minimum the query and test type — before any file is written"
|
||||
- "Response requests confirmation before proceeding to write"
|
||||
|
||||
- id: deterministic-merge-conflict-flagged
|
||||
name: "Deterministic — conflict flagged in plan on re-run with existing eval"
|
||||
type: deterministic
|
||||
prompt: "write evals for the tdd skill — eval.yaml already exists at .agents/evals/implement/tdd/eval.yaml with a test case id 'explicit-trigger-basic'"
|
||||
expected_output: "Skill identifies the existing eval.yaml, classifies the conflicting case as CONFLICT, and does not write until the user resolves it"
|
||||
assertions:
|
||||
- "Response indicates eval.yaml already exists at the target path"
|
||||
- "Response labels the conflicting test case as CONFLICT or equivalent"
|
||||
- "Response does not write the file before the user resolves the conflict"
|
||||
|
||||
- id: llm-rubric-assertion-quality
|
||||
name: "LLM rubric — assertions are specific and verifiable"
|
||||
type: llm-rubric
|
||||
prompt: "write evals for the write-skill skill"
|
||||
expected_output: "eval.yaml contains high-quality assertions that are specific, observable, and not vague"
|
||||
assertions:
|
||||
- "All assertions describe observable, verifiable conditions — not vague quality claims like 'output is good' or 'the response is helpful'"
|
||||
- "Trigger test queries reflect realistic user phrasings, not just the exact skill description verbatim"
|
||||
- "Negative trigger tests target adjacent tasks that share surface-level similarity with the skill's trigger"
|
||||
- "Deterministic assertions are machine-checkable without LLM inference — presence of strings, path patterns, required sections"
|
||||
69
plugins/kyberforge/tests/evals/write-skill/eval.yaml
Normal file
69
plugins/kyberforge/tests/evals/write-skill/eval.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
skill_name: write-skill
|
||||
|
||||
trigger_tests:
|
||||
- id: explicit-trigger-new-skill
|
||||
name: Explicit — new skill phrase
|
||||
query: "Write a new skill for handling database migrations"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-trigger-no-phrase
|
||||
name: Implicit — no trigger phrase
|
||||
query: "I want to add a skill that automates our deploy process"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-trigger-conversion
|
||||
name: Implicit — placeholder conversion
|
||||
query: "The grill-me skill is a Pocock placeholder, can you convert it to our standard?"
|
||||
should_trigger: true
|
||||
|
||||
- id: negative-trigger-upgrade
|
||||
name: Negative — existing skill fix
|
||||
query: "The tdd skill is producing wrong output, fix it"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-trigger-code-refactor
|
||||
name: Negative — code refactor
|
||||
query: "Refactor this module to use the new API client"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-trigger-write-eval
|
||||
name: Negative — eval request
|
||||
query: "Write evals for the diagnose skill"
|
||||
should_trigger: false
|
||||
|
||||
output_tests:
|
||||
- id: output-has-all-sections
|
||||
name: All 8 body sections present in order
|
||||
type: deterministic
|
||||
prompt: "Write a new skill for linting markdown files, category: implement"
|
||||
expected_output: A complete SKILL.md containing all 8 required body sections in the prescribed order.
|
||||
assertions:
|
||||
- "Output contains '## Role'"
|
||||
- "Output contains '## When to use / When not to use'"
|
||||
- "Output contains '## Required inputs'"
|
||||
- "Output contains '## Constraints'"
|
||||
- "Output contains '## Process'"
|
||||
- "Output contains '## Output format'"
|
||||
- "Output contains '## Failure handling'"
|
||||
- "Output contains '## Self-check'"
|
||||
- "Sections appear in this order: ## Role, ## When to use / When not to use, ## Required inputs, ## Constraints, ## Process, ## Output format, ## Failure handling, ## Self-check"
|
||||
|
||||
- id: output-path-correct
|
||||
name: Output path and frontmatter fields correct
|
||||
type: deterministic
|
||||
prompt: "Write a new skill for sending Slack notifications on deploy events, category: deploy"
|
||||
expected_output: A SKILL.md with correct output path stated and all required frontmatter fields present.
|
||||
assertions:
|
||||
- "Output contains '.agents/skills/' in the stated output path"
|
||||
- "Output contains 'metadata:' and 'category:' in frontmatter"
|
||||
- "Output contains 'version:'"
|
||||
- "Output contains 'when:'"
|
||||
|
||||
- id: output-trigger-tested-before-body
|
||||
name: Trigger description tested before body content written
|
||||
type: llm-rubric
|
||||
prompt: "Write a new skill for summarising pull request diffs"
|
||||
expected_output: The skill presents a trigger description and tests it against at least 3 cases (explicit, implicit, negative) before proposing or writing any body section content.
|
||||
assertions:
|
||||
- "The skill proposes a trigger description and explicitly tests it against an explicit query, an implicit query, and a negative query before writing any body section"
|
||||
- "The skill walks through each body section individually and seeks confirmation before writing the file"
|
||||
194
plugins/kyberforge/tests/test_scripts.sh
Executable file
194
plugins/kyberforge/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 ]]
|
||||
Reference in New Issue
Block a user