Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions .claude/cloud-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 "@<marketplace>" 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 <<EOF
$registered_dirs
EOF

# When the SessionStart hook is the caller, stdout is parsed as hook output —
# that is why every summary above goes to stderr — and this line asks for a
# skills re-scan for whatever the harness can pick up mid-session (the plugin
Expand Down
21 changes: 21 additions & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,35 @@ extend-ignore-re = [
abd = "abd"
# Business Associate Agreements (HIPAA) — provisioning.
BAAs = "BAAs"
# Python trust-store package (the certifi CA bundle) — claude-code-proxy.
certifi = "certifi"
# Scansion vocalization for a stressed syllable ("DUM da DUM da") — songwriting
# plugin, prosody/meter craft docs. Corrected to "DUMB" by default. Case-sensitive
# and whole-token, so lowercase "dum" and any word containing it are unaffected.
DUM = "DUM"
# schtasks output-format switch (/FO, documented uppercase in
# `schtasks /query /?`) — claude-code-proxy. Case-sensitive and whole-token, so
# lowercase "fo" is unaffected.
FO = "FO"
# Features on Demand (Windows optional-feature packaging) — provisioning.
FoD = "FoD"
# CC BY-ND license abbreviation (NoDerivatives) — claude-code-proxy.
# Case-sensitive and whole-token, so lowercase "nd" is unaffected.
ND = "ND"
# Plug and Play, the standard Windows abbreviation in prose ("PnP device
# enumeration") — claude-code-proxy. The splitter flags the `Pn` segment as
# "On". Whole-token: the PnP-prefixed identifiers below still need their own
# entries.
PnP = "PnP"
# NIC power-management registry value name — provisioning.
PnPCapabilities = "PnPCapabilities"
# Deliberate word-stem inside a documented grep pattern (matches rotate/rotation
# variants) — claude-code-proxy. Whole-token, so real "rotate" typos still flag.
rotat = "rotat"
# Win32 WMI device class — claude-code-proxy. The whole identifier is the
# token typos sees (its splitter flags the `Pn` segment as "On"), so the
# entry carries the full class name, not the bare PnPEntity suffix.
Win32_PnPEntity = "Win32_PnPEntity"
# Jasmine/Jest suite-skip global (the x-prefixed describe) — claude-code-plugins
# testing detector and any consumer with JS tests. Corrected to "describe" by
# default; whole-token, so prose "describe" is unaffected.
Expand Down
Loading