---
description: "Select the active stack(s) for this repo by enabling the matching marketplace plugin(s) in .claude/settings.json. Multi-select: pass several stacks (ios backend) or pick them in the native picker. Use when a repo's stack changed or the wrong plugins are enabled for it."
description-tr: "Bu repo için aktif stack'leri seçer: eşleşen marketplace plugin'lerini .claude/settings.json'da etkinleştirir. Çoklu seçim: birden fazla stack yazılabilir (ios backend) ya da native picker'dan seçilir."
argument-hint: "[ios|android|web|frontend|backend|mobile|fullstack|all ...]"
parameters: [{"name":"stacks","kind":"enum","repeat":true,"enum":["ios","android","web","frontend","backend","mobile","fullstack","all"]}]
gui: form
destructive: false
confirm: none
allowed-tools: Bash, Read, Edit, Write, AskUserQuestion
---

# multi-agent stack  -  Select Stack(s) via Plugin Enablement

> **Pickers follow** `$HOME/.claude/multi-agent-refs/picker-contract.md`: never a one-option call, and branch on the option selected, not on its text.

Stack skills ship as plugins in the `{owner}/multi-agent-plugins` marketplace. Selecting a stack = **enabling the matching plugin(s)** in the current repo's `.claude/settings.json` `enabledPlugins`. Two toolkits are stack-independent and always enabled alongside the stack plugin(s): `ai-common-toolkit` (accessibility audit, humanizer, Firebase) and `ai-analyst-toolkit` (GitHub and package-registry evidence, community signal for analysis work).

On Claude Code the marketplace plugins are the ONLY source of stack skills - nothing is copied into `~/.claude/skills` anymore. A stack that is not enabled here is simply absent from the session. Copilot CLI and Codex CLI have no plugin loader; they receive a local copy filtered to the enabled stacks at install time, which is why step 5 below offers to refresh those copies after a change.

This replaces the old `stack-swap.sh` mechanic that physically moved skill directories in `~/.claude/skills/`. There is no SessionStart hook and no directory shuffling - enablement is declarative, per-repo, and versioned.

## Usage

```bash
/multi-agent:stack                 # no arg → native multi-select picker (current state pre-noted)
/multi-agent:stack ios             # SwiftUI toolkit + common
/multi-agent:stack ios backend     # any combination, space-separated
/multi-agent:stack android         # Compose toolkit + common
/multi-agent:stack mobile          # alias: ios + android + common
/multi-agent:stack backend         # Python / Node spec-driven toolkit + common
/multi-agent:stack web        # React / TSX toolkit + common (alias: frontend)
/multi-agent:stack fullstack       # alias: web + backend + common
/multi-agent:stack all             # all four stack toolkits + common
```

## Stack → plugin map

Derived, not listed. `pluginsForStacks()` in `scripts/_stack-routing.mjs` owns
it, and this file deliberately carries no copy: the mapping was written out by
hand in four places, and every copy is a chance to get the one asymmetry wrong -
the detector says `web`, the marketplace ships `ai-frontend-toolkit`.

```bash
node -e 'import(process.env.HOME+"/.claude/scripts/_stack-routing.mjs").then(m=>{
  const st=process.argv.slice(1).flatMap(a=>m.expandStackArg(a));
  if(!st.length){console.error("Unknown stack. One of: "+m.KNOWN_STACKS.join(" ")+" mobile fullstack all");process.exit(1);}
  const r=m.pluginsForStacks(st);
  if(r.dropped.length) console.error("not in the marketplace, dropped: "+r.dropped.join(" "));
  console.log(r.plugins.join(" "));
})' "$@"
```

`ai-common-toolkit` and `ai-analyst-toolkit` are in every answer, including the
empty one: neither is stack-specific. Multiple args union their sets, and the
aliases (`frontend`, `mobile`, `fullstack`, `all`) expand in the module so
`/multi-agent:stack mobile` cannot mean one thing here and another in the
installer.

## Behaviour

1. **No arg → native multi-select picker.** Read `.claude/settings.json` (repo) + `~/.claude/settings.json` (global) to learn the current state, then ask with `AskUserQuestion` (`multiSelect: true`) - NEVER a numbered text menu:
   - `question` (in `outputLanguage`): which stacks should be active in this repo, noting the currently enabled ones
   - `header`: "Stacks" (English, UI contract)
   - `options` (4): `ios` / `android` / `web` / `backend`, each `description` (in `outputLanguage`) naming the plugin it enables and marking the ones already enabled with "(currently on)" / "(şu an açık)"
   - Map each selected label back to its canonical arg before step 2: the token before any parenthesis.
   - Empty selection or cancel → **status mode**: print the currently enabled `@multi-agent-plugins` plugins and exit without modifying anything. Status mode never runs the Implementation block below - that block is enable-mode only.
2. **Arg(s) present → enable mode.** Accept multiple space-separated stacks. Resolve each through the alias table (`frontend`→`web`, `mobile`→`ios android`, `fullstack`→`web backend`, `all`→every stack), union the plugin sets, then write the **current repo's** `.claude/settings.json`.
3. **Write rules:**
   - every plugin in the union is set to `true`
   - every stack toolkit **not** in the union is set to `false` (leave non-`@multi-agent-plugins` entries untouched)
   - `ai-common-toolkit@multi-agent-plugins` and `ai-analyst-toolkit@multi-agent-plugins` are always `true`
   - **legacy-key cleanup**: delete any `ai-*-engineering-toolkit@multi-agent-plugins` keys - those plugin names were retired by the `ai-<stack>-toolkit` rename and a stale `true` there enables a plugin that no longer exists in the marketplace
   - **Apple's Xcode skills**: when `~/.claude/xcode-apple-marketplace` exists (the installer linked this Mac's Xcode 27.1+ plugin), set `xcode-integration@xcode-apple` to `true` if `ios` is in the selection and `false` otherwise. Apple's skills are the reference for iPhone Duo and window resizing, SwiftUI, App Intents and the accessibility audits, and they are the first route for those iOS tasks. Without that directory there is nothing to enable; leave the key absent
4. **Unknown arg** → show the table above; do not guess.
5. **Copilot/Codex refresh offer.** Their local skill copies are filtered to the enabled stacks at install time, so after a change ask (single `AskUserQuestion`, not silent): "Refresh Copilot/Codex local copies now?" - Yes runs `node <pipelineRepo>/install.js --copilot --codex`, No leaves them stale with a one-line warning naming the command to run later. Skip this question entirely when neither `~/.copilot` nor `~/.codex` exists.

## Implementation

```bash
REPO_SETTINGS=".claude/settings.json"
MP="multi-agent-plugins"

# --- ensure the marketplace is known (idempotent) -------------------------
bash "$HOME/.claude/scripts/plugin-refresh.sh" {owner} --add-only \
  || echo "note: add the marketplace once with: claude plugin marketplace add {owner}/$MP"

# Enable-mode only: with zero args the write rules below would set every stack
# toolkit to false (nothing but the always-on pair is in $ON), silently wiping the repo's
# selection. No args belongs to the picker / status path (Behaviour step 1).
if [ "$#" -eq 0 ]; then
  echo "No stack given - nothing changed. Pass stacks (ios backend ...) or use the picker."
  exit 0
fi

# resolve every arg and derive the ON set - one owner, see the map above.
# The marketplace suffix is appended INSIDE node: `$ON` is a single string, and
# re-splitting it in the shell to suffix each name is the zsh word-splitting trap
# (`printf %s@x $ON` suffixes the whole string, not each word).
ON=$(MP="$MP" node -e 'import(process.env.HOME+"/.claude/scripts/_stack-routing.mjs").then(m=>{
  const st=process.argv.slice(1).flatMap(a=>m.expandStackArg(a));
  if(!st.length){console.error("Unknown stack. One of: "+m.KNOWN_STACKS.join(" ")+" mobile fullstack all");process.exit(1);}
  const r=m.pluginsForStacks(st);
  if(r.dropped.length) console.error("not in the marketplace, dropped: "+r.dropped.join(" "));
  console.log(r.plugins.map(p=>p+"@"+process.env.MP).join(" "));
})' "$@") || exit 1
```

After resolving `$ON`, edit `.claude/settings.json` (create `{ "enabledPlugins": {} }` if absent) so that:
- every plugin in `$ON` is set to `true`,
- every stack toolkit **not** in `$ON` is set to `false` (leave non-`@multi-agent-plugins` entries untouched),
- `ai-common-toolkit@multi-agent-plugins` and `ai-analyst-toolkit@multi-agent-plugins` are always `true`,
- every `ai-*-engineering-toolkit@multi-agent-plugins` key is **deleted** (retired names).
- `xcode-integration@xcode-apple` follows `ios` (`true` with it, `false` without), only when `~/.claude/xcode-apple-marketplace` exists.

Use the Read + Edit/Write tools (JSON must stay valid). Then print the resulting `enabledPlugins` block and run step 5 (Copilot/Codex refresh offer).

## Notes

- Enablement is per-repo and declarative - commit `.claude/settings.json` so teammates get the same stack.
- Restart the conversation (or reload the window) for Claude Code to pick up newly enabled plugins.
- Pipeline Phase 1 stack detection is independent (it reads project files); `stack` only sets which plugin skill set is active.
- The old `stack-swap.sh` skill-dir swap has been removed; stack selection is entirely plugin enablement.
