## Why
The manifest-fields tables in both skills used imprecise labels ("CC-only",
"Copilot-only") that conflated two distinct reasons a field appears in only
one manifest: platform constraint (the other tool does not support the field
at all) versus repo convention (both tools support it, but the scaffold places
it in one manifest by design). This caused agents to treat convention
boundaries as hard platform constraints, producing unnecessary errors when
updating manifests for dual-tool repos.
Provenance was also incomplete: sources.md files were missing entries for
sources that had been consulted and were already contributing to SKILL.md
and manifest-fields.md content, making the evidence chain unverifiable.
## Implementation Notes
Field classification now uses three explicit categories — shared, platform
(one tool does not support the field), and convention (both tools support it;
scaffold places it in one manifest by design). The distinction matters because
convention fields may legitimately appear in the other manifest when there is
a deliberate reason; platform fields may not.
New gotchas added to plugin-author: agent files silently ignore hooks,
mcpServers, and permissionMode frontmatter; claude plugin tag --push requires
a clean working tree; --dry-run preview before tagging; --strict flag on
validate. New gotchas in marketplace-author: metadata object as Copilot CLI
canonical location for top-level fields; strict: false for dual-tool plugins;
sha takes precedence over ref for pinning; --strict flag on validate.
tests/ removed from plugin-author because new-plugin.sh has no branching
logic warranting a bats suite at this stage.
## Impact
Skill prompt changes only — no runtime code affected. Agents using these
skills will now correctly distinguish convention from constraint when deciding
which manifest to update for a given field.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
149 lines
4.2 KiB
Bash
Executable File
149 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# source_keys: github-cli-plugin-reference github-plugins-creating
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: new-plugin.sh <plugin-name> <repo-root>
|
|
|
|
Scaffold a new plugin directory with both manifests and skeleton dirs.
|
|
|
|
Arguments:
|
|
plugin-name Kebab-case plugin identifier (e.g. my-tools, data-tools).
|
|
Must be lowercase letters, numbers, and hyphens only.
|
|
No leading, trailing, or consecutive hyphens.
|
|
repo-root Absolute or relative path to the repository root.
|
|
The plugin is created at <repo-root>/plugins/<plugin-name>/.
|
|
|
|
Created structure:
|
|
<repo-root>/plugins/<plugin-name>/
|
|
plugin.json Copilot CLI manifest (FILL_IN_* placeholders)
|
|
.claude-plugin/
|
|
plugin.json Claude Code manifest (FILL_IN_* placeholders)
|
|
skills/ Empty skeleton directory
|
|
agents/ Empty skeleton directory
|
|
hooks/ Empty skeleton directory
|
|
bin/ Empty skeleton directory
|
|
|
|
Each file and directory is a no-op if it already exists.
|
|
Does NOT touch marketplace.json.
|
|
|
|
Exit codes:
|
|
0 Files created or already existed (no-op)
|
|
1 Invalid arguments or missing root
|
|
EOF
|
|
}
|
|
|
|
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
|
|
if [[ $# -lt 2 ]]; then
|
|
echo "Error: plugin-name and repo-root are required." >&2
|
|
echo "" >&2
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
|
|
PLUGIN_NAME="$1"
|
|
REPO_ROOT="$2"
|
|
|
|
# Validate plugin name format
|
|
if ! echo "$PLUGIN_NAME" | grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$'; then
|
|
echo "Error: plugin-name must use lowercase letters, numbers, and hyphens only." >&2
|
|
echo " No leading, trailing, or consecutive hyphens." >&2
|
|
echo " Received: '$PLUGIN_NAME'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Expand tilde
|
|
REPO_ROOT="${REPO_ROOT/#\~/$HOME}"
|
|
|
|
# Resolve to absolute path
|
|
REPO_ROOT="$(cd "$REPO_ROOT" 2>/dev/null && pwd)" || {
|
|
echo "Error: repo-root directory '$2' does not exist." >&2
|
|
exit 1
|
|
}
|
|
|
|
PLUGIN_DIR="$REPO_ROOT/plugins/$PLUGIN_NAME"
|
|
CC_DIR="$PLUGIN_DIR/.claude-plugin"
|
|
COPILOT_MANIFEST="$PLUGIN_DIR/plugin.json"
|
|
CC_MANIFEST="$CC_DIR/plugin.json"
|
|
|
|
# Create directory skeleton
|
|
created_any=false
|
|
|
|
create_dir_if_missing() {
|
|
local dir="$1"
|
|
if [[ -d "$dir" ]]; then
|
|
echo "Skipping directory '$dir' — already exists." >&2
|
|
else
|
|
mkdir -p "$dir"
|
|
echo "Created directory: $dir" >&2
|
|
created_any=true
|
|
fi
|
|
}
|
|
|
|
create_dir_if_missing "$PLUGIN_DIR"
|
|
create_dir_if_missing "$CC_DIR"
|
|
create_dir_if_missing "$PLUGIN_DIR/skills"
|
|
create_dir_if_missing "$PLUGIN_DIR/agents"
|
|
create_dir_if_missing "$PLUGIN_DIR/hooks"
|
|
create_dir_if_missing "$PLUGIN_DIR/bin"
|
|
|
|
# Create Copilot manifest (plugin.json)
|
|
if [[ -f "$COPILOT_MANIFEST" ]]; then
|
|
echo "Skipping '$COPILOT_MANIFEST' — already exists." >&2
|
|
else
|
|
cat > "$COPILOT_MANIFEST" <<COPILOT_JSON
|
|
{
|
|
"name": "$PLUGIN_NAME",
|
|
"description": "FILL_IN_DESCRIPTION",
|
|
"version": "1.0.0",
|
|
"author": { "name": "FILL_IN_AUTHOR_NAME", "email": "FILL_IN_AUTHOR_EMAIL" },
|
|
"license": "MIT",
|
|
"keywords": [],
|
|
"agents": "agents/",
|
|
"skills": ["skills/"],
|
|
"hooks": "hooks.json",
|
|
"mcpServers": ".mcp.json"
|
|
}
|
|
COPILOT_JSON
|
|
echo "Created: $COPILOT_MANIFEST" >&2
|
|
created_any=true
|
|
fi
|
|
|
|
# Create Claude Code manifest (.claude-plugin/plugin.json)
|
|
if [[ -f "$CC_MANIFEST" ]]; then
|
|
echo "Skipping '$CC_MANIFEST' — already exists." >&2
|
|
else
|
|
cat > "$CC_MANIFEST" <<CC_JSON
|
|
{
|
|
"name": "$PLUGIN_NAME",
|
|
"displayName": "FILL_IN_DISPLAY_NAME",
|
|
"description": "FILL_IN_DESCRIPTION",
|
|
"version": "1.0.0",
|
|
"author": { "name": "FILL_IN_AUTHOR_NAME", "url": "FILL_IN_AUTHOR_URL" },
|
|
"license": "MIT",
|
|
"keywords": []
|
|
}
|
|
CC_JSON
|
|
echo "Created: $CC_MANIFEST" >&2
|
|
created_any=true
|
|
fi
|
|
|
|
if [[ "$created_any" == false ]]; then
|
|
echo "All files already exist — nothing to do." >&2
|
|
else
|
|
echo "" >&2
|
|
echo "Plugin: $PLUGIN_NAME" >&2
|
|
echo "Location: $PLUGIN_DIR" >&2
|
|
echo "" >&2
|
|
echo "Next steps:" >&2
|
|
echo " 1. Fill in $COPILOT_MANIFEST — replace all FILL_IN_* placeholders" >&2
|
|
echo " 2. Fill in $CC_MANIFEST — replace all FILL_IN_* placeholders" >&2
|
|
echo " 3. Verify version is identical in both manifests (version parity — ADR-0016)" >&2
|
|
echo " 4. Add plugin content: skills in skills/, agents in agents/, etc." >&2
|
|
fi
|