feat(superpowers): vendor the 7 wired skills at v6.4.1, drop the plugin

plugins.lock.json gains a superpowers entry (obra/superpowers @ 5bf4e78,
path skills, per-skill file lists, always_on) that lib/vendor-skills.sh
fetches byte-for-byte: brainstorming, writing-plans,
subagent-driven-development, test-driven-development,
requesting-code-review, using-git-worktrees, writing-skills. install-plugins
STEP 8e vendors it, update-all refreshes it at the pin, link.sh links the
seven, .gitignore ignores them. The plugin is no longer installed or
protected: its 8 other skills duplicated personal flows and its
SessionStart injection cost ~900 tokens per start, clear and compact.
detect_superpowers is one file test on the linked skill; doctor and
session-start stop charging the injection. doctor-vendored gains an
always_on class (third lock column) so always-on externals are
link-checked instead of reported parked.
This commit is contained in:
bastien
2026-09-28 14:54:52 +02:00
parent c39c0e1045
commit 18f8c898f8
12 changed files with 170 additions and 68 deletions
+23
View File
@@ -74,6 +74,15 @@ skills/scroll-scrubbed-visual-sequence
skills/scroll-scrubbed-word-reveal skills/scroll-scrubbed-word-reveal
skills/scroll-progress-timeline skills/scroll-progress-timeline
# superpowers, vendored (plugins.lock.json 'superpowers')
skills/brainstorming
skills/writing-plans
skills/subagent-driven-development
skills/test-driven-development
skills/requesting-code-review
skills/using-git-worktrees
skills/writing-skills
# Impeccable — NOT a symlink: `impeccable skills install --scope=global` # Impeccable — NOT a symlink: `impeccable skills install --scope=global`
# writes the skill dir (and its ~15 MB engine binary) straight in through the # writes the skill dir (and its ~15 MB engine binary) straight in through the
# ~/.claude/skills symlink. Machine-owned, regenerated by make plugin/update. # ~/.claude/skills symlink. Machine-owned, regenerated by make plugin/update.
@@ -206,6 +215,20 @@ skills-external/scroll-scrubbed-visual-sequence/
skills-external/scroll-scrubbed-word-reveal/ skills-external/scroll-scrubbed-word-reveal/
skills-external/scroll-progress-timeline/ skills-external/scroll-progress-timeline/
# superpowers, vendored (plugins.lock.json 'superpowers') — machine-owned,
# curl'd at the commit pinned in plugins.lock.json by install-plugins.sh
# Step 8e (when absent) and re-fetched at the SAME commit by update-all.sh,
# through the shared lib/vendor-skills.sh helper. Not vendored: this is a
# pin, not a tracked snapshot — bump the commit deliberately to pick up an
# upstream edit.
skills-external/brainstorming/
skills-external/writing-plans/
skills-external/subagent-driven-development/
skills-external/test-driven-development/
skills-external/requesting-code-review/
skills-external/using-git-worktrees/
skills-external/writing-skills/
# 21st.dev skill pack — machine-owned: `21st skills install` output, staged by # 21st.dev skill pack — machine-owned: `21st skills install` output, staged by
# install-plugins.sh Step 8.7 (the installer refuses to write through the # install-plugins.sh Step 8.7 (the installer refuses to write through the
# ~/.claude/skills symlink, so it runs under a throwaway HOME and the skills # ~/.claude/skills symlink, so it runs under a throwaway HOME and the skills
+5 -5
View File
@@ -223,9 +223,9 @@ else
fi fi
if detect_superpowers; then if detect_superpowers; then
pass "Superpowers plugin detected" pass "superpowers skills linked (brainstorming found); per-skill check under Vendored skills"
else else
fail "Superpowers not detected — orchestrators (/init-project, /ship-feature) will fail" fail "superpowers skills not linked — run: make plugin && make link"
fi fi
if detect_context7; then if detect_context7; then
@@ -417,10 +417,10 @@ SKILL_DESC_TOKENS=$((SKILL_DESC_CHARS / 4))
# Plugin passive cost estimates (tokens) — session-start injections and # Plugin passive cost estimates (tokens) — session-start injections and
# hook prompts that never show up as a skill description above. gstack, # hook prompts that never show up as a skill description above. gstack,
# context7 (find-docs) and graphifyy dropped 2026-09-28 (skill-catalog # context7 (find-docs) and graphifyy dropped 2026-09-28 (skill-catalog
# prune): their skills sit under ~/.claude/skills and are already counted # prune); superpowers dropped the same day (tier 2, vendored instead):
# by the stats above — a separate constant here double-counted them. # their skills sit under ~/.claude/skills and are already counted by the
# stats above — a separate constant here double-counted them.
PLUGIN_TOKENS=0 PLUGIN_TOKENS=0
if detect_superpowers 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 1500)); fi
if detect_uiux_pro_max 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 670)); fi if detect_uiux_pro_max 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 670)); fi
TOTAL_TOKENS=$((CLAUDE_MD_TOKENS + SKILL_DESC_TOKENS + PLUGIN_TOKENS)) TOTAL_TOKENS=$((CLAUDE_MD_TOKENS + SKILL_DESC_TOKENS + PLUGIN_TOKENS))
+2 -1
View File
@@ -118,8 +118,9 @@ esac
# Quick passive token cost estimate # Quick passive token cost estimate
# Only count plugins that are ACTIVE (detected as ON), not just installed # Only count plugins that are ACTIVE (detected as ON), not just installed
# superpowers dropped 2026-09-28 (tier 2 of the skill-catalog prune): its
# 7 vendored skills are counted by the skill catalog, not a plugin cost.
_passive_t=0 _passive_t=0
detect_superpowers 2>/dev/null && _passive_t=$((_passive_t + 800))
# Token costs for toggle plugins — map display name to cost # Token costs for toggle plugins — map display name to cost
declare -A _plugin_costs=( declare -A _plugin_costs=(
+19 -17
View File
@@ -487,8 +487,8 @@ install_plugin() {
# copies the plugin into ~/.claude/plugins/cache — it does NOT register # copies the plugin into ~/.claude/plugins/cache — it does NOT register
# it in settings.json's enabledPlugins map. Without an explicit enable, # it in settings.json's enabledPlugins map. Without an explicit enable,
# the plugin sits dormant. Use this for plugins that should be ALWAYS ON # the plugin sits dormant. Use this for plugins that should be ALWAYS ON
# (security-guidance, superpowers). Idempotent: skips if already # (security-guidance). Idempotent: skips if already present in
# present in enabledPlugins. # enabledPlugins.
enable_plugin() { enable_plugin() {
local name="$1" local name="$1"
local source="$2" local source="$2"
@@ -531,13 +531,10 @@ install_plugin "pr-review-toolkit" "claude-code-plugins"
echo "" echo ""
# Superpowers (always on) # Superpowers plugin removed 2026-09-28 (tier 2 of the skill-catalog prune):
info "Adding Superpowers marketplace..." # its 7 wired skills are vendored in Step 8e (plugins.lock.json
claude plugin marketplace add obra/superpowers-marketplace 2>/dev/null || true # 'superpowers'); a still-cached plugin is uninstalled by hand once
install_plugin "superpowers" "superpowers-marketplace" # (claude plugin uninstall superpowers@superpowers-marketplace), never here
enable_plugin "superpowers" "superpowers-marketplace"
echo ""
# UI/UX Pro Max (toggle) # UI/UX Pro Max (toggle)
info "Adding UI/UX Pro Max marketplace..." info "Adding UI/UX Pro Max marketplace..."
@@ -909,21 +906,25 @@ fi
echo "" echo ""
# ── Step 8e: Agent Skills (addyosmani/agent-skills) + Mengto scroll # ── Step 8e: Agent Skills (addyosmani/agent-skills) + Mengto scroll
# skills (MengTo/Skills) — both commit-pinned, vendored the emil-design-eng # skills (MengTo/Skills) + superpowers (obra/superpowers) — all
# way (curl → skills-external/<name>/, symlinked by link.sh) through the # commit-pinned, vendored the emil-design-eng way (curl →
# shared lib/vendor-skills.sh helper. Shas/paths/file-lists live in # skills-external/<name>/, symlinked by link.sh) through the shared
# plugins.lock.json ("agent-skills" / "mengto-skills" entries), never # lib/vendor-skills.sh helper. Shas/paths/file-lists live in
# hardcoded here. # plugins.lock.json ("agent-skills" / "mengto-skills" / "superpowers"
echo "── Step 8e: Agent Skills + Mengto scroll skills (pinned commit) ──" # entries), never hardcoded here.
echo "── Step 8e: Agent Skills + Mengto scroll skills + superpowers (pinned commit) ──"
echo "" echo ""
# shellcheck source=lib/vendor-skills.sh disable=SC1091 # shellcheck source=lib/vendor-skills.sh disable=SC1091
source "$REPO/lib/vendor-skills.sh" source "$REPO/lib/vendor-skills.sh"
EXT_SKILL_NAMES=(observability-and-instrumentation deprecation-and-migration EXT_SKILL_NAMES=(observability-and-instrumentation deprecation-and-migration
ci-cd-and-automation scroll-world-storytelling build-threejs-scroll-worlds ci-cd-and-automation scroll-world-storytelling build-threejs-scroll-worlds
scroll-scrubbed-visual-sequence scroll-scrubbed-word-reveal scroll-scrubbed-visual-sequence scroll-scrubbed-word-reveal
scroll-progress-timeline) scroll-progress-timeline brainstorming writing-plans
subagent-driven-development test-driven-development
requesting-code-review using-git-worktrees writing-skills)
vendor_pinned_skills agent-skills vendor_pinned_skills agent-skills
vendor_pinned_skills mengto-skills vendor_pinned_skills mengto-skills
vendor_pinned_skills superpowers
for _ext_skill in "${EXT_SKILL_NAMES[@]}"; do for _ext_skill in "${EXT_SKILL_NAMES[@]}"; do
if [ -L "$HOME/.claude/skills/$_ext_skill" ]; then if [ -L "$HOME/.claude/skills/$_ext_skill" ]; then
ok "$_ext_skill symlink OK" ok "$_ext_skill symlink OK"
@@ -1207,7 +1208,7 @@ echo ""
echo " ALWAYS ON (installed at user scope):" echo " ALWAYS ON (installed at user scope):"
echo " ✅ security-guidance — regex hints on Edit/Write + out-of-band LLM reviews on commit/push (Stop review off via ENABLE_STOP_REVIEW=0; quota, not context) [claude-code-plugins]" echo " ✅ security-guidance — regex hints on Edit/Write + out-of-band LLM reviews on commit/push (Stop review off via ENABLE_STOP_REVIEW=0; quota, not context) [claude-code-plugins]"
echo " ✅ rtk — token compression hook (0 tokens)" echo " ✅ rtk — token compression hook (0 tokens)"
echo " ✅ superpowers — brainstorm/plan/implement/debug workflow" echo " ✅ superpowers skills — 7 vendored (brainstorming, writing-plans, subagent-driven-development, test-driven-development, requesting-code-review, using-git-worktrees, writing-skills), pinned v6.4.1, curl → symlink, no plugin, no session injection"
echo "" echo ""
echo " TOGGLE (plugin state = settings.json enabledPlugins; skills/CLIs = profiles):" echo " TOGGLE (plugin state = settings.json enabledPlugins; skills/CLIs = profiles):"
echo " 🔄 gstack — disabled by default (toggle: lib/toggle-external.sh enable gstack)" echo " 🔄 gstack — disabled by default (toggle: lib/toggle-external.sh enable gstack)"
@@ -1232,6 +1233,7 @@ echo " Frontend Design at: ~/.claude/skills/frontend-design/ (symlink → skill
echo " Design Motion Principles at: ~/.claude/skills/design-motion-principles/ (symlink → skills-external)" echo " Design Motion Principles at: ~/.claude/skills/design-motion-principles/ (symlink → skills-external)"
echo " Agent Skills trio at: ~/.claude/skills/{observability-and-instrumentation,deprecation-and-migration,ci-cd-and-automation}/ (symlink → skills-external)" echo " Agent Skills trio at: ~/.claude/skills/{observability-and-instrumentation,deprecation-and-migration,ci-cd-and-automation}/ (symlink → skills-external)"
echo " Mengto scroll skills at: ~/.claude/skills/{scroll-world-storytelling,build-threejs-scroll-worlds,scroll-scrubbed-visual-sequence,scroll-scrubbed-word-reveal,scroll-progress-timeline}/ (symlink → skills-external)" echo " Mengto scroll skills at: ~/.claude/skills/{scroll-world-storytelling,build-threejs-scroll-worlds,scroll-scrubbed-visual-sequence,scroll-scrubbed-word-reveal,scroll-progress-timeline}/ (symlink → skills-external)"
echo " Superpowers skills at: ~/.claude/skills/{brainstorming,writing-plans,subagent-driven-development,test-driven-development,requesting-code-review,using-git-worktrees,writing-skills}/ (symlink → skills-external)"
echo " npx skills at: ~/.agents/skills/ (symlinked into ~/.claude/skills/)" echo " npx skills at: ~/.agents/skills/ (symlinked into ~/.claude/skills/)"
echo "" echo ""
echo " → Restart Claude Code — plugins load automatically" echo " → Restart Claude Code — plugins load automatically"
+4 -8
View File
@@ -16,14 +16,10 @@ detect_rtk() {
} }
detect_superpowers() { detect_superpowers() {
# Fast check: filesystem (plugin cache) # superpowers = 7 vendored skills since 2026-09-28; the plugin is gone.
local cache_dir="$HOME/.claude/plugins/cache" # One file test on the linked vendored skill: proves vendored AND
if [ -d "$cache_dir" ]; then # linked in one shot — no plugin cache glob, no `claude plugin list`.
compgen -G "$cache_dir"/*superpowers* &>/dev/null && return 0 [ -f "$HOME/.claude/skills/brainstorming/SKILL.md" ]
fi
# Slow fallback: CLI (only if fast check fails)
claude plugin list 2>/dev/null | grep -qi "superpowers" && return 0
return 1
} }
+53 -20
View File
@@ -5,8 +5,8 @@
# EXTERNAL_SKILLS array). doctor.sh's "GStack submodule" section only # EXTERNAL_SKILLS array). doctor.sh's "GStack submodule" section only
# covers the gstack submodule — this covers the OTHER external skill # covers the gstack submodule — this covers the OTHER external skill
# packs (emil-design-eng, the agent-skills trio, the five Mengto scroll # packs (emil-design-eng, the agent-skills trio, the five Mengto scroll
# skills, and any name link.sh links with no lock entry at all, e.g. # skills, the seven superpowers skills, and any name link.sh links with
# frontend-design, design-motion-principles). # no lock entry at all, e.g. frontend-design, design-motion-principles).
# #
# One entry point, `check_vendored_skills <repo> <claude_home> # One entry point, `check_vendored_skills <repo> <claude_home>
# [profile_file]`, sourced and called by doctor.sh. Two things checked # [profile_file]`, sourced and called by doctor.sh. Two things checked
@@ -21,7 +21,9 @@
# is passed — the "could not resolve the active profile" case), # is passed — the "could not resolve the active profile" case),
# the <claude_home>/skills/<name> symlink points at # the <claude_home>/skills/<name> symlink points at
# <repo>/skills-external/<name>. A name absent from the profile is # <repo>/skills-external/<name>. A name absent from the profile is
# reported parked, not failed. # reported parked, not failed — unless its lock entry is
# "always_on": true (the superpowers entry is), in which case the
# symlink is checked regardless of the profile (see _dv_check_link).
# #
# Lock parsing via python3 argv (never string-spliced) — same pattern as # Lock parsing via python3 argv (never string-spliced) — same pattern as
# lib/vendor-skills.sh's _vendor_read_lock. link.sh's EXTERNAL_SKILLS # lib/vendor-skills.sh's _vendor_read_lock. link.sh's EXTERNAL_SKILLS
@@ -61,11 +63,14 @@ fi
# _dv_lock_expectations <lockfile> — prints "<name>\t<file>" for every # _dv_lock_expectations <lockfile> — prints "<name>\t<file>" for every
# skill named under a plugins.lock.json entry whose "managed_by" is # skill named under a plugins.lock.json entry whose "managed_by" is
# "curl": a bare list defaults each name to ["SKILL.md"]; a dict names # "curl", plus a THIRD column "\t1" when that entry is "always_on": true
# its own per-skill file list; an entry with neither (the # (the superpowers entry is) — read by _dv_is_always_on, ignored by the
# emil-design-eng single-file "path" shape) is itself the skill name, # $1==n {print $2} awk in _dv_check_files: a bare list defaults each name
# file "SKILL.md" (the literal "path" value is upstream layout, not the # to ["SKILL.md"]; a dict names its own per-skill file list; an entry
# local dest — never used here). Reads the lockfile via argv only. # with neither (the emil-design-eng single-file "path" shape) is itself
# the skill name, file "SKILL.md" (the literal "path" value is upstream
# layout, not the local dest — never used here). Reads the lockfile via
# argv only.
# Every curl-managed entry's shape is validated ("skills" null, a list # Every curl-managed entry's shape is validated ("skills" null, a list
# of str, or a dict of str -> list of str; "path" a str when present) # of str, or a dict of str -> list of str; "path" a str when present)
# BEFORE it is used, so a malformed entry is the same clean failure as # BEFORE it is used, so a malformed entry is the same clean failure as
@@ -118,12 +123,13 @@ for key, entry in data.items():
sys.exit(1) sys.exit(1)
if not valid_skills(skills): if not valid_skills(skills):
sys.exit(1) sys.exit(1)
suffix = "\t1" if entry.get("always_on") is True else ""
if skills is None: if skills is None:
print(f"{key}\tSKILL.md") print(f"{key}\tSKILL.md{suffix}")
continue continue
for name, files in skill_files(skills).items(): for name, files in skill_files(skills).items():
for file in files: for file in files:
print(f"{name}\t{file}") print(f"{name}\t{file}{suffix}")
PY PY
} }
@@ -196,16 +202,30 @@ allowlist — skipped"
[ "$all_ok" -eq 1 ] [ "$all_ok" -eq 1 ]
} }
# _dv_check_link <claude_home> <repo> <name> <profile_file> — when # _dv_is_always_on <name> <lock_out> — true when <lock_out> (the
# <profile_file> is non-empty and does not list <name>, reports it # "<name>\t<file>[\t1]" lines from _dv_lock_expectations) carries the
# parked (info), not failed. Otherwise (listed, or no <profile_file> was # always_on third column for <name>'s lock entry.
# passed — active profile could not be resolved, every external is then _dv_is_always_on() {
# expected linked) checks the <claude_home>/skills/<name> symlink points local name="$1" lock_out="$2"
# at <repo>/skills-external/<name>. awk -F'\t' -v n="$name" '$1 == n && $3 == 1 { found=1 } \
END { exit !found }' <<< "$lock_out"
}
# _dv_check_link <claude_home> <repo> <name> <profile_file> <always_on> —
# when <always_on> is "1" (the name's lock entry is "always_on": true),
# the symlink is checked whatever <profile_file> says — never parked.
# Otherwise, when <profile_file> is non-empty and does not list <name>,
# reports it parked (info), not failed. Otherwise (listed, always_on, or
# no <profile_file> was passed — active profile could not be resolved,
# every external is then expected linked) checks the
# <claude_home>/skills/<name> symlink points at
# <repo>/skills-external/<name>.
_dv_check_link() { _dv_check_link() {
local claude_home="$1" repo="$2" name="$3" profile_file="$4" local claude_home="$1" repo="$2" name="$3" profile_file="$4" \
always_on="$5"
local link target label local link target label
if [ -n "$profile_file" ] && ! _dv_profile_has "$profile_file" "$name"; then if [ "$always_on" != "1" ] && [ -n "$profile_file" ] \
&& ! _dv_profile_has "$profile_file" "$name"; then
label="$(basename "$profile_file" .profile)" label="$(basename "$profile_file" .profile)"
info "$name: parked by profile $label" info "$name: parked by profile $label"
return return
@@ -220,6 +240,20 @@ lib/profile.sh apply <profile>)"
fi fi
} }
# _dv_check_name <repo> <claude_home> <name> <profile_file> <lock_out> —
# per-name dispatch for check_vendored_skills's loop: files first (the
# link check runs only when every expected file is present, same as
# before), then the symlink, passing _dv_is_always_on's verdict as
# _dv_check_link's 5th param.
_dv_check_name() {
local repo="$1" claude_home="$2" name="$3" profile_file="$4" lock_out="$5"
local always_on=""
_dv_is_always_on "$name" "$lock_out" && always_on=1
_dv_check_files "$repo" "$name" "$lock_out" \
&& _dv_check_link "$claude_home" "$repo" "$name" "$profile_file" \
"$always_on"
}
# check_vendored_skills <repo> <claude_home> [profile_file] — see the # check_vendored_skills <repo> <claude_home> [profile_file] — see the
# file header. Either the lock or link.sh being unreadable (or a # file header. Either the lock or link.sh being unreadable (or a
# malformed lock entry — _dv_lock_expectations rc 1) is a warn, never a # malformed lock entry — _dv_lock_expectations rc 1) is a warn, never a
@@ -251,7 +285,6 @@ check skipped"
item-name allowlist — skipped" item-name allowlist — skipped"
continue continue
fi fi
_dv_check_files "$repo" "$name" "$lock_out" \ _dv_check_name "$repo" "$claude_home" "$name" "$profile_file" "$lock_out"
&& _dv_check_link "$claude_home" "$repo" "$name" "$profile_file"
done <<< "$names" done <<< "$names"
} }
+8 -6
View File
@@ -18,8 +18,10 @@
# and MCPs in the MANAGED_* allowlists are disabled when the profile # and MCPs in the MANAGED_* allowlists are disabled when the profile
# does not list them — nothing outside those lists is ever auto-toggled. # does not list them — nothing outside those lists is ever auto-toggled.
# #
# Always-on plugins (never toggled by `set`): security-guidance, # Always-on plugins (never toggled by `set`): security-guidance + rtk
# superpowers + rtk hook + .claude internal. The script refuses to disable # hook + .claude internal. superpowers is vendored skills now, not a
# plugin (never in PROTECTED_PLUGINS, never in MANAGED_EXTERNALS — same
# always-on class as darwin-skill). The script refuses to disable
# anything in PROTECTED_PLUGINS. # anything in PROTECTED_PLUGINS.
# #
# Usage: # Usage:
@@ -61,9 +63,10 @@ DEFAULT_PROFILE="full" # profile in force when none is selected (cache absent,
source "$(dirname "${BASH_SOURCE[0]}")/gstack-removed.sh" source "$(dirname "${BASH_SOURCE[0]}")/gstack-removed.sh"
# Plugins that are toggle-managed by `set`. Anything NOT in this list is # Plugins that are toggle-managed by `set`. Anything NOT in this list is
# never auto-disabled — protects always-on plugins (security-guidance, # never auto-disabled — protects always-on plugins (security-guidance;
# superpowers) and unrelated user plugins. Add a plugin here only when its # superpowers is vendored skills now, not a plugin) and unrelated user
# enabled state is meaningfully driven by task type. # plugins. Add a plugin here only when its enabled state is meaningfully
# driven by task type.
MANAGED_PLUGINS=( MANAGED_PLUGINS=(
"ui-ux-pro-max@ui-ux-pro-max-skill" "ui-ux-pro-max@ui-ux-pro-max-skill"
"plugin-dev@claude-code-plugins" "plugin-dev@claude-code-plugins"
@@ -106,7 +109,6 @@ MANAGED_MCPS=()
# MANAGED_PLUGINS allowlist.) # MANAGED_PLUGINS allowlist.)
PROTECTED_PLUGINS=( PROTECTED_PLUGINS=(
"security-guidance@claude-code-plugins" "security-guidance@claude-code-plugins"
"superpowers@superpowers-marketplace"
) )
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BLUE='\033[0;34m'; NC='\033[0m' GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BLUE='\033[0;34m'; NC='\033[0m'
+26 -7
View File
@@ -17,9 +17,11 @@
# "skills" is neither null/list/dict degrading the same way with no # "skills" is neither null/list/dict degrading the same way with no
# Python traceback leaking (LOCK_MALFORMED_ENTRY, rc 0), the # Python traceback leaking (LOCK_MALFORMED_ENTRY, rc 0), the
# profile-name allowlist rejecting a path-traversal value # profile-name allowlist rejecting a path-traversal value
# (REJECTS_BAD_PROFILE_NAME), and the item-name allowlist rejecting a # (REJECTS_BAD_PROFILE_NAME), the item-name allowlist rejecting a
# link.sh entry with a ".." segment — warned and skipped, not failed # link.sh entry with a ".." segment — warned and skipped, not failed
# (REJECTS_BAD_NAME). # (REJECTS_BAD_NAME), and an "always_on": true lock entry's name, absent
# from the profile and with no symlink, checked (and failed) instead of
# reported parked (ALWAYS_ON_LINK_CHECKED).
set -u set -u
ROOT="$(cd "$(dirname "$0")/../.." && pwd)" ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
LIB="$ROOT/lib/doctor-vendored.sh" LIB="$ROOT/lib/doctor-vendored.sh"
@@ -57,6 +59,11 @@ cat > "$REPO/plugins.lock.json" <<'JSON'
"dict-entry": { "dict-entry": {
"managed_by": "curl", "managed_by": "curl",
"skills": {"dict-skill": ["SKILL.md", "references/notes.md"]} "skills": {"dict-skill": ["SKILL.md", "references/notes.md"]}
},
"always-on-entry": {
"managed_by": "curl",
"always_on": true,
"skills": ["always-on-skill"]
} }
} }
JSON JSON
@@ -66,25 +73,27 @@ cat > "$REPO/link.sh" <<'SH'
#!/usr/bin/env bash #!/usr/bin/env bash
EXTERNAL_SKILLS=(ok-skill missing-skill dict-skill EXTERNAL_SKILLS=(ok-skill missing-skill dict-skill
active-nolink-skill active-wronglink-skill active-nolink-skill active-wronglink-skill
parked-skill noprofile-skill) parked-skill noprofile-skill always-on-skill)
SH SH
# ── skills-external/ tree: every name's SKILL.md present, except # ── skills-external/ tree: every name's SKILL.md present, except
# missing-skill (nothing at all) and dict-skill's references/notes.md. # missing-skill (nothing at all) and dict-skill's references/notes.md.
# always-on-skill has its SKILL.md too — only its symlink is missing.
for n in ok-skill dict-skill active-nolink-skill active-wronglink-skill \ for n in ok-skill dict-skill active-nolink-skill active-wronglink-skill \
parked-skill noprofile-skill; do parked-skill noprofile-skill always-on-skill; do
mkdir -p "$REPO/skills-external/$n" mkdir -p "$REPO/skills-external/$n"
echo "v1" > "$REPO/skills-external/$n/SKILL.md" echo "v1" > "$REPO/skills-external/$n/SKILL.md"
done done
# ── claude_home symlinks: ok-skill correct, active-wronglink-skill # ── claude_home symlinks: ok-skill correct, active-wronglink-skill
# points elsewhere, active-nolink-skill and noprofile-skill have none. # points elsewhere, active-nolink-skill, noprofile-skill and
# always-on-skill have none.
ln -sf "$REPO/skills-external/ok-skill" "$CLAUDE_HOME/skills/ok-skill" ln -sf "$REPO/skills-external/ok-skill" "$CLAUDE_HOME/skills/ok-skill"
mkdir -p "$WORK/elsewhere" mkdir -p "$WORK/elsewhere"
ln -sf "$WORK/elsewhere" "$CLAUDE_HOME/skills/active-wronglink-skill" ln -sf "$WORK/elsewhere" "$CLAUDE_HOME/skills/active-wronglink-skill"
# ── active.profile: lists everything EXCEPT parked-skill and # ── active.profile: lists everything EXCEPT parked-skill,
# noprofile-skill (both proven absent from it). # noprofile-skill and always-on-skill (all three proven absent from it).
cat > "$REPO/active.profile" <<'PROF' cat > "$REPO/active.profile" <<'PROF'
# DESC: fixture profile # DESC: fixture profile
ok-skill external ok-skill external
@@ -132,6 +141,16 @@ check_bool SYMLINK_PARKED \
grep -qF 'parked-skill: symlink missing/wrong' \ grep -qF 'parked-skill: symlink missing/wrong' \
&& echo 1 || echo 0)" && echo 1 || echo 0)"
# ── always-on-skill: absent from active.profile (same as parked-skill)
# but its lock entry is "always_on": true — checked (and failed, no
# symlink) instead of reported parked.
check_bool ALWAYS_ON_LINK_CHECKED \
"$(printf '%s' "$out1" | \
grep -qF 'always-on-skill: symlink missing/wrong' \
&& ! printf '%s' "$out1" | \
grep -qF 'always-on-skill: parked by profile' \
&& echo 1 || echo 0)"
# ── No profile file passed at all: noprofile-skill (absent from # ── No profile file passed at all: noprofile-skill (absent from
# active.profile, parked above) must now be treated as expected-linked. # active.profile, parked above) must now be treated as expected-linked.
out2="$(check_vendored_skills "$REPO" "$CLAUDE_HOME" 2>&1)" out2="$(check_vendored_skills "$REPO" "$CLAUDE_HOME" 2>&1)"
+3
View File
@@ -18,6 +18,9 @@
# `skills` as a bare list defaults every named skill to `["SKILL.md"]` and # `skills` as a bare list defaults every named skill to `["SKILL.md"]` and
# `path` to "skills" (the agent-skills shape); `skills` as a dict carries an # `path` to "skills" (the agent-skills shape); `skills` as a dict carries an
# explicit per-skill file list (references/*, etc.) and `path` is required. # explicit per-skill file list (references/*, etc.) and `path` is required.
# `"always_on": true` (optional) is ignored by this helper (fetch is the
# same either way) — lib/doctor-vendored.sh reads it to expect the
# entry's skills linked regardless of the active profile.
# #
# Raw URL: https://raw.githubusercontent.com/<owner>/<repo>/<sha>/<path>/ # Raw URL: https://raw.githubusercontent.com/<owner>/<repo>/<sha>/<path>/
# <skill>/<file>. VENDOR_BASE_URL overrides the "https://…/<repo>" prefix # <skill>/<file>. VENDOR_BASE_URL overrides the "https://…/<repo>" prefix
+3 -1
View File
@@ -79,7 +79,9 @@ EXTERNAL_SKILLS=(emil-design-eng frontend-design design-motion-principles
observability-and-instrumentation deprecation-and-migration ci-cd-and-automation observability-and-instrumentation deprecation-and-migration ci-cd-and-automation
scroll-world-storytelling build-threejs-scroll-worlds scroll-world-storytelling build-threejs-scroll-worlds
scroll-scrubbed-visual-sequence scroll-scrubbed-word-reveal scroll-scrubbed-visual-sequence scroll-scrubbed-word-reveal
scroll-progress-timeline) scroll-progress-timeline brainstorming writing-plans
subagent-driven-development test-driven-development
requesting-code-review using-git-worktrees writing-skills)
for _ext_skill in "${EXTERNAL_SKILLS[@]}"; do for _ext_skill in "${EXTERNAL_SKILLS[@]}"; do
if [ -d "$REPO/skills-external/$_ext_skill" ]; then if [ -d "$REPO/skills-external/$_ext_skill" ]; then
if [ -L "$CLAUDE/skills/$_ext_skill" ] && [ "$(readlink "$CLAUDE/skills/$_ext_skill")" = "$REPO/skills-external/$_ext_skill" ]; then if [ -L "$CLAUDE/skills/$_ext_skill" ] && [ "$(readlink "$CLAUDE/skills/$_ext_skill")" = "$REPO/skills-external/$_ext_skill" ]; then
+17
View File
@@ -64,6 +64,23 @@
"managed_by": "curl", "managed_by": "curl",
"note": "Five scroll-choreography skills from MengTo/Skills (agent-skills/web-design), vendored the agent-skills way but with an explicit per-skill file list (SKILL.md + REFERENCES.md, or references/*.md + references/scroll-conductor.js for build-threejs-scroll-worlds) instead of the SKILL.md-only default. Never vendored: demo/, agents/, or any binary asset upstream ships alongside each skill. Text-only, byte-for-byte copies (Codex-isms in the source text stay). Bump the commit deliberately to pick up an upstream edit; install-plugins.sh Step 8e and update-all.sh 7.3 both read it from here via lib/vendor-skills.sh's vendor_pinned_skills(), never hardcoded." "note": "Five scroll-choreography skills from MengTo/Skills (agent-skills/web-design), vendored the agent-skills way but with an explicit per-skill file list (SKILL.md + REFERENCES.md, or references/*.md + references/scroll-conductor.js for build-threejs-scroll-worlds) instead of the SKILL.md-only default. Never vendored: demo/, agents/, or any binary asset upstream ships alongside each skill. Text-only, byte-for-byte copies (Codex-isms in the source text stay). Bump the commit deliberately to pick up an upstream edit; install-plugins.sh Step 8e and update-all.sh 7.3 both read it from here via lib/vendor-skills.sh's vendor_pinned_skills(), never hardcoded."
}, },
"superpowers": {
"source": "https://github.com/obra/superpowers",
"commit": "5bf4e78011075bcfc0dc295f0724994cd123ee71",
"path": "skills",
"skills": {
"brainstorming": ["SKILL.md", "spec-document-reviewer-prompt.md", "visual-companion.md", "scripts/frame-template.html", "scripts/helper.js", "scripts/server.cjs", "scripts/start-server.sh", "scripts/stop-server.sh"],
"writing-plans": ["SKILL.md", "plan-document-reviewer-prompt.md"],
"subagent-driven-development": ["SKILL.md", "implementer-prompt.md", "re-review-prompt.md", "task-reviewer-prompt.md", "scripts/review-package", "scripts/sdd-workspace", "scripts/task-brief"],
"test-driven-development": ["SKILL.md", "writing-good-tests.md"],
"requesting-code-review": ["SKILL.md", "code-reviewer.md"],
"using-git-worktrees": ["SKILL.md"],
"writing-skills": ["SKILL.md", "anthropic-best-practices.md", "examples/CLAUDE_MD_TESTING.md", "graphviz-conventions.dot", "persuasion-principles.md", "render-graphs.js", "testing-skills-with-subagents.md"]
},
"managed_by": "curl",
"always_on": true,
"note": "Seven superpowers skills vendored byte-for-byte at the v6.4.1 tag commit (obra/superpowers), always on (no profile lists them). Bump the commit deliberately. Scripts inside run as `bash scripts/<x>`, no exec bit needed. Upstream cross-references to the plugin prefix and to the 8 non-vendored skills stay in the text; CLAUDE.global.md Skill routing maps them."
},
"impeccable": { "impeccable": {
"source": "npm:impeccable", "source": "npm:impeccable",
"version": "4.1.0", "version": "4.1.0",
+7 -3
View File
@@ -377,9 +377,10 @@ else
info "design-motion-principles not installed — skipping" info "design-motion-principles not installed — skipping"
fi fi
# ── 7.3. Update Agent Skills + Mengto scroll skills (pinned commit) ── # ── 7.3. Update Agent Skills + Mengto scroll skills + superpowers
# Both re-fetched at the SAME pinned commit (never advances the pin) via # (pinned commit) — all three re-fetched at the SAME pinned commit
# the shared lib/vendor-skills.sh helper — see install-plugins.sh Step 8e. # (never advances the pin) via the shared lib/vendor-skills.sh helper —
# see install-plugins.sh Step 8e.
echo "" echo ""
echo "── Updating Agent Skills (addyosmani/agent-skills)..." echo "── Updating Agent Skills (addyosmani/agent-skills)..."
# shellcheck source=lib/vendor-skills.sh disable=SC1091 # shellcheck source=lib/vendor-skills.sh disable=SC1091
@@ -388,6 +389,9 @@ vendor_pinned_skills agent-skills refresh
echo "" echo ""
echo "── Updating Mengto scroll skills (MengTo/Skills)..." echo "── Updating Mengto scroll skills (MengTo/Skills)..."
vendor_pinned_skills mengto-skills refresh vendor_pinned_skills mengto-skills refresh
echo ""
echo "── Updating superpowers skills (obra/superpowers)..."
vendor_pinned_skills superpowers refresh
# ── Impeccable (design detector + skill + subagents) ── # ── Impeccable (design detector + skill + subagents) ──
# Global scope: the installer writes through the ~/.claude/{skills,agents} # Global scope: the installer writes through the ~/.claude/{skills,agents}