#!/bin/bash
# sync_skills.sh
#
# Walks the manifest table in ai_dev_support/SKILLS_INDEX.md (this skill's
# sibling directory) and, for each skill whose ~/.claude/skills/<name>
# symlink already exists and resolves into a real P4 client workspace,
# syncs that workspace's skill subtree to head.
#
# This is the "keep what I already have up to date" half of the skills-sync
# skill. It deliberately does NOT auto-create P4 clients for skills that
# aren't set up yet on this machine -- see the skill's SKILL.md for the
# guided bootstrap procedure for that case, since choosing a client Root
# and reusing (vs. creating) a workspace is a judgment call, not something
# safe to script unattended.
set -u
declare ThisScript=${0##*/}
declare -i ErrorCount=0
declare IndexFile="${1:-$HOME/pub/p4-sdp_dev_bot/ai_dev_support/SKILLS_INDEX.md}"
declare SkillsDir="$HOME/.claude/skills"
function msg () { echo -e "$*"; }
function errmsg () { msg "\\nError: ${1:-Unknown Error}\\n"; ErrorCount+=1; }
[[ -r "$IndexFile" ]] || { errmsg "Cannot read manifest [$IndexFile]."; exit 1; }
# Find the nearest ancestor of $1 containing a '.p4config*' file and echo
# its basename -- each client in this project may use a differently-named
# config file (.p4config, .p4config.bot_Claude_Anthropic, etc.), and this
# script must not assume the invoking shell's own P4CONFIG applies to every
# skill's workspace.
function find_p4config_name () {
declare dir="$1"
while [[ "$dir" != "/" && -n "$dir" ]]; do
declare hit
hit=$(find "$dir" -maxdepth 1 -name ".p4config*" 2>/dev/null | head -1)
if [[ -n "$hit" ]]; then
basename "$hit"
return 0
fi
dir=$(dirname "$dir")
done
return 1
}
# Parse manifest table rows: | Skill | Server | Depot Path | Purpose |
# Only real rows have a Depot Path starting with '//' -- this also skips
# the header, the separator row, and any prose elsewhere in the file that
# happens to contain a literal '|' (e.g. this file's own "|-delimited"
# phrase in the naming note).
while IFS='|' read -r _ name server path _; do
name=$(echo "$name" | xargs)
server=$(echo "$server" | xargs)
path=$(echo "$path" | xargs)
name=${name//\`/}
server=${server//\`/}
path=${path//\`/}
[[ "$path" == //* ]] || continue
msg "=== $name ==="
LinkTarget=$(readlink "$SkillsDir/$name" 2>/dev/null)
if [[ -z "$LinkTarget" ]]; then
msg " Not set up on this machine yet (no ~/.claude/skills/$name symlink)."
msg " See this skill's SKILL.md for the bootstrap procedure (server: $server, path: $path)."
continue
fi
if [[ ! -d "$LinkTarget" ]]; then
errmsg "$name: symlink target [$LinkTarget] does not exist. Investigate before re-syncing."
continue
fi
ConfigName=$(find_p4config_name "$LinkTarget")
if [[ -z "$ConfigName" ]]; then
errmsg "$name: no .p4config* found in [$LinkTarget] or any ancestor. Skipping."
continue
fi
ClientRoot=$(cd "$LinkTarget" && P4CONFIG="$ConfigName" p4 -ztag -F %clientRoot% info 2>/dev/null)
ClientStream=$(cd "$LinkTarget" && P4CONFIG="$ConfigName" p4 -ztag -F %Stream% client -o 2>/dev/null)
msg " Symlink target: $LinkTarget (P4CONFIG=$ConfigName)"
msg " P4 client root: ${ClientRoot:-<unknown>}, stream: ${ClientStream:-<unknown>}"
(cd "$LinkTarget" && P4CONFIG="$ConfigName" p4 sync ./... 2>&1 | sed 's/^/ /')
done < "$IndexFile"
if [[ "$ErrorCount" -eq 0 ]]; then
msg "\\nDone: $ThisScript completed with no errors."
else
errmsg "$ErrorCount skill(s) had problems; see above."
fi
exit "$ErrorCount"
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 33424 | Claude (AI Agent by Anthropic) |
Add ai_dev_support/ scaffolding to //p4-sdp/dev: master skills index and the skills-sync skill. SKILLS_INDEX.md is the master manifest of every Claude Code Skill across this effort (currently 6: 2 in test-install_sdp, 3 in BattleSchool/Gen7 on PPN, this new skills-sync here) -- name, P4 server, depot path, purpose. Kept as a markdown table that doubles as skills-sync's parseable manifest, so the two can't drift out of sync with each other. skills-sync covers both halves of the 'get a new machine set up' problem: sync_skills.sh automates re-syncing already-symlinked skills to head, and the SKILL.md documents the guided (not scripted) bootstrap procedure for wiring up ~/.claude/skills/* symlinks on a machine that doesn't have them yet, since picking a new P4 client's Root is a per-person, per-machine judgment call. This is our own internal dev-process tooling (isolate ai_dev_support/... on main), distinct from the still-open SDP-1386 ai/dev + ai/ops plan for shipped, customer/contributor-facing skills. Agent: Claude Code, Model: Claude Sonnet 5 (claude-sonnet-5), operating as bot_Claude_Anthropic. |