diff --git a/.claude/cloud-bootstrap.sh b/.claude/cloud-bootstrap.sh index 0808629..6eb43c0 100755 --- a/.claude/cloud-bootstrap.sh +++ b/.claude/cloud-bootstrap.sh @@ -40,8 +40,7 @@ cd -- "$repo_root" # Environment-snapshot inventory line: the shared environment's setup script # writes its version + build time to this stamp as its last action. Logging it # from every session makes "which snapshot is this account booting" visible -# without a per-account audit; a missing stamp means an unmanaged environment -# or an interrupted cache build. +# without a per-account audit. if [[ -f /opt/melodic-env-setup.done ]]; then echo "cloud-bootstrap: env snapshot $(cat /opt/melodic-env-setup.done)" >&2 else @@ -75,7 +74,7 @@ fi grep -qxF "$1" "$CLAUDE_ENV_FILE" 2>/dev/null || printf '%s\n' "$1" >>"$CLAUDE_ENV_FILE" } - # Node from .node-version (VM nvm at /opt/nvm; image ships 20/21/22). + # Node from .node-version, via the VM's nvm. if [[ -f .node-version ]]; then node_pin="$(tr -d '[:space:]' <.node-version)" if [[ "$(node --version 2>/dev/null)" != "v$node_pin" ]]; then @@ -232,6 +231,45 @@ EOF echo "cloud-bootstrap: $enabled enabled, $installed newly installed" >&2 +# Catalog inventory line. Everything above installs strictly what the repo +# declares, so a plugin added to a marketplace after this settings.json was +# written is not installed and nothing says so — the gap surfaces only when +# someone types a slash command that does not resolve, which is exactly how it +# has surfaced. Each registered marketplace is already cloned to disk by the +# `marketplace add` above, so naming the difference costs one jq pass and no +# network. Deliberately an inventory line and not a WARN: a repo may declare a +# subset on purpose, and this must not read as a defect on every session. +registered_dirs=$(claude plugin marketplace list --json 2>/dev/null | + jq -r '.[] | select((.installLocation // "") != "") + | [.name, .installLocation] | @tsv' 2>/dev/null || true) +declared_mps=$(jq -r '(.extraKnownMarketplaces // {}) | keys[]' "$settings" 2>/dev/null || true) +while IFS=$'\t' read -r mp_name mp_dir; do + [[ -n "$mp_name" ]] || continue + # That listing is machine-global while this question is repo-scoped: the + # `marketplace add --scope user` above registers into the user scope, so a + # machine that has bootstrapped other repos carries their marketplaces here + # too. This repo enables nothing from a marketplace it never declared, so + # comparing against one would report its entire catalog as undeclared every + # session and bury the real signal. Same scoping the fleet-side check + # applies, and the same membership idiom used for `registered` above. + [[ $'\n'"$declared_mps"$'\n' == *$'\n'"$mp_name"$'\n'* ]] || continue + catalog="$mp_dir/.claude-plugin/marketplace.json" + [[ -f "$catalog" ]] || continue + # The "@" suffix is stripped by length rather than by sub(): + # the name is interpolated text, and sub() would read any regex + # metacharacter in it as syntax. + missing=$(jq -r --arg mp "$mp_name" --slurpfile s "$settings" ' + [.plugins[]?.name] as $names + | [$s[0].enabledPlugins // {} | to_entries[] | select(.value == true) | .key + | select(endswith("@" + $mp)) | .[:length - ($mp | length) - 1]] as $declared + | ($names - $declared) | join(", ")' "$catalog" 2>/dev/null || true) + if [[ -n "$missing" ]]; then + echo "cloud-bootstrap: $mp_name carries plugins this repo does not declare: $missing" >&2 + fi +done <