Adds marketplace scaffolding (.claude-plugin/marketplace.json, .github/plugin/marketplace.json) and a hello-world proof-of-concept plugin to validate the format before migrating real skills. Also fixes inventory.sh to include .agents/ in the hidden-dir exception list so audits correctly surface the 16 existing skills. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
122 lines
3.4 KiB
Bash
Executable File
122 lines
3.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Scan a repository and classify every asset as skill/command/agent/hook/prompt/MCP.
|
|
# Outputs a markdown table of findings plus a list of cross-reference warnings.
|
|
#
|
|
# Usage: inventory.sh <repo-path>
|
|
|
|
set -euo pipefail
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
echo "Usage: inventory.sh <repo-path>" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ROOT="$(cd "$1" && pwd)"
|
|
|
|
if [[ ! -d "$ROOT" ]]; then
|
|
echo "Error: $ROOT is not a directory" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ── classify assets ──────────────────────────────────────────────────────────
|
|
|
|
declare -a ROWS=()
|
|
declare -a CROSS_REFS=()
|
|
|
|
while IFS= read -r -d '' path; do
|
|
rel="${path#"$ROOT/"}"
|
|
name="$(basename "$path")"
|
|
dir="$(dirname "$rel")"
|
|
parent="$(basename "$dir")"
|
|
|
|
# Skip hidden dirs except .claude-plugin and .github
|
|
skip=false
|
|
IFS='/' read -ra parts <<< "$dir"
|
|
for part in "${parts[@]}"; do
|
|
if [[ "$part" == .* && "$part" != ".claude-plugin" && "$part" != ".github" && "$part" != ".agents" ]]; then
|
|
skip=true; break
|
|
fi
|
|
done
|
|
$skip && continue
|
|
|
|
asset_type=""
|
|
|
|
case "$name" in
|
|
SKILL.md) asset_type="skill" ;;
|
|
hooks.json) asset_type="hook" ;;
|
|
.mcp.json) asset_type="mcp" ;;
|
|
.lsp.json) asset_type="lsp" ;;
|
|
plugin.json) asset_type="manifest-plugin" ;;
|
|
marketplace.json) asset_type="manifest-marketplace" ;;
|
|
*.agent.md) asset_type="agent-copilot" ;;
|
|
*.md)
|
|
if [[ "$parent" == "agents" ]]; then
|
|
asset_type="agent-claude"
|
|
elif [[ "$parent" == "commands" ]]; then
|
|
asset_type="command"
|
|
elif [[ "$rel" != *"/skills/"* && "$rel" != *"/commands/"* && "$rel" != *"/agents/"* ]]; then
|
|
asset_type="prompt"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
[[ -n "$asset_type" ]] && ROWS+=("$asset_type|$rel")
|
|
|
|
# Check for cross-references in text files
|
|
case "$name" in *.md|*.json|*.sh)
|
|
if grep -q '\.\.\/' "$path" 2>/dev/null; then
|
|
while IFS= read -r line; do
|
|
lineno="${line%%:*}"
|
|
content="${line#*:}"
|
|
CROSS_REFS+=("$rel:$lineno: $content")
|
|
done < <(grep -n '\.\.\/' "$path" 2>/dev/null | head -20)
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
done < <(find "$ROOT" -type f -print0 | sort -z)
|
|
|
|
# ── report ───────────────────────────────────────────────────────────────────
|
|
|
|
echo "# Asset Inventory: $ROOT"
|
|
echo ""
|
|
echo "## Assets"
|
|
echo ""
|
|
echo "| Type | Path |"
|
|
echo "|---|---|"
|
|
for row in "${ROWS[@]+"${ROWS[@]}"}"; do
|
|
type="${row%%|*}"
|
|
path="${row#*|}"
|
|
echo "| \`$type\` | \`$path\` |"
|
|
done | sort
|
|
|
|
total="${#ROWS[@]}"
|
|
echo ""
|
|
echo "**Total: $total assets**"
|
|
echo ""
|
|
|
|
# Summary by type
|
|
echo "## Summary by type"
|
|
echo ""
|
|
for row in "${ROWS[@]+"${ROWS[@]}"}"; do
|
|
echo "${row%%|*}"
|
|
done | sort | uniq -c | while read -r count type; do
|
|
echo "- \`$type\`: $count"
|
|
done
|
|
|
|
# Cross-reference warnings
|
|
echo ""
|
|
if [[ ${#CROSS_REFS[@]} -gt 0 ]]; then
|
|
echo "## ⚠️ Cross-reference warnings (${#CROSS_REFS[@]} found)"
|
|
echo ""
|
|
echo "These \`../\` references will break after install-time caching:"
|
|
echo ""
|
|
for ref in "${CROSS_REFS[@]}"; do
|
|
echo "- \`$ref\`"
|
|
done
|
|
else
|
|
echo "## Cross-references"
|
|
echo ""
|
|
echo "No \`../\` cross-references found. Safe to proceed with plugin boundaries."
|
|
fi
|