← Files ConductorARCHIVED FILE
scripts/verify-package.sh
29.9 KB · Oct 3, 2026 · 06:32 UTC
#!/usr/bin/env bash
# Pre-publish verification — pack the npm tarball and exercise every agent
# install/uninstall path in an isolated HOME so it can't touch real settings.
#
# Usage:
# scripts/verify-package.sh # full suite (recommended)
# scripts/verify-package.sh --quick # skip the npm-install smoke test
# scripts/verify-package.sh --keep-sandbox # preserve sandbox on success
#
set -uo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
if [ -t 1 ]; then
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m'
else
RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC=''
fi
step() { printf "\n${BOLD}${BLUE}▸ %s${NC}\n" "$*"; }
pass() { printf " ${GREEN}✓${NC} %s\n" "$*"; PASS=$((PASS+1)); }
fail() { printf " ${RED}✗${NC} %s\n" "$*"; FAIL=$((FAIL+1)); FAILURES+=("$*"); }
info() { printf " ${BLUE}·${NC} %s\n" "$*"; }
die() { printf "${RED}fatal:${NC} %s\n" "$*" >&2; exit 1; }
PASS=0; FAIL=0; FAILURES=()
QUICK=false
KEEP_SANDBOX=false
for arg in "$@"; do
case "$arg" in
--quick) QUICK=true ;;
--keep-sandbox) KEEP_SANDBOX=true ;;
-h|--help)
sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'
exit 0 ;;
*) die "unknown flag: $arg" ;;
esac
done
assert() {
local label="$1" cond="$2"
if eval "$cond"; then pass "$label"; else fail "$label"; fi
}
cat <<EOF
${BOLD}Conductor Skills — pre-publish verification${NC}
What this script does:
• Builds the npm tarball (\`npm pack\`)
• Tests every agent × global+project install/uninstall path
• Validates the Claude upgrade flow (stale legacy dir + cache cleanup)
• Simulates an end-user \`npm install -g\` from the tarball
What this script does ${BOLD}NOT${NC} do:
• Touch your real \$HOME or ~/.claude/ — everything runs in mktemp sandboxes
• Install anything for you to use — this is a ${BOLD}regression test${NC}, not an installer
If you want to actually install/upgrade Conductor on your real machine:
bash install.sh --agent claude --global
─────────────────────────────────────────────────────────────
EOF
# ─────────────────────────────────────────────────────────────────────────────
# 0. Preflight — versions, required tools
# ─────────────────────────────────────────────────────────────────────────────
step "Preflight"
command -v npm >/dev/null || die "npm not found on PATH"
command -v node >/dev/null || die "node not found on PATH"
command -v python3 >/dev/null || die "python3 not found on PATH"
command -v bash >/dev/null || die "bash not found on PATH"
PKG_VERSION=$(node -p "require('$REPO_ROOT/package.json').version")
FILE_VERSION=$(cat "$REPO_ROOT/VERSION" | tr -d '[:space:]')
INSTALL_SH_VERSION=$(grep '^VERSION=' "$REPO_ROOT/install.sh" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
PLUGIN_VERSION=$(python3 -c "import json; print(json.load(open('$REPO_ROOT/.claude-plugin/plugin.json'))['version'])")
info "package.json: $PKG_VERSION"
info "VERSION: $FILE_VERSION"
info "install.sh: $INSTALL_SH_VERSION"
info ".claude-plugin/plugin.json: $PLUGIN_VERSION"
# Per the marketplace schema, plugin version lives in plugin.json, not in
# marketplace.json's plugin entry. So we check 4 places, not 5.
assert "all four versions agree" \
"[ '$PKG_VERSION' = '$FILE_VERSION' ] && [ '$FILE_VERSION' = '$INSTALL_SH_VERSION' ] && [ '$INSTALL_SH_VERSION' = '$PLUGIN_VERSION' ]"
# ─────────────────────────────────────────────────────────────────────────────
# 0a. Marketplace.json schema validation
#
# This block catches runtime errors that JSON-parse validation alone misses.
# History: we shipped 1.6.1 with `source: "."` — JSON-valid, schema-shaped, but
# Claude Code rejected it with "source type your Claude Code version does not
# support". This step would have caught that before publish.
#
# Valid source values (based on the schema used by claude-plugins-official):
# - String relative path STARTING WITH "./" (e.g. "./", "./plugins/foo")
# - Object with discriminator: {"source": "github"|"git-subdir"|"url", ...}
# ─────────────────────────────────────────────────────────────────────────────
step "Marketplace.json schema validation"
market_check=$(python3 - "$REPO_ROOT/.claude-plugin/marketplace.json" <<'PY'
import json, re, sys
path = sys.argv[1]
with open(path) as f:
m = json.load(f)
errors = []
warnings = []
if 'name' not in m:
errors.append("missing top-level 'name'")
if 'plugins' not in m or not isinstance(m['plugins'], list):
errors.append("missing or non-list 'plugins'")
print("\n".join(errors)); sys.exit(1)
VALID_OBJECT_SOURCES = {"github", "git-subdir", "url", "git", "local"}
for i, p in enumerate(m.get('plugins', [])):
label = f"plugins[{i}] (name={p.get('name','<missing>')!r})"
if 'name' not in p:
errors.append(f"{label}: missing 'name'")
if 'source' not in p:
errors.append(f"{label}: missing 'source'")
continue
src = p['source']
if isinstance(src, str):
# String form must begin with './' to be recognized as a relative path.
# The exact bug we hit in 1.6.1: source="." (no slash) caused
# "source type your Claude Code version does not support".
# Bare './' (the marketplace root) IS valid: for a single-plugin repo
# where the plugin IS the marketplace root, './' is the canonical form
# (verified installing on Claude Code 2.1.179; same form used by other
# single-plugin marketplaces). It also avoids a redundant network clone
# that the object 'github' form triggers — and the SSH-rewrite failures
# that clone causes for users with url.insteadOf git configs.
if not src.startswith('./'):
errors.append(
f"{label}: source string {src!r} must start with './' "
f"(e.g. './' for the repo root, or './plugins/foo' for a subdir)"
)
elif isinstance(src, dict):
kind = src.get('source')
if kind not in VALID_OBJECT_SOURCES:
errors.append(
f"{label}: source object .source={kind!r} must be one of {sorted(VALID_OBJECT_SOURCES)}"
)
else:
errors.append(f"{label}: source must be string or object, got {type(src).__name__}")
if 'version' in p:
warnings.append(f"{label}: 'version' in marketplace.json plugin entry — schema says version lives in plugin.json")
if errors:
print("ERRORS:")
for e in errors:
print(f" {e}")
sys.exit(1)
if warnings:
for w in warnings:
print(f" WARN: {w}", file=sys.stderr)
PY
)
market_rc=$?
if [ $market_rc -eq 0 ]; then
pass "marketplace.json source format is recognized by Claude Code"
else
fail "marketplace.json schema check"
echo "$market_check" | sed 's/^/ /'
fi
# Cross-check against a known-good marketplace if we have one cached locally —
# protects against drift if Anthropic adds new source types in the future.
if [ -f "$HOME/.claude/plugins/marketplaces/claude-plugins-official/.claude-plugin/marketplace.json" ]; then
known_good=$(python3 -c "
import json
m = json.load(open('$HOME/.claude/plugins/marketplaces/claude-plugins-official/.claude-plugin/marketplace.json'))
seen = set()
for p in m['plugins']:
s = p.get('source')
if isinstance(s, str):
seen.add('string-./*' if s.startswith('./') else f'string-{s!r}')
elif isinstance(s, dict):
seen.add(f\"object-{s.get('source')}\")
print(' '.join(sorted(seen)))
")
info "known-good source formats observed upstream: $known_good"
fi
# ─────────────────────────────────────────────────────────────────────────────
# 1. Lint shell scripts
# ─────────────────────────────────────────────────────────────────────────────
step "Lint install.sh"
bash -n "$REPO_ROOT/install.sh" 2>&1 && pass "install.sh parses" || fail "install.sh parse error"
# ─────────────────────────────────────────────────────────────────────────────
# 2. npm pack — build the actual tarball that would publish
# ─────────────────────────────────────────────────────────────────────────────
step "Build npm tarball"
SANDBOX=$(mktemp -d -t verify-package-XXXXXX)
trap 'if [ "$KEEP_SANDBOX" = "false" ] || [ $FAIL -gt 0 ]; then :; fi; if [ "$KEEP_SANDBOX" = "false" ] && [ $FAIL -eq 0 ]; then rm -rf "$SANDBOX"; fi' EXIT
info "sandbox: $SANDBOX"
cd "$SANDBOX"
TARBALL_NAME=$(npm pack "$REPO_ROOT" 2>/dev/null | tail -1)
TARBALL="$SANDBOX/$TARBALL_NAME"
assert "tarball created: $TARBALL_NAME" "[ -f '$TARBALL' ]"
# Required files in tarball
step "Verify tarball contents"
EXTRACT="$SANDBOX/extracted"
mkdir -p "$EXTRACT"
tar -xzf "$TARBALL" -C "$EXTRACT"
PKG_DIR="$EXTRACT/package"
for required in \
"package.json" \
"VERSION" \
"install.sh" \
"install.ps1" \
"bin/conductor-skills.js" \
".claude-plugin/plugin.json" \
".claude-plugin/marketplace.json" \
"skills/conductor/SKILL.md" \
"skills/conductor/references/setup.md" \
"skills/conductor/references/cli-index.md" \
"skills/conductor/examples/llm-chat.md" \
"skills/conductor/scripts/conductor_api.py"
do
assert "tarball contains $required" "[ -f '$PKG_DIR/$required' ]"
done
# Claude Code's plugin loader scans `agents/` for *.md subagent definitions.
# If we ship non-MD files (or no agents/ at all), that's fine. But the moment
# someone adds a YAML or other-format file there, Claude shows "1 error" in
# the /plugin UI. Guard against accidental reintroduction.
if [ -d "$PKG_DIR/agents" ]; then
bad_count=$(find "$PKG_DIR/agents" -maxdepth 1 -type f ! -name '*.md' 2>/dev/null | wc -l | tr -d ' ')
assert "tarball: agents/ has only *.md files (Claude Code's subagent format)" \
"[ $bad_count -eq 0 ]"
fi
assert "tarball: no agents/openai.yaml (the regression we just fixed)" \
"[ ! -f '$PKG_DIR/agents/openai.yaml' ]"
# ─────────────────────────────────────────────────────────────────────────────
# 3. Agent matrix — install/reinstall/uninstall every supported agent
# ─────────────────────────────────────────────────────────────────────────────
step "Agent matrix (12 agents × install/reinstall/uninstall × global+project)"
info "running against extracted tarball at $PKG_DIR"
# agent | global_path | project_path | supports_global
SPECS=(
"claude|.claude/settings.json|.claude/settings.json|yes"
"codex|.codex/AGENTS.md|AGENTS.md|yes"
"gemini|.gemini/GEMINI.md|GEMINI.md|yes"
"cursor|.cursor/skills/conductor/SKILL.md|.cursor/rules/conductor.mdc|yes"
"windsurf|.codeium/windsurf/memories/global_rules.md|.windsurfrules|yes"
"roo|.roo/rules/conductor.md|.roo/rules/conductor.md|yes"
"amp|.config/AGENTS.md|.amp/instructions.md|yes"
"aider|.conductor-skills/SKILL.md|.conductor-skills/SKILL.md|yes"
"opencode|.config/opencode/skills/conductor/SKILL.md|AGENTS.md|yes"
"cline|-|.clinerules|no"
"copilot|-|.github/copilot-instructions.md|no"
"amazonq|-|.amazonq/rules/conductor.md|no"
)
run_install_sh() {
local agent_home="$1" agent_proj="$2"; shift 2
HOME="$agent_home" CONDUCTOR_SKILLS_LOCAL_DIR="$PKG_DIR" \
bash "$PKG_DIR/install.sh" --project-dir "$agent_proj" "$@" \
>"$agent_home/last.log" 2>&1
}
test_agent() {
local agent="$1" gpath="$2" ppath="$3" mode="$4"
local box="$SANDBOX/matrix/${agent}-${mode}"
mkdir -p "$box/home" "$box/proj"
local target manifest flag
if [ "$mode" = "global" ]; then
target="$box/home/$gpath"
manifest="$box/home/.conductor-skills/manifest.json"
flag="--global"
else
target="$box/proj/$ppath"
manifest="$box/proj/.conductor-skills/manifest.json"
flag=""
fi
run_install_sh "$box/home" "$box/proj" --agent "$agent" $flag
assert "[$agent/$mode] install ok" "[ \$? -eq 0 ]"
assert "[$agent/$mode] target exists ($([[ $mode == global ]] && echo ~/ || echo \$proj/)$([[ $mode == global ]] && echo "$gpath" || echo "$ppath"))" "[ -e '$target' ]"
assert "[$agent/$mode] manifest records v$PKG_VERSION" "grep -q '\"version\": \"$PKG_VERSION\"' '$manifest' 2>/dev/null"
if [ "$agent" = "claude" ]; then
assert "[claude/$mode] extraKnownMarketplaces.conductor-skills key written" "grep -q 'conductor-skills' '$target'"
assert "[claude/$mode] enabledPlugins.conductor@conductor-skills key written" "grep -q 'conductor@conductor-skills' '$target'"
# Dual install — skill files also written to ~/.claude/skills/conductor/
assert "[claude/$mode] skill files copied to ~/.claude/skills/conductor/SKILL.md" \
"[ -f '$box/home/.claude/skills/conductor/SKILL.md' ]"
assert "[claude/$mode] skill includes references/" \
"[ -d '$box/home/.claude/skills/conductor/references' ]"
assert "[claude/$mode] skill includes examples/" \
"[ -d '$box/home/.claude/skills/conductor/examples' ]"
fi
# Idempotent re-install — claude is the exception, always re-runs to bust caches
run_install_sh "$box/home" "$box/proj" --agent "$agent" $flag
if [ "$agent" = "claude" ]; then
assert "[claude/$mode] re-install runs unconditionally (cache bust)" \
"! grep -q 'already at v$PKG_VERSION' '$box/home/last.log'"
else
assert "[$agent/$mode] re-install is idempotent" \
"grep -q 'already at v$PKG_VERSION' '$box/home/last.log'"
fi
# Uninstall
run_install_sh "$box/home" "$box/proj" --agent "$agent" $flag --uninstall
if [ "$agent" = "claude" ]; then
assert "[claude/$mode] settings.json preserved after uninstall" "[ -f '$target' ]"
assert "[claude/$mode] Conductor marketplace key removed" "! grep -q 'conductor-skills' '$target'"
assert "[claude/$mode] Conductor plugin key removed" "! grep -q 'conductor@conductor-skills' '$target'"
assert "[claude/$mode] skill dir removed on uninstall" \
"[ ! -d '$box/home/.claude/skills/conductor' ]"
elif [ "$agent" = "aider" ]; then
local skilldir
if [ "$mode" = "global" ]; then skilldir="$box/home/.conductor-skills"
else skilldir="$box/proj/.conductor-skills"; fi
assert "[aider/$mode] skill dir removed on uninstall" "[ ! -d '$skilldir' ]"
else
assert "[$agent/$mode] target removed on uninstall" "[ ! -e '$target' ]"
fi
}
for spec in "${SPECS[@]}"; do
IFS='|' read -r agent gpath ppath supports_global <<< "$spec"
if [ "$supports_global" = "yes" ]; then
test_agent "$agent" "$gpath" "$ppath" "global"
fi
test_agent "$agent" "$gpath" "$ppath" "project"
done
# ─────────────────────────────────────────────────────────────────────────────
# 3b. Upgrade-from-older-version path — file-based agents (the 1.5.0→1.6.x bug)
#
# Historical bug: `bash install.sh --all` against a v1.5.0 install ran the
# "Upgrading codex from v1.5.0 to v1.6.3" log line but silently skipped the
# actual file write — because safe_write's `Overwrite? [y/N]` prompt got an
# empty answer from non-interactive stdin. Fix: when manifest says older
# version, force-overwrite (file is ours, user already consented on install).
# ─────────────────────────────────────────────────────────────────────────────
step "Upgrade-from-older-version (file-based agents must actually overwrite)"
for agent in codex gemini windsurf; do
UPGHOME="$SANDBOX/upg-$agent"
mkdir -p "$UPGHOME"
# Seed: manifest claims agent is at v1.5.0, AGENTS.md contains old marker text.
manifest="$UPGHOME/.conductor-skills/manifest.json"
mkdir -p "$(dirname "$manifest")"
cat > "$manifest" <<EOF
{
"schema_version": 1,
"installations": {
"$agent": {
"version": "1.5.0",
"installed_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z",
"mode": "global",
"target_path": "/PLACEHOLDER"
}
}
}
EOF
case "$agent" in
codex) target="$UPGHOME/.codex/AGENTS.md" ;;
gemini) target="$UPGHOME/.gemini/GEMINI.md" ;;
windsurf) target="$UPGHOME/.codeium/windsurf/memories/global_rules.md" ;;
esac
mkdir -p "$(dirname "$target")"
echo "STALE-1.5.0-MARKER" > "$target"
# Run install WITHOUT --upgrade, WITHOUT --force — simulating real user
# running `npx ... --all` on an old install.
HOME="$UPGHOME" CONDUCTOR_SKILLS_LOCAL_DIR="$PKG_DIR" \
bash "$PKG_DIR/install.sh" --agent "$agent" --global </dev/null \
>"$UPGHOME/upgrade.log" 2>&1
assert "[$agent upgrade] file overwritten (no STALE-1.5.0-MARKER remains)" \
"! grep -q 'STALE-1.5.0-MARKER' '$target'"
assert "[$agent upgrade] file contains fresh skill content" \
"grep -q 'name: conductor' '$target'"
assert "[$agent upgrade] manifest now records new version" \
"grep -q '\"version\": \"$PKG_VERSION\"' '$manifest'"
done
# ─────────────────────────────────────────────────────────────────────────────
# 3a. Claude upgrade path — stale legacy dir + old plugin cache must be busted
# ─────────────────────────────────────────────────────────────────────────────
step "Claude upgrade path (legacy skill dir + old cache must be cleaned)"
UPHOME="$SANDBOX/claude-upgrade-home"
mkdir -p "$UPHOME/.claude/skills/conductor" \
"$UPHOME/.claude/plugins/cache/conductor-skills/conductor/1.0.0" \
"$UPHOME/.claude/plugins/marketplaces/conductor-skills"
echo "# stale" > "$UPHOME/.claude/skills/conductor/SKILL.md"
echo "stale cache content" > "$UPHOME/.claude/plugins/cache/conductor-skills/conductor/1.0.0/marker.txt"
echo "stale marketplace clone" > "$UPHOME/.claude/plugins/marketplaces/conductor-skills/marker.txt"
# Pre-existing settings.json with an OLD-style enabledPlugins entry already set,
# which would normally make the install a silent no-op.
mkdir -p "$UPHOME/proj"
cat > "$UPHOME/.claude/settings.json" <<EOF
{
"model": "opus[1m]",
"enabledPlugins": {
"conductor@conductor-skills": true,
"some-other@marketplace": true
}
}
EOF
HOME="$UPHOME" CONDUCTOR_SKILLS_LOCAL_DIR="$PKG_DIR" \
bash "$PKG_DIR/install.sh" --project-dir "$UPHOME/proj" --agent claude --global \
>"$UPHOME/upgrade.log" 2>&1
assert "upgrade: ~/.claude/skills/conductor OVERWRITTEN with fresh files (not the stale stub)" \
"[ -f '$UPHOME/.claude/skills/conductor/SKILL.md' ] && ! grep -q '# stale' '$UPHOME/.claude/skills/conductor/SKILL.md'"
assert "upgrade: stale plugin cache removed" \
"[ ! -d '$UPHOME/.claude/plugins/cache/conductor-skills' ]"
assert "upgrade: stale marketplace clone removed" \
"[ ! -d '$UPHOME/.claude/plugins/marketplaces/conductor-skills' ]"
# Also stage a stale entry in installed_plugins.json and make sure we clean it
mkdir -p "$UPHOME/.claude/plugins"
cat > "$UPHOME/.claude/plugins/installed_plugins.json" <<'EOF'
{
"version": 2,
"plugins": {
"frontend-design@claude-plugins-official": [{"scope":"user","version":"unknown"}],
"conductor@conductor-skills": [{"scope":"project","installPath":"/old/v1.0.0","version":"1.0.0"}]
}
}
EOF
HOME="$UPHOME" CONDUCTOR_SKILLS_LOCAL_DIR="$PKG_DIR" \
bash "$PKG_DIR/install.sh" --project-dir "$UPHOME/proj" --agent claude --global \
>>"$UPHOME/upgrade.log" 2>&1
assert "upgrade: stale registry entry for conductor@conductor-skills removed" \
"! python3 -c 'import json; r=json.load(open(\"$UPHOME/.claude/plugins/installed_plugins.json\")); exit(0 if \"conductor@conductor-skills\" in r.get(\"plugins\",{}) else 1)'"
assert "upgrade: unrelated registry entries preserved" \
"python3 -c 'import json; r=json.load(open(\"$UPHOME/.claude/plugins/installed_plugins.json\")); exit(0 if \"frontend-design@claude-plugins-official\" in r.get(\"plugins\",{}) else 1)'"
assert "upgrade: extraKnownMarketplaces added" \
"grep -q 'conductor-skills' '$UPHOME/.claude/settings.json'"
assert "upgrade: other enabledPlugins preserved" \
"grep -q 'some-other@marketplace' '$UPHOME/.claude/settings.json'"
assert "upgrade: other settings (model) preserved" \
"grep -q 'opus\\[1m\\]' '$UPHOME/.claude/settings.json'"
# Re-run install when manifest is already up-to-date — claude must NOT skip
# (other agents do skip when manifest matches; claude's plugin cache lives
# outside the manifest so the cache bust must run unconditionally).
echo "# new stale" > "$UPHOME/.claude/skills/conductor/SKILL.md"
HOME="$UPHOME" CONDUCTOR_SKILLS_LOCAL_DIR="$PKG_DIR" \
bash "$PKG_DIR/install.sh" --project-dir "$UPHOME/proj" --agent claude --global \
>"$UPHOME/upgrade-rerun.log" 2>&1
assert "claude rerun: did NOT show 'already at version, skipping'" \
"! grep -q 'claude already at v.*skipping' '$UPHOME/upgrade-rerun.log'"
assert "claude rerun: skill dir refreshed on re-install (no stale content)" \
"[ -f '$UPHOME/.claude/skills/conductor/SKILL.md' ] && ! grep -q '# new stale' '$UPHOME/.claude/skills/conductor/SKILL.md'"
# But other agents (e.g. codex) should still skip when manifest matches
mkdir -p "$UPHOME/proj"
HOME="$UPHOME" CONDUCTOR_SKILLS_LOCAL_DIR="$PKG_DIR" \
bash "$PKG_DIR/install.sh" --project-dir "$UPHOME/proj" --agent codex --global \
>"$UPHOME/codex-first.log" 2>&1
HOME="$UPHOME" CONDUCTOR_SKILLS_LOCAL_DIR="$PKG_DIR" \
bash "$PKG_DIR/install.sh" --project-dir "$UPHOME/proj" --agent codex --global \
>"$UPHOME/codex-rerun.log" 2>&1
assert "codex rerun: DOES show 'already at version, skipping' (existing behavior preserved)" \
"grep -q 'codex already at v.*skipping' '$UPHOME/codex-rerun.log'"
# Uninstall removes skill dir + settings.json keys + caches
HOME="$UPHOME" CONDUCTOR_SKILLS_LOCAL_DIR="$PKG_DIR" \
bash "$PKG_DIR/install.sh" --agent claude --global --uninstall >>"$UPHOME/upgrade.log" 2>&1
assert "uninstall: ~/.claude/skills/conductor removed" \
"[ ! -d '$UPHOME/.claude/skills/conductor' ]"
assert "uninstall: enabledPlugins.conductor@conductor-skills removed" \
"! grep -q 'conductor@conductor-skills' '$UPHOME/.claude/settings.json'"
assert "uninstall: other enabledPlugins still preserved" \
"grep -q 'some-other@marketplace' '$UPHOME/.claude/settings.json'"
# ─────────────────────────────────────────────────────────────────────────────
# 4. npx invocation — `npx --package=<tarball> conductor-skills --all`
# This is the EXACT code path that `npx @conductor-oss/conductor-skills --all`
# will take after publish. Different from `npm install -g` (no PATH symlink;
# npx extracts the tarball into its own cache and resolves `bin` via package.json).
# ─────────────────────────────────────────────────────────────────────────────
if [ "$QUICK" = "true" ]; then
step "Skipping npx invocation test (--quick)"
else
step "npx invocation (simulates 'npx @conductor-oss/conductor-skills --all' post-publish)"
NPX_HOME="$SANDBOX/npx-home"
mkdir -p "$NPX_HOME"
# Seed with stale state — exactly what an existing user would have
mkdir -p "$NPX_HOME/.claude/skills/conductor"
echo "stale legacy" > "$NPX_HOME/.claude/skills/conductor/SKILL.md"
mkdir -p "$NPX_HOME/.claude/plugins/cache/conductor-skills/conductor/1.0.0"
echo "old cache" > "$NPX_HOME/.claude/plugins/cache/conductor-skills/conductor/1.0.0/marker.txt"
cat > "$NPX_HOME/.claude/settings.json" <<'EOF'
{
"model": "opus[1m]",
"enabledPlugins": { "unrelated@marketplace": true }
}
EOF
HOME="$NPX_HOME" npx --yes --package="$TARBALL" conductor-skills --all \
>"$SANDBOX/npx-all.log" 2>&1
assert "npx invocation exits 0" "[ \$? -eq 0 ]"
assert "npx invocation: --version reports $PKG_VERSION" \
"HOME='$NPX_HOME' npx --yes --package='$TARBALL' conductor-skills --version 2>/dev/null | tr -d '[:space:]' | grep -qx '$PKG_VERSION'"
assert "npx invocation: claude was configured (settings.json updated)" \
"grep -q 'conductor@conductor-skills' '$NPX_HOME/.claude/settings.json'"
assert "npx invocation: skill dir overwritten with fresh content (not stale stub)" \
"[ -f '$NPX_HOME/.claude/skills/conductor/SKILL.md' ] && ! grep -q 'stale legacy' '$NPX_HOME/.claude/skills/conductor/SKILL.md'"
assert "npx invocation: stale plugin cache cleaned" \
"[ ! -d '$NPX_HOME/.claude/plugins/cache/conductor-skills' ]"
assert "npx invocation: pre-existing unrelated plugin preserved" \
"grep -q 'unrelated@marketplace' '$NPX_HOME/.claude/settings.json'"
assert "npx invocation: pre-existing model preserved" \
"grep -q 'opus\\[1m\\]' '$NPX_HOME/.claude/settings.json'"
assert "npx invocation: manifest tracks the install" \
"[ -f '$NPX_HOME/.conductor-skills/manifest.json' ]"
assert "npx invocation: restart-required messaging is shown" \
"grep -q 'ACTION REQUIRED' '$SANDBOX/npx-all.log'"
fi
# ─────────────────────────────────────────────────────────────────────────────
# 5. End-user fidelity — `npm install -g` from the tarball, then run the bin
# ─────────────────────────────────────────────────────────────────────────────
if [ "$QUICK" = "true" ]; then
step "Skipping end-user fidelity smoke test (--quick)"
else
step "End-user fidelity (npm install -g from tarball)"
USER_HOME="$SANDBOX/user-home"
PREFIX="$SANDBOX/npm-prefix"
mkdir -p "$USER_HOME" "$PREFIX"
# Seed ~/.claude so `--all` detects claude via the directory fallback rather
# than relying on a `claude` binary being on the host PATH. Without this the
# smoke test only passes on machines where the Claude CLI is installed.
mkdir -p "$USER_HOME/.claude"
info "npm install -g --prefix=$PREFIX $TARBALL_NAME"
npm install -g --prefix="$PREFIX" "$TARBALL" >"$SANDBOX/npm-install.log" 2>&1
assert "npm install -g succeeded" "[ \$? -eq 0 ]"
assert "conductor-skills binary installed" "[ -x '$PREFIX/bin/conductor-skills' ]"
# Run the installed binary exactly as a published user would
out=$(HOME="$USER_HOME" PATH="$PREFIX/bin:$PATH" conductor-skills --version 2>&1)
assert "binary --version reports $PKG_VERSION (got: $out)" "[ '$out' = '$PKG_VERSION' ]"
HOME="$USER_HOME" PATH="$PREFIX/bin:$PATH" conductor-skills --all >"$SANDBOX/conductor-all.log" 2>&1
assert "conductor-skills --all completed" "[ \$? -eq 0 ]"
assert "--all installed claude settings.json" "[ -f '$USER_HOME/.claude/settings.json' ]"
assert "--all wrote enabledPlugins" "grep -q 'conductor@conductor-skills' '$USER_HOME/.claude/settings.json'"
assert "--all wrote extraKnownMarketplaces" "grep -q 'conductor-skills' '$USER_HOME/.claude/settings.json'"
assert "--all wrote manifest" "[ -f '$USER_HOME/.conductor-skills/manifest.json' ]"
fi
# ─────────────────────────────────────────────────────────────────────────────
# Summary
# ─────────────────────────────────────────────────────────────────────────────
echo ""
echo "═════════════════════════════════════════════════════════════"
TOTAL=$((PASS+FAIL))
if [ $FAIL -eq 0 ]; then
printf "${GREEN}${BOLD}✓ All %d checks passed.${NC} Tarball is ready: ${BOLD}%s${NC}\n" "$TOTAL" "$TARBALL_NAME"
echo ""
if [ "$KEEP_SANDBOX" = "true" ]; then
info "sandbox preserved at: $SANDBOX"
fi
echo "Next: ${BOLD}npm publish --access public --dry-run${NC} (one more sanity check)"
echo "Then: ${BOLD}npm publish --access public${NC}"
exit 0
else
printf "${RED}${BOLD}✗ %d of %d checks failed.${NC}\n" "$FAIL" "$TOTAL"
echo ""
echo "Failures:"
for f in "${FAILURES[@]}"; do echo " - $f"; done
echo ""
echo "Sandbox preserved at: $SANDBOX"
echo "Recent logs:"
echo " - $SANDBOX/npm-install.log"
echo " - $SANDBOX/conductor-all.log"
echo " - $SANDBOX/matrix/<agent>-<mode>/home/last.log"
exit 1
fi
SHA-256: 344bddc1e466b8eb16b893c37fa8e96f609a0a97956bc7e4f94ec3df837c6d36