From 0417b54166bc29bf7899b554d56580d822930e5f Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 05:14:37 +0200 Subject: [PATCH 01/18] feat(marketplace): distribute Magpie 0.2.0 via agent plugin marketplaces Package the framework as a single 'magpie' plugin (skills: ./skills, all 70) and add the manifests each agent marketplace needs, referencing the existing skills tree with no vendored copies: - Claude Code: .claude-plugin/marketplace.json + plugin.json - Codex CLI: .codex-plugin/plugin.json + .agents/plugins/marketplace.json - Copilot: marketplace.json (repo root) - Gemini CLI: gemini-extension.json (skills auto-discovered) - microsoft/apm: apm.yml (type: skill; multiplexes to Claude/Cursor/Codex/Copilot/Gemini) Adds docs/setup/marketplaces.md (per-agent install, incl. Kiro/OpenCode git-install and the Windsurf/Goose non-mappings), positions the marketplace path as a discovery/trial channel alongside /magpie-setup, keeps the ASF source release canonical, and wires the version-bearing manifests into version_manifest_files so release-prepare keeps them in sync. Refs #518. --- .agents/plugins/marketplace.json | 12 +++ .claude-plugin/marketplace.json | 13 +++ .claude-plugin/plugin.json | 19 +++++ .codex-plugin/plugin.json | 6 ++ README.md | 18 ++--- apm.yml | 26 ++++++ docs/setup/marketplaces.md | 83 ++++++++++++++++++++ gemini-extension.json | 5 ++ marketplace.json | 12 +++ projects/magpie/release-management-config.md | 2 +- 10 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .claude-plugin/marketplace.json create mode 100644 .claude-plugin/plugin.json create mode 100644 .codex-plugin/plugin.json create mode 100644 apm.yml create mode 100644 docs/setup/marketplaces.md create mode 100644 gemini-extension.json create mode 100644 marketplace.json diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 000000000..defdb4373 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,12 @@ +{ + "name": "apache-magpie", + "interface": { "displayName": "Apache Magpie" }, + "plugins": [ + { + "name": "magpie", + "source": { "source": "local", "path": "." }, + "category": "maintenance", + "policy": { "installation": "manual", "authentication": "none" } + } + ] +} diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 000000000..24b6940d7 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,13 @@ +{ + "name": "apache-magpie", + "owner": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" }, + "description": "Apache Magpie agentic skills for maintaining open-source projects.", + "plugins": [ + { + "name": "magpie", + "source": ".", + "version": "0.2.0", + "description": "All Apache Magpie skills (release management, security, PR/issue workflows, contributor growth, repo health)." + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 000000000..b9b7d0afc --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,19 @@ +{ + "name": "magpie", + "version": "0.2.0", + "description": "Apache Magpie — a reusable, governance-agnostic framework of agentic skills for maintaining open-source projects: release management, security triage, PR and issue workflows, contributor growth, and repo health.", + "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", + "keywords": [ + "apache", + "agent-skills", + "maintenance", + "release-management", + "security", + "pr-management", + "open-source" + ], + "skills": "./skills" +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 000000000..48229064a --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "magpie", + "version": "0.2.0", + "description": "Apache Magpie — agentic skills for maintaining open-source projects.", + "skills": "./skills" +} diff --git a/README.md b/README.md index 244a2aa3c..98049a1d3 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,11 @@ review, mentoring contributors, managing security reports, and more. Magpie is distributed per the [ASF release policy](https://www.apache.org/legal/release-policy.html) (see [release-distribution](https://infra.apache.org/release-distribution.html) -for the canonical mechanism); you adopt it by pinning a release. - -> [!NOTE] -> A **public skill marketplace is planned but not yet implemented.** -> Today you adopt Magpie directly from the released source, not from a -> marketplace. +for the canonical mechanism). Two ways in: adopt a release with +`/magpie-setup` (full snapshot + override adoption), or install the skills +directly from your agent's plugin marketplace — Claude Code, Codex, Copilot, +Gemini, Cursor, and more. See +[`docs/setup/marketplaces.md`](docs/setup/marketplaces.md). > [!IMPORTANT] > The motivation, scope, and design commitments behind this work @@ -81,9 +80,10 @@ into their repo. That skill manages everything else: [`docs/setup/agentic-overrides.md`](docs/setup/agentic-overrides.md) for the contract. -**No git submodules. No marketplace (yet). No vendored copies of -framework skills.** Just one committed skill (the bootstrap), -a gitignored snapshot, and agent-readable override files. +**No git submodules. No vendored copies of framework skills.** Just one +committed skill (the bootstrap), a gitignored snapshot, and agent-readable +override files. (Prefer a marketplace? See +[`docs/setup/marketplaces.md`](docs/setup/marketplaces.md).) ## Adopting the framework diff --git a/apm.yml b/apm.yml new file mode 100644 index 000000000..14d5d6aca --- /dev/null +++ b/apm.yml @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: Apache-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 +# +# microsoft/apm (Agent Package Manager) package manifest. apm compiles this +# single source to multiple agent clients (Claude, Cursor, Codex, Copilot, +# Gemini). Schema is v0.1 and may change — verify against the current +# https://microsoft.github.io/apm/ before publishing. +name: magpie +version: 0.2.0 +type: skill +description: >- + Apache Magpie — a reusable, governance-agnostic framework of agentic skills + for maintaining open-source projects: release management, security triage, + PR and issue workflows, contributor growth, and repo health. +author: Apache Magpie +license: Apache-2.0 +homepage: https://magpie.apache.org/ +repository: https://github.com/apache/magpie +keywords: + - apache + - agent-skills + - maintenance + - release-management + - security +includes: + - skills/ diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md new file mode 100644 index 000000000..39baceebc --- /dev/null +++ b/docs/setup/marketplaces.md @@ -0,0 +1,83 @@ + + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Installing Apache Magpie from agent marketplaces](#installing-apache-magpie-from-agent-marketplaces) + - [Supported agents](#supported-agents) + - [Not supported](#not-supported) + - [Versioning](#versioning) + - [Verification status](#verification-status) + + + + + +# Installing Apache Magpie from agent marketplaces + +From 0.2.0, Apache Magpie ships manifests so its skills can be installed +through the plugin/extension mechanisms of the major AI coding agents, +**in addition to** the canonical `/magpie-setup` snapshot adoption (see +[`install-recipes.md`](install-recipes.md)). + +> [!IMPORTANT] +> The marketplace path is a **discovery and trial** channel: it drops the +> 70 skills into your agent so you can use them immediately. It does **not** +> set up the full adoption machinery (the committed pin, the gitignored +> snapshot, drift detection, agentic overrides, or the secure-agent setup). +> For a project that adopts Magpie for real, use `/magpie-setup` — the +> marketplace install and full adoption are complementary, not exclusive. + +> [!NOTE] +> The **canonical release** of Apache Magpie remains the signed source +> artefact on `dist.apache.org` per the +> [ASF release policy](https://www.apache.org/legal/release-policy.html). +> Marketplace entries are a convenience layer that reference the released +> `X.Y.Z` git tag; they are derived from — not a substitute for — the ASF +> source release. + +All entries package the whole framework as a single **`magpie`** plugin +that exposes every skill under the `skills/` tree. Skills are then invoked +with the installing agent's namespacing (e.g. `/magpie:release-vote-tally`). + +## Supported agents + +| Agent | Install | Manifest in this repo | +|---|---|---| +| **Claude Code** | `/plugin marketplace add apache/magpie` then `/plugin install magpie@apache-magpie` | `.claude-plugin/marketplace.json`, `.claude-plugin/plugin.json` | +| **OpenAI Codex CLI** | `codex plugin marketplace add apache/magpie` then install `magpie` | `.codex-plugin/plugin.json`, `.agents/plugins/marketplace.json` | +| **GitHub Copilot** | add the repo as a plugin marketplace, then install `magpie` | `marketplace.json` (repo root) | +| **Google Gemini CLI** | `gemini extensions install https://github.com/apache/magpie` | `gemini-extension.json` | +| **Cursor** | add via the plugin/skill install flow pointing at the repo | consumes the plugin manifests above | +| **microsoft/apm** | `apm install apache/magpie` (compiles to Claude/Cursor/Codex/Copilot/Gemini) | `apm.yml` | +| **Kiro** | install from the GitHub subdirectory of a skill (Kiro installs per-skill from a subdir, not the repo root) | native `skills//SKILL.md` | +| **OpenCode** | drop the skills into `.opencode/skills/`, or use a community installer against this repo | native `skills//SKILL.md` | + +### Not supported + +- **Windsurf** — has no skills/rules marketplace; project rules are plain + `.windsurfrules` files with no install mechanism. Skills would have to be + converted by hand; there is no distribution channel. +- **Goose (Block)** — its extension registry is Model Context Protocol + (MCP) servers, not `SKILL.md` skills. Distributing Magpie there would + require wrapping skills behind an MCP server (a rebuild, not packaging). + +## Versioning + +The plugin version tracks the framework release (`0.2.0`). The manifest +version strings are kept in sync with `pyproject.toml` by the +`release-prepare` version bump — see +[`release-management-config.md`](../../projects/magpie/release-management-config.md) +(`version_manifest_files`). + +## Verification status + +The Claude Code and Gemini CLI manifests follow the current published +schemas. The Codex CLI, GitHub Copilot, and `apm` (schema v0.1) formats +move quickly; re-check each against the vendor's current documentation +before a marketplace publish. Manifests that fail live validation should be +fixed here and re-released — they never change how the ASF source release +is built or signed. diff --git a/gemini-extension.json b/gemini-extension.json new file mode 100644 index 000000000..116dd7b24 --- /dev/null +++ b/gemini-extension.json @@ -0,0 +1,5 @@ +{ + "name": "magpie", + "version": "0.2.0", + "description": "Apache Magpie — agentic skills for maintaining open-source projects. Skills are auto-discovered from ./skills." +} diff --git a/marketplace.json b/marketplace.json new file mode 100644 index 000000000..798cfcb6e --- /dev/null +++ b/marketplace.json @@ -0,0 +1,12 @@ +{ + "name": "apache-magpie", + "owner": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" }, + "description": "Apache Magpie agentic skills for maintaining open-source projects (GitHub Copilot plugin marketplace catalog).", + "plugins": [ + { + "name": "magpie", + "source": ".", + "description": "All Apache Magpie skills (release management, security, PR/issue workflows, contributor growth, repo health)." + } + ] +} diff --git a/projects/magpie/release-management-config.md b/projects/magpie/release-management-config.md index 7d084310d..f7b856b98 100644 --- a/projects/magpie/release-management-config.md +++ b/projects/magpie/release-management-config.md @@ -71,7 +71,7 @@ mandatory ASF approval + announce mechanisms (`dev-list-vote`, | `git_upstream_remote` | `upstream` | | `release_planning_issue_template` | *(none — uses the `release-prepare` default template)* | | `release_branch_base` | `main` | -| `version_manifest_files` | `pyproject.toml` | +| `version_manifest_files` | `pyproject.toml`, `uv.lock`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, `.codex-plugin/plugin.json`, `gemini-extension.json`, `apm.yml` | ## Backends From a749ef55032a20c1677a234af892229c53a88f6e Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 05:19:03 +0200 Subject: [PATCH 02/18] feat(marketplace): auto-detect plugin updates and prompt for upgrade Add a Claude Code SessionStart hook (hooks/check-upgrade.sh, wired via the plugin's hooks block) that detects when the marketplace has updated the Magpie plugin to a new version and prompts the user to run `/magpie-setup upgrade` to reconcile the snapshot, agentic overrides, and drift. Detect-and-prompt, not auto-run: Claude Code hooks cannot invoke a slash command, and Magpie never mutates an adopter repo without the guided skill's confirmation, so the trigger is automatic while the changes stay confirmed. Claude Code-only; Codex/Gemini have no equivalent lifecycle hook. --- .claude-plugin/plugin.json | 16 ++++++++++++- docs/setup/marketplaces.md | 21 +++++++++++++++++ hooks/check-upgrade.sh | 47 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100755 hooks/check-upgrade.sh diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index b9b7d0afc..12c029ba4 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -15,5 +15,19 @@ "pr-management", "open-source" ], - "skills": "./skills" + "skills": "./skills", + "hooks": { + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/check-upgrade.sh", + "timeout": 10 + } + ] + } + ] + } } diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index 39baceebc..dfaf66620 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -8,6 +8,7 @@ - [Installing Apache Magpie from agent marketplaces](#installing-apache-magpie-from-agent-marketplaces) - [Supported agents](#supported-agents) - [Not supported](#not-supported) + - [Automatic upgrade detection (Claude Code)](#automatic-upgrade-detection-claude-code) - [Versioning](#versioning) - [Verification status](#verification-status) @@ -65,6 +66,26 @@ with the installing agent's namespacing (e.g. `/magpie:release-vote-tally`). (MCP) servers, not `SKILL.md` skills. Distributing Magpie there would require wrapping skills behind an MCP server (a rebuild, not packaging). +## Automatic upgrade detection (Claude Code) + +The Claude Code plugin ships a `SessionStart` hook +([`hooks/check-upgrade.sh`](../../hooks/check-upgrade.sh)) that fires when a +session starts. It compares the installed plugin version against a marker in +the plugin's persistent data directory and, when the marketplace has updated +the plugin to a new version, prompts you to run **`/magpie-setup upgrade`** — +which reconciles the gitignored snapshot, the agentic overrides, and drift. + +The hook **detects and prompts**; it does not run the upgrade itself. Claude +Code hooks cannot invoke a slash command, and — by design — Magpie never +mutates an adopter repo without the guided skill's confirmation, so the +*trigger* is automatic while the *changes* stay confirmed. The hook is +read-only apart from writing its own version marker, makes no network calls, +and touches nothing in the adopter repo. + +This lifecycle wiring is **Claude Code-only**: Codex CLI and Gemini CLI have +no equivalent plugin update/lifecycle hook, so on those agents re-run +`/magpie-setup upgrade` yourself after updating the package. + ## Versioning The plugin version tracks the framework release (`0.2.0`). The manifest diff --git a/hooks/check-upgrade.sh b/hooks/check-upgrade.sh new file mode 100755 index 000000000..db7823580 --- /dev/null +++ b/hooks/check-upgrade.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Claude Code SessionStart hook for the Apache Magpie plugin. +# +# Detects when the installed plugin version has changed since the last +# session (i.e. the marketplace updated it) and prompts the user to run +# `/magpie-setup upgrade`, which reconciles the gitignored snapshot, the +# agentic overrides, and drift. +# +# Deliberately DETECT-AND-PROMPT, not auto-run: a Claude Code hook cannot +# invoke a slash command, and Magpie never mutates an adopter repo without +# the guided skill's confirmation. The hook is read-only apart from writing +# its own version marker; it makes no network calls and touches nothing in +# the adopter repo. +set -euo pipefail + +# Drain the event JSON delivered on stdin (unused). +cat >/dev/null 2>&1 || true + +root="${CLAUDE_PLUGIN_ROOT:-.}" +data="${CLAUDE_PLUGIN_DATA:-$root}" +manifest="$root/.claude-plugin/plugin.json" + +[ -f "$manifest" ] || exit 0 + +# Extract "version" without a jq dependency. +current="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest" | head -n1)" +[ -n "$current" ] || exit 0 + +marker="$data/magpie-plugin-version" +stored="" +[ -f "$marker" ] && stored="$(cat "$marker" 2>/dev/null || true)" + +if [ "$current" != "$stored" ]; then + mkdir -p "$data" 2>/dev/null || true + printf '%s' "$current" >"$marker" 2>/dev/null || true + + if [ -n "$stored" ]; then + echo "Apache Magpie plugin updated ($stored -> $current). If this repo adopts Magpie, run \`/magpie-setup upgrade\` to reconcile the snapshot, agentic overrides, and drift." >&2 + else + echo "Apache Magpie plugin $current is active. If this repo already adopts Magpie, run \`/magpie-setup upgrade\` to reconcile; otherwise run \`/magpie-setup\` to adopt." >&2 + fi +fi + +exit 0 From e3a9006914be01a65f89cfea37147ef25a53653a Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 05:30:27 +0200 Subject: [PATCH 03/18] feat(marketplace): prompt Codex and Gemini users to run upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the update-detection prompt beyond Claude Code: - Make hooks/check-upgrade.sh agent-neutral (resolves CLAUDE_/CODEX_ plugin root+data env vars; reads version from whichever plugin manifest is present). - Codex CLI: wire the same script via the plugin's hooks block (schema best-effort, flagged for verification before publish). - Gemini CLI: ship GEMINI.md extension context (contextFileName) instructing the agent to compare the extension version to a recorded marker and prompt for /magpie-setup upgrade on change — Gemini has no lifecycle hook. Documented per-agent in docs/setup/marketplaces.md. --- .codex-plugin/plugin.json | 15 ++++++++++++++- GEMINI.md | 38 +++++++++++++++++++++++++++++++++++++ docs/setup/marketplaces.md | 39 +++++++++++++++++++------------------- gemini-extension.json | 3 ++- hooks/check-upgrade.sh | 32 +++++++++++++++++++------------ 5 files changed, 93 insertions(+), 34 deletions(-) create mode 100644 GEMINI.md diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 48229064a..ac6a827cc 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -2,5 +2,18 @@ "name": "magpie", "version": "0.2.0", "description": "Apache Magpie — agentic skills for maintaining open-source projects.", - "skills": "./skills" + "skills": "./skills", + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "${CODEX_PLUGIN_ROOT}/hooks/check-upgrade.sh", + "timeout": 10 + } + ] + } + ] + } } diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 000000000..af5373d2c --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,38 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Apache Magpie — Gemini CLI extension context](#apache-magpie--gemini-cli-extension-context) + - [Upgrade prompt](#upgrade-prompt) + + + + + +# Apache Magpie — Gemini CLI extension context + +This context ships with the Apache Magpie Gemini CLI extension. Gemini has no +plugin update/lifecycle hook (unlike Claude Code), so this file is how the +extension prompts you to reconcile after an update. + +## Upgrade prompt + +The Magpie extension declares its version in `gemini-extension.json`. At the +start of a session, compare that `version` against the value you last recorded +in `magpie-plugin-version` under the extension's state directory (create the +file if it does not exist): + +- If the version has **changed** since you last recorded it — the marketplace + updated the extension — tell the user, once and non-intrusively: + + > Apache Magpie was updated to ``. If this repo adopts Magpie, run + > `/magpie-setup upgrade` to reconcile the snapshot, agentic overrides, and + > drift. + + Then record the new version. + +- If the version is unchanged, say nothing. + +Never run the upgrade yourself: `/magpie-setup upgrade` is a guided, +user-confirmed action, and Magpie never mutates an adopter repo unattended. diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index dfaf66620..0f04317fa 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -8,7 +8,7 @@ - [Installing Apache Magpie from agent marketplaces](#installing-apache-magpie-from-agent-marketplaces) - [Supported agents](#supported-agents) - [Not supported](#not-supported) - - [Automatic upgrade detection (Claude Code)](#automatic-upgrade-detection-claude-code) + - [Automatic upgrade detection](#automatic-upgrade-detection) - [Versioning](#versioning) - [Verification status](#verification-status) @@ -66,25 +66,24 @@ with the installing agent's namespacing (e.g. `/magpie:release-vote-tally`). (MCP) servers, not `SKILL.md` skills. Distributing Magpie there would require wrapping skills behind an MCP server (a rebuild, not packaging). -## Automatic upgrade detection (Claude Code) - -The Claude Code plugin ships a `SessionStart` hook -([`hooks/check-upgrade.sh`](../../hooks/check-upgrade.sh)) that fires when a -session starts. It compares the installed plugin version against a marker in -the plugin's persistent data directory and, when the marketplace has updated -the plugin to a new version, prompts you to run **`/magpie-setup upgrade`** — -which reconciles the gitignored snapshot, the agentic overrides, and drift. - -The hook **detects and prompts**; it does not run the upgrade itself. Claude -Code hooks cannot invoke a slash command, and — by design — Magpie never -mutates an adopter repo without the guided skill's confirmation, so the -*trigger* is automatic while the *changes* stay confirmed. The hook is -read-only apart from writing its own version marker, makes no network calls, -and touches nothing in the adopter repo. - -This lifecycle wiring is **Claude Code-only**: Codex CLI and Gemini CLI have -no equivalent plugin update/lifecycle hook, so on those agents re-run -`/magpie-setup upgrade` yourself after updating the package. +## Automatic upgrade detection + +When the marketplace updates the plugin to a new version, Magpie prompts you +to run **`/magpie-setup upgrade`** — which reconciles the gitignored snapshot, +the agentic overrides, and drift. This is **detect-and-prompt, not auto-run**: +a plugin hook cannot invoke a slash command, and Magpie never mutates an +adopter repo without the guided skill's confirmation, so the *trigger* is +automatic while the *changes* stay confirmed. + +| Agent | Mechanism | +|---|---| +| **Claude Code** | `SessionStart` hook [`hooks/check-upgrade.sh`](../../hooks/check-upgrade.sh) compares the installed version to a marker in the plugin's persistent data dir and prompts on change. Deterministic. | +| **Codex CLI** | The same [`hooks/check-upgrade.sh`](../../hooks/check-upgrade.sh) wired via the plugin's `hooks` block. Codex's hook schema is **not yet verified** — confirm before publish. | +| **Gemini CLI** | No lifecycle hook; the extension context file [`GEMINI.md`](../../GEMINI.md) instructs the agent to compare the extension version to a recorded marker and prompt on change (LLM-driven, advisory). | +| Other agents | Re-run `/magpie-setup upgrade` manually after updating the package. | + +The hook script is read-only apart from writing its own version marker, makes +no network calls, and touches nothing in the adopter repo. ## Versioning diff --git a/gemini-extension.json b/gemini-extension.json index 116dd7b24..b93946885 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,5 +1,6 @@ { "name": "magpie", "version": "0.2.0", - "description": "Apache Magpie — agentic skills for maintaining open-source projects. Skills are auto-discovered from ./skills." + "description": "Apache Magpie — agentic skills for maintaining open-source projects. Skills are auto-discovered from ./skills.", + "contextFileName": "GEMINI.md" } diff --git a/hooks/check-upgrade.sh b/hooks/check-upgrade.sh index db7823580..f3256a722 100755 --- a/hooks/check-upgrade.sh +++ b/hooks/check-upgrade.sh @@ -2,30 +2,38 @@ # SPDX-License-Identifier: Apache-2.0 # https://www.apache.org/licenses/LICENSE-2.0 # -# Claude Code SessionStart hook for the Apache Magpie plugin. +# Session-start hook for the Apache Magpie plugin. Wired by the Claude Code +# plugin (SessionStart) and — best-effort, pending schema verification — by +# the Codex CLI plugin, where a session hook is available. # # Detects when the installed plugin version has changed since the last # session (i.e. the marketplace updated it) and prompts the user to run # `/magpie-setup upgrade`, which reconciles the gitignored snapshot, the # agentic overrides, and drift. # -# Deliberately DETECT-AND-PROMPT, not auto-run: a Claude Code hook cannot -# invoke a slash command, and Magpie never mutates an adopter repo without -# the guided skill's confirmation. The hook is read-only apart from writing -# its own version marker; it makes no network calls and touches nothing in -# the adopter repo. +# Deliberately DETECT-AND-PROMPT, not auto-run: a plugin hook cannot invoke +# a slash command, and Magpie never mutates an adopter repo without the +# guided skill's confirmation. The hook is read-only apart from writing its +# own version marker; it makes no network calls and touches nothing in the +# adopter repo. +# +# Agents without a session hook (e.g. Gemini CLI) surface the same prompt via +# their extension context file (GEMINI.md) instead. set -euo pipefail -# Drain the event JSON delivered on stdin (unused). +# Drain any event JSON delivered on stdin (unused). cat >/dev/null 2>&1 || true -root="${CLAUDE_PLUGIN_ROOT:-.}" -data="${CLAUDE_PLUGIN_DATA:-$root}" -manifest="$root/.claude-plugin/plugin.json" +root="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-.}}" +data="${CLAUDE_PLUGIN_DATA:-${CODEX_PLUGIN_DATA:-$root/.magpie-state}}" -[ -f "$manifest" ] || exit 0 +# Read the plugin version from whichever manifest is present. +manifest="" +for m in "$root/.claude-plugin/plugin.json" "$root/.codex-plugin/plugin.json"; do + [ -f "$m" ] && { manifest="$m"; break; } +done +[ -n "$manifest" ] || exit 0 -# Extract "version" without a jq dependency. current="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest" | head -n1)" [ -n "$current" ] || exit 0 From 4f6223e48196e42cacb766e67267a9d7f4609eea Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 16:14:10 +0200 Subject: [PATCH 04/18] docs(readme): fold #901 README simplification; lead with install (adopt alias) Folds the top-level README simplification from #901 (by @meonkeys) into the 0.2.0 marketplace work, and shifts the primary term to 'install' now that marketplace installation exists: - Keeps Adam's simplified Install / Usage / Update / Skill-families structure. - Install section covers both paths: agent marketplace (easiest, nothing committed) and the committed snapshot via /magpie-setup. - 'install' is the primary verb/command; '/magpie-setup adopt' stays an alias; 'Adopt a Magpie' remains the landing-page slogan; 'adopter'/'adopter repo' kept as the ecosystem noun. - Repoints inbound doc links from the removed README#adopting-the-framework anchor to README#install and updates their labels. Supersedes the standalone #901 (its Fix #899 goal already merged via #905). Credit to @meonkeys for the simplification. --- README.md | 215 +++++++++----------------------- docs/index.md | 4 +- docs/issue-management/README.md | 2 +- docs/pr-management/README.md | 2 +- docs/security/README.md | 2 +- docs/setup/README.md | 2 +- docs/setup/unadopt.md | 2 +- 7 files changed, 67 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index 98049a1d3..204d21f4a 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,11 @@ **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Apache Magpie](#apache-magpie) - - [How adoption works](#how-adoption-works) - - [Adopting the framework](#adopting-the-framework) - - [1. Bootstrap (copy-pasteable shell)](#1-bootstrap-copy-pasteable-shell) - - [2. Skill takeover](#2-skill-takeover) - - [Subsequent contributors](#subsequent-contributors) - - [Drift detection](#drift-detection) + - [Install](#install) + - [Usage](#usage) + - [Update / maintain](#update--maintain) - [Skill families](#skill-families) - [External skill sources](#external-skill-sources) - - [Maintenance](#maintenance) - [Acknowledgements](#acknowledgements) - [Cross-references](#cross-references) @@ -27,155 +23,77 @@ [![Magpie](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/apache/magpie/main/assets/badge.json)](https://magpie.apache.org/) -Apache Magpie provides high-quality recipes for agent-assisted software project -maintenance. +**Adopt a Magpie.** Apache Magpie provides high-quality recipes for +agent-assisted software project maintenance. These recipes allow human maintainers working with AIs to efficiently handle -the repetitive parts of running an open-source project: Issue triage, PR +the repetitive parts of running an open-source project: issue triage, PR review, mentoring contributors, managing security reports, and more. -Magpie is distributed per the [ASF release policy](https://www.apache.org/legal/release-policy.html) -(see [release-distribution](https://infra.apache.org/release-distribution.html) -for the canonical mechanism). Two ways in: adopt a release with -`/magpie-setup` (full snapshot + override adoption), or install the skills -directly from your agent's plugin marketplace — Claude Code, Codex, Copilot, -Gemini, Cursor, and more. See -[`docs/setup/marketplaces.md`](docs/setup/marketplaces.md). +Magpie is currently in development for ASF projects + Python Core team +friendlies. Testers welcome! > [!IMPORTANT] > The motivation, scope, and design commitments behind this work > live in [`MISSION.md`](MISSION.md) — the founding mission of the > Apache Magpie Top-Level Project, originally filed as its > establishment proposal. Read that for the *why*; this README is -> the *how* once you've decided to adopt. - -## How adoption works - -The framework uses a **snapshot + agentic-override** adoption -model. An adopter project commits a single skill — -[`setup`](skills/setup/SKILL.md) — -into their repo. That skill manages everything else: - -1. **Snapshot.** `setup` downloads the framework into - a **gitignored** `/.apache-magpie/` directory. - The snapshot is a build artefact, not source — refreshed - by `/magpie-setup upgrade`, never committed. -2. **Symlinks.** `setup` symlinks the framework's - skills (security, pr-management, the rest of setup) under - one canonical home — `.agents/skills/` (the path shared by - Codex, Cursor, Gemini CLI, Copilot, …) — and gives every - other agent dir (`.claude/skills/`, `.github/skills/`, …) a - thin per-skill **relay** symlink pointing back at the - canonical entry. This is the same regardless of how the - adopter previously organised those dirs. The symlinks are - **also gitignored** — they ultimately target the gitignored - snapshot, so they would dangle on a fresh clone before - `/magpie-setup` runs. -3. **Overrides.** Adopter-specific modifications to framework - workflows live as agent-readable markdown under - `/.apache-magpie-overrides/.md`, - **committed** in the adopter repo. The framework's skills - consult those files at run-time and apply the overrides - before executing default behaviour. See - [`docs/setup/agentic-overrides.md`](docs/setup/agentic-overrides.md) - for the contract. - -**No git submodules. No vendored copies of framework skills.** Just one -committed skill (the bootstrap), a gitignored snapshot, and agent-readable -override files. (Prefer a marketplace? See -[`docs/setup/marketplaces.md`](docs/setup/marketplaces.md).) - -## Adopting the framework - -Two phases — a **shell bootstrap** that gets `setup` -into your repo, then the **skill takeover** that wires up the -rest interactively. - -### 1. Bootstrap (copy-pasteable shell) - -Pick an install method and follow the verbatim recipe in -[**`docs/setup/install-recipes.md`**](docs/setup/install-recipes.md): - -| Method | When to use | Reproducibility | -|---|---|---| -| `svn-zip` | Production once ASF official releases ship to `dist.apache.org` (signed + checksummed) | Frozen by version | -| `git-tag` | Pin a specific framework version | Frozen by tag | -| `git-branch` (default `main`) | WIP path — track the framework's `main` directly for the latest unreleased changes. | Tracks tip | - -Each recipe is a single shell block that: - -1. Adds `.apache-magpie/`, `.apache-magpie.local.lock`, and - the framework-skill symlinks to `.gitignore`. -2. Downloads + verifies + extracts the framework into - `.apache-magpie/` (gitignored — build artefact, not - source). -3. Copies the - [`setup`](skills/setup/SKILL.md) - skill into the canonical `.agents/skills/magpie-setup/` and - adds a relay symlink to it from each agent dir you use - (`.claude/skills/magpie-setup`, `.github/skills/magpie-setup`). - -After the recipe completes, the framework snapshot is on -disk and the bootstrap skill is in your repo. - -### 2. Skill takeover - -Tell your agent: **"adopt apache/magpie in my repo"** -(or invoke `/magpie-setup` directly). The skill walks -through the rest: - -- writes `.apache-magpie.lock` (committed) — the project's - pin: install method + URL + ref + verification anchor; -- writes `.apache-magpie.local.lock` (gitignored) — what - this machine actually fetched + when; -- asks which skill families (`security`, `pr-management`) to - symlink in; -- creates the gitignored framework-skill symlinks; -- scaffolds `.apache-magpie-overrides/` (committed) for any - local workflow modifications; -- installs a `post-checkout` git hook so worktrees re-create - runtime state automatically; -- updates your project documentation with a brief mention. - -After the skill finishes, you commit the small, focused -diff — the bootstrap skill, the `.gitignore` entries, the -two lock files (committed + gitignore exclusion for the -local one), the overrides scaffold, the doc note — and you're -done. Open a PR. - -### Subsequent contributors - -Future contributors who clone your repo just say "adopt -Magpie in this repo" (or invoke `/magpie-setup`). -The skill reads `.apache-magpie.lock` (already committed) -and re-installs to the same version your project pinned. No -need to redo the manual recipe — the committed lock is the -project's source-of-truth. - -### Drift detection - -Every framework skill compares the gitignored -`.apache-magpie.local.lock` against the committed -`.apache-magpie.lock` at the top of its run. If they have -drifted (project lead bumped the pin, or the local install -is stale on a `main`-tracking adopter), the skill surfaces -the gap and proposes `/magpie-setup upgrade`. `upgrade` -deletes the gitignored snapshot, re-installs per the -committed pin, refreshes the gitignored symlinks, and -reconciles any agentic overrides — see -[`docs/setup/install-recipes.md`](docs/setup/install-recipes.md) -and -[`skills/setup/upgrade.md`](skills/setup/upgrade.md) -for the full flow. +> the *how* once you've decided to install. + +## Install + +Magpie's recipes are plain-text instructions followed by an AI agent you +control. There are two ways to install them: + +**From an agent marketplace (easiest).** Install the skills directly into your +agent — Claude Code, Codex, Copilot, Gemini, Cursor, and more — with nothing +committed to your repository. See +[`docs/setup/marketplaces.md`](docs/setup/marketplaces.md). + +**As a committed snapshot (full install into your repo).** For a project that +installs Magpie as part of its own source — pin a release and let +`/magpie-setup` set up the snapshot, overrides, and drift detection in the +adopter repo: + +1. [Download / pin a release](https://magpie.apache.org/downloads/) +2. Set up the symlinks and git-ignores — see + [`docs/setup/install-recipes.md`](docs/setup/install-recipes.md) +3. Ask your agent to complete the install: `/magpie-setup install` + (`/magpie-setup adopt` is an alias) + +## Usage + +Magpie is used by interacting with your AI agents. You'll use plain-language +prompts like + +> review PR #5193 + +or + +> triage the latest security reports + +or skill calls starting with a slash, like + +> /dependency-audit + +## Update / maintain + +- `/magpie-setup upgrade` — refresh the snapshot to a newer + framework version + reconcile any overrides against the new + framework structure. +- `/magpie-setup verify` — read-only health check (snapshot + intact, symlinks live, `.gitignore` correct, etc.). +- `/magpie-setup override ` — open or + scaffold an override file for a framework skill. ## Skill families -Ten skill families ship in the framework, all at `experimental` or +The following skill families ship in the framework, all at `experimental` or `stable`, and each skill declares its family in a `family:` frontmatter -key. At adoption (and on every upgrade), `/magpie-setup` offers the +key. At install (and on every upgrade), `/magpie-setup` offers the **opt-in** families — and the optional **MCP servers** (`ponymail`, `apache-projects`, `gmail-plaintext`) — in a single install choice; -symlinks for the picked families land in the adopter's skill directory. +symlinks for the picked families land in the skill directory. The two **always-on** families (`setup`, `utilities`) are wired unconditionally and never prompted for. @@ -185,7 +103,7 @@ means and which modes are still proposed vs. shipping today. | Family | Type | Modes | Purpose | Detail | |---|---|---|---|---| -| [**setup**](docs/setup/README.md) | always-on | (infra) | Isolated agent setup, framework adoption + maintenance, shared-config sync. The prerequisite — at minimum the `setup` skill itself runs out of this family. | 9 skills, [`docs/setup/`](docs/setup/) | +| [**setup**](docs/setup/README.md) | always-on | (infra) | Isolated agent setup, framework install + maintenance, shared-config sync. The prerequisite — at minimum the `setup` skill itself runs out of this family. | 9 skills, [`docs/setup/`](docs/setup/) | | **utilities** | always-on | (meta) | Framework meta-skills: author skills (`write-skill`), restructure them (`optimize-skill`), reconcile skill state (`skill-reconciler`), and print a live index (`list-skills`). | 4 skills | | [**security**](docs/security/README.md) | opt-in | Triage, Drafting | 16-step security-issue handling lifecycle — from `security@` import through CVE publication, including state sync. Maintainer-only. | 12 skills, [`docs/security/`](docs/security/) | | [**pr-management**](docs/pr-management/README.md) | opt-in | Triage | Maintainer-facing PR-queue management — triage, stats, deep code review, express-lane merge, stale-sweep, reviewer routing, and pre-first-PR checks. | 8 skills, [`docs/pr-management/`](docs/pr-management/README.md) | @@ -198,7 +116,7 @@ means and which modes are still proposed vs. shipping today. ### External skill sources -Beyond the in-tree families, an adopter can pull a skill or whole family +Skill families or individual skills can be pulled from a **trusted external source** — a repo other than `apache/magpie` that ships Magpie-shaped skills (with their evals and tests). Where a skill directory would sit, a `skills//source.md` **redirect** names a @@ -209,19 +127,6 @@ skill. Nothing is fetched unless the adopter commits the pin — see [`PRINCIPLES.md` §13](PRINCIPLES.md#13-snapshot-plus-override-never-vendored-copies), and [`RFC-AI-0006`](docs/rfcs/RFC-AI-0006.md). -## Maintenance - -After the initial adoption, the same skill handles ongoing -maintenance: - -- `/magpie-setup upgrade` — refresh the snapshot to a newer - framework version + reconcile any overrides against the new - framework structure. -- `/magpie-setup verify` — read-only health check (snapshot - intact, symlinks live, `.gitignore` correct, etc.). -- `/magpie-setup override ` — open or - scaffold an override file for a framework skill. - ## Acknowledgements Apache Magpie was first developed and proven inside **Apache Airflow**, and was diff --git a/docs/index.md b/docs/index.md index c4a85e3e1..f47ad5d3f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -78,7 +78,7 @@ Start with [`setup`](setup/README.md) regardless — it is the prerequisite ever You have an open-source project with an issue tracker and/or PR queue, and you want agent assistance with the mechanical parts. -→ Start with the [README](../README.md#adopting-the-framework) (adoption steps) and [install recipes](setup/install-recipes.md). +→ Start with the [README](../README.md#install) (install steps) and [install recipes](setup/install-recipes.md). ### Security team members @@ -128,7 +128,7 @@ from mailing lists, slack etc. | Pull a skill/family from a trusted external source | [skill-sources/README.md](skill-sources/README.md) | | Extend Magpie (project / org / individual) | [extending.md](extending.md) | | See what skills exist today | [modes.md](modes.md) | -| Adopt in my project | [README → Adopting](../README.md#adopting-the-framework) | +| Install in my project | [README → Install](../README.md#install) | | Set up the secure agent sandbox | [setup/](setup/README.md) | | Understand the security workflow | [security/](security/README.md) | | Know what it costs to run | [mode-economics.md](mode-economics.md) | diff --git a/docs/issue-management/README.md b/docs/issue-management/README.md index 7283ea5c6..0a91550da 100644 --- a/docs/issue-management/README.md +++ b/docs/issue-management/README.md @@ -128,7 +128,7 @@ uv run --project tools/pilot-report-validator pilot-report-validate /` layout, ## Cross-references -- [Top-level README — Adopting the framework](../../README.md#adopting-the-framework) — 3-step bootstrap. +- [Top-level README — Install](../../README.md#install) — 3-step bootstrap. - [`projects/_template/README.md`](../../projects/_template/README.md) — adopter scaffold index, including the PR-management config files. - [`tools/spec-loop/specs/pr-management-family.md`](../../tools/spec-loop/specs/pr-management-family.md) — functional spec: acceptance criteria, validation commands, and known gaps. - [`docs/mentoring/README.md`](../mentoring/README.md) — `pr-management-mentor` family overview. diff --git a/docs/security/README.md b/docs/security/README.md index acd2ae8b1..cf1345f43 100644 --- a/docs/security/README.md +++ b/docs/security/README.md @@ -113,7 +113,7 @@ Optional but commonly needed: ## Cross-references -- [Top-level README — Adopting the framework](../../README.md#adopting-the-framework) — 3-step bootstrap. +- [Top-level README — Install](../../README.md#install) — 3-step bootstrap. - [`docs/prerequisites.md`](../prerequisites.md) — what a security triager / remediation developer / release manager needs installed before invoking any skill. diff --git a/docs/setup/README.md b/docs/setup/README.md index 134c312db..5e95f2886 100644 --- a/docs/setup/README.md +++ b/docs/setup/README.md @@ -108,6 +108,6 @@ file under `~/.claude-config/`). ## Cross-references -- [Top-level README — Adopting the framework](../../README.md#adopting-the-framework) — 3-step bootstrap. +- [Top-level README — Install](../../README.md#install) — 3-step bootstrap. - [`docs/prerequisites.md`](../prerequisites.md) — what each framework skill needs (Claude Code, Gmail MCP, GitHub auth, browser, etc.). diff --git a/docs/setup/unadopt.md b/docs/setup/unadopt.md index 6e8ff62dd..0443643c1 100644 --- a/docs/setup/unadopt.md +++ b/docs/setup/unadopt.md @@ -237,7 +237,7 @@ a first-time adopter takes. - [`docs/setup/README.md`](README.md) — the setup skill family overview (verify, upgrade, shared-config sync). -- [Top-level README — Adopting the framework](../../README.md#adopting-the-framework) +- [Top-level README — Install](../../README.md#install) — the original 3-step bootstrap. - [`install-recipes.md`](install-recipes.md) — the counterpart to this page. From 94afe9fe2b994d4d3c05a3577a5b6c22656be596 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 16:33:27 +0200 Subject: [PATCH 05/18] docs(readme): project adopts Magpie; installation is the mechanism Keep 'adopt' for the project relationship and 'install/installation' for the process, per the terminology model: a project adopts Magpie into its source; /magpie-setup installs the snapshot. --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 204d21f4a..be6f9ccd6 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,9 @@ agent — Claude Code, Codex, Copilot, Gemini, Cursor, and more — with nothing committed to your repository. See [`docs/setup/marketplaces.md`](docs/setup/marketplaces.md). -**As a committed snapshot (full install into your repo).** For a project that -installs Magpie as part of its own source — pin a release and let -`/magpie-setup` set up the snapshot, overrides, and drift detection in the -adopter repo: +**As a committed snapshot (installed into your repo).** For a project that +adopts Magpie into its own source — pin a release and let `/magpie-setup` +install the snapshot, overrides, and drift detection in the adopter repo: 1. [Download / pin a release](https://magpie.apache.org/downloads/) 2. Set up the symlinks and git-ignores — see From 4a9e1f57df834cc61dafe09efc8296ef6a54af96 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 16:39:24 +0200 Subject: [PATCH 06/18] docs(readme): explain adopt (relationship) vs install (process) in the Install section --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index be6f9ccd6..b63e7f7dd 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ friendlies. Testers welcome! ## Install -Magpie's recipes are plain-text instructions followed by an AI agent you -control. There are two ways to install them: +You **adopt** Magpie once — the decision to bring the framework into your +project — while **installation** is how you carry that out. There are two +ways to install: **From an agent marketplace (easiest).** Install the skills directly into your agent — Claude Code, Codex, Copilot, Gemini, Cursor, and more — with nothing From a5cc86d5bbd1958e0ce1e811e7e04b2708923bef Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 16:45:24 +0200 Subject: [PATCH 07/18] docs(marketplaces): detailed per-agent install steps from the GitHub marketplace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand docs/setup/marketplaces.md from a one-line-per-agent table into step-by-step instructions for installing Magpie from the apache/magpie GitHub repo-as-marketplace: add marketplace, install, verify, invoke, update — for Claude Code, Codex, Copilot, Gemini, Cursor, apm, Kiro, OpenCode. States plainly that the GitHub repo is the marketplace (no vendor official directory needed) and how to pin a tag vs track main. Verify-flagged the still-evolving CLIs (Codex/Copilot/apm/Cursor). --- docs/setup/marketplaces.md | 169 +++++++++++++++++++++++++++++++++++-- 1 file changed, 162 insertions(+), 7 deletions(-) diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index 0f04317fa..6b418d676 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -7,6 +7,14 @@ - [Installing Apache Magpie from agent marketplaces](#installing-apache-magpie-from-agent-marketplaces) - [Supported agents](#supported-agents) + - [Claude Code](#claude-code) + - [OpenAI Codex CLI](#openai-codex-cli) + - [GitHub Copilot](#github-copilot) + - [Google Gemini CLI](#google-gemini-cli) + - [Cursor](#cursor) + - [microsoft/apm (multiplexer)](#microsoftapm-multiplexer) + - [Kiro (AWS)](#kiro-aws) + - [OpenCode](#opencode) - [Not supported](#not-supported) - [Automatic upgrade detection](#automatic-upgrade-detection) - [Versioning](#versioning) @@ -46,16 +54,163 @@ with the installing agent's namespacing (e.g. `/magpie:release-vote-tally`). ## Supported agents -| Agent | Install | Manifest in this repo | +Every method below uses the **GitHub repository +[`apache/magpie`](https://github.com/apache/magpie)** as the marketplace — +no third-party or vendor "official" directory is required. Pin to a released +tag (e.g. `0.2.0`) for reproducibility, or track `main` for the latest. + +Quick reference: + +| Agent | One-liner | Manifest in this repo | |---|---|---| -| **Claude Code** | `/plugin marketplace add apache/magpie` then `/plugin install magpie@apache-magpie` | `.claude-plugin/marketplace.json`, `.claude-plugin/plugin.json` | -| **OpenAI Codex CLI** | `codex plugin marketplace add apache/magpie` then install `magpie` | `.codex-plugin/plugin.json`, `.agents/plugins/marketplace.json` | -| **GitHub Copilot** | add the repo as a plugin marketplace, then install `magpie` | `marketplace.json` (repo root) | +| **Claude Code** | `/plugin marketplace add apache/magpie` → `/plugin install magpie@apache-magpie` | `.claude-plugin/marketplace.json` + `plugin.json` | +| **OpenAI Codex CLI** | `codex plugin marketplace add apache/magpie` → install `magpie` | `.codex-plugin/plugin.json`, `.agents/plugins/marketplace.json` | +| **GitHub Copilot** | add `apache/magpie` as a plugin marketplace → install `magpie` | `marketplace.json` (repo root) | | **Google Gemini CLI** | `gemini extensions install https://github.com/apache/magpie` | `gemini-extension.json` | -| **Cursor** | add via the plugin/skill install flow pointing at the repo | consumes the plugin manifests above | +| **Cursor** | add via the plugin/skill install flow pointing at the repo | the plugin manifests above | | **microsoft/apm** | `apm install apache/magpie` (compiles to Claude/Cursor/Codex/Copilot/Gemini) | `apm.yml` | -| **Kiro** | install from the GitHub subdirectory of a skill (Kiro installs per-skill from a subdir, not the repo root) | native `skills//SKILL.md` | -| **OpenCode** | drop the skills into `.opencode/skills/`, or use a community installer against this repo | native `skills//SKILL.md` | +| **Kiro** | install per-skill from a GitHub subdirectory | native `skills//SKILL.md` | +| **OpenCode** | clone skills into `.opencode/skills/`, or use a community installer | native `skills//SKILL.md` | + +Detailed steps per agent follow. + +### Claude Code + +1. In a Claude Code session, add the marketplace from GitHub — this clones + the repo and reads `.claude-plugin/marketplace.json`: + + ```text + /plugin marketplace add apache/magpie + ``` + +2. Install the `magpie` plugin from it: + + ```text + /plugin install magpie@apache-magpie + ``` + +3. Confirm it is enabled (the `magpie` plugin should appear as installed): + + ```text + /plugin + ``` + +4. Invoke any skill under the plugin namespace, e.g.: + + ```text + /magpie:release-vote-tally + /magpie:security-issue-triage + ``` + +5. **Update** later with `/plugin marketplace update apache-magpie` then + `/plugin update magpie@apache-magpie`. On a version change the bundled + `SessionStart` hook also prompts you to run `/magpie-setup upgrade`. + +To pin a specific version instead of tracking `main`, add the marketplace +from the tag: `/plugin marketplace add apache/magpie@0.2.0`. + +### OpenAI Codex CLI + +1. Add the marketplace (reads `.agents/plugins/marketplace.json`): + + ```bash + codex plugin marketplace add apache/magpie + ``` + +2. Install the plugin: + + ```bash + codex plugin install magpie + ``` + +3. List / verify — inside Codex run `/plugins`, or from the shell + `codex plugin list`. + +> Codex's plugin/marketplace verbs are still evolving. If a command name +> differs, check `codex plugin --help`. + +### GitHub Copilot + +Copilot treats a GitHub repo with a root `marketplace.json` as a plugin +marketplace. + +1. Add `apache/magpie` as a plugin marketplace in Copilot (CLI or the + coding-agent settings). +2. Install the `magpie` plugin from it. +3. The skills become available to the agent under the plugin. + +> Copilot's plugin-marketplace commands are still stabilising (enterprise- +> managed plugins are in preview). Confirm the exact `add` / `install` +> syntax in the current Copilot documentation. + +### Google Gemini CLI + +1. Install the extension straight from GitHub (reads `gemini-extension.json` + and auto-discovers the skills under `skills/`): + + ```bash + gemini extensions install https://github.com/apache/magpie + ``` + +2. Verify: + + ```bash + gemini extensions list + ``` + +3. Use the skills by asking the agent in natural language or by skill name. + +4. **Update** with `gemini extensions update magpie`. Gemini has no lifecycle + hook, so the shipped [`GEMINI.md`](../../GEMINI.md) reminds you to run + `/magpie-setup upgrade` when the version changes. + +### Cursor + +Cursor consumes the same plugin/skill manifests. Add Magpie through Cursor's +plugin/skill install flow (Customize → Plugins/Skills) pointing at +`github.com/apache/magpie`. + +> Confirm the exact add flow in Cursor's current docs — its self-serve +> marketplace surface is evolving. + +### microsoft/apm (multiplexer) + +`apm` compiles one package to several agents at once (Claude, Cursor, Codex, +Copilot, Gemini). + +1. From your project root: + + ```bash + apm install apache/magpie + ``` + + (reads `apm.yml`, `type: skill`). + +2. `apm` deploys the skills into each supported agent's directory and writes + an `apm.lock.yaml` — commit it to pin the exact resolved commit. + +> `apm` schema is **v0.1** and may change; verify verbs with `apm --help`. + +### Kiro (AWS) + +Kiro installs skills **per-skill from a GitHub subdirectory** (it does not +consume the repo root). For each skill you want, point Kiro's "install from +GitHub" at that skill's subdir on a pinned tag, e.g.: + +```text +https://github.com/apache/magpie/tree/0.2.0/skills/release-vote-tally +``` + +Kiro reads the `skills//SKILL.md` there. + +### OpenCode + +OpenCode reads native Agent Skills from `.opencode/skills/`. Either: + +- clone the skill directories you want into `.opencode/skills/` (project) or + `~/.opencode/skills/` (personal) from `github.com/apache/magpie`, or +- use a community installer (e.g. the `opencode-skills-collection` npm + package) pointed at this repo. ### Not supported From 558ece576567e840d544163736b1d8fa0adac765 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 17:01:32 +0200 Subject: [PATCH 08/18] feat(marketplace): split into per-family plugins to cut always-on token cost Live-tested that Claude Code's plugin 'skills' field rejects paths outside the plugin dir, so per-family plugins reference the shared skills/ tree via single-hop symlinks (no copies, single source of truth). - Add 10 family plugins under plugins/magpie-/ (security, release- management, setup, pr-management, issue, repo-health, contributor-growth, utilities, mentoring, pairing), each a .claude-plugin/plugin.json + a skills/ dir of symlinks into ../../../skills/. - marketplace.json now lists the all-in-one 'magpie' plugin plus the 10 families. Family plugin.jsons omit version and inherit it from the marketplace entry (centralised versioning). - Verified via real installs: e.g. magpie-security = 12 skills / ~3.9k always-on, magpie-pairing = 2 / ~0.6k, vs the all-in-one 70 / ~21.7k. - Namespacing confirmed clean: /magpie-: (dir name, no magpie- frontmatter double-prefix). Docs: docs/setup/marketplaces.md gains the all-in-one-vs-per-family guidance and per-family token costs. --- .claude-plugin/marketplace.json | 69 ++++++++++++++++++- docs/setup/marketplaces.md | 42 +++++++++-- .../.claude-plugin/plugin.json | 5 ++ .../skills/committer-onboarding | 1 + .../skills/contributor-activity-sweep | 1 + .../skills/contributor-nomination | 1 + .../skills/contributor-sentiment | 1 + .../skills/contributor-to-committer | 1 + .../skills/onboarding-concierge | 1 + .../magpie-issue/.claude-plugin/plugin.json | 5 ++ .../magpie-issue/skills/issue-backlog-stats | 1 + plugins/magpie-issue/skills/issue-deduplicate | 1 + .../magpie-issue/skills/issue-fix-workflow | 1 + plugins/magpie-issue/skills/issue-reassess | 1 + .../magpie-issue/skills/issue-reassess-stats | 1 + plugins/magpie-issue/skills/issue-reproducer | 1 + plugins/magpie-issue/skills/issue-stale-sweep | 1 + plugins/magpie-issue/skills/issue-triage | 1 + .../.claude-plugin/plugin.json | 5 ++ .../skills/good-first-issue-author | 1 + .../skills/good-first-issue-sweep | 1 + .../magpie-mentoring/skills/mentoring-welcome | 1 + .../skills/newcomer-issue-explainer | 1 + .../magpie-pairing/.claude-plugin/plugin.json | 5 ++ .../skills/pairing-multi-agent-review | 1 + .../magpie-pairing/skills/pairing-self-review | 1 + .../.claude-plugin/plugin.json | 5 ++ .../skills/pr-management-code-review | 1 + .../skills/pr-management-mentor | 1 + .../skills/pr-management-quick-merge | 1 + .../skills/pr-management-stats | 1 + .../skills/pr-management-triage | 1 + .../skills/pr-stale-sweep | 1 + .../skills/pre-first-pr-check | 1 + .../skills/reviewer-routing | 1 + .../.claude-plugin/plugin.json | 5 ++ .../skills/release-announce-draft | 1 + .../skills/release-archive-sweep | 1 + .../skills/release-audit-report | 1 + .../skills/release-keys-sync | 1 + .../skills/release-prepare | 1 + .../skills/release-promote | 1 + .../skills/release-rc-cut | 1 + .../skills/release-verify-rc | 1 + .../skills/release-vote-draft | 1 + .../skills/release-vote-tally | 1 + .../.claude-plugin/plugin.json | 5 ++ .../skills/audit-finding-fix | 1 + .../magpie-repo-health/skills/ci-runner-audit | 1 + .../skills/dependency-audit | 1 + .../skills/dependency-license-audit | 1 + .../skills/flaky-test-triage | 1 + .../skills/license-compliance-audit | 1 + .../skills/workflow-security-audit | 1 + .../.claude-plugin/plugin.json | 5 ++ .../skills/security-cve-allocate | 1 + .../skills/security-issue-deduplicate | 1 + .../magpie-security/skills/security-issue-fix | 1 + .../skills/security-issue-import | 1 + .../skills/security-issue-import-from-md | 1 + .../skills/security-issue-import-from-pr | 1 + .../skills/security-issue-import-from-scan | 1 + .../security-issue-import-via-forwarder | 1 + .../skills/security-issue-invalidate | 1 + .../skills/security-issue-sync | 1 + .../skills/security-issue-triage | 1 + .../skills/security-tracker-stats-dashboard | 1 + .../magpie-setup/.claude-plugin/plugin.json | 5 ++ plugins/magpie-setup/skills/setup | 1 + .../skills/setup-isolated-setup-doctor | 1 + .../skills/setup-isolated-setup-install | 1 + .../skills/setup-isolated-setup-update | 1 + .../skills/setup-isolated-setup-verify | 1 + .../skills/setup-override-upstream | 1 + .../skills/setup-shared-config-sync | 1 + plugins/magpie-setup/skills/setup-status | 1 + .../magpie-setup/skills/setup-upstream-fix | 1 + .../.claude-plugin/plugin.json | 5 ++ plugins/magpie-utilities/skills/list-skills | 1 + .../magpie-utilities/skills/optimize-skill | 1 + .../magpie-utilities/skills/skill-reconciler | 1 + plugins/magpie-utilities/skills/write-skill | 1 + 82 files changed, 223 insertions(+), 8 deletions(-) create mode 100644 plugins/magpie-contributor-growth/.claude-plugin/plugin.json create mode 120000 plugins/magpie-contributor-growth/skills/committer-onboarding create mode 120000 plugins/magpie-contributor-growth/skills/contributor-activity-sweep create mode 120000 plugins/magpie-contributor-growth/skills/contributor-nomination create mode 120000 plugins/magpie-contributor-growth/skills/contributor-sentiment create mode 120000 plugins/magpie-contributor-growth/skills/contributor-to-committer create mode 120000 plugins/magpie-contributor-growth/skills/onboarding-concierge create mode 100644 plugins/magpie-issue/.claude-plugin/plugin.json create mode 120000 plugins/magpie-issue/skills/issue-backlog-stats create mode 120000 plugins/magpie-issue/skills/issue-deduplicate create mode 120000 plugins/magpie-issue/skills/issue-fix-workflow create mode 120000 plugins/magpie-issue/skills/issue-reassess create mode 120000 plugins/magpie-issue/skills/issue-reassess-stats create mode 120000 plugins/magpie-issue/skills/issue-reproducer create mode 120000 plugins/magpie-issue/skills/issue-stale-sweep create mode 120000 plugins/magpie-issue/skills/issue-triage create mode 100644 plugins/magpie-mentoring/.claude-plugin/plugin.json create mode 120000 plugins/magpie-mentoring/skills/good-first-issue-author create mode 120000 plugins/magpie-mentoring/skills/good-first-issue-sweep create mode 120000 plugins/magpie-mentoring/skills/mentoring-welcome create mode 120000 plugins/magpie-mentoring/skills/newcomer-issue-explainer create mode 100644 plugins/magpie-pairing/.claude-plugin/plugin.json create mode 120000 plugins/magpie-pairing/skills/pairing-multi-agent-review create mode 120000 plugins/magpie-pairing/skills/pairing-self-review create mode 100644 plugins/magpie-pr-management/.claude-plugin/plugin.json create mode 120000 plugins/magpie-pr-management/skills/pr-management-code-review create mode 120000 plugins/magpie-pr-management/skills/pr-management-mentor create mode 120000 plugins/magpie-pr-management/skills/pr-management-quick-merge create mode 120000 plugins/magpie-pr-management/skills/pr-management-stats create mode 120000 plugins/magpie-pr-management/skills/pr-management-triage create mode 120000 plugins/magpie-pr-management/skills/pr-stale-sweep create mode 120000 plugins/magpie-pr-management/skills/pre-first-pr-check create mode 120000 plugins/magpie-pr-management/skills/reviewer-routing create mode 100644 plugins/magpie-release-management/.claude-plugin/plugin.json create mode 120000 plugins/magpie-release-management/skills/release-announce-draft create mode 120000 plugins/magpie-release-management/skills/release-archive-sweep create mode 120000 plugins/magpie-release-management/skills/release-audit-report create mode 120000 plugins/magpie-release-management/skills/release-keys-sync create mode 120000 plugins/magpie-release-management/skills/release-prepare create mode 120000 plugins/magpie-release-management/skills/release-promote create mode 120000 plugins/magpie-release-management/skills/release-rc-cut create mode 120000 plugins/magpie-release-management/skills/release-verify-rc create mode 120000 plugins/magpie-release-management/skills/release-vote-draft create mode 120000 plugins/magpie-release-management/skills/release-vote-tally create mode 100644 plugins/magpie-repo-health/.claude-plugin/plugin.json create mode 120000 plugins/magpie-repo-health/skills/audit-finding-fix create mode 120000 plugins/magpie-repo-health/skills/ci-runner-audit create mode 120000 plugins/magpie-repo-health/skills/dependency-audit create mode 120000 plugins/magpie-repo-health/skills/dependency-license-audit create mode 120000 plugins/magpie-repo-health/skills/flaky-test-triage create mode 120000 plugins/magpie-repo-health/skills/license-compliance-audit create mode 120000 plugins/magpie-repo-health/skills/workflow-security-audit create mode 100644 plugins/magpie-security/.claude-plugin/plugin.json create mode 120000 plugins/magpie-security/skills/security-cve-allocate create mode 120000 plugins/magpie-security/skills/security-issue-deduplicate create mode 120000 plugins/magpie-security/skills/security-issue-fix create mode 120000 plugins/magpie-security/skills/security-issue-import create mode 120000 plugins/magpie-security/skills/security-issue-import-from-md create mode 120000 plugins/magpie-security/skills/security-issue-import-from-pr create mode 120000 plugins/magpie-security/skills/security-issue-import-from-scan create mode 120000 plugins/magpie-security/skills/security-issue-import-via-forwarder create mode 120000 plugins/magpie-security/skills/security-issue-invalidate create mode 120000 plugins/magpie-security/skills/security-issue-sync create mode 120000 plugins/magpie-security/skills/security-issue-triage create mode 120000 plugins/magpie-security/skills/security-tracker-stats-dashboard create mode 100644 plugins/magpie-setup/.claude-plugin/plugin.json create mode 120000 plugins/magpie-setup/skills/setup create mode 120000 plugins/magpie-setup/skills/setup-isolated-setup-doctor create mode 120000 plugins/magpie-setup/skills/setup-isolated-setup-install create mode 120000 plugins/magpie-setup/skills/setup-isolated-setup-update create mode 120000 plugins/magpie-setup/skills/setup-isolated-setup-verify create mode 120000 plugins/magpie-setup/skills/setup-override-upstream create mode 120000 plugins/magpie-setup/skills/setup-shared-config-sync create mode 120000 plugins/magpie-setup/skills/setup-status create mode 120000 plugins/magpie-setup/skills/setup-upstream-fix create mode 100644 plugins/magpie-utilities/.claude-plugin/plugin.json create mode 120000 plugins/magpie-utilities/skills/list-skills create mode 120000 plugins/magpie-utilities/skills/optimize-skill create mode 120000 plugins/magpie-utilities/skills/skill-reconciler create mode 120000 plugins/magpie-utilities/skills/write-skill diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 24b6940d7..c85b5d809 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,13 +1,76 @@ { "name": "apache-magpie", - "owner": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" }, - "description": "Apache Magpie agentic skills for maintaining open-source projects.", + "owner": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "description": "Apache Magpie agentic skills for maintaining open-source projects. Install the all-in-one `magpie` plugin, or per-family plugins to load only what you use.", "plugins": [ { "name": "magpie", "source": ".", "version": "0.2.0", - "description": "All Apache Magpie skills (release management, security, PR/issue workflows, contributor growth, repo health)." + "description": "All Apache Magpie skills (all 10 families; ~21.7k always-on tokens)." + }, + { + "name": "magpie-contributor-growth", + "source": "./plugins/magpie-contributor-growth", + "version": "0.2.0", + "description": "Apache Magpie contributor-growth family (6 skills)." + }, + { + "name": "magpie-issue", + "source": "./plugins/magpie-issue", + "version": "0.2.0", + "description": "Apache Magpie issue family (8 skills)." + }, + { + "name": "magpie-mentoring", + "source": "./plugins/magpie-mentoring", + "version": "0.2.0", + "description": "Apache Magpie mentoring family (4 skills)." + }, + { + "name": "magpie-pairing", + "source": "./plugins/magpie-pairing", + "version": "0.2.0", + "description": "Apache Magpie pairing family (2 skills)." + }, + { + "name": "magpie-pr-management", + "source": "./plugins/magpie-pr-management", + "version": "0.2.0", + "description": "Apache Magpie pr-management family (8 skills)." + }, + { + "name": "magpie-release-management", + "source": "./plugins/magpie-release-management", + "version": "0.2.0", + "description": "Apache Magpie release-management family (10 skills)." + }, + { + "name": "magpie-repo-health", + "source": "./plugins/magpie-repo-health", + "version": "0.2.0", + "description": "Apache Magpie repo-health family (7 skills)." + }, + { + "name": "magpie-security", + "source": "./plugins/magpie-security", + "version": "0.2.0", + "description": "Apache Magpie security family (12 skills)." + }, + { + "name": "magpie-setup", + "source": "./plugins/magpie-setup", + "version": "0.2.0", + "description": "Apache Magpie setup family (9 skills)." + }, + { + "name": "magpie-utilities", + "source": "./plugins/magpie-utilities", + "version": "0.2.0", + "description": "Apache Magpie utilities family (4 skills)." } ] } diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index 6b418d676..085bb1aab 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -6,6 +6,7 @@ **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Installing Apache Magpie from agent marketplaces](#installing-apache-magpie-from-agent-marketplaces) + - [Choosing a plugin: all-in-one vs per-family](#choosing-a-plugin-all-in-one-vs-per-family) - [Supported agents](#supported-agents) - [Claude Code](#claude-code) - [OpenAI Codex CLI](#openai-codex-cli) @@ -48,9 +49,38 @@ through the plugin/extension mechanisms of the major AI coding agents, > `X.Y.Z` git tag; they are derived from — not a substitute for — the ASF > source release. -All entries package the whole framework as a single **`magpie`** plugin -that exposes every skill under the `skills/` tree. Skills are then invoked -with the installing agent's namespacing (e.g. `/magpie:release-vote-tally`). +## Choosing a plugin: all-in-one vs per-family + +The framework ships as **eleven** plugins so you install only what you use: + +- **`magpie`** (all-in-one) — every skill, all ten families. Simplest, but its + always-on metadata costs **~21.7k tokens in every session** (each installed + skill advertises a short description to the model on every turn — see + ["always-on" cost](#versioning) below). +- **Per-family plugins** — `magpie-security`, `magpie-release-management`, … — + install only the families you use, so only their skills are always-on. + **Recommended.** + +| Family plugin | Skills | ~Always-on tokens | +|---|---|---| +| `magpie-security` | 12 | ~3.9k | +| `magpie-release-management` | 10 | ~2.9k | +| `magpie-setup` | 9 | ~2.6k | +| `magpie-pr-management` | 8 | ~2.4k | +| `magpie-issue` | 8 | ~2.4k | +| `magpie-repo-health` | 7 | ~2.1k | +| `magpie-contributor-growth` | 6 | ~1.8k | +| `magpie-utilities` | 4 | ~1.4k | +| `magpie-mentoring` | 4 | ~1.2k | +| `magpie-pairing` | 2 | ~0.6k | +| **`magpie`** (all) | **70** | **~21.7k** | + +Skills are invoked under the installing plugin's namespace — e.g. +`/magpie:release-vote-tally` (all-in-one) or +`/magpie-release-management:release-vote-tally` (family plugin). + +Per-family plugins reference the shared `skills/` tree via symlinks (no copies), +so there is a single source of truth for every skill. ## Supported agents @@ -83,10 +113,12 @@ Detailed steps per agent follow. /plugin marketplace add apache/magpie ``` -2. Install the `magpie` plugin from it: +2. Install the all-in-one plugin, **or** just the families you use: ```text - /plugin install magpie@apache-magpie + /plugin install magpie@apache-magpie # everything (~21.7k always-on) + /plugin install magpie-security@apache-magpie # one family (~3.9k always-on) + /plugin install magpie-release-management@apache-magpie ``` 3. Confirm it is enabled (the `magpie` plugin should appear as installed): diff --git a/plugins/magpie-contributor-growth/.claude-plugin/plugin.json b/plugins/magpie-contributor-growth/.claude-plugin/plugin.json new file mode 100644 index 000000000..82e669b2a --- /dev/null +++ b/plugins/magpie-contributor-growth/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-contributor-growth", + "description": "Apache Magpie \u2014 path-to-committer: activity sweeps, nominations, sentiment, readiness, committer/post-vote onboarding.", + "skills": "./skills" +} diff --git a/plugins/magpie-contributor-growth/skills/committer-onboarding b/plugins/magpie-contributor-growth/skills/committer-onboarding new file mode 120000 index 000000000..b83c90500 --- /dev/null +++ b/plugins/magpie-contributor-growth/skills/committer-onboarding @@ -0,0 +1 @@ +../../../skills/committer-onboarding \ No newline at end of file diff --git a/plugins/magpie-contributor-growth/skills/contributor-activity-sweep b/plugins/magpie-contributor-growth/skills/contributor-activity-sweep new file mode 120000 index 000000000..fb487ce3e --- /dev/null +++ b/plugins/magpie-contributor-growth/skills/contributor-activity-sweep @@ -0,0 +1 @@ +../../../skills/contributor-activity-sweep \ No newline at end of file diff --git a/plugins/magpie-contributor-growth/skills/contributor-nomination b/plugins/magpie-contributor-growth/skills/contributor-nomination new file mode 120000 index 000000000..2124598e5 --- /dev/null +++ b/plugins/magpie-contributor-growth/skills/contributor-nomination @@ -0,0 +1 @@ +../../../skills/contributor-nomination \ No newline at end of file diff --git a/plugins/magpie-contributor-growth/skills/contributor-sentiment b/plugins/magpie-contributor-growth/skills/contributor-sentiment new file mode 120000 index 000000000..1831aa68c --- /dev/null +++ b/plugins/magpie-contributor-growth/skills/contributor-sentiment @@ -0,0 +1 @@ +../../../skills/contributor-sentiment \ No newline at end of file diff --git a/plugins/magpie-contributor-growth/skills/contributor-to-committer b/plugins/magpie-contributor-growth/skills/contributor-to-committer new file mode 120000 index 000000000..b8094a483 --- /dev/null +++ b/plugins/magpie-contributor-growth/skills/contributor-to-committer @@ -0,0 +1 @@ +../../../skills/contributor-to-committer \ No newline at end of file diff --git a/plugins/magpie-contributor-growth/skills/onboarding-concierge b/plugins/magpie-contributor-growth/skills/onboarding-concierge new file mode 120000 index 000000000..91029b53f --- /dev/null +++ b/plugins/magpie-contributor-growth/skills/onboarding-concierge @@ -0,0 +1 @@ +../../../skills/onboarding-concierge \ No newline at end of file diff --git a/plugins/magpie-issue/.claude-plugin/plugin.json b/plugins/magpie-issue/.claude-plugin/plugin.json new file mode 100644 index 000000000..ba22cc0ea --- /dev/null +++ b/plugins/magpie-issue/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-issue", + "description": "Apache Magpie \u2014 issue lifecycle: triage, reproduction, fix drafting, reassess, stale-sweep, dedup, backlog stats.", + "skills": "./skills" +} diff --git a/plugins/magpie-issue/skills/issue-backlog-stats b/plugins/magpie-issue/skills/issue-backlog-stats new file mode 120000 index 000000000..d12cad954 --- /dev/null +++ b/plugins/magpie-issue/skills/issue-backlog-stats @@ -0,0 +1 @@ +../../../skills/issue-backlog-stats \ No newline at end of file diff --git a/plugins/magpie-issue/skills/issue-deduplicate b/plugins/magpie-issue/skills/issue-deduplicate new file mode 120000 index 000000000..c6367f27b --- /dev/null +++ b/plugins/magpie-issue/skills/issue-deduplicate @@ -0,0 +1 @@ +../../../skills/issue-deduplicate \ No newline at end of file diff --git a/plugins/magpie-issue/skills/issue-fix-workflow b/plugins/magpie-issue/skills/issue-fix-workflow new file mode 120000 index 000000000..8914695dd --- /dev/null +++ b/plugins/magpie-issue/skills/issue-fix-workflow @@ -0,0 +1 @@ +../../../skills/issue-fix-workflow \ No newline at end of file diff --git a/plugins/magpie-issue/skills/issue-reassess b/plugins/magpie-issue/skills/issue-reassess new file mode 120000 index 000000000..c5f4026ee --- /dev/null +++ b/plugins/magpie-issue/skills/issue-reassess @@ -0,0 +1 @@ +../../../skills/issue-reassess \ No newline at end of file diff --git a/plugins/magpie-issue/skills/issue-reassess-stats b/plugins/magpie-issue/skills/issue-reassess-stats new file mode 120000 index 000000000..35fc4ffe0 --- /dev/null +++ b/plugins/magpie-issue/skills/issue-reassess-stats @@ -0,0 +1 @@ +../../../skills/issue-reassess-stats \ No newline at end of file diff --git a/plugins/magpie-issue/skills/issue-reproducer b/plugins/magpie-issue/skills/issue-reproducer new file mode 120000 index 000000000..1deca5bfd --- /dev/null +++ b/plugins/magpie-issue/skills/issue-reproducer @@ -0,0 +1 @@ +../../../skills/issue-reproducer \ No newline at end of file diff --git a/plugins/magpie-issue/skills/issue-stale-sweep b/plugins/magpie-issue/skills/issue-stale-sweep new file mode 120000 index 000000000..4bb581f40 --- /dev/null +++ b/plugins/magpie-issue/skills/issue-stale-sweep @@ -0,0 +1 @@ +../../../skills/issue-stale-sweep \ No newline at end of file diff --git a/plugins/magpie-issue/skills/issue-triage b/plugins/magpie-issue/skills/issue-triage new file mode 120000 index 000000000..e4c48d1ba --- /dev/null +++ b/plugins/magpie-issue/skills/issue-triage @@ -0,0 +1 @@ +../../../skills/issue-triage \ No newline at end of file diff --git a/plugins/magpie-mentoring/.claude-plugin/plugin.json b/plugins/magpie-mentoring/.claude-plugin/plugin.json new file mode 100644 index 000000000..c59f30eff --- /dev/null +++ b/plugins/magpie-mentoring/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-mentoring", + "description": "Apache Magpie \u2014 newcomer mentoring: welcome, newcomer-issue explanations, good-first-issue authoring + sweep.", + "skills": "./skills" +} diff --git a/plugins/magpie-mentoring/skills/good-first-issue-author b/plugins/magpie-mentoring/skills/good-first-issue-author new file mode 120000 index 000000000..0333f7f04 --- /dev/null +++ b/plugins/magpie-mentoring/skills/good-first-issue-author @@ -0,0 +1 @@ +../../../skills/good-first-issue-author \ No newline at end of file diff --git a/plugins/magpie-mentoring/skills/good-first-issue-sweep b/plugins/magpie-mentoring/skills/good-first-issue-sweep new file mode 120000 index 000000000..f5b5458ca --- /dev/null +++ b/plugins/magpie-mentoring/skills/good-first-issue-sweep @@ -0,0 +1 @@ +../../../skills/good-first-issue-sweep \ No newline at end of file diff --git a/plugins/magpie-mentoring/skills/mentoring-welcome b/plugins/magpie-mentoring/skills/mentoring-welcome new file mode 120000 index 000000000..27df0a2eb --- /dev/null +++ b/plugins/magpie-mentoring/skills/mentoring-welcome @@ -0,0 +1 @@ +../../../skills/mentoring-welcome \ No newline at end of file diff --git a/plugins/magpie-mentoring/skills/newcomer-issue-explainer b/plugins/magpie-mentoring/skills/newcomer-issue-explainer new file mode 120000 index 000000000..c6ecbde9e --- /dev/null +++ b/plugins/magpie-mentoring/skills/newcomer-issue-explainer @@ -0,0 +1 @@ +../../../skills/newcomer-issue-explainer \ No newline at end of file diff --git a/plugins/magpie-pairing/.claude-plugin/plugin.json b/plugins/magpie-pairing/.claude-plugin/plugin.json new file mode 100644 index 000000000..d6a868ea0 --- /dev/null +++ b/plugins/magpie-pairing/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-pairing", + "description": "Apache Magpie \u2014 pair a change with a structured self-review or a multi-agent adversarial review.", + "skills": "./skills" +} diff --git a/plugins/magpie-pairing/skills/pairing-multi-agent-review b/plugins/magpie-pairing/skills/pairing-multi-agent-review new file mode 120000 index 000000000..6219e2ba4 --- /dev/null +++ b/plugins/magpie-pairing/skills/pairing-multi-agent-review @@ -0,0 +1 @@ +../../../skills/pairing-multi-agent-review \ No newline at end of file diff --git a/plugins/magpie-pairing/skills/pairing-self-review b/plugins/magpie-pairing/skills/pairing-self-review new file mode 120000 index 000000000..84052f6e5 --- /dev/null +++ b/plugins/magpie-pairing/skills/pairing-self-review @@ -0,0 +1 @@ +../../../skills/pairing-self-review \ No newline at end of file diff --git a/plugins/magpie-pr-management/.claude-plugin/plugin.json b/plugins/magpie-pr-management/.claude-plugin/plugin.json new file mode 100644 index 000000000..573658805 --- /dev/null +++ b/plugins/magpie-pr-management/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-pr-management", + "description": "Apache Magpie \u2014 PR-queue management: triage, stats, deep code review, quick-merge, stale-sweep, reviewer routing, pre-first-PR checks.", + "skills": "./skills" +} diff --git a/plugins/magpie-pr-management/skills/pr-management-code-review b/plugins/magpie-pr-management/skills/pr-management-code-review new file mode 120000 index 000000000..831ee72b1 --- /dev/null +++ b/plugins/magpie-pr-management/skills/pr-management-code-review @@ -0,0 +1 @@ +../../../skills/pr-management-code-review \ No newline at end of file diff --git a/plugins/magpie-pr-management/skills/pr-management-mentor b/plugins/magpie-pr-management/skills/pr-management-mentor new file mode 120000 index 000000000..686e9d943 --- /dev/null +++ b/plugins/magpie-pr-management/skills/pr-management-mentor @@ -0,0 +1 @@ +../../../skills/pr-management-mentor \ No newline at end of file diff --git a/plugins/magpie-pr-management/skills/pr-management-quick-merge b/plugins/magpie-pr-management/skills/pr-management-quick-merge new file mode 120000 index 000000000..5f1ca0c25 --- /dev/null +++ b/plugins/magpie-pr-management/skills/pr-management-quick-merge @@ -0,0 +1 @@ +../../../skills/pr-management-quick-merge \ No newline at end of file diff --git a/plugins/magpie-pr-management/skills/pr-management-stats b/plugins/magpie-pr-management/skills/pr-management-stats new file mode 120000 index 000000000..e7513a192 --- /dev/null +++ b/plugins/magpie-pr-management/skills/pr-management-stats @@ -0,0 +1 @@ +../../../skills/pr-management-stats \ No newline at end of file diff --git a/plugins/magpie-pr-management/skills/pr-management-triage b/plugins/magpie-pr-management/skills/pr-management-triage new file mode 120000 index 000000000..e62d08d8f --- /dev/null +++ b/plugins/magpie-pr-management/skills/pr-management-triage @@ -0,0 +1 @@ +../../../skills/pr-management-triage \ No newline at end of file diff --git a/plugins/magpie-pr-management/skills/pr-stale-sweep b/plugins/magpie-pr-management/skills/pr-stale-sweep new file mode 120000 index 000000000..66f2e99ed --- /dev/null +++ b/plugins/magpie-pr-management/skills/pr-stale-sweep @@ -0,0 +1 @@ +../../../skills/pr-stale-sweep \ No newline at end of file diff --git a/plugins/magpie-pr-management/skills/pre-first-pr-check b/plugins/magpie-pr-management/skills/pre-first-pr-check new file mode 120000 index 000000000..f2bc033b2 --- /dev/null +++ b/plugins/magpie-pr-management/skills/pre-first-pr-check @@ -0,0 +1 @@ +../../../skills/pre-first-pr-check \ No newline at end of file diff --git a/plugins/magpie-pr-management/skills/reviewer-routing b/plugins/magpie-pr-management/skills/reviewer-routing new file mode 120000 index 000000000..989f9fc30 --- /dev/null +++ b/plugins/magpie-pr-management/skills/reviewer-routing @@ -0,0 +1 @@ +../../../skills/reviewer-routing \ No newline at end of file diff --git a/plugins/magpie-release-management/.claude-plugin/plugin.json b/plugins/magpie-release-management/.claude-plugin/plugin.json new file mode 100644 index 000000000..15bf156e1 --- /dev/null +++ b/plugins/magpie-release-management/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-release-management", + "description": "Apache Magpie \u2014 ASF release lifecycle: plan, RC cut/sign, vote, tally, promote, announce, archive, audit.", + "skills": "./skills" +} diff --git a/plugins/magpie-release-management/skills/release-announce-draft b/plugins/magpie-release-management/skills/release-announce-draft new file mode 120000 index 000000000..983a5efc6 --- /dev/null +++ b/plugins/magpie-release-management/skills/release-announce-draft @@ -0,0 +1 @@ +../../../skills/release-announce-draft \ No newline at end of file diff --git a/plugins/magpie-release-management/skills/release-archive-sweep b/plugins/magpie-release-management/skills/release-archive-sweep new file mode 120000 index 000000000..650df1824 --- /dev/null +++ b/plugins/magpie-release-management/skills/release-archive-sweep @@ -0,0 +1 @@ +../../../skills/release-archive-sweep \ No newline at end of file diff --git a/plugins/magpie-release-management/skills/release-audit-report b/plugins/magpie-release-management/skills/release-audit-report new file mode 120000 index 000000000..9b0d7aae8 --- /dev/null +++ b/plugins/magpie-release-management/skills/release-audit-report @@ -0,0 +1 @@ +../../../skills/release-audit-report \ No newline at end of file diff --git a/plugins/magpie-release-management/skills/release-keys-sync b/plugins/magpie-release-management/skills/release-keys-sync new file mode 120000 index 000000000..5c6f2bea2 --- /dev/null +++ b/plugins/magpie-release-management/skills/release-keys-sync @@ -0,0 +1 @@ +../../../skills/release-keys-sync \ No newline at end of file diff --git a/plugins/magpie-release-management/skills/release-prepare b/plugins/magpie-release-management/skills/release-prepare new file mode 120000 index 000000000..9fab6454a --- /dev/null +++ b/plugins/magpie-release-management/skills/release-prepare @@ -0,0 +1 @@ +../../../skills/release-prepare \ No newline at end of file diff --git a/plugins/magpie-release-management/skills/release-promote b/plugins/magpie-release-management/skills/release-promote new file mode 120000 index 000000000..38407b4f4 --- /dev/null +++ b/plugins/magpie-release-management/skills/release-promote @@ -0,0 +1 @@ +../../../skills/release-promote \ No newline at end of file diff --git a/plugins/magpie-release-management/skills/release-rc-cut b/plugins/magpie-release-management/skills/release-rc-cut new file mode 120000 index 000000000..6734629b7 --- /dev/null +++ b/plugins/magpie-release-management/skills/release-rc-cut @@ -0,0 +1 @@ +../../../skills/release-rc-cut \ No newline at end of file diff --git a/plugins/magpie-release-management/skills/release-verify-rc b/plugins/magpie-release-management/skills/release-verify-rc new file mode 120000 index 000000000..d4c32799b --- /dev/null +++ b/plugins/magpie-release-management/skills/release-verify-rc @@ -0,0 +1 @@ +../../../skills/release-verify-rc \ No newline at end of file diff --git a/plugins/magpie-release-management/skills/release-vote-draft b/plugins/magpie-release-management/skills/release-vote-draft new file mode 120000 index 000000000..92c924cfe --- /dev/null +++ b/plugins/magpie-release-management/skills/release-vote-draft @@ -0,0 +1 @@ +../../../skills/release-vote-draft \ No newline at end of file diff --git a/plugins/magpie-release-management/skills/release-vote-tally b/plugins/magpie-release-management/skills/release-vote-tally new file mode 120000 index 000000000..2c469d205 --- /dev/null +++ b/plugins/magpie-release-management/skills/release-vote-tally @@ -0,0 +1 @@ +../../../skills/release-vote-tally \ No newline at end of file diff --git a/plugins/magpie-repo-health/.claude-plugin/plugin.json b/plugins/magpie-repo-health/.claude-plugin/plugin.json new file mode 100644 index 000000000..02b1c1d7b --- /dev/null +++ b/plugins/magpie-repo-health/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-repo-health", + "description": "Apache Magpie \u2014 read-only repo-health audits: runner labels, workflow security, dependency/license/NOTICE, flaky tests, audit-finding fixes.", + "skills": "./skills" +} diff --git a/plugins/magpie-repo-health/skills/audit-finding-fix b/plugins/magpie-repo-health/skills/audit-finding-fix new file mode 120000 index 000000000..a606cc6ce --- /dev/null +++ b/plugins/magpie-repo-health/skills/audit-finding-fix @@ -0,0 +1 @@ +../../../skills/audit-finding-fix \ No newline at end of file diff --git a/plugins/magpie-repo-health/skills/ci-runner-audit b/plugins/magpie-repo-health/skills/ci-runner-audit new file mode 120000 index 000000000..e9c23b4d0 --- /dev/null +++ b/plugins/magpie-repo-health/skills/ci-runner-audit @@ -0,0 +1 @@ +../../../skills/ci-runner-audit \ No newline at end of file diff --git a/plugins/magpie-repo-health/skills/dependency-audit b/plugins/magpie-repo-health/skills/dependency-audit new file mode 120000 index 000000000..35b24b551 --- /dev/null +++ b/plugins/magpie-repo-health/skills/dependency-audit @@ -0,0 +1 @@ +../../../skills/dependency-audit \ No newline at end of file diff --git a/plugins/magpie-repo-health/skills/dependency-license-audit b/plugins/magpie-repo-health/skills/dependency-license-audit new file mode 120000 index 000000000..1fac0ccd7 --- /dev/null +++ b/plugins/magpie-repo-health/skills/dependency-license-audit @@ -0,0 +1 @@ +../../../skills/dependency-license-audit \ No newline at end of file diff --git a/plugins/magpie-repo-health/skills/flaky-test-triage b/plugins/magpie-repo-health/skills/flaky-test-triage new file mode 120000 index 000000000..993b2ad9f --- /dev/null +++ b/plugins/magpie-repo-health/skills/flaky-test-triage @@ -0,0 +1 @@ +../../../skills/flaky-test-triage \ No newline at end of file diff --git a/plugins/magpie-repo-health/skills/license-compliance-audit b/plugins/magpie-repo-health/skills/license-compliance-audit new file mode 120000 index 000000000..96861730d --- /dev/null +++ b/plugins/magpie-repo-health/skills/license-compliance-audit @@ -0,0 +1 @@ +../../../skills/license-compliance-audit \ No newline at end of file diff --git a/plugins/magpie-repo-health/skills/workflow-security-audit b/plugins/magpie-repo-health/skills/workflow-security-audit new file mode 120000 index 000000000..eedd5ab9f --- /dev/null +++ b/plugins/magpie-repo-health/skills/workflow-security-audit @@ -0,0 +1 @@ +../../../skills/workflow-security-audit \ No newline at end of file diff --git a/plugins/magpie-security/.claude-plugin/plugin.json b/plugins/magpie-security/.claude-plugin/plugin.json new file mode 100644 index 000000000..41ad7c4bf --- /dev/null +++ b/plugins/magpie-security/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-security", + "description": "Apache Magpie \u2014 security-issue handling lifecycle \u2014 import through CVE publication, triage, sync, dedup, invalidate. Maintainer-only.", + "skills": "./skills" +} diff --git a/plugins/magpie-security/skills/security-cve-allocate b/plugins/magpie-security/skills/security-cve-allocate new file mode 120000 index 000000000..4b8ddcc84 --- /dev/null +++ b/plugins/magpie-security/skills/security-cve-allocate @@ -0,0 +1 @@ +../../../skills/security-cve-allocate \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-deduplicate b/plugins/magpie-security/skills/security-issue-deduplicate new file mode 120000 index 000000000..27467f599 --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-deduplicate @@ -0,0 +1 @@ +../../../skills/security-issue-deduplicate \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-fix b/plugins/magpie-security/skills/security-issue-fix new file mode 120000 index 000000000..ca3a85797 --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-fix @@ -0,0 +1 @@ +../../../skills/security-issue-fix \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-import b/plugins/magpie-security/skills/security-issue-import new file mode 120000 index 000000000..f2e97f241 --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-import @@ -0,0 +1 @@ +../../../skills/security-issue-import \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-import-from-md b/plugins/magpie-security/skills/security-issue-import-from-md new file mode 120000 index 000000000..44c90e3c1 --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-import-from-md @@ -0,0 +1 @@ +../../../skills/security-issue-import-from-md \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-import-from-pr b/plugins/magpie-security/skills/security-issue-import-from-pr new file mode 120000 index 000000000..b640559cb --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-import-from-pr @@ -0,0 +1 @@ +../../../skills/security-issue-import-from-pr \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-import-from-scan b/plugins/magpie-security/skills/security-issue-import-from-scan new file mode 120000 index 000000000..75d352a6b --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-import-from-scan @@ -0,0 +1 @@ +../../../skills/security-issue-import-from-scan \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-import-via-forwarder b/plugins/magpie-security/skills/security-issue-import-via-forwarder new file mode 120000 index 000000000..4b835055d --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-import-via-forwarder @@ -0,0 +1 @@ +../../../skills/security-issue-import-via-forwarder \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-invalidate b/plugins/magpie-security/skills/security-issue-invalidate new file mode 120000 index 000000000..c35602c5b --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-invalidate @@ -0,0 +1 @@ +../../../skills/security-issue-invalidate \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-sync b/plugins/magpie-security/skills/security-issue-sync new file mode 120000 index 000000000..0f524f33f --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-sync @@ -0,0 +1 @@ +../../../skills/security-issue-sync \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-issue-triage b/plugins/magpie-security/skills/security-issue-triage new file mode 120000 index 000000000..bbe501c5e --- /dev/null +++ b/plugins/magpie-security/skills/security-issue-triage @@ -0,0 +1 @@ +../../../skills/security-issue-triage \ No newline at end of file diff --git a/plugins/magpie-security/skills/security-tracker-stats-dashboard b/plugins/magpie-security/skills/security-tracker-stats-dashboard new file mode 120000 index 000000000..912859aa6 --- /dev/null +++ b/plugins/magpie-security/skills/security-tracker-stats-dashboard @@ -0,0 +1 @@ +../../../skills/security-tracker-stats-dashboard \ No newline at end of file diff --git a/plugins/magpie-setup/.claude-plugin/plugin.json b/plugins/magpie-setup/.claude-plugin/plugin.json new file mode 100644 index 000000000..8c55abd77 --- /dev/null +++ b/plugins/magpie-setup/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-setup", + "description": "Apache Magpie \u2014 framework install/maintenance: install (adopt), upgrade, verify, override, status, secure-agent setup, shared-config sync.", + "skills": "./skills" +} diff --git a/plugins/magpie-setup/skills/setup b/plugins/magpie-setup/skills/setup new file mode 120000 index 000000000..8ae30b1a9 --- /dev/null +++ b/plugins/magpie-setup/skills/setup @@ -0,0 +1 @@ +../../../skills/setup \ No newline at end of file diff --git a/plugins/magpie-setup/skills/setup-isolated-setup-doctor b/plugins/magpie-setup/skills/setup-isolated-setup-doctor new file mode 120000 index 000000000..729daa07a --- /dev/null +++ b/plugins/magpie-setup/skills/setup-isolated-setup-doctor @@ -0,0 +1 @@ +../../../skills/setup-isolated-setup-doctor \ No newline at end of file diff --git a/plugins/magpie-setup/skills/setup-isolated-setup-install b/plugins/magpie-setup/skills/setup-isolated-setup-install new file mode 120000 index 000000000..b142191ff --- /dev/null +++ b/plugins/magpie-setup/skills/setup-isolated-setup-install @@ -0,0 +1 @@ +../../../skills/setup-isolated-setup-install \ No newline at end of file diff --git a/plugins/magpie-setup/skills/setup-isolated-setup-update b/plugins/magpie-setup/skills/setup-isolated-setup-update new file mode 120000 index 000000000..e1dd883e5 --- /dev/null +++ b/plugins/magpie-setup/skills/setup-isolated-setup-update @@ -0,0 +1 @@ +../../../skills/setup-isolated-setup-update \ No newline at end of file diff --git a/plugins/magpie-setup/skills/setup-isolated-setup-verify b/plugins/magpie-setup/skills/setup-isolated-setup-verify new file mode 120000 index 000000000..31a127698 --- /dev/null +++ b/plugins/magpie-setup/skills/setup-isolated-setup-verify @@ -0,0 +1 @@ +../../../skills/setup-isolated-setup-verify \ No newline at end of file diff --git a/plugins/magpie-setup/skills/setup-override-upstream b/plugins/magpie-setup/skills/setup-override-upstream new file mode 120000 index 000000000..fb63dc407 --- /dev/null +++ b/plugins/magpie-setup/skills/setup-override-upstream @@ -0,0 +1 @@ +../../../skills/setup-override-upstream \ No newline at end of file diff --git a/plugins/magpie-setup/skills/setup-shared-config-sync b/plugins/magpie-setup/skills/setup-shared-config-sync new file mode 120000 index 000000000..236211e34 --- /dev/null +++ b/plugins/magpie-setup/skills/setup-shared-config-sync @@ -0,0 +1 @@ +../../../skills/setup-shared-config-sync \ No newline at end of file diff --git a/plugins/magpie-setup/skills/setup-status b/plugins/magpie-setup/skills/setup-status new file mode 120000 index 000000000..ab87fce5a --- /dev/null +++ b/plugins/magpie-setup/skills/setup-status @@ -0,0 +1 @@ +../../../skills/setup-status \ No newline at end of file diff --git a/plugins/magpie-setup/skills/setup-upstream-fix b/plugins/magpie-setup/skills/setup-upstream-fix new file mode 120000 index 000000000..3fa8f25df --- /dev/null +++ b/plugins/magpie-setup/skills/setup-upstream-fix @@ -0,0 +1 @@ +../../../skills/setup-upstream-fix \ No newline at end of file diff --git a/plugins/magpie-utilities/.claude-plugin/plugin.json b/plugins/magpie-utilities/.claude-plugin/plugin.json new file mode 100644 index 000000000..c8fa0933b --- /dev/null +++ b/plugins/magpie-utilities/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "magpie-utilities", + "description": "Apache Magpie \u2014 framework meta-skills: write-skill, optimize-skill, skill-reconciler, list-skills.", + "skills": "./skills" +} diff --git a/plugins/magpie-utilities/skills/list-skills b/plugins/magpie-utilities/skills/list-skills new file mode 120000 index 000000000..97f92b85a --- /dev/null +++ b/plugins/magpie-utilities/skills/list-skills @@ -0,0 +1 @@ +../../../skills/list-skills \ No newline at end of file diff --git a/plugins/magpie-utilities/skills/optimize-skill b/plugins/magpie-utilities/skills/optimize-skill new file mode 120000 index 000000000..f25d4c312 --- /dev/null +++ b/plugins/magpie-utilities/skills/optimize-skill @@ -0,0 +1 @@ +../../../skills/optimize-skill \ No newline at end of file diff --git a/plugins/magpie-utilities/skills/skill-reconciler b/plugins/magpie-utilities/skills/skill-reconciler new file mode 120000 index 000000000..495f0f27e --- /dev/null +++ b/plugins/magpie-utilities/skills/skill-reconciler @@ -0,0 +1 @@ +../../../skills/skill-reconciler \ No newline at end of file diff --git a/plugins/magpie-utilities/skills/write-skill b/plugins/magpie-utilities/skills/write-skill new file mode 120000 index 000000000..2d8e4f960 --- /dev/null +++ b/plugins/magpie-utilities/skills/write-skill @@ -0,0 +1 @@ +../../../skills/write-skill \ No newline at end of file From 95fac777c2bc0c3ff24ec3bc1f26fb1005b3dec4 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 17:07:32 +0200 Subject: [PATCH 09/18] build(prek): add check-family-plugins hook; document Windows symlink caveat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tools/dev/check-family-plugins.py: validates that plugins/magpie-/ match the skills' family: frontmatter (each family's skills present as single-hop symlinks into skills/, and listed in marketplace.json). Catches a new skill, a changed family, or a stale symlink. --fix regenerates them. - Wire it as a local prek hook (runs when a SKILL.md, a family plugin, or the marketplace manifest changes). - docs/setup/marketplaces.md: document that per-family plugins rely on git symlinks — on Windows enable core.symlinks + Developer Mode, or use the all-in-one magpie plugin (no symlinks). Verified symlinks survive a GitHub clone on macOS. --- .pre-commit-config.yaml | 14 +++ docs/setup/marketplaces.md | 13 +++ tools/dev/check-family-plugins.py | 160 ++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 tools/dev/check-family-plugins.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 48075459d..0bce61b86 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -233,6 +233,20 @@ repos: files: ^(\.gitattributes|\.(agents|claude|github|kiro)/skills/.*|skills/.*|projects/_template/.*)$ types_or: [symlink, file] pass_filenames: false + # Family-plugin consistency guard. The per-family marketplace plugins under + # plugins/magpie-/ must stay in sync with the skills' `family:` + # frontmatter (the source of truth): each family's skills present as + # single-hop symlinks into the shared skills/ tree, and each family listed in + # .claude-plugin/marketplace.json alongside the all-in-one `magpie`. Catches a + # new skill, a changed family, or a stale symlink. `--fix` regenerates them. + - repo: local + hooks: + - id: check-family-plugins + name: check-family-plugins (marketplace families vs frontmatter) + language: system + entry: python3 tools/dev/check-family-plugins.py + files: ^(skills/.*/SKILL\.md|plugins/.*|\.claude-plugin/marketplace\.json)$ + pass_filenames: false # Workspace-membership guard. The single source of truth for # which Python projects get pre-commit hooks + the CI pytest # matrix is the `[tool.uv.workspace] members` array in the root diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index 085bb1aab..4fee76983 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -82,6 +82,19 @@ Skills are invoked under the installing plugin's namespace — e.g. Per-family plugins reference the shared `skills/` tree via symlinks (no copies), so there is a single source of truth for every skill. +> [!IMPORTANT] +> **Windows + per-family plugins.** The per-family plugins rely on git symlinks +> (each `plugins/magpie-/skills/` links to the shared +> `skills/`). Git for Windows does **not** materialise real symlinks +> unless `core.symlinks` is enabled *and* the account may create them (Windows +> Developer Mode, or an elevated shell) — otherwise the clone writes each +> symlink as a plain text file and that family's skills won't load. On Windows, +> either enable symlink support +> (`git config --global core.symlinks true` + Developer Mode) **or** install the +> **all-in-one `magpie` plugin**, which uses the real `skills/` directory and +> needs no symlinks. macOS and Linux are unaffected. (Verified on macOS: a +> `/plugin marketplace add` GitHub clone preserves and resolves the symlinks.) + ## Supported agents Every method below uses the **GitHub repository diff --git a/tools/dev/check-family-plugins.py b/tools/dev/check-family-plugins.py new file mode 100644 index 000000000..ee6a36ad5 --- /dev/null +++ b/tools/dev/check-family-plugins.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 +"""Validate the per-family marketplace plugins against the skills' `family:` +frontmatter — the source of truth. + +For every family that skills declare in their `family:` frontmatter there must +be a `plugins/magpie-/` plugin whose `skills/` directory contains +exactly that family's skills as single-hop symlinks into the shared +`skills/` tree, and `.claude-plugin/marketplace.json` must list it (plus +the all-in-one `magpie`). Drift — a new skill, a changed family, a stale +symlink — fails the check. + +Run with `--fix` to regenerate the family plugins + symlinks + marketplace +entries from the frontmatter. +""" +from __future__ import annotations + +import argparse +import glob +import json +import os +import re +import sys + +MARKETPLACE = ".claude-plugin/marketplace.json" +SYMLINK_TARGET = "../../../skills/{skill}" # relative to plugins/magpie-/skills/ + +# Per-family descriptions used when (re)generating manifests. Keep in sync with +# the family README; `check` does not enforce these (only structure/symlinks). +DESC = { + "security": "security-issue handling lifecycle — import through CVE publication, triage, sync, dedup, invalidate. Maintainer-only.", + "release-management": "ASF release lifecycle: plan, RC cut/sign, vote, tally, promote, announce, archive, audit.", + "setup": "framework install/maintenance: install (adopt), upgrade, verify, override, status, secure-agent setup, shared-config sync.", + "pr-management": "PR-queue management: triage, stats, deep code review, quick-merge, stale-sweep, reviewer routing, pre-first-PR checks.", + "issue": "issue lifecycle: triage, reproduction, fix drafting, reassess, stale-sweep, dedup, backlog stats.", + "repo-health": "read-only repo-health audits: runner labels, workflow security, dependency/license/NOTICE, flaky tests, audit-finding fixes.", + "contributor-growth": "path-to-committer: activity sweeps, nominations, sentiment, readiness, committer/post-vote onboarding.", + "utilities": "framework meta-skills: write-skill, optimize-skill, skill-reconciler, list-skills.", + "mentoring": "newcomer mentoring: welcome, newcomer-issue explanations, good-first-issue authoring + sweep.", + "pairing": "pair a change with a structured self-review or a multi-agent adversarial review.", +} + + +def families_from_frontmatter() -> dict[str, set[str]]: + fam: dict[str, set[str]] = {} + for path in sorted(glob.glob("skills/*/SKILL.md")): + m = re.search(r"^family:\s*(\S+)", open(path, encoding="utf-8").read(), re.M) + if m: + fam.setdefault(m.group(1), set()).add(os.path.basename(os.path.dirname(path))) + return fam + + +def check(fam: dict[str, set[str]]) -> list[str]: + errors: list[str] = [] + + try: + market = json.load(open(MARKETPLACE, encoding="utf-8")) + listed = {p["name"] for p in market.get("plugins", [])} + except (OSError, ValueError) as exc: + return [f"{MARKETPLACE}: cannot read/parse ({exc})"] + + if "magpie" not in listed: + errors.append(f"{MARKETPLACE}: missing the all-in-one 'magpie' plugin entry") + + for family, skills in sorted(fam.items()): + name = f"magpie-{family}" + pdir = f"plugins/{name}" + if not os.path.isfile(f"{pdir}/.claude-plugin/plugin.json"): + errors.append(f"{name}: missing {pdir}/.claude-plugin/plugin.json") + continue + if name not in listed: + errors.append(f"{MARKETPLACE}: missing entry for '{name}'") + + sdir = f"{pdir}/skills" + have: dict[str, str] = {} + for entry in os.listdir(sdir) if os.path.isdir(sdir) else []: + fp = os.path.join(sdir, entry) + if os.path.islink(fp): + have[entry] = os.readlink(fp) + else: + errors.append(f"{name}: {fp} is not a symlink") + + for skill in sorted(skills - set(have)): + errors.append(f"{name}: missing symlink for '{skill}' (family={family})") + for skill in sorted(set(have) - skills): + errors.append(f"{name}: stale symlink '{skill}' — its skill is not family={family}") + for skill in sorted(skills & set(have)): + want = SYMLINK_TARGET.format(skill=skill) + if have[skill] != want: + errors.append(f"{name}: {sdir}/{skill} -> {have[skill]} (expected {want})") + + for pdir in sorted(glob.glob("plugins/magpie-*")): + family = os.path.basename(pdir)[len("magpie-"):] + if family not in fam: + errors.append(f"orphan plugin '{os.path.basename(pdir)}': no skill declares family '{family}'") + + return errors + + +def fix(fam: dict[str, set[str]]) -> None: + import shutil + + for pdir in glob.glob("plugins/magpie-*"): + shutil.rmtree(pdir) + for family, skills in sorted(fam.items()): + name = f"magpie-{family}" + sdir = f"plugins/{name}/skills" + os.makedirs(f"plugins/{name}/.claude-plugin") + os.makedirs(sdir) + for skill in sorted(skills): + os.symlink(SYMLINK_TARGET.format(skill=skill), os.path.join(sdir, skill)) + manifest = { + "name": name, + "description": f"Apache Magpie — {DESC.get(family, family + ' family skills')}", + "skills": "./skills", + } + with open(f"plugins/{name}/.claude-plugin/plugin.json", "w", encoding="utf-8") as fh: + json.dump(manifest, fh, indent=2) + fh.write("\n") + + market = json.load(open(MARKETPLACE, encoding="utf-8")) + keep = [p for p in market["plugins"] if p["name"] == "magpie"] + for family, skills in sorted(fam.items()): + keep.append({ + "name": f"magpie-{family}", + "source": f"./plugins/magpie-{family}", + "version": "0.2.0", + "description": f"Apache Magpie {family} family ({len(skills)} skills).", + }) + market["plugins"] = keep + with open(MARKETPLACE, "w", encoding="utf-8") as fh: + json.dump(market, fh, indent=2) + fh.write("\n") + print("Regenerated per-family plugins + marketplace entries from frontmatter.") + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--fix", action="store_true", help="regenerate plugins from frontmatter") + ap.add_argument("files", nargs="*", help="(ignored; present for pre-commit)") + args = ap.parse_args() + + fam = families_from_frontmatter() + if args.fix: + fix(fam) + return 0 + + errors = check(fam) + if errors: + print("Family plugins are out of sync with skills' `family:` frontmatter:", file=sys.stderr) + for e in errors: + print(f" - {e}", file=sys.stderr) + print("\nRun `python3 tools/dev/check-family-plugins.py --fix` to regenerate.", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From f98880f35e320b7d37de1ab48937902573743703 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 17:08:50 +0200 Subject: [PATCH 10/18] docs(marketplaces): explain all-in-one vs per-family install trade-offs Spell out that you can install either the all-in-one magpie plugin or any number of per-family plugins (and mix families), with the pros/cons of each: simplicity + Windows-safe (no symlinks) vs proportional always-on token cost. --- docs/setup/marketplaces.md | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index 4fee76983..265caf923 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -51,15 +51,35 @@ through the plugin/extension mechanisms of the major AI coding agents, ## Choosing a plugin: all-in-one vs per-family -The framework ships as **eleven** plugins so you install only what you use: - -- **`magpie`** (all-in-one) — every skill, all ten families. Simplest, but its - always-on metadata costs **~21.7k tokens in every session** (each installed - skill advertises a short description to the model on every turn — see - ["always-on" cost](#versioning) below). -- **Per-family plugins** — `magpie-security`, `magpie-release-management`, … — - install only the families you use, so only their skills are always-on. - **Recommended.** +The framework ships as **eleven** plugins. You can install **either** the +all-in-one plugin **or** any number of per-family plugins — and you can mix +several families. Pick based on the trade-off between install simplicity and +always-on token cost (each installed skill advertises a short description to +the model on **every** turn — see ["always-on" cost](#versioning) below). + +**All-in-one — `magpie`** + +- ✅ One install; all 70 skills; nothing to decide. Uses the real `skills/` + directory, so **no symlinks** — works on Windows out of the box. +- ⚠️ Adds **~21.7k always-on tokens to every session**, including families you + may never use — that context (and cost) is spent whether or not you invoke a + Magpie skill that turn. +- Best when you genuinely want everything, or you're on Windows without symlink + support. + +**Per-family — `magpie-`** *(recommended)* + +- ✅ Install only the families you use, so the always-on cost is proportional + (`magpie-security` ≈ 3.9k, `magpie-pairing` ≈ 0.6k). Install several to mix + and match. +- ⚠️ You manage a few installs instead of one; adding a family later is a + separate install; relies on git symlinks (see the Windows note below). +- Best for day-to-day use where you want a lean context window. + +Mixing is fine — e.g. install `magpie-release-management` + `magpie-security` +and nothing else. The two are **not** exclusive with the all-in-one either, but +installing both `magpie` *and* a family plugin just double-loads those skills, +so pick one approach. | Family plugin | Skills | ~Always-on tokens | |---|---|---| From 047b71f8e0138bcf71fbaed957e821e903c02621 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 17:12:09 +0200 Subject: [PATCH 11/18] docs(marketplaces): flag marketplace support as experimental; point to the portable non-marketplace install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a WARNING callout: plugin/marketplace mechanisms across the agentic CLIs (Claude Code, Codex, Copilot, Gemini, apm) are experimental and change between releases. The non-marketplace /magpie-setup install from the signed SVN release or the GitHub repo is always available, harness-neutral, and portable — it works on every agentic CLI via the universal .agents/skills/ layout, not just the ones with a marketplace. --- docs/setup/marketplaces.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index 265caf923..40d9c9213 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -49,6 +49,21 @@ through the plugin/extension mechanisms of the major AI coding agents, > `X.Y.Z` git tag; they are derived from — not a substitute for — the ASF > source release. +> [!WARNING] +> **Marketplace/plugin support is experimental in most agentic CLIs.** The +> plugin and marketplace mechanisms of Claude Code, Codex, Copilot, Gemini, +> `apm`, and others are new and evolving — manifest schemas and install +> commands change between releases (see [Verification status](#verification-status)). +> If a marketplace install breaks, or your agent has no marketplace at all, the +> **non-marketplace install is always available, universal, and portable**: +> adopt Magpie with `/magpie-setup` from either the **signed SVN release** +> (`dist.apache.org`) or the **GitHub repo** (git tag or branch) — see +> [`install-recipes.md`](install-recipes.md). That path is **harness-neutral**: +> it wires the skills into *any* agent's directory via the universal +> `.agents/skills/` layout, so it works on **every** agentic CLI — not only the +> ones with a marketplace. Rule of thumb: use a marketplace for a quick trial +> on a supported agent; use `/magpie-setup` for a stable, portable install. + ## Choosing a plugin: all-in-one vs per-family The framework ships as **eleven** plugins. You can install **either** the From 05fa2e3721f407b652ba9e65bd58673b2640bbb6 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 17:16:37 +0200 Subject: [PATCH 12/18] docs(marketplaces): explain skill-name differences across install methods Same skill, different invocation name: portable /magpie-setup install bakes a magpie- prefix into each skill (invoked /magpie-), whereas a marketplace plugin namespaces with plugin:skill and keeps the bare name (/magpie: for the all-in-one, /magpie-: for a family plugin). Adds a comparison table + examples and notes that repo cross-references use the portable form. --- docs/setup/marketplaces.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index 40d9c9213..daf1e2f6e 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -7,6 +7,7 @@ - [Installing Apache Magpie from agent marketplaces](#installing-apache-magpie-from-agent-marketplaces) - [Choosing a plugin: all-in-one vs per-family](#choosing-a-plugin-all-in-one-vs-per-family) + - [Skill names differ by install method](#skill-names-differ-by-install-method) - [Supported agents](#supported-agents) - [Claude Code](#claude-code) - [OpenAI Codex CLI](#openai-codex-cli) @@ -130,6 +131,39 @@ so there is a single source of truth for every skill. > needs no symlinks. macOS and Linux are unaffected. (Verified on macOS: a > `/plugin marketplace add` GitHub clone preserves and resolves the symlinks.) +## Skill names differ by install method + +The **same skill** is invoked by a **different name** depending on how you +installed it. The portable `/magpie-setup` install bakes a `magpie-` prefix into +each skill's name (so framework skills never collide with your own); the +marketplace plugins namespace with `plugin:skill` and keep the bare skill name. + +| Skill (directory) | Portable — `/magpie-setup` snapshot | Marketplace — all-in-one `magpie` | Marketplace — family plugin | +|---|---|---|---| +| `release-vote-tally` | `/magpie-release-vote-tally` | `/magpie:release-vote-tally` | `/magpie-release-management:release-vote-tally` | +| `security-issue-triage` | `/magpie-security-issue-triage` | `/magpie:security-issue-triage` | `/magpie-security:security-issue-triage` | +| `setup` | `/magpie-setup` | `/magpie:setup` | `/magpie-setup:setup` | + +Why the difference: + +- **Portable install** (`/magpie-setup` snapshot) — the `setup` skill symlinks + each framework skill under a `magpie-` entry (e.g. + `skills/release-vote-tally/` → `magpie-release-vote-tally`), and the skill's + own frontmatter `name:` carries the same `magpie-` prefix. It is therefore + invoked as a **single hyphenated token**, `/magpie-`. The prefix *is* the + namespace — it keeps framework skills from clashing with the adopter's own + skills. +- **Marketplace install** — the **plugin name** is the namespace, applied with a + **colon**: `/:`. The `magpie-` frontmatter prefix is ignored + (the plugin already namespaces), so the skill keeps its bare directory name. + With the all-in-one plugin that's `/magpie:`; with a family plugin it's + `/magpie-:`. + +Throughout this repo's own docs and skills, cross-references use the +**portable** form (`/magpie-`), because that is the canonical install. +When you install via a marketplace, translate `/magpie-` to +`/:` (drop the `magpie-` prefix, add the plugin namespace). + ## Supported agents Every method below uses the **GitHub repository From 0bddcb6fad789897a844f307f55dc48efabe18a6 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 17:28:21 +0200 Subject: [PATCH 13/18] fix(security): close files in check-family-plugins; use pathlib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the 3 CodeQL 'file is not always closed' alerts (35/36/37) flagged on #907. Rewrite the family-plugin validator with pathlib — Path.read_text / write_text open-and-close by construction, so no dangling handles — and the same read/write/symlink logic reads cleaner. Behaviour unchanged (check/--fix verified: passes, idempotent, catches drift). --- tools/dev/check-family-plugins.py | 68 +++++++++++++++---------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/tools/dev/check-family-plugins.py b/tools/dev/check-family-plugins.py index ee6a36ad5..f0096ac4d 100644 --- a/tools/dev/check-family-plugins.py +++ b/tools/dev/check-family-plugins.py @@ -17,13 +17,15 @@ from __future__ import annotations import argparse -import glob import json -import os import re +import shutil import sys +from pathlib import Path -MARKETPLACE = ".claude-plugin/marketplace.json" +SKILLS = Path("skills") +PLUGINS = Path("plugins") +MARKETPLACE = Path(".claude-plugin/marketplace.json") SYMLINK_TARGET = "../../../skills/{skill}" # relative to plugins/magpie-/skills/ # Per-family descriptions used when (re)generating manifests. Keep in sync with @@ -44,10 +46,10 @@ def families_from_frontmatter() -> dict[str, set[str]]: fam: dict[str, set[str]] = {} - for path in sorted(glob.glob("skills/*/SKILL.md")): - m = re.search(r"^family:\s*(\S+)", open(path, encoding="utf-8").read(), re.M) + for path in sorted(SKILLS.glob("*/SKILL.md")): + m = re.search(r"^family:\s*(\S+)", path.read_text(encoding="utf-8"), re.M) if m: - fam.setdefault(m.group(1), set()).add(os.path.basename(os.path.dirname(path))) + fam.setdefault(m.group(1), set()).add(path.parent.name) return fam @@ -55,7 +57,7 @@ def check(fam: dict[str, set[str]]) -> list[str]: errors: list[str] = [] try: - market = json.load(open(MARKETPLACE, encoding="utf-8")) + market = json.loads(MARKETPLACE.read_text(encoding="utf-8")) listed = {p["name"] for p in market.get("plugins", [])} except (OSError, ValueError) as exc: return [f"{MARKETPLACE}: cannot read/parse ({exc})"] @@ -65,61 +67,59 @@ def check(fam: dict[str, set[str]]) -> list[str]: for family, skills in sorted(fam.items()): name = f"magpie-{family}" - pdir = f"plugins/{name}" - if not os.path.isfile(f"{pdir}/.claude-plugin/plugin.json"): + pdir = PLUGINS / name + if not (pdir / ".claude-plugin" / "plugin.json").is_file(): errors.append(f"{name}: missing {pdir}/.claude-plugin/plugin.json") continue if name not in listed: errors.append(f"{MARKETPLACE}: missing entry for '{name}'") - sdir = f"{pdir}/skills" - have: dict[str, str] = {} - for entry in os.listdir(sdir) if os.path.isdir(sdir) else []: - fp = os.path.join(sdir, entry) - if os.path.islink(fp): - have[entry] = os.readlink(fp) + sdir = pdir / "skills" + have: dict[str, Path] = {} + for entry in sorted(sdir.iterdir()) if sdir.is_dir() else []: + if entry.is_symlink(): + have[entry.name] = entry.readlink() else: - errors.append(f"{name}: {fp} is not a symlink") + errors.append(f"{name}: {entry} is not a symlink") for skill in sorted(skills - set(have)): errors.append(f"{name}: missing symlink for '{skill}' (family={family})") for skill in sorted(set(have) - skills): errors.append(f"{name}: stale symlink '{skill}' — its skill is not family={family}") for skill in sorted(skills & set(have)): - want = SYMLINK_TARGET.format(skill=skill) + want = Path(SYMLINK_TARGET.format(skill=skill)) if have[skill] != want: - errors.append(f"{name}: {sdir}/{skill} -> {have[skill]} (expected {want})") + errors.append(f"{name}: {sdir / skill} -> {have[skill]} (expected {want})") - for pdir in sorted(glob.glob("plugins/magpie-*")): - family = os.path.basename(pdir)[len("magpie-"):] + for pdir in sorted(PLUGINS.glob("magpie-*")): + family = pdir.name[len("magpie-"):] if family not in fam: - errors.append(f"orphan plugin '{os.path.basename(pdir)}': no skill declares family '{family}'") + errors.append(f"orphan plugin '{pdir.name}': no skill declares family '{family}'") return errors def fix(fam: dict[str, set[str]]) -> None: - import shutil - - for pdir in glob.glob("plugins/magpie-*"): + for pdir in PLUGINS.glob("magpie-*"): shutil.rmtree(pdir) for family, skills in sorted(fam.items()): name = f"magpie-{family}" - sdir = f"plugins/{name}/skills" - os.makedirs(f"plugins/{name}/.claude-plugin") - os.makedirs(sdir) + pdir = PLUGINS / name + sdir = pdir / "skills" + (pdir / ".claude-plugin").mkdir(parents=True) + sdir.mkdir(parents=True) for skill in sorted(skills): - os.symlink(SYMLINK_TARGET.format(skill=skill), os.path.join(sdir, skill)) + (sdir / skill).symlink_to(SYMLINK_TARGET.format(skill=skill)) manifest = { "name": name, "description": f"Apache Magpie — {DESC.get(family, family + ' family skills')}", "skills": "./skills", } - with open(f"plugins/{name}/.claude-plugin/plugin.json", "w", encoding="utf-8") as fh: - json.dump(manifest, fh, indent=2) - fh.write("\n") + (pdir / ".claude-plugin" / "plugin.json").write_text( + json.dumps(manifest, indent=2) + "\n", encoding="utf-8" + ) - market = json.load(open(MARKETPLACE, encoding="utf-8")) + market = json.loads(MARKETPLACE.read_text(encoding="utf-8")) keep = [p for p in market["plugins"] if p["name"] == "magpie"] for family, skills in sorted(fam.items()): keep.append({ @@ -129,9 +129,7 @@ def fix(fam: dict[str, set[str]]) -> None: "description": f"Apache Magpie {family} family ({len(skills)} skills).", }) market["plugins"] = keep - with open(MARKETPLACE, "w", encoding="utf-8") as fh: - json.dump(market, fh, indent=2) - fh.write("\n") + MARKETPLACE.write_text(json.dumps(market, indent=2) + "\n", encoding="utf-8") print("Regenerated per-family plugins + marketplace entries from frontmatter.") From d0f59ed268c547cec1e2c04c0d3d70154c2d78e7 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Sun, 19 Jul 2026 17:44:08 +0200 Subject: [PATCH 14/18] build(prek): validate all plugin manifests, not just family symlinks Extend check-family-plugins to confirm every plugin is properly defined: - all-in-one 'magpie' manifest names itself, declares skills: ./skills, and wires the hooks/check-upgrade.sh SessionStart hook (which must exist); - every marketplace.json entry resolves to a matching, uniquely-named plugin.json (catches dangling sources, name mismatches, duplicates); - each per-family manifest is well-formed (name/skills/description) in addition to the existing symlink-vs-frontmatter check. Also trigger the hook on .claude-plugin/plugin.json and hooks/check-upgrade.sh changes. --- .pre-commit-config.yaml | 18 +++--- tools/dev/check-family-plugins.py | 103 ++++++++++++++++++++++++------ 2 files changed, 94 insertions(+), 27 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0bce61b86..999ae6dc1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -233,19 +233,21 @@ repos: files: ^(\.gitattributes|\.(agents|claude|github|kiro)/skills/.*|skills/.*|projects/_template/.*)$ types_or: [symlink, file] pass_filenames: false - # Family-plugin consistency guard. The per-family marketplace plugins under - # plugins/magpie-/ must stay in sync with the skills' `family:` - # frontmatter (the source of truth): each family's skills present as - # single-hop symlinks into the shared skills/ tree, and each family listed in - # .claude-plugin/marketplace.json alongside the all-in-one `magpie`. Catches a - # new skill, a changed family, or a stale symlink. `--fix` regenerates them. + # Marketplace-plugin consistency guard. Validates that every plugin is + # properly defined and in sync with the skills' `family:` frontmatter (the + # source of truth): the all-in-one `magpie` manifest + its upgrade-hook + # script, each per-family manifest (name / skills / description), every + # `.claude-plugin/marketplace.json` entry resolving to a matching, uniquely + # named plugin.json, and each family's skills present as single-hop symlinks + # into the shared skills/ tree. Catches a new skill, a changed family, a stale + # symlink, or a malformed manifest. `--fix` regenerates the family plugins. - repo: local hooks: - id: check-family-plugins - name: check-family-plugins (marketplace families vs frontmatter) + name: check-family-plugins (marketplace plugins vs frontmatter) language: system entry: python3 tools/dev/check-family-plugins.py - files: ^(skills/.*/SKILL\.md|plugins/.*|\.claude-plugin/marketplace\.json)$ + files: ^(skills/.*/SKILL\.md|plugins/.*|\.claude-plugin/(marketplace|plugin)\.json|hooks/check-upgrade\.sh)$ pass_filenames: false # Workspace-membership guard. The single source of truth for # which Python projects get pre-commit hooks + the CI pytest diff --git a/tools/dev/check-family-plugins.py b/tools/dev/check-family-plugins.py index f0096ac4d..b81b6d4ce 100644 --- a/tools/dev/check-family-plugins.py +++ b/tools/dev/check-family-plugins.py @@ -1,15 +1,23 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 # https://www.apache.org/licenses/LICENSE-2.0 -"""Validate the per-family marketplace plugins against the skills' `family:` -frontmatter — the source of truth. +"""Validate the marketplace plugins against the skills' `family:` frontmatter — +the source of truth. -For every family that skills declare in their `family:` frontmatter there must -be a `plugins/magpie-/` plugin whose `skills/` directory contains -exactly that family's skills as single-hop symlinks into the shared -`skills/` tree, and `.claude-plugin/marketplace.json` must list it (plus -the all-in-one `magpie`). Drift — a new skill, a changed family, a stale -symlink — fails the check. +Checks that every plugin is properly defined: + +- the all-in-one `magpie` manifest (`.claude-plugin/plugin.json`) names itself + correctly, declares `skills: ./skills`, and wires the `hooks/check-upgrade.sh` + SessionStart hook (which must exist); +- every `.claude-plugin/marketplace.json` entry resolves to a matching, + uniquely-named `plugin.json`; +- for every family declared in a skill's `family:` frontmatter there is a + `plugins/magpie-/` plugin whose manifest is well-formed and whose + `skills/` directory contains exactly that family's skills as single-hop + symlinks into the shared `skills/` tree. + +Drift — a new skill, a changed family, a stale symlink, a malformed or +mis-named manifest, a dangling marketplace entry — fails the check. Run with `--fix` to regenerate the family plugins + symlinks + marketplace entries from the frontmatter. @@ -26,6 +34,8 @@ SKILLS = Path("skills") PLUGINS = Path("plugins") MARKETPLACE = Path(".claude-plugin/marketplace.json") +ROOT_MANIFEST = Path(".claude-plugin/plugin.json") # the all-in-one `magpie` plugin +HOOK_SCRIPT = Path("hooks/check-upgrade.sh") # referenced by the all-in-one plugin hook SYMLINK_TARGET = "../../../skills/{skill}" # relative to plugins/magpie-/skills/ # Per-family descriptions used when (re)generating manifests. Keep in sync with @@ -44,6 +54,31 @@ } +def load_json(path: Path): + try: + return json.loads(path.read_text(encoding="utf-8")), None + except (OSError, ValueError) as exc: + return None, f"{path}: cannot read/parse ({exc})" + + +def validate_manifest(path: Path, expected_name: str) -> list[str]: + """A plugin.json must exist, be valid JSON, name itself correctly, and + declare its skills + a description.""" + if not path.is_file(): + return [f"{path}: missing plugin manifest"] + data, err = load_json(path) + if err: + return [err] + errors: list[str] = [] + if data.get("name") != expected_name: + errors.append(f"{path}: name is {data.get('name')!r}, expected {expected_name!r}") + if data.get("skills") != "./skills": + errors.append(f"{path}: 'skills' is {data.get('skills')!r}, expected './skills'") + if not str(data.get("description", "")).strip(): + errors.append(f"{path}: missing/empty 'description'") + return errors + + def families_from_frontmatter() -> dict[str, set[str]]: fam: dict[str, set[str]] = {} for path in sorted(SKILLS.glob("*/SKILL.md")): @@ -56,31 +91,60 @@ def families_from_frontmatter() -> dict[str, set[str]]: def check(fam: dict[str, set[str]]) -> list[str]: errors: list[str] = [] - try: - market = json.loads(MARKETPLACE.read_text(encoding="utf-8")) - listed = {p["name"] for p in market.get("plugins", [])} - except (OSError, ValueError) as exc: - return [f"{MARKETPLACE}: cannot read/parse ({exc})"] + market, err = load_json(MARKETPLACE) + if err: + return [err] + entries = market.get("plugins", []) + listed = {p.get("name") for p in entries} + # 1) The all-in-one `magpie` plugin: well-formed manifest, listed, and its + # SessionStart hook script is present + referenced. + errors += validate_manifest(ROOT_MANIFEST, "magpie") if "magpie" not in listed: errors.append(f"{MARKETPLACE}: missing the all-in-one 'magpie' plugin entry") + if not HOOK_SCRIPT.is_file(): + errors.append(f"{HOOK_SCRIPT}: missing (referenced by the all-in-one plugin's SessionStart hook)") + root_data, root_err = load_json(ROOT_MANIFEST) + if root_data is not None and "check-upgrade.sh" not in json.dumps(root_data.get("hooks", {})): + errors.append(f"{ROOT_MANIFEST}: SessionStart hook does not reference hooks/check-upgrade.sh") + + # 2) Every marketplace entry resolves to a matching, uniquely-named manifest. + seen: set[str] = set() + for ent in entries: + name = ent.get("name") + source = ent.get("source") + if not name or not source: + errors.append(f"{MARKETPLACE}: entry missing 'name'/'source': {ent}") + continue + if name in seen: + errors.append(f"{MARKETPLACE}: duplicate plugin entry '{name}'") + seen.add(name) + manifest = ROOT_MANIFEST if source == "." else Path(source) / ".claude-plugin" / "plugin.json" + if not manifest.is_file(): + errors.append(f"{MARKETPLACE}: '{name}' source '{source}' has no {manifest}") + continue + data, jerr = load_json(manifest) + if jerr is None and data.get("name") != name: + errors.append(f"{MARKETPLACE}: '{name}' resolves to plugin.json named {data.get('name')!r}") + # 3) Per-family plugins: manifest well-formed + symlinks match the frontmatter. for family, skills in sorted(fam.items()): name = f"magpie-{family}" pdir = PLUGINS / name - if not (pdir / ".claude-plugin" / "plugin.json").is_file(): - errors.append(f"{name}: missing {pdir}/.claude-plugin/plugin.json") + manifest_errs = validate_manifest(pdir / ".claude-plugin" / "plugin.json", name) + errors += manifest_errs + if manifest_errs: continue if name not in listed: errors.append(f"{MARKETPLACE}: missing entry for '{name}'") sdir = pdir / "skills" have: dict[str, Path] = {} - for entry in sorted(sdir.iterdir()) if sdir.is_dir() else []: - if entry.is_symlink(): - have[entry.name] = entry.readlink() + for link in sorted(sdir.iterdir()) if sdir.is_dir() else []: + if link.is_symlink(): + have[link.name] = link.readlink() else: - errors.append(f"{name}: {entry} is not a symlink") + errors.append(f"{name}: {link} is not a symlink") for skill in sorted(skills - set(have)): errors.append(f"{name}: missing symlink for '{skill}' (family={family})") @@ -91,6 +155,7 @@ def check(fam: dict[str, set[str]]) -> list[str]: if have[skill] != want: errors.append(f"{name}: {sdir / skill} -> {have[skill]} (expected {want})") + # 4) No orphan plugin dirs (a magpie- with no skills declaring family x). for pdir in sorted(PLUGINS.glob("magpie-*")): family = pdir.name[len("magpie-"):] if family not in fam: From 1265aba6f62274a507abccbfed96b982a88b43d9 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Wed, 29 Jul 2026 23:14:24 +0200 Subject: [PATCH 15/18] fix(marketplace): give family plugins version + author metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `claude plugin validate . --strict` reported 20 warnings — each of the ten per-family plugin manifests was missing `version` and `author`. Nothing was broken (a family plugin installs and enables fine), but --strict treats the warnings as errors, so the repo root failed validation. Rather than hand-edit ten generated files, make the shared metadata inherited: `tools/dev/check-family-plugins.py` now copies `version`, `author`, `homepage`, `repository`, and `license` from the all-in-one `.claude-plugin/plugin.json` into every family manifest, and `check` fails if any of them — or a marketplace entry version — drifts from the root. This also removes the hardcoded `"version": "0.2.0"` in `--fix`, which would have silently reset the family marketplace entries to 0.2.0 after a release bump. A bump now has one edit point (the root manifest) plus `python3 tools/dev/check-family-plugins.py --fix` to propagate; the prek hook catches a missed propagation. Documented in the release config's `version_manifest_files` notes and docs/setup/marketplaces.md. `claude plugin validate . --strict` now passes with 0 warnings. --- docs/setup/marketplaces.md | 7 ++ .../.claude-plugin/plugin.json | 8 ++ .../magpie-issue/.claude-plugin/plugin.json | 8 ++ .../.claude-plugin/plugin.json | 8 ++ .../magpie-pairing/.claude-plugin/plugin.json | 8 ++ .../.claude-plugin/plugin.json | 8 ++ .../.claude-plugin/plugin.json | 8 ++ .../.claude-plugin/plugin.json | 8 ++ .../.claude-plugin/plugin.json | 8 ++ .../magpie-setup/.claude-plugin/plugin.json | 8 ++ .../.claude-plugin/plugin.json | 8 ++ projects/magpie/release-management-config.md | 16 +++- tools/dev/check-family-plugins.py | 76 +++++++++++++++---- 13 files changed, 165 insertions(+), 14 deletions(-) diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index daf1e2f6e..c1b6d4f60 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -362,6 +362,13 @@ version strings are kept in sync with `pyproject.toml` by the [`release-management-config.md`](../../projects/magpie/release-management-config.md) (`version_manifest_files`). +The per-family plugin manifests do not carry their own version: they inherit +`version`, `author`, `homepage`, `repository`, and `license` from the +all-in-one [`.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json), so +a bump has one edit point. Propagate it with +`python3 tools/dev/check-family-plugins.py --fix`; the same script, run as a +prek hook, fails the build on any manifest left behind at the old version. + ## Verification status The Claude Code and Gemini CLI manifests follow the current published diff --git a/plugins/magpie-contributor-growth/.claude-plugin/plugin.json b/plugins/magpie-contributor-growth/.claude-plugin/plugin.json index 82e669b2a..2fdd90ef1 100644 --- a/plugins/magpie-contributor-growth/.claude-plugin/plugin.json +++ b/plugins/magpie-contributor-growth/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-contributor-growth", "description": "Apache Magpie \u2014 path-to-committer: activity sweeps, nominations, sentiment, readiness, committer/post-vote onboarding.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/plugins/magpie-issue/.claude-plugin/plugin.json b/plugins/magpie-issue/.claude-plugin/plugin.json index ba22cc0ea..1a56c617c 100644 --- a/plugins/magpie-issue/.claude-plugin/plugin.json +++ b/plugins/magpie-issue/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-issue", "description": "Apache Magpie \u2014 issue lifecycle: triage, reproduction, fix drafting, reassess, stale-sweep, dedup, backlog stats.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/plugins/magpie-mentoring/.claude-plugin/plugin.json b/plugins/magpie-mentoring/.claude-plugin/plugin.json index c59f30eff..9e25cdcae 100644 --- a/plugins/magpie-mentoring/.claude-plugin/plugin.json +++ b/plugins/magpie-mentoring/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-mentoring", "description": "Apache Magpie \u2014 newcomer mentoring: welcome, newcomer-issue explanations, good-first-issue authoring + sweep.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/plugins/magpie-pairing/.claude-plugin/plugin.json b/plugins/magpie-pairing/.claude-plugin/plugin.json index d6a868ea0..55b90b642 100644 --- a/plugins/magpie-pairing/.claude-plugin/plugin.json +++ b/plugins/magpie-pairing/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-pairing", "description": "Apache Magpie \u2014 pair a change with a structured self-review or a multi-agent adversarial review.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/plugins/magpie-pr-management/.claude-plugin/plugin.json b/plugins/magpie-pr-management/.claude-plugin/plugin.json index 573658805..61b492381 100644 --- a/plugins/magpie-pr-management/.claude-plugin/plugin.json +++ b/plugins/magpie-pr-management/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-pr-management", "description": "Apache Magpie \u2014 PR-queue management: triage, stats, deep code review, quick-merge, stale-sweep, reviewer routing, pre-first-PR checks.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/plugins/magpie-release-management/.claude-plugin/plugin.json b/plugins/magpie-release-management/.claude-plugin/plugin.json index 15bf156e1..346c4b6ac 100644 --- a/plugins/magpie-release-management/.claude-plugin/plugin.json +++ b/plugins/magpie-release-management/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-release-management", "description": "Apache Magpie \u2014 ASF release lifecycle: plan, RC cut/sign, vote, tally, promote, announce, archive, audit.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/plugins/magpie-repo-health/.claude-plugin/plugin.json b/plugins/magpie-repo-health/.claude-plugin/plugin.json index 02b1c1d7b..ece010f5c 100644 --- a/plugins/magpie-repo-health/.claude-plugin/plugin.json +++ b/plugins/magpie-repo-health/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-repo-health", "description": "Apache Magpie \u2014 read-only repo-health audits: runner labels, workflow security, dependency/license/NOTICE, flaky tests, audit-finding fixes.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/plugins/magpie-security/.claude-plugin/plugin.json b/plugins/magpie-security/.claude-plugin/plugin.json index 41ad7c4bf..236d7111b 100644 --- a/plugins/magpie-security/.claude-plugin/plugin.json +++ b/plugins/magpie-security/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-security", "description": "Apache Magpie \u2014 security-issue handling lifecycle \u2014 import through CVE publication, triage, sync, dedup, invalidate. Maintainer-only.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/plugins/magpie-setup/.claude-plugin/plugin.json b/plugins/magpie-setup/.claude-plugin/plugin.json index 8c55abd77..197b9c109 100644 --- a/plugins/magpie-setup/.claude-plugin/plugin.json +++ b/plugins/magpie-setup/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-setup", "description": "Apache Magpie \u2014 framework install/maintenance: install (adopt), upgrade, verify, override, status, secure-agent setup, shared-config sync.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/plugins/magpie-utilities/.claude-plugin/plugin.json b/plugins/magpie-utilities/.claude-plugin/plugin.json index c8fa0933b..b4a66690b 100644 --- a/plugins/magpie-utilities/.claude-plugin/plugin.json +++ b/plugins/magpie-utilities/.claude-plugin/plugin.json @@ -1,5 +1,13 @@ { "name": "magpie-utilities", "description": "Apache Magpie \u2014 framework meta-skills: write-skill, optimize-skill, skill-reconciler, list-skills.", + "version": "0.2.0", + "author": { + "name": "Apache Magpie", + "url": "https://magpie.apache.org/" + }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", "skills": "./skills" } diff --git a/projects/magpie/release-management-config.md b/projects/magpie/release-management-config.md index f7b856b98..53b2b9aa0 100644 --- a/projects/magpie/release-management-config.md +++ b/projects/magpie/release-management-config.md @@ -71,7 +71,21 @@ mandatory ASF approval + announce mechanisms (`dev-list-vote`, | `git_upstream_remote` | `upstream` | | `release_planning_issue_template` | *(none — uses the `release-prepare` default template)* | | `release_branch_base` | `main` | -| `version_manifest_files` | `pyproject.toml`, `uv.lock`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, `.codex-plugin/plugin.json`, `gemini-extension.json`, `apm.yml` | +| `version_manifest_files` | `pyproject.toml`, `uv.lock`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, `.codex-plugin/plugin.json`, `gemini-extension.json`, `apm.yml`, and the ten generated `plugins/magpie-/.claude-plugin/plugin.json` manifests | + +The ten per-family plugin manifests and the family entries in +`.claude-plugin/marketplace.json` are **generated** — they inherit `version` +(and `author`, `homepage`, `repository`, `license`) from +`.claude-plugin/plugin.json`. Bump the version in that root manifest, then +propagate it mechanically instead of editing the ten files by hand: + +```bash +python3 tools/dev/check-family-plugins.py --fix +``` + +`tools/dev/check-family-plugins.py` (a prek hook) fails the build if any +family manifest or marketplace entry is left behind at the previous version, +so a missed propagation cannot reach a release. ## Backends diff --git a/tools/dev/check-family-plugins.py b/tools/dev/check-family-plugins.py index b81b6d4ce..f5d799ada 100644 --- a/tools/dev/check-family-plugins.py +++ b/tools/dev/check-family-plugins.py @@ -10,14 +10,16 @@ correctly, declares `skills: ./skills`, and wires the `hooks/check-upgrade.sh` SessionStart hook (which must exist); - every `.claude-plugin/marketplace.json` entry resolves to a matching, - uniquely-named `plugin.json`; + uniquely-named `plugin.json` and carries the root manifest's version; - for every family declared in a skill's `family:` frontmatter there is a - `plugins/magpie-/` plugin whose manifest is well-formed and whose - `skills/` directory contains exactly that family's skills as single-hop - symlinks into the shared `skills/` tree. + `plugins/magpie-/` plugin whose manifest is well-formed, inherits the + root manifest's shared metadata (version, author, homepage, repository, + license), and whose `skills/` directory contains exactly that family's skills + as single-hop symlinks into the shared `skills/` tree. Drift — a new skill, a changed family, a stale symlink, a malformed or -mis-named manifest, a dangling marketplace entry — fails the check. +mis-named manifest, a dangling marketplace entry, a family manifest left behind +at the previous release's version — fails the check. Run with `--fix` to regenerate the family plugins + symlinks + marketplace entries from the frontmatter. @@ -38,6 +40,12 @@ HOOK_SCRIPT = Path("hooks/check-upgrade.sh") # referenced by the all-in-one plugin hook SYMLINK_TARGET = "../../../skills/{skill}" # relative to plugins/magpie-/skills/ +# Metadata the per-family plugins and the marketplace entries inherit verbatim +# from the all-in-one manifest, so a release bump has exactly one edit point. +# `version` + `author` are what `claude plugin validate --strict` warns about +# when absent; the rest carry attribution into a marketplace listing. +INHERITED = ("version", "author", "homepage", "repository", "license") + # Per-family descriptions used when (re)generating manifests. Keep in sync with # the family README; `check` does not enforce these (only structure/symlinks). DESC = { @@ -61,9 +69,12 @@ def load_json(path: Path): return None, f"{path}: cannot read/parse ({exc})" -def validate_manifest(path: Path, expected_name: str) -> list[str]: +def validate_manifest( + path: Path, expected_name: str, inherited: dict | None = None +) -> list[str]: """A plugin.json must exist, be valid JSON, name itself correctly, and - declare its skills + a description.""" + declare its skills + a description. Per-family manifests additionally carry + the root manifest's shared metadata (`inherited`) verbatim.""" if not path.is_file(): return [f"{path}: missing plugin manifest"] data, err = load_json(path) @@ -76,9 +87,26 @@ def validate_manifest(path: Path, expected_name: str) -> list[str]: errors.append(f"{path}: 'skills' is {data.get('skills')!r}, expected './skills'") if not str(data.get("description", "")).strip(): errors.append(f"{path}: missing/empty 'description'") + for key, want in (inherited or {}).items(): + if data.get(key) != want: + errors.append( + f"{path}: {key!r} is {data.get(key)!r}, expected {want!r} " + f"(inherited from {ROOT_MANIFEST})" + ) return errors +def root_metadata() -> tuple[dict, list[str]]: + """The subset of the all-in-one manifest that the family plugins inherit.""" + data, err = load_json(ROOT_MANIFEST) + if err: + return {}, [err] + missing = [k for k in INHERITED if not data.get(k)] + if missing: + return {}, [f"{ROOT_MANIFEST}: missing {', '.join(repr(k) for k in missing)}"] + return {k: data[k] for k in INHERITED}, [] + + def families_from_frontmatter() -> dict[str, set[str]]: fam: dict[str, set[str]] = {} for path in sorted(SKILLS.glob("*/SKILL.md")): @@ -97,6 +125,9 @@ def check(fam: dict[str, set[str]]) -> list[str]: entries = market.get("plugins", []) listed = {p.get("name") for p in entries} + shared, meta_errs = root_metadata() + errors += meta_errs + # 1) The all-in-one `magpie` plugin: well-formed manifest, listed, and its # SessionStart hook script is present + referenced. errors += validate_manifest(ROOT_MANIFEST, "magpie") @@ -126,12 +157,19 @@ def check(fam: dict[str, set[str]]) -> list[str]: data, jerr = load_json(manifest) if jerr is None and data.get("name") != name: errors.append(f"{MARKETPLACE}: '{name}' resolves to plugin.json named {data.get('name')!r}") + if shared and ent.get("version") != shared["version"]: + errors.append( + f"{MARKETPLACE}: '{name}' version is {ent.get('version')!r}, " + f"expected {shared['version']!r} (from {ROOT_MANIFEST})" + ) # 3) Per-family plugins: manifest well-formed + symlinks match the frontmatter. for family, skills in sorted(fam.items()): name = f"magpie-{family}" pdir = PLUGINS / name - manifest_errs = validate_manifest(pdir / ".claude-plugin" / "plugin.json", name) + manifest_errs = validate_manifest( + pdir / ".claude-plugin" / "plugin.json", name, inherited=shared or None + ) errors += manifest_errs if manifest_errs: continue @@ -164,7 +202,18 @@ def check(fam: dict[str, set[str]]) -> list[str]: return errors -def fix(fam: dict[str, set[str]]) -> None: +def fix(fam: dict[str, set[str]]) -> int: + shared, meta_errs = root_metadata() + if meta_errs: + for e in meta_errs: + print(f" - {e}", file=sys.stderr) + print( + f"\nCannot regenerate: the family plugins inherit " + f"{', '.join(INHERITED)} from {ROOT_MANIFEST}.", + file=sys.stderr, + ) + return 1 + for pdir in PLUGINS.glob("magpie-*"): shutil.rmtree(pdir) for family, skills in sorted(fam.items()): @@ -178,6 +227,7 @@ def fix(fam: dict[str, set[str]]) -> None: manifest = { "name": name, "description": f"Apache Magpie — {DESC.get(family, family + ' family skills')}", + **shared, "skills": "./skills", } (pdir / ".claude-plugin" / "plugin.json").write_text( @@ -185,17 +235,18 @@ def fix(fam: dict[str, set[str]]) -> None: ) market = json.loads(MARKETPLACE.read_text(encoding="utf-8")) - keep = [p for p in market["plugins"] if p["name"] == "magpie"] + keep = [p | {"version": shared["version"]} for p in market["plugins"] if p["name"] == "magpie"] for family, skills in sorted(fam.items()): keep.append({ "name": f"magpie-{family}", "source": f"./plugins/magpie-{family}", - "version": "0.2.0", + "version": shared["version"], "description": f"Apache Magpie {family} family ({len(skills)} skills).", }) market["plugins"] = keep MARKETPLACE.write_text(json.dumps(market, indent=2) + "\n", encoding="utf-8") print("Regenerated per-family plugins + marketplace entries from frontmatter.") + return 0 def main() -> int: @@ -206,8 +257,7 @@ def main() -> int: fam = families_from_frontmatter() if args.fix: - fix(fam) - return 0 + return fix(fam) errors = check(fam) if errors: From 4189362d99fbd88ffdfed6fbe4ace5a01eaa471d Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Thu, 30 Jul 2026 09:17:24 +0200 Subject: [PATCH 16/18] fix(marketplace): mirror pyproject's dev version in every manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every plugin manifest claimed version `0.2.0` while `pyproject.toml` sits at `0.2.0.dev0` (the post-0.1.0 bump, #906). Only `0.1.0` is tagged, so the manifests were advertising a release that does not exist yet — and the docs claimed they were "kept in sync with pyproject.toml", which they were not. Make `pyproject.toml`'s `project.version` the single authority and mirror it verbatim, `.devN` suffix included. The version now flows outward in two hops, neither hand-edited: pyproject.toml -> .claude-plugin/plugin.json, .codex-plugin/plugin.json, gemini-extension.json, apm.yml -> the 10 generated family manifests + 11 marketplace entries `check` fails on drift at either hop; `--fix` propagates both. A release bump is now one edit plus one command. Mirroring the PEP 440 string rather than translating it to a semver prerelease (`0.2.0-dev.0`) is deliberate: `release-prepare` step 2a bumps `version_manifest_files` by literal search/replace, which only works if every manifest carries the identical string — and dev versions are never published to a marketplace, so the suffix never reaches a consumer. `claude plugin validate --strict` accepts it, and hooks/check-upgrade.sh compares versions as opaque strings. Version rewrites are textual substitutions, not json.dumps() round-trips, so bumping does not reformat these hand-authored manifests (escaping em-dashes, expanding inline objects); each write is verified by re-parsing. Verified: `claude plugin validate . --strict` passes; simulating a bump to 0.3.0 propagates to all 25 version sites and is idempotent; prek passes (end-of-file-fixer skipped locally — sandbox denies it a write to an unrelated file). --- .claude-plugin/marketplace.json | 22 ++-- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- .pre-commit-config.yaml | 2 +- apm.yml | 2 +- docs/setup/marketplaces.md | 24 ++-- gemini-extension.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../magpie-issue/.claude-plugin/plugin.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../magpie-pairing/.claude-plugin/plugin.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../magpie-setup/.claude-plugin/plugin.json | 2 +- .../.claude-plugin/plugin.json | 2 +- projects/magpie/release-management-config.md | 28 +++-- tools/dev/check-family-plugins.py | 117 +++++++++++++++++- 19 files changed, 176 insertions(+), 45 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index c85b5d809..f49505bbb 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,67 +9,67 @@ { "name": "magpie", "source": ".", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "All Apache Magpie skills (all 10 families; ~21.7k always-on tokens)." }, { "name": "magpie-contributor-growth", "source": "./plugins/magpie-contributor-growth", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie contributor-growth family (6 skills)." }, { "name": "magpie-issue", "source": "./plugins/magpie-issue", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie issue family (8 skills)." }, { "name": "magpie-mentoring", "source": "./plugins/magpie-mentoring", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie mentoring family (4 skills)." }, { "name": "magpie-pairing", "source": "./plugins/magpie-pairing", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie pairing family (2 skills)." }, { "name": "magpie-pr-management", "source": "./plugins/magpie-pr-management", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie pr-management family (8 skills)." }, { "name": "magpie-release-management", "source": "./plugins/magpie-release-management", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie release-management family (10 skills)." }, { "name": "magpie-repo-health", "source": "./plugins/magpie-repo-health", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie repo-health family (7 skills)." }, { "name": "magpie-security", "source": "./plugins/magpie-security", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie security family (12 skills)." }, { "name": "magpie-setup", "source": "./plugins/magpie-setup", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie setup family (9 skills)." }, { "name": "magpie-utilities", "source": "./plugins/magpie-utilities", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie utilities family (4 skills)." } ] diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 12c029ba4..13d44272e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "magpie", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie — a reusable, governance-agnostic framework of agentic skills for maintaining open-source projects: release management, security triage, PR and issue workflows, contributor growth, and repo health.", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" }, "homepage": "https://magpie.apache.org/", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index ac6a827cc..b0b770d4e 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "magpie", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie — agentic skills for maintaining open-source projects.", "skills": "./skills", "hooks": { diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 999ae6dc1..1506ee19d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -247,7 +247,7 @@ repos: name: check-family-plugins (marketplace plugins vs frontmatter) language: system entry: python3 tools/dev/check-family-plugins.py - files: ^(skills/.*/SKILL\.md|plugins/.*|\.claude-plugin/(marketplace|plugin)\.json|hooks/check-upgrade\.sh)$ + files: ^(skills/.*/SKILL\.md|plugins/.*|\.claude-plugin/(marketplace|plugin)\.json|\.codex-plugin/plugin\.json|gemini-extension\.json|apm\.yml|pyproject\.toml|hooks/check-upgrade\.sh)$ pass_filenames: false # Workspace-membership guard. The single source of truth for # which Python projects get pre-commit hooks + the CI pytest diff --git a/apm.yml b/apm.yml index 14d5d6aca..287ed305c 100644 --- a/apm.yml +++ b/apm.yml @@ -6,7 +6,7 @@ # Gemini). Schema is v0.1 and may change — verify against the current # https://microsoft.github.io/apm/ before publishing. name: magpie -version: 0.2.0 +version: 0.2.0.dev0 type: skill description: >- Apache Magpie — a reusable, governance-agnostic framework of agentic skills diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index c1b6d4f60..07e148da6 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -356,19 +356,23 @@ no network calls, and touches nothing in the adopter repo. ## Versioning -The plugin version tracks the framework release (`0.2.0`). The manifest -version strings are kept in sync with `pyproject.toml` by the -`release-prepare` version bump — see +The plugin version tracks the framework version in `pyproject.toml`, which is +the single authority every manifest mirrors verbatim — **including the `.devN` +suffix**. Between releases the manifests therefore read `0.2.0.dev0`, not +`0.2.0`: a bare `0.2.0` would advertise a release that does not exist yet. Only +a tagged release carries a bare version, and only released versions are ever +published to a marketplace, so the PEP 440 suffix never reaches a consumer. + +Nothing is hand-edited. `pyproject.toml` feeds the four ecosystem manifests, +and the all-in-one [`.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json) +in turn feeds the ten per-family manifests and the marketplace entries, which +also inherit `author`, `homepage`, `repository`, and `license`. Bump +`project.version` and run `python3 tools/dev/check-family-plugins.py --fix`; the +same script, run as a prek hook, fails the build on any manifest left behind at +the old version. See [`release-management-config.md`](../../projects/magpie/release-management-config.md) (`version_manifest_files`). -The per-family plugin manifests do not carry their own version: they inherit -`version`, `author`, `homepage`, `repository`, and `license` from the -all-in-one [`.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json), so -a bump has one edit point. Propagate it with -`python3 tools/dev/check-family-plugins.py --fix`; the same script, run as a -prek hook, fails the build on any manifest left behind at the old version. - ## Verification status The Claude Code and Gemini CLI manifests follow the current published diff --git a/gemini-extension.json b/gemini-extension.json index b93946885..5b5e3c620 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "magpie", - "version": "0.2.0", + "version": "0.2.0.dev0", "description": "Apache Magpie — agentic skills for maintaining open-source projects. Skills are auto-discovered from ./skills.", "contextFileName": "GEMINI.md" } diff --git a/plugins/magpie-contributor-growth/.claude-plugin/plugin.json b/plugins/magpie-contributor-growth/.claude-plugin/plugin.json index 2fdd90ef1..3bcf85b2f 100644 --- a/plugins/magpie-contributor-growth/.claude-plugin/plugin.json +++ b/plugins/magpie-contributor-growth/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-contributor-growth", "description": "Apache Magpie \u2014 path-to-committer: activity sweeps, nominations, sentiment, readiness, committer/post-vote onboarding.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/plugins/magpie-issue/.claude-plugin/plugin.json b/plugins/magpie-issue/.claude-plugin/plugin.json index 1a56c617c..6bf44f247 100644 --- a/plugins/magpie-issue/.claude-plugin/plugin.json +++ b/plugins/magpie-issue/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-issue", "description": "Apache Magpie \u2014 issue lifecycle: triage, reproduction, fix drafting, reassess, stale-sweep, dedup, backlog stats.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/plugins/magpie-mentoring/.claude-plugin/plugin.json b/plugins/magpie-mentoring/.claude-plugin/plugin.json index 9e25cdcae..242625fd8 100644 --- a/plugins/magpie-mentoring/.claude-plugin/plugin.json +++ b/plugins/magpie-mentoring/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-mentoring", "description": "Apache Magpie \u2014 newcomer mentoring: welcome, newcomer-issue explanations, good-first-issue authoring + sweep.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/plugins/magpie-pairing/.claude-plugin/plugin.json b/plugins/magpie-pairing/.claude-plugin/plugin.json index 55b90b642..cf1c8118c 100644 --- a/plugins/magpie-pairing/.claude-plugin/plugin.json +++ b/plugins/magpie-pairing/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-pairing", "description": "Apache Magpie \u2014 pair a change with a structured self-review or a multi-agent adversarial review.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/plugins/magpie-pr-management/.claude-plugin/plugin.json b/plugins/magpie-pr-management/.claude-plugin/plugin.json index 61b492381..ebc6f4e61 100644 --- a/plugins/magpie-pr-management/.claude-plugin/plugin.json +++ b/plugins/magpie-pr-management/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-pr-management", "description": "Apache Magpie \u2014 PR-queue management: triage, stats, deep code review, quick-merge, stale-sweep, reviewer routing, pre-first-PR checks.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/plugins/magpie-release-management/.claude-plugin/plugin.json b/plugins/magpie-release-management/.claude-plugin/plugin.json index 346c4b6ac..0dcee0838 100644 --- a/plugins/magpie-release-management/.claude-plugin/plugin.json +++ b/plugins/magpie-release-management/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-release-management", "description": "Apache Magpie \u2014 ASF release lifecycle: plan, RC cut/sign, vote, tally, promote, announce, archive, audit.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/plugins/magpie-repo-health/.claude-plugin/plugin.json b/plugins/magpie-repo-health/.claude-plugin/plugin.json index ece010f5c..615819455 100644 --- a/plugins/magpie-repo-health/.claude-plugin/plugin.json +++ b/plugins/magpie-repo-health/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-repo-health", "description": "Apache Magpie \u2014 read-only repo-health audits: runner labels, workflow security, dependency/license/NOTICE, flaky tests, audit-finding fixes.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/plugins/magpie-security/.claude-plugin/plugin.json b/plugins/magpie-security/.claude-plugin/plugin.json index 236d7111b..10eff7dad 100644 --- a/plugins/magpie-security/.claude-plugin/plugin.json +++ b/plugins/magpie-security/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-security", "description": "Apache Magpie \u2014 security-issue handling lifecycle \u2014 import through CVE publication, triage, sync, dedup, invalidate. Maintainer-only.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/plugins/magpie-setup/.claude-plugin/plugin.json b/plugins/magpie-setup/.claude-plugin/plugin.json index 197b9c109..451b21c70 100644 --- a/plugins/magpie-setup/.claude-plugin/plugin.json +++ b/plugins/magpie-setup/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-setup", "description": "Apache Magpie \u2014 framework install/maintenance: install (adopt), upgrade, verify, override, status, secure-agent setup, shared-config sync.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/plugins/magpie-utilities/.claude-plugin/plugin.json b/plugins/magpie-utilities/.claude-plugin/plugin.json index b4a66690b..30572ef9d 100644 --- a/plugins/magpie-utilities/.claude-plugin/plugin.json +++ b/plugins/magpie-utilities/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "magpie-utilities", "description": "Apache Magpie \u2014 framework meta-skills: write-skill, optimize-skill, skill-reconciler, list-skills.", - "version": "0.2.0", + "version": "0.2.0.dev0", "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" diff --git a/projects/magpie/release-management-config.md b/projects/magpie/release-management-config.md index 53b2b9aa0..425c5101c 100644 --- a/projects/magpie/release-management-config.md +++ b/projects/magpie/release-management-config.md @@ -73,19 +73,33 @@ mandatory ASF approval + announce mechanisms (`dev-list-vote`, | `release_branch_base` | `main` | | `version_manifest_files` | `pyproject.toml`, `uv.lock`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, `.codex-plugin/plugin.json`, `gemini-extension.json`, `apm.yml`, and the ten generated `plugins/magpie-/.claude-plugin/plugin.json` manifests | -The ten per-family plugin manifests and the family entries in -`.claude-plugin/marketplace.json` are **generated** — they inherit `version` -(and `author`, `homepage`, `repository`, `license`) from -`.claude-plugin/plugin.json`. Bump the version in that root manifest, then -propagate it mechanically instead of editing the ten files by hand: +`pyproject.toml`'s `project.version` is the **single authority** for the +framework version; every other file above mirrors it verbatim, including a +`.devN` suffix (between releases the manifests read `0.2.0.dev0`, not `0.2.0`). +Dev versions are never published to a marketplace, so the PEP 440 suffix never +reaches a consumer — and keeping one identical string across every manifest is +what lets the Step 2a bump work as a single literal search/replace. + +The version flows outward in two hops, neither of them hand-edited: + +1. `pyproject.toml` → the four ecosystem manifests + (`.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, + `gemini-extension.json`, `apm.yml`); +2. `.claude-plugin/plugin.json` → the ten generated + `plugins/magpie-/.claude-plugin/plugin.json` manifests and the + family entries in `.claude-plugin/marketplace.json`, which also inherit + `author`, `homepage`, `repository`, and `license`. + +So a bump is one edit plus one command: ```bash +# edit project.version in pyproject.toml, then: python3 tools/dev/check-family-plugins.py --fix ``` `tools/dev/check-family-plugins.py` (a prek hook) fails the build if any -family manifest or marketplace entry is left behind at the previous version, -so a missed propagation cannot reach a release. +manifest or marketplace entry is left behind at the previous version, so a +missed propagation cannot reach a release. ## Backends diff --git a/tools/dev/check-family-plugins.py b/tools/dev/check-family-plugins.py index f5d799ada..393199140 100644 --- a/tools/dev/check-family-plugins.py +++ b/tools/dev/check-family-plugins.py @@ -6,6 +6,10 @@ Checks that every plugin is properly defined: +- every ecosystem manifest that declares the framework version + (`.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, + `gemini-extension.json`, `apm.yml`) mirrors `pyproject.toml`'s + `project.version` verbatim — including a `.devN` suffix; - the all-in-one `magpie` manifest (`.claude-plugin/plugin.json`) names itself correctly, declares `skills: ./skills`, and wires the `hooks/check-upgrade.sh` SessionStart hook (which must exist); @@ -21,8 +25,9 @@ mis-named manifest, a dangling marketplace entry, a family manifest left behind at the previous release's version — fails the check. -Run with `--fix` to regenerate the family plugins + symlinks + marketplace -entries from the frontmatter. +Run with `--fix` to propagate the version from `pyproject.toml` and regenerate +the family plugins + symlinks + marketplace entries from the frontmatter. A +release bump therefore has one edit point: `pyproject.toml`, then `--fix`. """ from __future__ import annotations @@ -31,6 +36,7 @@ import re import shutil import sys +import tomllib from pathlib import Path SKILLS = Path("skills") @@ -40,6 +46,24 @@ HOOK_SCRIPT = Path("hooks/check-upgrade.sh") # referenced by the all-in-one plugin hook SYMLINK_TARGET = "../../../skills/{skill}" # relative to plugins/magpie-/skills/ +# `pyproject.toml` is the single authority for the framework version — it is what +# the post-release `chore: bump version` commit edits. Every ecosystem manifest +# mirrors that string verbatim (including a `.devN` suffix), so `release-prepare` +# can bump them all with one literal search/replace. Dev versions are never +# published to a marketplace, so a PEP 440 suffix never reaches a consumer. +PYPROJECT = Path("pyproject.toml") +ECOSYSTEM_MANIFESTS = ( + ROOT_MANIFEST, + Path(".codex-plugin/plugin.json"), + Path("gemini-extension.json"), + Path("apm.yml"), +) +APM_VERSION_RE = re.compile(r"^(version:[ \t]*)(\S+)[ \t]*$", re.M) +# Substituted rather than re-serialised: these manifests are hand-authored, and a +# json.dumps() round-trip would reformat them (escaping em-dashes, expanding +# inline objects) far beyond the one string being bumped. +JSON_VERSION_RE = re.compile(r'("version"[ \t]*:[ \t]*")[^"]*(")') + # Metadata the per-family plugins and the marketplace entries inherit verbatim # from the all-in-one manifest, so a release bump has exactly one edit point. # `version` + `author` are what `claude plugin validate --strict` warns about @@ -96,6 +120,67 @@ def validate_manifest( return errors +def pyproject_version() -> tuple[str | None, list[str]]: + """The framework version — the single authority every manifest mirrors.""" + try: + version = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))["project"]["version"] + except (OSError, ValueError, KeyError) as exc: + return None, [f"{PYPROJECT}: cannot read project.version ({exc})"] + return str(version), [] + + +def manifest_version(path: Path) -> tuple[str | None, str | None]: + """Read the version out of a manifest in whichever format it uses.""" + if path.suffix in (".yml", ".yaml"): + try: + m = APM_VERSION_RE.search(path.read_text(encoding="utf-8")) + except OSError as exc: + return None, f"{path}: cannot read ({exc})" + return (m.group(2) if m else None), None + data, err = load_json(path) + if err: + return None, err + return data.get("version"), None + + +def write_manifest_version(path: Path, version: str) -> list[str]: + """Rewrite a manifest's version in place, touching nothing else.""" + pattern = APM_VERSION_RE if path.suffix in (".yml", ".yaml") else JSON_VERSION_RE + text = path.read_text(encoding="utf-8") + new, n = pattern.subn( + (rf"\g<1>{version}" if pattern is APM_VERSION_RE else rf"\g<1>{version}\g<2>"), + text, + count=1, + ) + if n != 1: + return [f"{path}: no 'version' declaration to rewrite"] + path.write_text(new, encoding="utf-8") + # The substitution is textual — confirm it produced the intended value. + have, err = manifest_version(path) + if err: + return [err] + if have != version: + return [f"{path}: rewrite produced {have!r}, expected {version!r}"] + return [] + + +def check_ecosystem_versions(version: str) -> list[str]: + """Every ecosystem manifest mirrors `pyproject.toml`'s version verbatim.""" + errors: list[str] = [] + for path in ECOSYSTEM_MANIFESTS: + if not path.is_file(): + errors.append(f"{path}: missing (declares the framework version)") + continue + have, err = manifest_version(path) + if err: + errors.append(err) + elif have != version: + errors.append( + f"{path}: version is {have!r}, expected {version!r} (from {PYPROJECT})" + ) + return errors + + def root_metadata() -> tuple[dict, list[str]]: """The subset of the all-in-one manifest that the family plugins inherit.""" data, err = load_json(ROOT_MANIFEST) @@ -128,6 +213,12 @@ def check(fam: dict[str, set[str]]) -> list[str]: shared, meta_errs = root_metadata() errors += meta_errs + # 0) Every ecosystem manifest mirrors pyproject.toml's version. + version, verr = pyproject_version() + errors += verr + if version is not None: + errors += check_ecosystem_versions(version) + # 1) The all-in-one `magpie` plugin: well-formed manifest, listed, and its # SessionStart hook script is present + referenced. errors += validate_manifest(ROOT_MANIFEST, "magpie") @@ -203,6 +294,28 @@ def check(fam: dict[str, set[str]]) -> list[str]: def fix(fam: dict[str, set[str]]) -> int: + # Propagate the authoritative version outward before reading the root + # manifest's metadata, so a bump in pyproject.toml alone is enough. + version, verr = pyproject_version() + if verr or version is None: + for e in verr or [f"{PYPROJECT}: no project.version"]: + print(f" - {e}", file=sys.stderr) + return 1 + for path in ECOSYSTEM_MANIFESTS: + if not path.is_file(): + print(f" - {path}: missing (declares the framework version)", file=sys.stderr) + return 1 + have, err = manifest_version(path) + if err: + print(f" - {err}", file=sys.stderr) + return 1 + if have != version: + if write_errs := write_manifest_version(path, version): + for e in write_errs: + print(f" - {e}", file=sys.stderr) + return 1 + print(f"{path}: version {have} -> {version}") + shared, meta_errs = root_metadata() if meta_errs: for e in meta_errs: From cc86a20806984b065598bb679e551211d54f8059 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Thu, 13 Aug 2026 13:21:58 +0200 Subject: [PATCH 17/18] feat(marketplace): support Agent Plugins 1.0 alongside the client manifests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent Plugins 1.0.0 was published on 2026-08-06 — a vendor-neutral standard for packaging Agent Skills and MCP servers, from a TSC drawn from Amazon, Cursor, Microsoft, OpenAI and Vercel, with Google since joined. Support is GA in VS Code, Copilot CLI, the Copilot app and the Copilot SDK as of 2026-08-12. This PR predates it, so it packaged for five client-specific formats and none of the standard. Add the AP1 manifest without disturbing any of them: - New root `plugin.json` — the AP1 manifest, pinning the canonical `$schema`. VS Code auto-detects the plugin format from the root manifest and reads that field as the AP1 marker; without it the same file is treated as a legacy Copilot manifest. Conformance needed no file moves: AP1 fixes skills at `skills//SKILL.md`, which is already the layout, and its schema is closed, so it carries metadata only — no `skills` path, no `hooks` block. - `check-family-plugins.py` validates the new manifest (pinned `$schema`, name pattern, the closed ten-field set, `author` sub-keys, metadata inherited from the Claude manifest) and adds it to the version-mirroring set, so a bump stays one edit plus `--fix`. - Wire it into the prek hook's `files:` pattern and `version_manifest_files`. Verified: validates clean against the published `plugin.schema.json`; 70 skills resolve under the AP1 discovery rule; no symlink under `skills/` escapes the plugin root; `claude plugin validate . --strict` still passes; all 23 prek hooks green. Also fixes three defects found while checking each manifest against its vendor's current documentation: - `hooks/check-upgrade.sh` wrote its upgrade prompt to **stderr** and exited 0. For a `SessionStart` hook exiting 0 it is stdout that is added to the session context; stderr only reaches the debug log. Since the version marker was written first, the next session saw no change and stayed silent too — so the prompt was delivered exactly zero times. Now on stdout, and tested across all three transitions. - The marker's fallback directory was `$root/.magpie-state`, inside a git working tree that nothing ignores and that a plugin update can replace wholesale. `CLAUDE_PLUGIN_DATA` is real and documented, but `CODEX_PLUGIN_DATA` is not; fall back to `$XDG_STATE_HOME/magpie` instead, never into the checkout. - `.codex-plugin/plugin.json` invoked the hook via `${CODEX_PLUGIN_ROOT}`, which Codex does not export. It exports `PLUGIN_ROOT`/`PLUGIN_DATA` and the `CLAUDE_*` pair for compatibility. Docs: `marketplaces.md` gains a section on the two manifest families and which client reads which, an install path for VS Code/Copilot, a per-manifest verification table replacing the prose "best-effort" note, and the recorded limitation that the per-family plugins cannot be AP1 packages — their skill symlinks deliberately escape the family root, and materialising them would mean vendored copies that PRINCIPLES §13 rules out. AP1 clients install the all-in-one plugin, whose `skills/` is the real tree. Also notes openai/codex#16430, under which Codex may not fire plugin-local hooks yet, and drops a duplicated SPDX header. --- .codex-plugin/plugin.json | 2 +- .pre-commit-config.yaml | 2 +- docs/setup/marketplaces.md | 165 +++++++++++++++---- hooks/check-upgrade.sh | 34 +++- plugin.json | 19 +++ projects/magpie/release-management-config.md | 12 +- tools/dev/check-family-plugins.py | 98 ++++++++++- 7 files changed, 281 insertions(+), 51 deletions(-) create mode 100644 plugin.json diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index b0b770d4e..4b5868917 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -9,7 +9,7 @@ "hooks": [ { "type": "command", - "command": "${CODEX_PLUGIN_ROOT}/hooks/check-upgrade.sh", + "command": "${PLUGIN_ROOT}/hooks/check-upgrade.sh", "timeout": 10 } ] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1506ee19d..0fffd0e59 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -247,7 +247,7 @@ repos: name: check-family-plugins (marketplace plugins vs frontmatter) language: system entry: python3 tools/dev/check-family-plugins.py - files: ^(skills/.*/SKILL\.md|plugins/.*|\.claude-plugin/(marketplace|plugin)\.json|\.codex-plugin/plugin\.json|gemini-extension\.json|apm\.yml|pyproject\.toml|hooks/check-upgrade\.sh)$ + files: ^(skills/.*/SKILL\.md|plugins/.*|\.claude-plugin/(marketplace|plugin)\.json|\.codex-plugin/plugin\.json|plugin\.json|gemini-extension\.json|apm\.yml|pyproject\.toml|hooks/check-upgrade\.sh)$ pass_filenames: false # Workspace-membership guard. The single source of truth for # which Python projects get pre-commit hooks + the CI pytest diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index 07e148da6..354b017eb 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -6,12 +6,13 @@ **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Installing Apache Magpie from agent marketplaces](#installing-apache-magpie-from-agent-marketplaces) + - [Two manifest families: Agent Plugins 1.0 and client-specific](#two-manifest-families-agent-plugins-10-and-client-specific) - [Choosing a plugin: all-in-one vs per-family](#choosing-a-plugin-all-in-one-vs-per-family) - [Skill names differ by install method](#skill-names-differ-by-install-method) - [Supported agents](#supported-agents) - [Claude Code](#claude-code) - [OpenAI Codex CLI](#openai-codex-cli) - - [GitHub Copilot](#github-copilot) + - [VS Code and GitHub Copilot](#vs-code-and-github-copilot) - [Google Gemini CLI](#google-gemini-cli) - [Cursor](#cursor) - [microsoft/apm (multiplexer)](#microsoftapm-multiplexer) @@ -24,9 +25,6 @@ - - # Installing Apache Magpie from agent marketplaces From 0.2.0, Apache Magpie ships manifests so its skills can be installed @@ -51,10 +49,12 @@ through the plugin/extension mechanisms of the major AI coding agents, > source release. > [!WARNING] -> **Marketplace/plugin support is experimental in most agentic CLIs.** The -> plugin and marketplace mechanisms of Claude Code, Codex, Copilot, Gemini, -> `apm`, and others are new and evolving — manifest schemas and install -> commands change between releases (see [Verification status](#verification-status)). +> **Marketplace/plugin support is still young in most agentic CLIs.** +> [Agent Plugins 1.0](#two-manifest-families-agent-plugins-10-and-client-specific) +> standardised the *package format* in August 2026, but it deliberately +> specifies no install mechanism or marketplace format — so install commands, +> catalog schemas, and the client-specific manifests still change between +> releases (see [Verification status](#verification-status)). > If a marketplace install breaks, or your agent has no marketplace at all, the > **non-marketplace install is always available, universal, and portable**: > adopt Magpie with `/magpie-setup` from either the **signed SVN release** @@ -65,6 +65,45 @@ through the plugin/extension mechanisms of the major AI coding agents, > ones with a marketplace. Rule of thumb: use a marketplace for a quick trial > on a supported agent; use `/magpie-setup` for a stable, portable install. +## Two manifest families: Agent Plugins 1.0 and client-specific + +Magpie ships **both** of the manifest shapes an agent may look for, because as +of 2026-08 no single one is read by every client. + +**[Agent Plugins 1.0](https://agent-plugins.org/specification)** (published +2026-08-06 by a TSC drawn from Amazon, Cursor, Microsoft, OpenAI, and Vercel; +Google has since joined) is the vendor-neutral standard. A conformant plugin is +a directory with a root [`plugin.json`](../../plugin.json) declaring the +canonical `$schema`, plus skills in `skills//SKILL.md` and — optionally — +MCP servers in a root `mcp.json`. Magpie's skill tree already had exactly that +layout, so conformance needed **no file moves**: the root `plugin.json` is the +only addition. + +**Client-specific manifests** stay alongside it, because the clients that +predate the standard still read their own: + +| Manifest | Read by | Why it is still needed | +|---|---|---| +| [`plugin.json`](../../plugin.json) (root) | VS Code, GitHub Copilot (CLI + app + SDK) | The AP1 manifest. VS Code auto-detects the format from the root manifest and treats the `$schema` value as the AP1 marker | +| [`.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json) | Claude Code (also read by VS Code) | Claude Code documents only this path, and AP1's schema is closed — it has no place for the `hooks` block or the `skills` path | +| [`.codex-plugin/plugin.json`](../../.codex-plugin/plugin.json) | OpenAI Codex CLI | Codex documents this as its plugin entry point, with its own `interface` / `apps` / `hooks` fields | +| [`gemini-extension.json`](../../gemini-extension.json) | Google Gemini CLI | Gemini's extension format is unrelated to AP1; Google has announced support for the standard but not a migration for this file | +| [`apm.yml`](../../apm.yml) | `microsoft/apm` | A cross-client compiler, not a client — its own package schema | + +The manifests do not conflict: they sit at different paths, each client reads +the one it documents, and every one of them points at the same single `skills/` +tree. `tools/dev/check-family-plugins.py` enforces that they all carry the same +version and shared metadata, and that the AP1 manifest stays inside its closed +ten-field schema — a Claude-only key such as `skills` or `hooks` copied into it +is a **fatal** manifest error for an AP1 client, not an ignorable one. + +> [!NOTE] +> **AP1 covers skills and MCP servers only.** It deliberately specifies no +> hooks, agents, commands, or marketplace/registry format. So Magpie's +> `SessionStart` upgrade prompt and its marketplace catalogs remain +> client-specific by necessity, not by choice — see +> [Automatic upgrade detection](#automatic-upgrade-detection). + ## Choosing a plugin: all-in-one vs per-family The framework ships as **eleven** plugins. You can install **either** the @@ -131,6 +170,21 @@ so there is a single source of truth for every skill. > needs no symlinks. macOS and Linux are unaffected. (Verified on macOS: a > `/plugin marketplace add` GitHub clone preserves and resolves the symlinks.) +> [!IMPORTANT] +> **The per-family plugins are not Agent Plugins 1.0 packages.** AP1 requires a +> symlink's final target to resolve *inside* the plugin root, and each family +> plugin's `skills/` deliberately points out of its own root at the +> shared `../../../skills/` tree. Materialising them as real directories +> would mean vendored copies of every skill — which +> [PRINCIPLES §13](../../PRINCIPLES.md) rules out, and which would leave eleven +> divergent copies to keep in sync. So the families stay a **Claude Code** +> feature (Claude Code resolves the symlinks, as verified above), and AP1 +> clients install the **all-in-one `magpie` plugin**, whose `skills/` *is* the +> real tree and needs no symlink at all. If per-family granularity on AP1 +> clients turns out to be worth its cost, the way to get it is to generate +> materialised family directories as a **release artefact** rather than commit +> them — deliberately deferred, not overlooked. + ## Skill names differ by install method The **same skill** is invoked by a **different name** depending on how you @@ -175,13 +229,13 @@ Quick reference: | Agent | One-liner | Manifest in this repo | |---|---|---| -| **Claude Code** | `/plugin marketplace add apache/magpie` → `/plugin install magpie@apache-magpie` | `.claude-plugin/marketplace.json` + `plugin.json` | +| **Claude Code** | `/plugin marketplace add apache/magpie` → `/plugin install magpie@apache-magpie` | `.claude-plugin/marketplace.json` + `.claude-plugin/plugin.json` | | **OpenAI Codex CLI** | `codex plugin marketplace add apache/magpie` → install `magpie` | `.codex-plugin/plugin.json`, `.agents/plugins/marketplace.json` | -| **GitHub Copilot** | add `apache/magpie` as a plugin marketplace → install `magpie` | `marketplace.json` (repo root) | +| **VS Code / GitHub Copilot** | install straight from the repo URL `https://github.com/apache/magpie`, or add it as a plugin marketplace | root `plugin.json` (AP1), `marketplace.json` (repo root) | | **Google Gemini CLI** | `gemini extensions install https://github.com/apache/magpie` | `gemini-extension.json` | -| **Cursor** | add via the plugin/skill install flow pointing at the repo | the plugin manifests above | +| **Cursor** | add via the plugin/skill install flow pointing at the repo | root `plugin.json` (AP1) | | **microsoft/apm** | `apm install apache/magpie` (compiles to Claude/Cursor/Codex/Copilot/Gemini) | `apm.yml` | -| **Kiro** | install per-skill from a GitHub subdirectory | native `skills//SKILL.md` | +| **Kiro** | install per-skill from a GitHub subdirectory, or the AP1 package | root `plugin.json` (AP1), native `skills//SKILL.md` | | **OpenCode** | clone skills into `.opencode/skills/`, or use a community installer | native `skills//SKILL.md` | Detailed steps per agent follow. @@ -243,19 +297,36 @@ from the tag: `/plugin marketplace add apache/magpie@0.2.0`. > Codex's plugin/marketplace verbs are still evolving. If a command name > differs, check `codex plugin --help`. -### GitHub Copilot +### VS Code and GitHub Copilot + +Agent Plugins 1.0 support is generally available in VS Code, Copilot CLI, the +Copilot app, and the Copilot SDK on all Copilot plans. VS Code auto-detects the +plugin format from the root manifest, and Magpie's root +[`plugin.json`](../../plugin.json) declares the AP1 `$schema`, so it is loaded +as an AP1 package. Two ways in: + +1. **Straight from the repo URL** — no marketplace needed. Point VS Code's + plugin install at: + + ```text + https://github.com/apache/magpie + ``` -Copilot treats a GitHub repo with a root `marketplace.json` as a plugin -marketplace. + VS Code clones the repo and installs the plugin. -1. Add `apache/magpie` as a plugin marketplace in Copilot (CLI or the - coding-agent settings). -2. Install the `magpie` plugin from it. -3. The skills become available to the agent under the plugin. +2. **As a marketplace** — add `apache/magpie` as a plugin marketplace (CLI or + the coding-agent settings) and install `magpie` from it. That path reads the + root [`marketplace.json`](../../marketplace.json). -> Copilot's plugin-marketplace commands are still stabilising (enterprise- -> managed plugins are in preview). Confirm the exact `add` / `install` -> syntax in the current Copilot documentation. +Either way the 70 skills become available to the agent under the plugin. + +> [!NOTE] +> VS Code **ignores client extension data and directories** in an AP1 package. +> Magpie's `.claude-plugin/` hook block is therefore inert here — the upgrade +> prompt is Claude Code-only (see +> [Automatic upgrade detection](#automatic-upgrade-detection)). Existing +> Copilot plugins that do not target AP1 remain supported, so the root +> `marketplace.json` keeps working regardless. ### Google Gemini CLI @@ -280,9 +351,10 @@ marketplace. ### Cursor -Cursor consumes the same plugin/skill manifests. Add Magpie through Cursor's -plugin/skill install flow (Customize → Plugins/Skills) pointing at -`github.com/apache/magpie`. +Cursor is one of the Agent Plugins 1.0 launch clients (and sits on the spec's +TSC), so it reads the root [`plugin.json`](../../plugin.json). Add Magpie +through Cursor's plugin/skill install flow (Customize → Plugins/Skills) +pointing at `github.com/apache/magpie`. > Confirm the exact add flow in Cursor's current docs — its self-serve > marketplace surface is evolving. @@ -347,12 +419,26 @@ automatic while the *changes* stay confirmed. | Agent | Mechanism | |---|---| | **Claude Code** | `SessionStart` hook [`hooks/check-upgrade.sh`](../../hooks/check-upgrade.sh) compares the installed version to a marker in the plugin's persistent data dir and prompts on change. Deterministic. | -| **Codex CLI** | The same [`hooks/check-upgrade.sh`](../../hooks/check-upgrade.sh) wired via the plugin's `hooks` block. Codex's hook schema is **not yet verified** — confirm before publish. | +| **Codex CLI** | The same [`hooks/check-upgrade.sh`](../../hooks/check-upgrade.sh), wired inline via the plugin's `hooks` block — Codex uses the same event schema and the same `SessionStart` event. Codex sets `PLUGIN_ROOT`/`PLUGIN_DATA` (and the `CLAUDE_*` pair for compatibility), which the script reads. See the caveat below. | +| **VS Code / Copilot, Cursor, Kiro (AP1)** | None. Agent Plugins 1.0 specifies no hook component and VS Code ignores client extension directories, so there is nothing to fire. Re-run `/magpie-setup upgrade` after updating. | | **Gemini CLI** | No lifecycle hook; the extension context file [`GEMINI.md`](../../GEMINI.md) instructs the agent to compare the extension version to a recorded marker and prompt on change (LLM-driven, advisory). | | Other agents | Re-run `/magpie-setup upgrade` manually after updating the package. | -The hook script is read-only apart from writing its own version marker, makes -no network calls, and touches nothing in the adopter repo. +> [!WARNING] +> **Codex plugin-local hooks may not fire yet.** [openai/codex#16430](https://github.com/openai/codex/issues/16430) +> reports that the runtime executes only the global `hooks.json` even though the +> plugin docs describe plugin-local hooks. The manifest is written to the +> documented schema so it starts working when the runtime catches up; until +> then, treat the Codex upgrade prompt as best-effort and re-run +> `/magpie-setup upgrade` manually. + +The hook writes its prompt to **stdout**, which is what a `SessionStart` hook +exiting 0 has added to the session context — stderr on a zero exit reaches only +the debug log. It is read-only apart from writing its own version marker, which +goes to the client-provided persistent data directory (`CLAUDE_PLUGIN_DATA` / +`PLUGIN_DATA`), falling back to `$XDG_STATE_HOME/magpie` — never inside the +plugin checkout, which a plugin update may replace wholesale. It makes no +network calls and touches nothing in the adopter repo. ## Versioning @@ -375,9 +461,20 @@ the old version. See ## Verification status -The Claude Code and Gemini CLI manifests follow the current published -schemas. The Codex CLI, GitHub Copilot, and `apm` (schema v0.1) formats -move quickly; re-check each against the vendor's current documentation -before a marketplace publish. Manifests that fail live validation should be -fixed here and re-released — they never change how the ASF source release -is built or signed. +Every manifest here has been checked against the vendor's **published +documentation**; what varies is whether it has also been exercised against a +**live install**. + +| Manifest | Schema source | Status | +|---|---|---| +| root `plugin.json` | [Agent Plugins 1.0.0 spec](https://github.com/agentplugins/agent-plugins-spec/blob/main/spec/1.0.0.md) + [`plugin.schema.json`](https://agent-plugins.org/schemas/1.0.0/plugin.schema.json) | Conforms to the published closed schema; enforced by `check-family-plugins.py`. Not yet live-installed | +| `.claude-plugin/*` | Claude Code plugins reference | Verified live — `claude plugin validate . --strict` passes with 0 warnings; a family plugin installs and loads from a local marketplace replica | +| `.codex-plugin/plugin.json`, `.agents/plugins/marketplace.json` | Codex plugin docs (`Package your plugin`) | Matches the documented entry point, field set, and repo-marketplace path. Not yet live-installed; see the plugin-local hooks caveat above | +| root `marketplace.json` | Copilot / VS Code plugin marketplace docs | Legacy-format catalog, explicitly still supported alongside AP1. Not yet live-installed | +| `gemini-extension.json` | Gemini CLI extensions docs | Follows the published schema. Google has joined the AP1 TSC but has published no migration for this file — keep both | +| `apm.yml` | `microsoft/apm` schema **v0.1** | Pre-1.0 and the most likely to churn; re-check before publish | + +Re-check each against the vendor's current documentation before a marketplace +publish. Manifests that fail live validation should be fixed here and +re-released — none of them change how the ASF source release is built or +signed. diff --git a/hooks/check-upgrade.sh b/hooks/check-upgrade.sh index f3256a722..e6cadf416 100755 --- a/hooks/check-upgrade.sh +++ b/hooks/check-upgrade.sh @@ -3,8 +3,9 @@ # https://www.apache.org/licenses/LICENSE-2.0 # # Session-start hook for the Apache Magpie plugin. Wired by the Claude Code -# plugin (SessionStart) and — best-effort, pending schema verification — by -# the Codex CLI plugin, where a session hook is available. +# plugin (SessionStart) and by the Codex CLI plugin, which uses the same event +# schema. Agent Plugins 1.0 defines no hook component, so an AP1-only client +# does not run this at all — see docs/setup/marketplaces.md. # # Detects when the installed plugin version has changed since the last # session (i.e. the marketplace updated it) and prompts the user to run @@ -24,12 +25,24 @@ set -euo pipefail # Drain any event JSON delivered on stdin (unused). cat >/dev/null 2>&1 || true -root="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-.}}" -data="${CLAUDE_PLUGIN_DATA:-${CODEX_PLUGIN_DATA:-$root/.magpie-state}}" +# Claude Code exports `CLAUDE_PLUGIN_ROOT` / `CLAUDE_PLUGIN_DATA` to hook +# processes; Codex exports `PLUGIN_ROOT` / `PLUGIN_DATA` (the Agent Plugins 1.0 +# names) *and* the `CLAUDE_*` pair for compatibility. There is no +# `CODEX_PLUGIN_ROOT`. +root="${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-.}}" -# Read the plugin version from whichever manifest is present. +# The final fallback is the XDG state dir — never a path inside `$root`, which +# for a `marketplace add` install (or this repo's own local marketplace) is a +# git working tree, and which a plugin update may replace wholesale. +data="${CLAUDE_PLUGIN_DATA:-${PLUGIN_DATA:-${XDG_STATE_HOME:-$HOME/.local/state}/magpie}}" + +# Read the plugin version from whichever manifest is present. The root +# `plugin.json` is the Agent Plugins 1.0 manifest; the two client-specific +# manifests carry the same version string (enforced by +# `tools/dev/check-family-plugins.py`), so the order only matters for which +# file is read, not for the value. manifest="" -for m in "$root/.claude-plugin/plugin.json" "$root/.codex-plugin/plugin.json"; do +for m in "$root/.claude-plugin/plugin.json" "$root/.codex-plugin/plugin.json" "$root/plugin.json"; do [ -f "$m" ] && { manifest="$m"; break; } done [ -n "$manifest" ] || exit 0 @@ -45,10 +58,15 @@ if [ "$current" != "$stored" ]; then mkdir -p "$data" 2>/dev/null || true printf '%s' "$current" >"$marker" 2>/dev/null || true + # stdout, not stderr: for a `SessionStart` hook exiting 0, Claude Code adds + # stdout to the session context (stderr on a zero exit only reaches the debug + # log). Writing the prompt to stderr would deliver it to nobody — and because + # the marker is written first, the next session would see no change and stay + # silent too. if [ -n "$stored" ]; then - echo "Apache Magpie plugin updated ($stored -> $current). If this repo adopts Magpie, run \`/magpie-setup upgrade\` to reconcile the snapshot, agentic overrides, and drift." >&2 + echo "Apache Magpie plugin updated ($stored -> $current). If this repo adopts Magpie, run \`/magpie-setup upgrade\` to reconcile the snapshot, agentic overrides, and drift." else - echo "Apache Magpie plugin $current is active. If this repo already adopts Magpie, run \`/magpie-setup upgrade\` to reconcile; otherwise run \`/magpie-setup\` to adopt." >&2 + echo "Apache Magpie plugin $current is active. If this repo already adopts Magpie, run \`/magpie-setup upgrade\` to reconcile; otherwise run \`/magpie-setup\` to adopt." fi fi diff --git a/plugin.json b/plugin.json new file mode 100644 index 000000000..ea3036de6 --- /dev/null +++ b/plugin.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "magpie", + "version": "0.2.0.dev0", + "description": "Apache Magpie — a reusable, governance-agnostic framework of agentic skills for maintaining open-source projects: release management, security triage, PR and issue workflows, contributor growth, and repo health.", + "author": { "name": "Apache Magpie", "url": "https://magpie.apache.org/" }, + "homepage": "https://magpie.apache.org/", + "repository": "https://github.com/apache/magpie", + "license": "Apache-2.0", + "keywords": [ + "apache", + "agent-skills", + "maintenance", + "release-management", + "security", + "pr-management", + "open-source" + ] +} diff --git a/projects/magpie/release-management-config.md b/projects/magpie/release-management-config.md index 425c5101c..8cead13cf 100644 --- a/projects/magpie/release-management-config.md +++ b/projects/magpie/release-management-config.md @@ -71,7 +71,7 @@ mandatory ASF approval + announce mechanisms (`dev-list-vote`, | `git_upstream_remote` | `upstream` | | `release_planning_issue_template` | *(none — uses the `release-prepare` default template)* | | `release_branch_base` | `main` | -| `version_manifest_files` | `pyproject.toml`, `uv.lock`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, `.codex-plugin/plugin.json`, `gemini-extension.json`, `apm.yml`, and the ten generated `plugins/magpie-/.claude-plugin/plugin.json` manifests | +| `version_manifest_files` | `pyproject.toml`, `uv.lock`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, `plugin.json` (Agent Plugins 1.0), `.codex-plugin/plugin.json`, `gemini-extension.json`, `apm.yml`, and the ten generated `plugins/magpie-/.claude-plugin/plugin.json` manifests | `pyproject.toml`'s `project.version` is the **single authority** for the framework version; every other file above mirrors it verbatim, including a @@ -80,11 +80,15 @@ Dev versions are never published to a marketplace, so the PEP 440 suffix never reaches a consumer — and keeping one identical string across every manifest is what lets the Step 2a bump work as a single literal search/replace. +`uv.lock` is the one entry `--fix` does **not** touch: it carries the version +because it locks this workspace's own package, and it is refreshed by +`uv lock` (which the Step 2a bump runs), not by the manifest propagation below. + The version flows outward in two hops, neither of them hand-edited: -1. `pyproject.toml` → the four ecosystem manifests - (`.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, - `gemini-extension.json`, `apm.yml`); +1. `pyproject.toml` → the five ecosystem manifests + (`.claude-plugin/plugin.json`, the Agent Plugins 1.0 `plugin.json`, + `.codex-plugin/plugin.json`, `gemini-extension.json`, `apm.yml`); 2. `.claude-plugin/plugin.json` → the ten generated `plugins/magpie-/.claude-plugin/plugin.json` manifests and the family entries in `.claude-plugin/marketplace.json`, which also inherit diff --git a/tools/dev/check-family-plugins.py b/tools/dev/check-family-plugins.py index 393199140..05d196fea 100644 --- a/tools/dev/check-family-plugins.py +++ b/tools/dev/check-family-plugins.py @@ -7,12 +7,15 @@ Checks that every plugin is properly defined: - every ecosystem manifest that declares the framework version - (`.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, - `gemini-extension.json`, `apm.yml`) mirrors `pyproject.toml`'s - `project.version` verbatim — including a `.devN` suffix; + (`.claude-plugin/plugin.json`, the Agent Plugins 1.0 `plugin.json`, + `.codex-plugin/plugin.json`, `gemini-extension.json`, `apm.yml`) mirrors + `pyproject.toml`'s `project.version` verbatim — including a `.devN` suffix; - the all-in-one `magpie` manifest (`.claude-plugin/plugin.json`) names itself correctly, declares `skills: ./skills`, and wires the `hooks/check-upgrade.sh` SessionStart hook (which must exist); +- the root `plugin.json` conforms to Agent Plugins 1.0 — the pinned `$schema`, + the name pattern, the closed ten-field set (so a Claude-only component path + never leaks in), and the same shared metadata as the Claude manifest; - every `.claude-plugin/marketplace.json` entry resolves to a matching, uniquely-named `plugin.json` and carries the root manifest's version; - for every family declared in a skill's `family:` frontmatter there is a @@ -46,6 +49,33 @@ HOOK_SCRIPT = Path("hooks/check-upgrade.sh") # referenced by the all-in-one plugin hook SYMLINK_TARGET = "../../../skills/{skill}" # relative to plugins/magpie-/skills/ +# The vendor-neutral Agent Plugins 1.0 manifest for the same all-in-one plugin. +# It lives at the repo root (the spec permits no alternative location) and is +# read by VS Code / Copilot, which auto-detect the format from the root manifest +# and treat the `$schema` value as the AP1 marker. Its schema is *closed*: only +# the ten fields below are permitted, so component paths (`skills`, `hooks`) +# must NOT appear — AP1 fixes skills at `skills/` and defines no hook component. +AP1_MANIFEST = Path("plugin.json") +AP1_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json" +AP1_FIELDS = frozenset( + { + "$schema", + "name", + "version", + "description", + "author", + "homepage", + "repository", + "license", + "keywords", + "extensions", + } +) +AP1_AUTHOR_FIELDS = frozenset({"name", "email", "url"}) +# `name`: 1–64 chars, lowercase alphanumeric plus `-`/`.`, no `--`/`..`, +# alphanumeric at both ends. +AP1_NAME_RE = re.compile(r"^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$") + # `pyproject.toml` is the single authority for the framework version — it is what # the post-release `chore: bump version` commit edits. Every ecosystem manifest # mirrors that string verbatim (including a `.devN` suffix), so `release-prepare` @@ -54,6 +84,7 @@ PYPROJECT = Path("pyproject.toml") ECOSYSTEM_MANIFESTS = ( ROOT_MANIFEST, + AP1_MANIFEST, Path(".codex-plugin/plugin.json"), Path("gemini-extension.json"), Path("apm.yml"), @@ -181,6 +212,66 @@ def check_ecosystem_versions(version: str) -> list[str]: return errors +def validate_ap1_manifest(inherited: dict | None = None) -> list[str]: + """The root `plugin.json` conforms to Agent Plugins 1.0. + + Enforced here rather than by a JSON-Schema dependency: the schema is small, + closed, and the failure we actually care about is someone copying a + Claude-only field (`skills`, `hooks`, `mcpServers`) into it, which AP1 + clients reject as a fatal manifest error rather than ignore. + """ + if not AP1_MANIFEST.is_file(): + return [f"{AP1_MANIFEST}: missing (Agent Plugins 1.0 manifest)"] + data, err = load_json(AP1_MANIFEST) + if err: + return [err] + errors: list[str] = [] + + if data.get("$schema") != AP1_SCHEMA: + errors.append( + f"{AP1_MANIFEST}: '$schema' is {data.get('$schema')!r}, expected {AP1_SCHEMA!r} " + f"(without it, VS Code/Copilot fall back to the legacy format)" + ) + name = data.get("name") + if name != "magpie": + errors.append(f"{AP1_MANIFEST}: name is {name!r}, expected 'magpie'") + elif not AP1_NAME_RE.match(name): + errors.append(f"{AP1_MANIFEST}: name {name!r} violates the AP1 name pattern") + + extra = sorted(set(data) - AP1_FIELDS) + if extra: + errors.append( + f"{AP1_MANIFEST}: {', '.join(repr(k) for k in extra)} not permitted — " + f"the AP1 manifest schema is closed to {len(AP1_FIELDS)} fields " + f"(client-specific data belongs in 'extensions' or the client's own manifest)" + ) + + author = data.get("author") + if author is not None: + if not isinstance(author, dict): + errors.append(f"{AP1_MANIFEST}: 'author' must be an object") + else: + bad = sorted(set(author) - AP1_AUTHOR_FIELDS) + if bad: + errors.append( + f"{AP1_MANIFEST}: author has {', '.join(repr(k) for k in bad)}; " + f"AP1 permits only {', '.join(sorted(AP1_AUTHOR_FIELDS))}" + ) + + # AP1 fixes the skills location; there is no manifest field to point + # elsewhere, so the real `skills/` tree is what any AP1 client will load. + if not SKILLS.is_dir(): + errors.append(f"{SKILLS}: missing — AP1 clients discover skills only here") + + for key, want in (inherited or {}).items(): + if data.get(key) != want: + errors.append( + f"{AP1_MANIFEST}: {key!r} is {data.get(key)!r}, expected {want!r} " + f"(inherited from {ROOT_MANIFEST})" + ) + return errors + + def root_metadata() -> tuple[dict, list[str]]: """The subset of the all-in-one manifest that the family plugins inherit.""" data, err = load_json(ROOT_MANIFEST) @@ -222,6 +313,7 @@ def check(fam: dict[str, set[str]]) -> list[str]: # 1) The all-in-one `magpie` plugin: well-formed manifest, listed, and its # SessionStart hook script is present + referenced. errors += validate_manifest(ROOT_MANIFEST, "magpie") + errors += validate_ap1_manifest(shared) if "magpie" not in listed: errors.append(f"{MARKETPLACE}: missing the all-in-one 'magpie' plugin entry") if not HOOK_SCRIPT.is_file(): From bc522109129442c00da887280ef5c1c93c48d2d8 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Thu, 13 Aug 2026 13:44:21 +0200 Subject: [PATCH 18/18] fix(marketplace): close the open items from the review of #907 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four findings from Justin's review, plus the one they turned up. `--fix` destroyed anything it did not generate. It ran `shutil.rmtree()` over every `plugins/magpie-*` before regenerating. That is safe only while those directories hold nothing but a generated manifest and symlinks; the first time a family grows a `commands/`, an `agents/`, or a README, the next `--fix` would delete it without a word. It now enumerates what regeneration owns — `.claude-plugin/plugin.json` and symlinks under `skills/` — and refuses, naming the file, on anything else. The scan runs over all ten families *before* the first delete, so a stray file in the last one cannot leave the first nine destroyed. `--fix` could write a marketplace with only the family entries. The all-in-one entry is carried over rather than regenerated, so `[p | ... for p in market["plugins"] if p["name"] == "magpie"]` silently yielded `[]` if that entry was ever absent or renamed — and the destructive rewrite landed before `check` could catch it on the next run. It now bails with a message that says what to restore. The same line raised a bare `KeyError` on a malformed catalog instead of the tidy error the rest of the script produces; both lookups are guarded. The Codex and Copilot catalogs were covered by nothing. Neither `.agents/plugins/marketplace.json` nor the root `marketplace.json` was read by `check`, by `--fix`, or by the prek hook's `files:` pattern. `check` now verifies both list the all-in-one plugin and *only* that, and both are in the hook pattern. Only-that is the right rule rather than an omission to fix: the family plugins reach their skills through symlinks that resolve outside the family's own root, which Agent Plugins 1.0 forbids, so offering them to Codex or Copilot would advertise something those clients cannot install. `marketplaces.md` says so in both install sections. `uv.lock` was listed under `version_manifest_files` beside manifests that `--fix` propagates, though `ECOSYSTEM_MANIFESTS` does not include it and neither `--fix` nor the prek check touches it. Noted as refreshed by `uv lock`, which the Step 2a bump already runs. The `` placeholder in a skill description is conformant, and this is the one finding that needed checking rather than fixing. It is not one skill: 41 of the 70 descriptions carry the framework's `` syntax, so "rewrite it" would mean unpicking the convention in AGENTS.md. The Agent Skills specification — which AP1 defers to for skill validity — constrains `description` on length alone (1–1024 characters, non-empty) and says nothing about angle brackets; the character-class rules apply to `name`, which every skill satisfies. Longest description is 953 characters. Recorded in `marketplaces.md` so the question does not get re-opened from scratch. Verified: each of the three destructive paths aborts with the tree intact (ten families still present, marketplace byte-identical); the catalog rule fires on a planted family entry; `--fix` stays idempotent; all 23 prek hooks green. --- .pre-commit-config.yaml | 2 +- docs/setup/marketplaces.md | 21 ++++++- tools/dev/check-family-plugins.py | 101 +++++++++++++++++++++++++++++- 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0fffd0e59..fde95b865 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -247,7 +247,7 @@ repos: name: check-family-plugins (marketplace plugins vs frontmatter) language: system entry: python3 tools/dev/check-family-plugins.py - files: ^(skills/.*/SKILL\.md|plugins/.*|\.claude-plugin/(marketplace|plugin)\.json|\.codex-plugin/plugin\.json|plugin\.json|gemini-extension\.json|apm\.yml|pyproject\.toml|hooks/check-upgrade\.sh)$ + files: ^(skills/.*/SKILL\.md|plugins/.*|\.claude-plugin/(marketplace|plugin)\.json|\.codex-plugin/plugin\.json|\.agents/plugins/marketplace\.json|(plugin|marketplace)\.json|gemini-extension\.json|apm\.yml|pyproject\.toml|hooks/check-upgrade\.sh)$ pass_filenames: false # Workspace-membership guard. The single source of truth for # which Python projects get pre-commit hooks + the CI pytest diff --git a/docs/setup/marketplaces.md b/docs/setup/marketplaces.md index 354b017eb..32635dade 100644 --- a/docs/setup/marketplaces.md +++ b/docs/setup/marketplaces.md @@ -294,6 +294,12 @@ from the tag: `/plugin marketplace add apache/magpie@0.2.0`. 3. List / verify — inside Codex run `/plugins`, or from the shell `codex plugin list`. +Only the **all-in-one** `magpie` plugin is offered here — the per-family +plugins are Claude Code-only, for the reason recorded +[above](#choosing-a-plugin-all-in-one-vs-per-family). The catalog is checked +against that rule by `tools/dev/check-family-plugins.py`, so it cannot drift +into advertising a plugin Codex could not install. + > Codex's plugin/marketplace verbs are still evolving. If a command name > differs, check `codex plugin --help`. @@ -318,7 +324,10 @@ as an AP1 package. Two ways in: the coding-agent settings) and install `magpie` from it. That path reads the root [`marketplace.json`](../../marketplace.json). -Either way the 70 skills become available to the agent under the plugin. +Either way the 70 skills become available to the agent under the plugin. As +with Codex, only the **all-in-one** `magpie` plugin is offered — the per-family +plugins are Claude Code-only, for the reason recorded +[above](#choosing-a-plugin-all-in-one-vs-per-family). > [!NOTE] > VS Code **ignores client extension data and directories** in an AP1 package. @@ -474,6 +483,16 @@ documentation**; what varies is whether it has also been exercised against a | `gemini-extension.json` | Gemini CLI extensions docs | Follows the published schema. Google has joined the AP1 TSC but has published no migration for this file — keep both | | `apm.yml` | `microsoft/apm` schema **v0.1** | Pre-1.0 and the most likely to churn; re-check before publish | +The skills themselves are checked against the +[Agent Skills specification](https://agentskills.io/specification), which AP1 +defers to. Worth stating explicitly, because it looks like a problem and is +not: 41 of the 70 `description` fields contain the framework's +`` syntax (``, ``, …). The spec constrains +`description` on **length only** — 1–1024 characters, non-empty — and places no +restriction on angle brackets; the character-class rules apply to `name`, which +every skill satisfies. So the placeholders are conformant, not a portability +risk to design around. + Re-check each against the vendor's current documentation before a marketplace publish. Manifests that fail live validation should be fixed here and re-released — none of them change how the ASF source release is built or diff --git a/tools/dev/check-family-plugins.py b/tools/dev/check-family-plugins.py index 05d196fea..128ec324d 100644 --- a/tools/dev/check-family-plugins.py +++ b/tools/dev/check-family-plugins.py @@ -18,6 +18,10 @@ never leaks in), and the same shared metadata as the Claude manifest; - every `.claude-plugin/marketplace.json` entry resolves to a matching, uniquely-named `plugin.json` and carries the root manifest's version; +- the Codex and Copilot catalogs (`.agents/plugins/marketplace.json`, + `marketplace.json`) list the all-in-one plugin and *only* that — the family + plugins are Claude Code-only, so advertising them there would offer those + clients something they cannot install; - for every family declared in a skill's `family:` frontmatter there is a `plugins/magpie-/` plugin whose manifest is well-formed, inherits the root manifest's shared metadata (version, author, homepage, repository, @@ -72,6 +76,18 @@ } ) AP1_AUTHOR_FIELDS = frozenset({"name", "email", "url"}) + +# The non-Claude catalogs. These list the all-in-one plugin *only*: the family +# plugins reach their skills through symlinks that deliberately resolve outside +# the family's own root, which Agent Plugins 1.0 forbids (a symlink's final +# target must stay within the plugin root). Materialising them into real +# directories would mean vendored copies of every skill — PRINCIPLES §13. So +# per-family is a Claude Code feature, and this check keeps the other catalogs +# from drifting into advertising something their clients cannot install. +CLIENT_CATALOGS = ( + Path(".agents/plugins/marketplace.json"), # Codex CLI repo marketplace + Path("marketplace.json"), # GitHub Copilot / VS Code +) # `name`: 1–64 chars, lowercase alphanumeric plus `-`/`.`, no `--`/`..`, # alphanumeric at both ends. AP1_NAME_RE = re.compile(r"^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$") @@ -272,6 +288,33 @@ def validate_ap1_manifest(inherited: dict | None = None) -> list[str]: return errors +def check_client_catalogs() -> list[str]: + """The Codex and Copilot catalogs list the all-in-one plugin and only that.""" + errors: list[str] = [] + for path in CLIENT_CATALOGS: + if not path.is_file(): + errors.append(f"{path}: missing (client plugin catalog)") + continue + data, err = load_json(path) + if err: + errors.append(err) + continue + entries = data.get("plugins") + if not isinstance(entries, list): + errors.append(f"{path}: 'plugins' is missing or not a list") + continue + names = [e.get("name") for e in entries if isinstance(e, dict)] + if "magpie" not in names: + errors.append(f"{path}: missing the all-in-one 'magpie' plugin entry") + if families := sorted(n for n in names if n and n.startswith("magpie-")): + errors.append( + f"{path}: lists per-family plugin(s) {', '.join(families)} — the family " + f"plugins are Claude Code-only (their skill symlinks resolve outside the " + f"family plugin root, which Agent Plugins 1.0 forbids)" + ) + return errors + + def root_metadata() -> tuple[dict, list[str]]: """The subset of the all-in-one manifest that the family plugins inherit.""" data, err = load_json(ROOT_MANIFEST) @@ -314,6 +357,7 @@ def check(fam: dict[str, set[str]]) -> list[str]: # SessionStart hook script is present + referenced. errors += validate_manifest(ROOT_MANIFEST, "magpie") errors += validate_ap1_manifest(shared) + errors += check_client_catalogs() if "magpie" not in listed: errors.append(f"{MARKETPLACE}: missing the all-in-one 'magpie' plugin entry") if not HOOK_SCRIPT.is_file(): @@ -385,6 +429,32 @@ def check(fam: dict[str, set[str]]) -> list[str]: return errors +def unowned_entries(pdir: Path) -> list[str]: + """Anything in a family plugin dir that `--fix` did not generate. + + A blanket `rmtree` is safe only for as long as these directories hold + nothing but a generated manifest and symlinks. The moment a family grows a + `commands/`, an `agents/`, or a README, a blanket delete would silently + discard it. So enumerate what regeneration owns and report anything else, + so the operator gets an error naming the file instead of a quiet loss. + """ + if not pdir.is_dir(): + return [] + mdir, sdir = pdir / ".claude-plugin", pdir / "skills" + unexpected = sorted(p for p in pdir.iterdir() if p not in {mdir, sdir}) + if mdir.is_dir(): + unexpected += sorted(p for p in mdir.iterdir() if p.name != "plugin.json") + if sdir.is_dir(): + unexpected += sorted(p for p in sdir.iterdir() if not p.is_symlink()) + if not unexpected: + return [] + return [ + f"{pdir}: refusing to regenerate — not generated by --fix: " + f"{', '.join(str(p) for p in unexpected)} " + f"(move it out, or teach --fix to generate it)" + ] + + def fix(fam: dict[str, set[str]]) -> int: # Propagate the authoritative version outward before reading the root # manifest's metadata, so a bump in pyproject.toml alone is enough. @@ -419,7 +489,14 @@ def fix(fam: dict[str, set[str]]) -> int: ) return 1 - for pdir in PLUGINS.glob("magpie-*"): + # Check every family dir *before* deleting any of them, so a stray file in + # the last one does not leave the first nine already destroyed. + stale = sorted(PLUGINS.glob("magpie-*")) + if rm_errs := [e for pdir in stale for e in unowned_entries(pdir)]: + for e in rm_errs: + print(f" - {e}", file=sys.stderr) + return 1 + for pdir in stale: shutil.rmtree(pdir) for family, skills in sorted(fam.items()): name = f"magpie-{family}" @@ -439,8 +516,26 @@ def fix(fam: dict[str, set[str]]) -> int: json.dumps(manifest, indent=2) + "\n", encoding="utf-8" ) - market = json.loads(MARKETPLACE.read_text(encoding="utf-8")) - keep = [p | {"version": shared["version"]} for p in market["plugins"] if p["name"] == "magpie"] + market, market_err = load_json(MARKETPLACE) + if market_err: + print(f" - {market_err}", file=sys.stderr) + return 1 + # The all-in-one entry is carried over, not regenerated, so it is the one + # thing here that `--fix` cannot reconstruct. Bail out rather than write a + # marketplace containing only the families: `check` would catch that on the + # next run, but the destructive rewrite would already have happened. + entries = market.get("plugins") + if not isinstance(entries, list): + print(f" - {MARKETPLACE}: 'plugins' is missing or not a list", file=sys.stderr) + return 1 + keep = [p | {"version": shared["version"]} for p in entries if p.get("name") == "magpie"] + if not keep: + print( + f" - {MARKETPLACE}: no all-in-one 'magpie' entry to carry over " + f"(--fix regenerates only the family entries; restore it before rerunning)", + file=sys.stderr, + ) + return 1 for family, skills in sorted(fam.items()): keep.append({ "name": f"magpie-{family}",