diff --git a/.gitignore b/.gitignore index 4c0dbdd..a8b9bf3 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,9 @@ skills/ios-sync skills/design-motion-principles skills/emil-design-eng skills/frontend-design +skills/ci-cd-and-automation +skills/deprecation-and-migration +skills/observability-and-instrumentation # Impeccable — NOT a symlink: `impeccable skills install --scope=global` # writes the skill dir (and its ~15 MB engine binary) straight in through the @@ -177,6 +180,15 @@ skills-external/frontend-design/ # an edit. The source is always re-fetched, so no offline copy is needed. skills-external/emil-design-eng/ +# Agent Skills trio (addyosmani/agent-skills) — machine-owned, curl'd at the +# commit pinned in plugins.lock.json ("agent-skills" entry) by +# install-plugins.sh Step 8e (when absent) and re-fetched at the SAME commit +# by update-all.sh. Not vendored: this is a pin, not a tracked snapshot — +# bump the commit deliberately to pick up an upstream edit. +skills-external/observability-and-instrumentation/ +skills-external/deprecation-and-migration/ +skills-external/ci-cd-and-automation/ + # 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 # ~/.claude/skills symlink, so it runs under a throwaway HOME and the skills diff --git a/install-plugins.sh b/install-plugins.sh index 59b730d..f126ff8 100644 --- a/install-plugins.sh +++ b/install-plugins.sh @@ -90,6 +90,22 @@ print(v) fi } +# Read a pinned commit sha from plugins.lock.json (agent-skills style entries +# — no "version", a "commit" field instead). Prints the sha, or "" if the +# entry or the field is absent. +# Usage: pinned_commit "agent-skills" → prints the commit sha or "" +pinned_commit() { + local key="$1" + if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then + python3 -c " +import json, sys +with open(sys.argv[1]) as f: + d = json.load(f) +print(d.get(sys.argv[2], {}).get('commit', '')) +" "$REPO/plugins.lock.json" "$key" 2>/dev/null || true + fi +} + # ============================================================ # DETECT OS # ============================================================ @@ -899,6 +915,43 @@ else fi echo "" +# ── Step 8e: Agent Skills (addyosmani/agent-skills, pinned commit) ── +# Three dev-lifecycle skills vendored the emil-design-eng way (curl → +# skills-external//SKILL.md, symlinked by link.sh) but COMMIT-pinned +# instead of tracking main: the sha lives in plugins.lock.json ("agent-skills" +# entry), never hardcoded here. +echo "── Step 8e: Agent Skills (addyosmani/agent-skills) ─────────" +echo "" +AGENT_SKILLS_NAMES=(observability-and-instrumentation deprecation-and-migration ci-cd-and-automation) +AGENT_SKILLS_SHA=$(pinned_commit "agent-skills") +if [ -z "$AGENT_SKILLS_SHA" ]; then + err "agent-skills: no commit pinned in plugins.lock.json — add an \"agent-skills\" entry with a \"commit\" field" +else + for _as_skill in "${AGENT_SKILLS_NAMES[@]}"; do + _as_dir="$REPO/skills-external/$_as_skill" + _as_url="https://raw.githubusercontent.com/addyosmani/agent-skills/$AGENT_SKILLS_SHA/skills/$_as_skill/SKILL.md" + mkdir -p "$_as_dir" + if [ -f "$_as_dir/SKILL.md" ]; then + ok "$_as_skill already downloaded" + else + info "Downloading SKILL.md from addyosmani/agent-skills ($_as_skill)..." + if curl -fsSL "$_as_url" -o "$_as_dir/SKILL.md.tmp" \ + && mv "$_as_dir/SKILL.md.tmp" "$_as_dir/SKILL.md"; then + ok "$_as_skill installed" + else + rm -f "$_as_dir/SKILL.md.tmp" + err "$_as_skill download failed — try: curl -fsSL $_as_url -o $_as_dir/SKILL.md" + fi + fi + if [ -L "$HOME/.claude/skills/$_as_skill" ]; then + ok "$_as_skill symlink OK" + else + info "Symlinking $_as_skill — will be created by link.sh" + fi + done +fi +echo "" + # ============================================================ # STEP 8.5 — EXTERNAL SKILLS (npx skills add …) # ============================================================ @@ -1186,6 +1239,7 @@ echo " 🔄 emil-design-eng — UI polish, animations, component craft (c echo " 🔄 frontend-design — distinctive frontend interfaces, anti-AI-slop (anthropic-agent-skills)" echo " 🔄 impeccable — /impeccable design verbs + 45-rule deterministic detector (npx impeccable detect)" echo " 🔄 design-motion-principles — motion/animation design, 3-designer lens (kylezantos)" +echo " 🔄 agent-skills trio — observability-and-instrumentation, deprecation-and-migration, ci-cd-and-automation (curl → symlink, pinned commit)" echo " 🔄 darwin-skill — autonomous skill optimizer (npx skills, ~/.agents/skills/)" echo " 🔄 21st skill pack — 21st.dev CLI skills; design ones follow the profile (full by default), publishing ones on demand (toggle: lib/toggle-external.sh enable 21st)" echo "" @@ -1194,6 +1248,7 @@ echo " GStack skills symlinked individually into ~/.claude/skills/ (→ submodu echo " Emil Design Eng at: ~/.claude/skills/emil-design-eng/ (symlink → skills-external)" echo " Frontend Design at: ~/.claude/skills/frontend-design/ (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 " npx skills at: ~/.agents/skills/ (symlinked into ~/.claude/skills/)" echo "" echo " → Restart Claude Code — plugins load automatically" diff --git a/lib/profile.sh b/lib/profile.sh index dd831ca..a88292f 100755 --- a/lib/profile.sh +++ b/lib/profile.sh @@ -79,6 +79,9 @@ MANAGED_EXTERNALS=( 21st-ui-review 21st-cli-use 21st-ai + observability-and-instrumentation + deprecation-and-migration + ci-cd-and-automation ) # MCP servers that are toggle-managed by `set`, both ways (enable AND @@ -756,7 +759,9 @@ NOTE: "set" toggles the MANAGED items automatically, both ways: plugins (ui-ux-pro-max, plugin-dev, pr-review-toolkit), external packs (emil-design-eng, frontend-design, design-motion-principles, impeccable, - the five 21st design skills). Anything outside those allowlists stays + the five 21st design skills, the agent-skills trio + observability-and-instrumentation/deprecation-and-migration/ + ci-cd-and-automation). Anything outside those allowlists stays advisory — run "claude plugin enable|disable" or "bash lib/toggle-external.sh enable|disable " yourself. EOF diff --git a/lib/profiles/backend.profile b/lib/profiles/backend.profile index 7b7c2d7..c200109 100644 --- a/lib/profiles/backend.profile +++ b/lib/profiles/backend.profile @@ -13,6 +13,11 @@ code-clean personal commit-change personal analyze personal +# Dev-lifecycle skills (agent-skills trio) +observability-and-instrumentation external +deprecation-and-migration external +ci-cd-and-automation external + # Ship + review + land ship review diff --git a/lib/profiles/dev.profile b/lib/profiles/dev.profile index 7e2fe91..3415189 100644 --- a/lib/profiles/dev.profile +++ b/lib/profiles/dev.profile @@ -19,6 +19,11 @@ refactor personal code-clean personal commit-change personal +# Dev-lifecycle skills (agent-skills trio) +observability-and-instrumentation external +deprecation-and-migration external +ci-cd-and-automation external + # Session hygiene context-save land-and-deploy diff --git a/lib/profiles/full.profile b/lib/profiles/full.profile index 5ecb2f3..942a1d2 100644 --- a/lib/profiles/full.profile +++ b/lib/profiles/full.profile @@ -83,6 +83,9 @@ emil-design-eng external frontend-design external design-motion-principles external impeccable external +observability-and-instrumentation external +deprecation-and-migration external +ci-cd-and-automation external ui-ux-pro-max plugin@ui-ux-pro-max-skill # pr-review-toolkit REMOVED from full (audit 2026-07-02 #12): heaviest # single plugin cost (~2.2k tokens of agent descriptions/session), useful diff --git a/lib/tests/profile-default.test.sh b/lib/tests/profile-default.test.sh index ffeae75..ff7a8b5 100644 --- a/lib/tests/profile-default.test.sh +++ b/lib/tests/profile-default.test.sh @@ -18,7 +18,8 @@ check_not() { case "$2" in *"$3"*) fail=$((fail+1)); FX="$(mktemp -d)"; trap 'rm -rf "$FX"' EXIT mkdir -p "$FX/skills" "$FX/skills-disabled" "$FX/lib/profiles" "$FX/bin" \ - "$FX/hooks" "$FX/skills-external/emil-design-eng" + "$FX/hooks" "$FX/skills-external/emil-design-eng" \ + "$FX/skills-external/observability-and-instrumentation" for g in gs-a gs-b gs-c; do mkdir -p "$FX/skills-external/gstack/$g" touch "$FX/skills-external/gstack/$g/SKILL.md" @@ -29,7 +30,8 @@ cp "$ROOT/hooks/statusline.sh" "$FX/hooks/" cat > "$FX/lib/profiles/full.profile" <<'EOF' gs-a gs-b -emil-design-eng external +emil-design-eng external +observability-and-instrumentation external EOF cat > "$FX/lib/profiles/otherish.profile" <<'EOF' gs-c @@ -89,6 +91,7 @@ check T5-gsa-on "$([ -e "$FX/skills/gs-a" ] && echo on || echo off)" on check T5-gsb-on "$([ -e "$FX/skills/gs-b" ] && echo on || echo off)" on check T5-gsc-off "$([ -e "$FX/skills/gs-c" ] && echo on || echo off)" off check T5-emil-on "$([ -e "$FX/skills/emil-design-eng" ] && echo on || echo off)" on +check T5-obs-on "$([ -e "$FX/skills/observability-and-instrumentation" ] && echo on || echo off)" on out="$(run current)" check T5-first-word "$(first_word "$out")" full check_has T5-match "$out" "100% match" diff --git a/lib/tests/profile-set-managed.test.sh b/lib/tests/profile-set-managed.test.sh index 8a4ba37..755749b 100644 --- a/lib/tests/profile-set-managed.test.sh +++ b/lib/tests/profile-set-managed.test.sh @@ -13,7 +13,8 @@ check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1)); FX="$(mktemp -d)"; trap 'rm -rf "$FX"' EXIT mkdir -p "$FX/skills" "$FX/skills-disabled" "$FX/lib/profiles" "$FX/bin" \ "$FX/skills-external/emil-design-eng" "$FX/skills-external/other-ext" \ - "$FX/skills-external/21st-ui-build" + "$FX/skills-external/21st-ui-build" \ + "$FX/skills-external/observability-and-instrumentation" for g in gs-a gs-b gs-c; do mkdir -p "$FX/skills-external/gstack/$g" touch "$FX/skills-external/gstack/$g/SKILL.md" @@ -26,8 +27,9 @@ ln -s "$FX/skills-external/other-ext" "$FX/skills/other-ext" cat > "$FX/lib/profiles/designish.profile" <<'EOF' gs-a gs-b -emil-design-eng external -21st-ui-build external +emil-design-eng external +21st-ui-build external +observability-and-instrumentation external EOF cat > "$FX/lib/profiles/backendish.profile" <<'EOF' gs-c @@ -53,6 +55,7 @@ check T2-gsb-on "$([ -e "$FX/skills/gs-b" ] && echo on || echo off)" on check T3-gsc-off "$([ -e "$FX/skills/gs-c" ] && echo on || echo off)" off check T4-emil-src "$([ -L "$FX/skills/emil-design-eng" ] && echo on || echo off)" on check T5-21st-src "$([ -L "$FX/skills/21st-ui-build" ] && echo on || echo off)" on +check T5b-obs-src "$([ -L "$FX/skills/observability-and-instrumentation" ] && echo on || echo off)" on check T6-no-mcp "$(grep -c '^mcp ' "$FX/claude-calls.log" || true)" 0 # --- set backendish: managed leftovers parked/unregistered --- @@ -63,6 +66,8 @@ check T9-emil-off "$([ -e "$FX/skills/emil-design-eng" ] && echo on || echo off check T10-emil-park "$([ -e "$FX/skills-disabled/emil-design-eng" ] && echo p || echo n)" p check T11-21st-off "$([ -e "$FX/skills/21st-ui-build" ] && echo on || echo off)" off check T12-21st-park "$([ -e "$FX/skills-disabled/21st-ui-build" ] && echo p || echo n)" p +check T12b-obs-off "$([ -e "$FX/skills/observability-and-instrumentation" ] && echo on || echo off)" off +check T12c-obs-park "$([ -e "$FX/skills-disabled/observability-and-instrumentation" ] && echo p || echo n)" p check T13-other-untouched "$([ -e "$FX/skills/other-ext" ] && echo on || echo off)" on # --- back to designish: parked external restored (not re-sourced) --- @@ -70,6 +75,8 @@ run set designish >/dev/null 2>&1 check T14-emil-back "$([ -e "$FX/skills/emil-design-eng" ] && echo on || echo off)" on check T15-park-gone "$([ -e "$FX/skills-disabled/emil-design-eng" ] && echo p || echo n)" n check T16-21st-back "$([ -e "$FX/skills/21st-ui-build" ] && echo on || echo off)" on +check T16b-obs-back "$([ -e "$FX/skills/observability-and-instrumentation" ] && echo on || echo off)" on +check T16c-obs-park-gone "$([ -e "$FX/skills-disabled/observability-and-instrumentation" ] && echo p || echo n)" n check T17-no-mcp-ever "$(grep -c '^mcp ' "$FX/claude-calls.log" || true)" 0 printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ] diff --git a/lib/tests/toggle-external-repo-resolution.test.sh b/lib/tests/toggle-external-repo-resolution.test.sh index 8c54109..7df0c68 100644 --- a/lib/tests/toggle-external-repo-resolution.test.sh +++ b/lib/tests/toggle-external-repo-resolution.test.sh @@ -14,6 +14,7 @@ check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1)); SANDBOX="$(mktemp -d)" mkdir -p "$SANDBOX/repo/lib" "$SANDBOX/repo/skills-external/emil-design-eng" \ + "$SANDBOX/repo/skills-external/observability-and-instrumentation" \ "$SANDBOX/repo/skills" "$SANDBOX/home/.claude" cp "$HELPER_SRC" "$SANDBOX/repo/lib/toggle-external.sh" # mark emil-design-eng ENABLED in the real (physical) repo tree @@ -24,5 +25,11 @@ ln -s "$SANDBOX/repo/lib" "$SANDBOX/home/.claude/lib" out="$(bash "$SANDBOX/home/.claude/lib/toggle-external.sh" status emil-design-eng)" check T1-repo-resolves-through-symlink "$out" enabled +# Same case arm, generalized to $tool for the agent-skills trio (this PR) — +# left unlinked, so it must resolve through the symlink as "disabled", not +# "missing" (which would mean REPO fell back to the wrong tree again). +out="$(bash "$SANDBOX/home/.claude/lib/toggle-external.sh" status observability-and-instrumentation)" +check T2-generalized-tool-resolves-through-symlink "$out" disabled + rm -rf "$SANDBOX" printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ] diff --git a/lib/toggle-external.sh b/lib/toggle-external.sh index f51be4b..5c3d4c8 100755 --- a/lib/toggle-external.sh +++ b/lib/toggle-external.sh @@ -21,6 +21,9 @@ # emil-design-eng — single symlink → skills-external/emil-design-eng # darwin-skill — single symlink → ~/.agents/skills/darwin-skill # 21st — 21st.dev skill pack (needs the `21st` CLI + login) +# observability-and-instrumentation, deprecation-and-migration, +# ci-cd-and-automation — the agent-skills trio, same single-symlink shape +# as emil-design-eng (commit-pinned instead of main-branch tracking) # # For fine-grained activation (only design skills, only qa skills, only # audit skills, etc.) instead of all-or-nothing gstack toggling, use: @@ -40,7 +43,8 @@ warn() { echo -e "${YELLOW}⚠${NC} $1"; } err() { echo -e "${RED}✗${NC} $1"; } # All non-plugin tools this script can toggle. -MANAGED_TOOLS=(gstack emil-design-eng darwin-skill 21st) +MANAGED_TOOLS=(gstack emil-design-eng darwin-skill 21st + observability-and-instrumentation deprecation-and-migration ci-cd-and-automation) # Prints the skill names that belong to the "21st" pack. Source of truth: # skills-external/21st-* — the `21st skills install` run in install-plugins.sh @@ -76,9 +80,9 @@ status_tool() { done < <(gstack_skills) echo "disabled" ;; - emil-design-eng) - [ -d "$REPO/skills-external/emil-design-eng" ] || { echo "missing"; return; } - [ -e "$SKILLS_DIR/emil-design-eng" ] && echo "enabled" || echo "disabled" + emil-design-eng|observability-and-instrumentation|deprecation-and-migration|ci-cd-and-automation) + [ -d "$REPO/skills-external/$tool" ] || { echo "missing"; return; } + [ -e "$SKILLS_DIR/$tool" ] && echo "enabled" || echo "disabled" ;; darwin-skill) [ -d "$HOME/.agents/skills/$tool" ] || { echo "missing"; return; } @@ -115,7 +119,7 @@ disable_tool() { done < <(gstack_skills) ok "gstack disabled ($moved symlinks moved)" ;; - emil-design-eng|darwin-skill) + emil-design-eng|darwin-skill|observability-and-instrumentation|deprecation-and-migration|ci-cd-and-automation) if [ -e "$SKILLS_DIR/$tool" ]; then rm -rf "${DISABLED_DIR:?}/${tool:?}" mv "$SKILLS_DIR/$tool" "$DISABLED_DIR/$tool" @@ -165,11 +169,11 @@ enable_tool() { ok "gstack enabled ($moved symlinks restored)" fi ;; - emil-design-eng|darwin-skill) + emil-design-eng|darwin-skill|observability-and-instrumentation|deprecation-and-migration|ci-cd-and-automation) local src case "$tool" in - emil-design-eng) src="$REPO/skills-external/$tool" ;; darwin-skill) src="$HOME/.agents/skills/$tool" ;; + *) src="$REPO/skills-external/$tool" ;; esac if [ -e "$DISABLED_DIR/$tool" ]; then rm -rf "${SKILLS_DIR:?}/${tool:?}" diff --git a/link.sh b/link.sh index d260640..f9c19d4 100644 --- a/link.sh +++ b/link.sh @@ -93,7 +93,8 @@ fi # impeccable is NOT here: its installer writes the skill straight into # skills/ (and its agents into agents/) at --scope=global, so there is no # skills-external/ copy to symlink. See install-plugins.sh Step 8d. -EXTERNAL_SKILLS=(emil-design-eng frontend-design design-motion-principles) +EXTERNAL_SKILLS=(emil-design-eng frontend-design design-motion-principles + observability-and-instrumentation deprecation-and-migration ci-cd-and-automation) for _ext_skill in "${EXTERNAL_SKILLS[@]}"; do 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 diff --git a/plugins.lock.json b/plugins.lock.json index db20c6f..60d223c 100644 --- a/plugins.lock.json +++ b/plugins.lock.json @@ -43,6 +43,13 @@ "managed_by": "curl", "note": "Emil Kowalski's design engineering skill — UI polish, animations, component craft. Machine-owned: curl'd to skills-external/emil-design-eng/ (gitignored, re-fetched by update-all.sh), symlinked by link.sh." }, + "agent-skills": { + "source": "https://github.com/addyosmani/agent-skills", + "commit": "2686b620fc1fed2e8f60c704839c766b8594c6b6", + "skills": ["observability-and-instrumentation", "deprecation-and-migration", "ci-cd-and-automation"], + "managed_by": "curl", + "note": "Three dev-lifecycle skills from addyosmani/agent-skills, vendored the emil-design-eng way but COMMIT-pinned (not main-branch tracking): each lands in skills-external//SKILL.md (gitignored, symlinked by link.sh), install-plugins.sh Step 8e curls all three at this commit when absent, update-all.sh re-fetches at the SAME commit on every run (a pin, not an auto-advance). Bump the commit deliberately to pick up upstream changes; the scripts read it from here, never hardcode it." + }, "impeccable": { "source": "npm:impeccable", "version": "4.1.0", diff --git a/update-all.sh b/update-all.sh index 8432110..f1384fc 100644 --- a/update-all.sh +++ b/update-all.sh @@ -379,6 +379,38 @@ else info "design-motion-principles not installed — skipping" fi +# ── 7.3. Update Agent Skills (addyosmani/agent-skills, pinned commit) ── +echo "" +echo "── Updating Agent Skills (addyosmani/agent-skills)..." +AGENT_SKILLS_SHA="" +if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then + AGENT_SKILLS_SHA=$(python3 -c " +import json, sys +with open(sys.argv[1]) as f: + d = json.load(f) +print(d.get(sys.argv[2], {}).get('commit', '')) +" "$REPO/plugins.lock.json" "agent-skills" 2>/dev/null || true) +fi +AGENT_SKILLS_NAMES=(observability-and-instrumentation deprecation-and-migration ci-cd-and-automation) +if [ -z "$AGENT_SKILLS_SHA" ]; then + warn "agent-skills: no commit pinned in plugins.lock.json — skipping" +else + for _as_skill in "${AGENT_SKILLS_NAMES[@]}"; do + _as_dir="$REPO/skills-external/$_as_skill" + if [ ! -d "$_as_dir" ]; then + info "$_as_skill not installed — skipping (run: make plugin)" + continue + fi + _as_url="https://raw.githubusercontent.com/addyosmani/agent-skills/$AGENT_SKILLS_SHA/skills/$_as_skill/SKILL.md" + if curl -fsSL "$_as_url" -o "$_as_dir/SKILL.md.tmp" \ + && mv "$_as_dir/SKILL.md.tmp" "$_as_dir/SKILL.md"; then + ok "$_as_skill re-fetched at pinned commit" + else + warn "$_as_skill update failed" + fi + done +fi + # ── Impeccable (design detector + skill + subagents) ── # Global scope: the installer writes through the ~/.claude/{skills,agents} # symlinks straight into this repo (install-plugins.sh Step 8d explains why