From foundry
Post-install setup for foundry plugin. Merges statusLine, permissions.allow, and enabledPlugins into ~/.claude/settings.json; symlinks rules and TEAM_PROTOCOL.md into ~/.claude/.
npx claudepluginhub borda/ai-rig --plugin foundryThis skill is limited to using the following tools:
<objective>
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Set up foundry on a new machine:
| Action | What happens |
|---|---|
Merge statusLine, permissions.allow, enabledPlugins → ~/.claude/settings.json | ✓ |
rules/*.md → ~/.claude/rules/ | symlink |
TEAM_PROTOCOL.md → ~/.claude/ | symlink |
hooks/hooks.json | auto — plugin system |
| Conflict review before overwriting existing user files | ✓ |
Why symlink rules (not copy)? Rules and TEAM_PROTOCOL.md are loaded at session startup. Symlinks mean every session always gets the plugin's current version — no stale copies, no need to re-run init after upgrades. A broken symlink after an upgrade produces an obvious error; a stale copy silently serves old content.
Why not symlink agents and skills? The Claude Code plugin system already exposes all plugin skills and agents at root namespace. Agents must always be referenced with the full plugin prefix (foundry:sw-engineer, not sw-engineer) for unambiguous dispatch regardless of what symlinks exist. Init creates no agent or skill symlinks.
Why hooks need no action? hooks/hooks.json inside the plugin is registered automatically by the Claude Code plugin system when the plugin is enabled. Init's only hook-adjacent step is writing the statusLine.command path (Step 3) — because statusLine is a top-level settings key, not part of hooks.json.
NOT for: editing project .claude/settings.json.
--approve — non-interactive mode; automatically accepts all recommended answers without prompting. Use for scripted or CI-style setups.Parse $ARGUMENTS for the presence of --approve (case-insensitive). If found, set APPROVE_ALL=true; otherwise APPROVE_ALL=false.
When APPROVE_ALL=true, every AskUserQuestion call below is skipped and the ★ recommended option is applied automatically. Print [--approve] auto-accepting recommended option in place of the question.
Read ~/.claude/plugins/installed_plugins.json using the Read tool. Find the entry whose key contains foundry (case-insensitive). Extract its installPath. If the file does not exist or contains no foundry entry, fall back to a filesystem scan:
PLUGIN_ROOT=$(jq -r 'to_entries[] | select(.key | ascii_downcase | contains("foundry")) | .value.installPath // empty' \
"$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null | head -1) # timeout: 5000
# Fallback when registry entry is absent (manual cache copies, partial installs)
if [ -z "$PLUGIN_ROOT" ]; then
PLUGIN_ROOT=$(find ~/.claude/plugins/cache -maxdepth 5 -name "plugin.json" 2>/dev/null \
| xargs grep -l 'foundry' 2>/dev/null \
| head -1 \
| xargs -I{} dirname {}) # timeout: 10000
[ -n "$PLUGIN_ROOT" ] && printf " Note: foundry not in installed_plugins.json — using cache scan result; consider reinstalling\n"
fi
If $PLUGIN_ROOT is empty after both attempts, stop and report: "foundry plugin not found — install it first with: claude plugin marketplace add /path/to/Borda-AI-Rig && claude plugin install foundry@borda-ai-rig"
Confirm $PLUGIN_ROOT/hooks/statusline.js exists. If not, stop and report.
[ ! -f ~/.claude/settings.json ] && echo '{}' > ~/.claude/settings.json # timeout: 5000
cp ~/.claude/settings.json ~/.claude/settings.json.bak # timeout: 5000
Report: "Backed up ~/.claude/settings.json → ~/.claude/settings.json.bak"
jq -e 'has("hooks")' ~/.claude/settings.json >/dev/null 2>&1 # timeout: 5000
If the hooks key exists, the user has a pre-plugin-migration settings block that will cause hooks to fire twice.
If APPROVE_ALL=true: print [--approve] auto-accepting: remove stale hooks block and proceed directly to removing it (apply option a below).
Otherwise, use AskUserQuestion:
hooks block now ★ recommended (backup already in place from Step 2)On (a): use jq to strip the hooks key and write back with the Write tool, then continue. On (b): warn "Double-firing risk: existing hooks block will fire alongside plugin-registered hooks." Continue.
Check if statusLine is already pointing to statusline.js:
jq -e '(.statusLine.command // "") | contains("statusline.js")' ~/.claude/settings.json >/dev/null 2>&1 # timeout: 5000
If already set: report "statusLine already set — skipping." Otherwise, use jq to set the value:
jq --arg cmd "node \"$PLUGIN_ROOT/hooks/statusline.js\"" \
'.statusLine = {"async":true,"command":$cmd,"type":"command"}' \
~/.claude/settings.json > /tmp/foundry_init_tmp.json # timeout: 5000
Write /tmp/foundry_init_tmp.json content back to ~/.claude/settings.json using the Write tool.
Read $PLUGIN_ROOT/.claude-plugin/permissions-allow.json using the Read tool. Merge into ~/.claude/settings.json — add only entries not already present (exact string match):
jq --slurpfile perms "$PLUGIN_ROOT/.claude-plugin/permissions-allow.json" \
'.permissions.allow = ((.permissions.allow // []) + $perms[0] | unique)' \
~/.claude/settings.json > /tmp/foundry_init_tmp.json # timeout: 5000
Write back with the Write tool. Report: "Added N new permissions.allow entries (M already present)."
Check whether $PLUGIN_ROOT/.claude-plugin/permissions-deny.json exists. If it does, read it using the Read tool and merge into ~/.claude/settings.json — add only entries not already present:
jq --slurpfile deny "$PLUGIN_ROOT/.claude-plugin/permissions-deny.json" \
'.permissions.deny = ((.permissions.deny // []) + $deny[0] | unique)' \
~/.claude/settings.json > /tmp/foundry_init_tmp.json # timeout: 5000
Write back with the Write tool. Report: "Added N new permissions.deny entries (M already present)."
Copy $PLUGIN_ROOT/permissions-guide.md to .claude/permissions-guide.md — only if the destination does not already exist (preserves project-local edits made via /manage):
if [ ! -f ".claude/permissions-guide.md" ]; then # timeout: 5000
cp "$PLUGIN_ROOT/permissions-guide.md" ".claude/permissions-guide.md"
printf " copied: permissions-guide.md\n"
else
printf " permissions-guide.md already present — skipping\n"
fi
jq -e '.enabledPlugins["codex@openai-codex"] == true' ~/.claude/settings.json >/dev/null 2>&1 # timeout: 5000
If already true: report "enabledPlugins already set — skipping." Otherwise:
jq '.enabledPlugins["codex@openai-codex"] = true' \
~/.claude/settings.json > /tmp/foundry_init_tmp.json # timeout: 5000
Write back with the Write tool.
After all writes, confirm the file parses as valid JSON:
jq empty ~/.claude/settings.json # timeout: 5000
If jq exits non-zero: restore from backup (cp ~/.claude/settings.json.bak ~/.claude/settings.json), report the error, and stop. If valid: continue.
Ensure target directory exists:
mkdir -p ~/.claude/rules # timeout: 5000
Conflict scan — identify rule files and TEAM_PROTOCOL.md that exist in ~/.claude/ as real files or symlinks pointing elsewhere:
LINK_CONFLICTS=()
for src in "$PLUGIN_ROOT/rules/"*.md; do
dest="$HOME/.claude/rules/$(basename "$src")"
if [ -L "$dest" ]; then
target=$(readlink "$dest")
echo "$target" | grep -q "$PLUGIN_ROOT" || LINK_CONFLICTS+=("rules/$(basename "$src") → $target")
elif [ -f "$dest" ]; then
LINK_CONFLICTS+=("rules/$(basename "$src") (real file)")
fi
done # timeout: 5000
src="$PLUGIN_ROOT/TEAM_PROTOCOL.md"; dest="$HOME/.claude/TEAM_PROTOCOL.md"
if [ -L "$dest" ]; then
target=$(readlink "$dest")
echo "$target" | grep -q "$PLUGIN_ROOT" || LINK_CONFLICTS+=("TEAM_PROTOCOL.md → $target")
elif [ -f "$dest" ]; then
LINK_CONFLICTS+=("TEAM_PROTOCOL.md (real file)")
fi # timeout: 5000
If conflicts exist:
If APPROVE_ALL=true: print [--approve] auto-accepting: replace all symlink conflicts and proceed with replacing all (apply option a below).
Otherwise, use AskUserQuestion:
These entries in ~/.claude/ would be replaced with symlinks to the foundry plugin:
- <name> (<current state>)
- …
Options:
On c: loop with AskUserQuestion — "Replace <name>? (y) Yes / (n) Skip".
Symlink — for each approved or already-absent entry, ln -sf atomically replaces:
for src in "$PLUGIN_ROOT/rules/"*.md; do
ln -sf "$src" "$HOME/.claude/rules/$(basename "$src")" # timeout: 5000
echo " linked: $(basename "$src")"
done # timeout: 10000
ln -sf "$PLUGIN_ROOT/TEAM_PROTOCOL.md" ~/.claude/TEAM_PROTOCOL.md # timeout: 5000
echo " linked: TEAM_PROTOCOL.md"
Print summary:
Suggest: "Re-run /foundry:init after any plugin upgrade to refresh symlinks to the new cache path."
Testing init changes: The init skill has no .claude/skills/init entry — it is only reachable as /foundry:init after the plugin is installed. To test changes: bump version in plugins/foundry/.claude-plugin/plugin.json, then run claude plugin install foundry@borda-ai-rig from the repo root to refresh the cache, then invoke /foundry:init. Upgrade path: After claude plugin install foundry@borda-ai-rig upgrades the version, symlinks will point to the old cache path. Re-run /foundry:init — Step 7 detects stale symlinks as conflicts and replaces them.