#!/usr/bin/env bash # update.sh — Full uplift cycle for skills on Linux/macOS (and git-bash on Windows) # # Steps: # 1. git pull --ff-only in skills repo # 2. Conditionally rebuild projects-meta-mcp (if source changed) # 3. Conditionally rebuild interns-mcp (if source changed) # 4. Install all skills via install.sh # 5. Show version diff (before → after) # 6. Print reload hints # # Usage: update.sh [--yes] # --yes — skip confirmation prompts (assume yes) set -euo pipefail YES_MODE=false [[ "${1:-}" == "--yes" ]] && YES_MODE=true # ── Paths ────────────────────────────────────────────────────────────────── ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SKILLS_SRC="$ROOT/skills" # Dual install targets — the "canon" is the git repo (skills/); both dirs are installs. TARGETS=("${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" "$HOME/.agents/skills") COMMON_ROOT="${COMMON_ROOT:-$HOME/projects/.common}" META_MCP="$COMMON_ROOT/lib/projects-meta-mcp" INTERNS_MCP="$COMMON_ROOT/lib/interns-mcp" # ── Helpers ──────────────────────────────────────────────────────────────── info() { echo -e "\033[1;34m[update]\033[0m $*"; } ok() { echo -e "\033[1;32m[ok]\033[0m $*"; } warn() { echo -e "\033[1;33m[warn]\033[0m $*"; } fail() { echo -e "\033[1;31m[fail]\033[0m $*" >&2; exit 1; } confirm() { local msg="$1" if $YES_MODE; then return 0; fi read -rp "$msg [y/N] " ans [[ "$ans" =~ ^[Yy] ]] } # ── Collect before-versions ──────────────────────────────────────────────── declare -A BEFORE_VERSIONS=() for target in "${TARGETS[@]}"; do [[ -d "$target" ]] || continue for skill_dir in "$target"/*/; do name="$(basename "$skill_dir")" vf="$skill_dir/SKILL.md" if [[ -f "$vf" ]]; then ver="$(grep -oP '^version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$vf" 2>/dev/null || echo '?')" BEFORE_VERSIONS["$name"]="$ver" fi done done # ── Step 1: git pull ──────────────────────────────────────────────────────── info "Pulling skills repo..." cd "$ROOT" if [[ -n "$(git status --porcelain)" ]]; then warn "Working tree has uncommitted changes. Stashing..." git stash push -m "update-sh-auto-stash" || fail "Cannot stash changes" STASHED=true else STASHED=false fi git pull --ff-only || fail "git pull --ff-only failed (diverged or no upstream)" ok "Repo updated." if $STASHED; then info "Popping stashed changes..." git stash pop || warn "Could not pop stash — resolve manually" fi # ── Step 2: projects-meta-mcp ───────────────────────────────────────────── if [[ -d "$META_MCP/.git" ]]; then info "Checking projects-meta-mcp..." cd "$META_MCP" PRE_SHA="$(git rev-parse HEAD)" git pull --ff-only 2>/dev/null || warn "projects-meta-mcp: pull failed or no upstream" POST_SHA="$(git rev-parse HEAD)" if [[ "$PRE_SHA" != "$POST_SHA" ]]; then info "Source changed, rebuilding projects-meta-mcp..." npm run build 2>/dev/null || npm install && npm run build ok "projects-meta-mcp rebuilt." MCP_REBUILT=true else ok "projects-meta-mcp: up to date." MCP_REBUILT=false fi else warn "projects-meta-mcp not found at $META_MCP — skipping." MCP_REBUILT=false fi # ── Step 3: interns-mcp ─────────────────────────────────────────────────── if [[ -d "$INTERNS_MCP/.git" ]]; then info "Checking interns-mcp..." cd "$INTERNS_MCP" PRE_SHA="$(git rev-parse HEAD)" git pull --ff-only 2>/dev/null || warn "interns-mcp: pull failed or no upstream" POST_SHA="$(git rev-parse HEAD)" if [[ "$PRE_SHA" != "$POST_SHA" ]]; then info "Source changed, reinstalling interns-mcp..." pip install -e . 2>/dev/null || pip3 install -e . ok "interns-mcp reinstalled." MCP_REBUILT=true else ok "interns-mcp: up to date." fi else warn "interns-mcp not found at $INTERNS_MCP — skipping." fi # ── Step 4: Install skills ───────────────────────────────────────────────── cd "$ROOT" info "Installing skills..." # 0. Lint skills against Agent Skills spec (fail on violations) python "$ROOT/scripts/lint-skills.py" "$ROOT/skills" || { error "SKILL.md lint failed - fix before install"; exit 1; } # 1. Install all skills via install.sh bash "$ROOT/scripts/install.sh" ok "Skills installed." # ── Step 5: Version diff ─────────────────────────────────────────────────── info "Version diff:" CHANGES=false for target in "${TARGETS[@]}"; do for skill_dir in "$target"/*/; do name="$(basename "$skill_dir")" vf="$skill_dir/SKILL.md" if [[ -f "$vf" ]]; then after="$(grep -oP '^version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$vf" 2>/dev/null || echo '?')" before="${BEFORE_VERSIONS[$name]:-new}" if [[ "$before" != "$after" ]]; then echo " $before → $after ($name)" CHANGES=true fi fi done done if ! $CHANGES; then echo " (no version changes)" fi # ── Step 6: New setup-skills hint ────────────────────────────────────────── NEW_SETUP=() for skill_dir in "$SKILLS_SRC"/*/; do name="$(basename "$skill_dir")" if [[ "$name" == setup-* ]]; then missing=false for target in "${TARGETS[@]}"; do [[ ! -d "$target/$name" ]] && missing=true done $missing && NEW_SETUP+=("$name") fi done if [[ ${#NEW_SETUP[@]} -gt 0 ]]; then echo "" info "New setup skills available:" for s in "${NEW_SETUP[@]}"; do echo " - $s" done echo " Run the setup skill to configure it." fi # ── Step 7: Reload hints ────────────────────────────────────────────────── echo "" ok "Update complete!" if $MCP_REBUILT; then echo " → Run /reload-mcp to reload MCP servers (source changed)" fi echo " → Start a new Claude session for skill changes to take full effect"