Files
conorbronsdon__avoid-ai-wri…/scripts/sync-plugin-skill.sh
Conor Bronsdon 049463a806 Distribute as a Claude Code / Cowork plugin (#17)
* feat: distribute as a Claude Code / Cowork plugin

Package the skill as a single-plugin marketplace so it can be installed
via /plugin instead of a bare clone. This is the only path that works in
Claude Cowork, which loads skills exclusively from installed plugins
(see #12).

Root SKILL.md stays the single source of truth; the plugin's bundled
copy at plugins/avoid-ai-writing/skills/avoid-ai-writing/SKILL.md is
generated by scripts/sync-plugin-skill.sh. A CI workflow regenerates it
and fails on drift or a plugin.json/SKILL.md version mismatch.

Validated with `claude plugin validate` and a full local
add-marketplace / install / inspect / uninstall cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: parse plugin version safely in sync script

* fix: harden plugin version parsing in sync script

* harden skill-version parsing + plugin install docs

Builds on Copilot's plugin.json JSON-parser fix:
- sync-plugin-skill.sh: read the version only from the SKILL.md
  frontmatter block (not anywhere in the file), strip CR, and fail with
  a clear message if it's unparseable — closes the loose/false-green
  side of the version check that the JSON parser already closed for
  plugin.json.
- .gitattributes: pin *.md to LF so a CRLF checkout can't forge a
  version mismatch or mask skill drift.
- README: add /reload-plugins to the install steps (skills don't
  activate until reload/restart) and note updates are version-gated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-27 23:31:07 -07:00

53 lines
1.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Regenerate the plugin's bundled skill from the canonical root SKILL.md.
# Root SKILL.md is the single source of truth; the plugin copy is generated.
# Run this after editing SKILL.md. CI fails if the copy is out of sync.
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
src="$repo_root/SKILL.md"
dest="$repo_root/plugins/avoid-ai-writing/skills/avoid-ai-writing/SKILL.md"
cp "$src" "$dest"
# Keep plugin.json's version in lockstep with the SKILL.md frontmatter version.
# Read the version only from the first YAML frontmatter block, and strip any CR
# so a CRLF checkout can't forge a mismatch on visually-identical strings.
skill_version="$(sed -n '/^---[[:space:]]*$/,/^---[[:space:]]*$/ s/^version:[[:space:]]*//p' "$src" | head -n1 | tr -d '\r')"
if [ -z "$skill_version" ]; then
echo "could not parse 'version:' from SKILL.md frontmatter" >&2
exit 1
fi
plugin_version="$(
python3 - "$repo_root/plugins/avoid-ai-writing/.claude-plugin/plugin.json" <<'PY'
import json
import sys
path = sys.argv[1]
try:
with open(path, encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Missing plugin manifest: {path}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Invalid JSON in plugin manifest: {path}: {e}", file=sys.stderr)
sys.exit(1)
version = data.get("version")
if not isinstance(version, str) or not version:
print(f'Invalid or missing "version" in plugin manifest: {path}', file=sys.stderr)
sys.exit(1)
print(version)
PY
)"
if [ "$skill_version" != "$plugin_version" ]; then
echo "version mismatch: SKILL.md=$skill_version plugin.json=$plugin_version" >&2
echo "Update plugin.json \"version\" to match SKILL.md frontmatter." >&2
exit 1
fi
echo "synced: plugin skill + version ($skill_version)"