---
name: semble-setup
description: "Installs, audits, repairs, updates, enables, reindexes or removes the semble_code semantic code-search MCP for a project. Triggers: semble, semantic code search, semble status, настрой semble, статус semble, переиндексируй, удали semble."
user-invocable: true
disable-model-invocation: true
argument-hint: "[prompt] [status|install|upgrade|enable|disable|uninstall|purge|reindex|optimize|resume]"
allowed-tools: [Read, Bash, AskUserQuestion]
model: opus
---

# Semble

> Lifecycle router for **`semble_code`** — a semantic repository-search MCP server (semble `0.5.5`, pinned) registered at **user scope** with `alwaysLoad: true`, indexing the corpus named by `SEMBLE_CONTENT_ARGS` into one **shared cache root**. This skill decides the **mode**, prints the state **before** touching anything, and delegates every mutation to the scripts under `scripts/`. No mutation logic lives in this file.

Two tools become available once it is wired:

| Tool | Purpose |
|------|---------|
| `mcp__semble_code__search` | find code by intent / behavior / name |
| `mcp__semble_code__find_related` | neighbors of a KNOWN location, after a useful seed |

Both take a **required `repo`** parameter — the absolute project root, or an explicit `https://`/`http://` git URL. It is never inferred. Since 0.5.5 both also accept optional `content=code|docs|config|all`; omitting it uses the server's registered `code docs config` corpus. Results carry `file_path`, `start_line`, `end_line`, `score` and optional result `content` — **there is no result `line` field**. MCP defaults are `top_k=5`, `max_snippet_lines=10`.

## Honest limits — state these to the user, never oversell

| Fact | Consequence |
|------|-------------|
| **There is no watcher and no daemon.** semble 0.5.5 has no background thread, no service, nothing to start or stop. | Never report a daemon as running, starting or stopped. Staleness is re-checked *inside each tool call*, behind a `3x last-build-duration` cooldown. |
| The embedding model is pre-loaded when the MCP server starts, and tool calls block until it is ready | The **first query on a cold cache downloads the embedding model (hundreds of MB) and is slow** — allow up to 600 s and say so before starting. Offline with a cold HuggingFace cache = every call errors. |
| The corpus is whatever `SEMBLE_CONTENT_ARGS` says — read the constant, never retype it | `.json`/`.json5`/`.csv`/`.tsv`/`.psv` are **excluded from every content type** — unreachable even with `--content all` — and `.mdx`/`.txt` are in no bucket at all. Use `rg` for those. Full table: `references/language-coverage.md`. |
| …with **one hole**: a `.gitignore`/`.sembleignore` `!` negation whose text ends in a file extension bypasses the extension filter entirely (`_is_ignored`'s `found` flag) | An un-ignored `.json` or `.png` **is** indexed, at any `--content` setting, and reads as decoded binary. In the Semble 0.5.4 measurement from 2026-08-08, one negated lockfile was 5.9% of the index; that share was not rerun on 0.5.5. The cure is a re-ignore in `.sembleignore`, which wins because its lines are appended last. Never propose a content-set change to fix it. |
| Retrieval split — Semble 0.5.4 measurement from 2026-08-08, not rerun on 0.5.5 | At `k=5`, semble took behaviour and vocabulary-mismatch questions **8 of 9**; `rg` took exhaustive enumeration (semble lost 2 of 5) and exact identifiers. A question containing "every"/"all"/"how many" is an `rg` question — say so instead of running a search. |
| 0.5.5 stores each exact content selection separately below one hashed repo directory | The registered combined corpus uses `index-code-config-docs`; code-only remains `index`; per-call `docs` and `config` use their narrower sibling variants; and `all` resolves to the same `code config docs` set and `index-code-config-docs` variant as the registered corpus. Never treat a pre-0.5.5 combined corpus left at bare `index` as the current variant. |
| A newly registered MCP server is unavailable until a **NEW session** | `install` therefore stops at a reload checkpoint written to `.claude/semble/state.json` and resumes at verification via `/brewcode:semble-setup resume`. |
| The PreToolUse reminder is insurance, not a measured adoption mechanism | Measured 0 nudges over 7 sessions / 59 **Bash** calls (not 59 judged searches - zero of them had the multi-word behaviour shape the gate fires on). Conversion after a nudge is undefined (0/0), never report a percentage, and there is no control figure - the published "8/59" is withdrawn. `SubagentStart` is proven to **deliver** (8/8 installed vs 0/6 removed), not to change tool choice: both arms' spawn prompts already ordered semble first, 14/14. |
| `semble` has no `status` and no `serve`; any argv outside `_CLI_DISPATCH_ARGS` starts a **blocking** stdio server. `--version`/`-V` joined that set in **0.5.4** (`cli.py:25`, `:215`) — on `0.5.3` and older it is unrecognized argv and hangs | Never run bare `semble`. Resolvability comes from `uvx --from 'semble[mcp]==0.5.5' semble --version` -> prints `0.5.5`, exit 0. The argv is chosen **from the pin** by `sc_semble_probe_arg`, which degrades to the always-safe `--help` for any pin below 0.5.4 — a hang is not recoverable by a fallback. Version of a `uv tool install`ed copy still comes from `uv tool list` first, for the same reason. |
| `semble install` writes an **unpinned** server named `semble` into `~/.claude.json` | This skill never runs it. An existing `semble` server is *detected* and reported as a conflict, never auto-removed. |
| `semble clear index` wipes **every** repo and content variant under the shared cache root | Per-repo rebuild has no CLI. `reindex` stages, verifies and replaces exactly the selected `index-code-config-docs` variant; other variants survive. |
| Windows is unsupported by this skill | On a non-macOS/Linux platform: print `⚠️ Windows is unsupported by this skill` and refuse every mutation. |
| Stock macOS ships no `timeout` binary | Every shell-out is bounded regardless: `sc_timeout` uses `timeout`/`gtimeout` when one exists and a pure-bash watchdog when none does — `.timeout.bounded` is always `true`. `coreutils` (for `gtimeout`) is an **optional** upgrade, never a requirement, and never a reason to fail a run. |

<instructions>

## Constants

| Const | Value |
|-------|-------|
| MCP server name | `semble_code` |
| Pin | `semble[mcp]==0.5.5` — **always single-quoted** (`zsh` globs `[ ]`) |
| Scope | `user` (the `claude mcp` CLI default is `local` — `-s user` is mandatory) |
| Corpus | `SEMBLE_CONTENT_ARGS` in `scripts/lib/semble-common.sh` — the single source of truth for the server/default CLI `--content` argv. Every default-corpus consumer passes it verbatim so both select `index-code-config-docs`; a per-call MCP content override selects the corresponding exact variant. Never copy the token list into a new invocation |
| `alwaysLoad` | `true`, written by `add-json` only (`claude mcp add` has no flag for it). Without it the two tools stay deferred behind `ToolSearch` and are effectively uncallable |
| Shared cache root | macOS `$HOME/Library/Caches/semble-code` · Linux `${XDG_CACHE_HOME:-$HOME/.cache}/semble-code` |
| State file | `<projectRoot>/.claude/semble/state.json` |
| Tools | `mcp__semble_code__search`, `mcp__semble_code__find_related` (exact, never a wildcard) |

References — read the one you need, not all of them:

| File | When |
|------|------|
| `references/intent-routing.md` | **always** — the normative routing table + the 5-step resolution algorithm |
| `references/output-contract.md` | **always** — the final report template, verbatim |
| `references/mcp-and-cache.md` | MCP detection states, exact registration commands, cache layout |
| `references/language-coverage.md` | which suffixes land in which bucket, and what is uncovered |
| `references/project-agent-migration.md` | how project agent frontmatter is patched |

---

## Prompt contract

Position 1 of `$ARGUMENTS` is a **free-form prompt** (RU/EN) — modes are optional and may follow in
any order. Nobody types keys: resolve mode + scope FROM the prompt.

Routing table, keyword scoring and the 5-step resolution algorithm are **not duplicated here** — read
`references/intent-routing.md` (already listed **always** above) and apply it literally in Step 2.

The **PLAN** block is the required output of that resolution: print it once, before the first mutation
and before the `status` report on a read-only run. `status` still asks nothing.

```
PLAN — brewcode:semble-setup
INPUT:  <$ARGUMENTS verbatim, or "(empty)">
MODE:   <resolved mode> — <matched keyword: X | default | checkpoint resume | no keyword matched>
SCOPE:  <project root, cache dir, MCP scope, pinned semble version>
DO:     <2-5 imperative bullets — the steps this mode actually runs>
RESULT: <what the user ends up holding: MCP registration, rule + hooks, warm index, report>
```

---

## Step 0 — Resolve the skill directory

`$CLAUDE_PLUGIN_ROOT` is **empty** in skill bash blocks. Resolve `${CLAUDE_SKILL_DIR}` with a plugin-cache fallback, and repeat these two lines at the top of **every** later block (a new Bash call inherits nothing).

`${CLAUDE_SKILL_DIR}` is a **text substitution on the skill prompt**, not an environment variable: `getPromptForCommand` runs `W.replace(/\$\{CLAUDE_SKILL_DIR\}/g, <skill dir>)` (verified in the CC 2.1.226 binary), and that regex matches the **bare literal only**. Write it bare and test for emptiness on the next line. A brace-modifier spelling such as `${CLAUDE_SKILL_DIR:-<fallback>}` is never matched, reaches the shell verbatim, and — since the name is genuinely unset in the Bash tool environment — makes the fallback win on *every* run.

The fallback matches **two** leaf names — `semble-setup` (current) and `semble` (the pre-rename layout still sitting in installed caches, e.g. `brewcode/4.10.1/skills/semble`) — and it uses `find`, not a shell glob: an unmatched glob is a hard error in `zsh` and an empty string in `bash`, and both spellings resolved to a silent miss. `sort -V | tail -1` keeps the newest version, and within one version prefers `semble-setup` over `semble`.

**EXECUTE** using Bash tool:

```bash
SD="${CLAUDE_SKILL_DIR}"
[ -n "$SD" ] || SD="$(find "$HOME/.claude/plugins/cache/claude-brewcode/brewcode" -maxdepth 3 -type d -path '*/skills/*' \( -name semble-setup -o -name semble \) 2>/dev/null | sort -V | tail -1)"
case "$SD" in "$HOME"/.claude/plugins/cache/*) echo "⚠️  SD came from the plugin CACHE, not \$CLAUDE_SKILL_DIR — assets may be older than the marketplace HEAD" ;; esac
test -d "$SD/scripts" && test -f "$SD/scripts/semble-status.sh" && echo "SD=$SD" && echo "✅" || echo "❌ FAILED — skill dir unresolved: SD='$SD'"
```

> **STOP if ❌** — the plugin cache is incomplete. Run `/brewtools:plugin-update` (or `claude plugin update brewcode@claude-brewcode`) and retry.
>
> **The ⚠️ line is load-bearing, not cosmetic.** It fires whenever the substitution did not happen — the skill body was copied into an agent prompt, or the block was re-run outside skill mode — and the cache then answers instead of the checkout. A run from an up-to-date working tree would silently install the cache's older hook assets over the newer ones. Whenever the ⚠️ fires, print `SD` in the report and say which version directory it names. Note `--plugin-dir <working tree>` alone does **not** redirect this resolution: the substituted value is the directory the skill was *loaded* from.

---

## Step 1 — STATUS FIRST, in every mode, before any decision

Never install, repair, reindex or remove blind. This block is read-only: `semble-status.sh` writes nothing under the project, the cache root or `~/.claude/settings.json`, not even a throttle marker (verified by full-tree snapshots).

Two side effects belong to the tools it shells out to, not to this skill — say them once, do not hide them:

| Side effect | Detail |
|-------------|--------|
| `claude mcp get semble_code` (MCP detection) | the real `claude` CLI may touch its own `~/.claude.json` / statsig files. The skill itself writes neither. |
| `uvx --from 'semble[mcp]==0.5.5' semble --version` (pin resolvability) | an **uncached network fetch on the first run** — slow on a cold uv cache, and it fails offline. Nothing is installed by it. |

**EXECUTE** using Bash tool:

```bash
SD="${CLAUDE_SKILL_DIR}"
[ -n "$SD" ] || SD="$(find "$HOME/.claude/plugins/cache/claude-brewcode/brewcode" -maxdepth 3 -type d -path '*/skills/*' \( -name semble-setup -o -name semble \) 2>/dev/null | sort -V | tail -1)"
bash "$SD/scripts/semble-status.sh" --section all --json; RC=$?
echo "RC=$RC"
[ "$RC" -eq 0 ] && echo "✅" || echo "❌ FAILED"
```

> **STOP if ❌** — a status run that cannot even produce a report means the scripts are broken or the platform is unsupported. Report the raw output; change nothing.

Read from the JSON: `.verdict`, `.state.phase`, `.state.enabled`, `.mcp.state`, `.mcp.scopes`, `.cache.staleness`, `.guidance`, `.agents.summary`, `.prereq`, `.nextStep`. A section that came back as `{"error": ...}` is reported as unknown for that section — it never suppresses the rest.

> **An unmeasurable half must never report `ready` with `--strict` exiting 0.** An **errored** `guidance` probe therefore always downgrades the verdict to `partial` (`guidance probe failed: <error>`), whatever the cause — "unit not installed" included, because that leaves the rule, the hooks and the permissions exactly as unproven as a probe that crashed. A **null** measurement never downgrades anything: null means the section was not requested (`--section mcp`), and treating an absent measurement as a defect would fail `--strict` on a healthy repo. `cache` and `agents` stay lenient by design — each witnesses one optional fact and carries its own error for the reader.

Print the **Detection** and **Before** blocks of `references/output-contract.md` now, from this JSON. `Before` is the pre-mutation snapshot and is never refreshed later.

---

## Step 2 — Resolve the mode

Read `references/intent-routing.md` and apply its 5-step algorithm to `$ARGUMENTS` **literally**. The load-bearing parts:

1. Empty / whitespace-only input -> **`status`**, read-only, no questions. Never a mutation.
2. `phase == "awaiting_reload"` and no mode named -> **`resume`** (checked before scoring).
3. Highest count of distinct matched keywords wins.
4. Ties: destructive involved -> ask; `status` involved -> `status`; two mutating modes -> first keyword in the prompt; all zero -> run `status` and offer two plausible modes in **Next Step**.
5. `AskUserQuestion` at most **once** per invocation, only for a destructive tie, the removal flavour, a scope conflict, the reindex deletion confirmation, or the `install` prerequisite gate (Step 3.1 — a machine-level `brew install`). The `uv` gate (3.1b) and the `coreutils` offer (3.1d) are **mutually exclusive**: 3.1b's question already covers both installs, so 3.1d only runs when 3.1b did not.

Print the **PLAN** block now (`## Prompt contract` above / `references/intent-routing.md`) — once,
before Step 1's `status` report and before any mutation. Anything else -> decide, do not ask.

### Early exit

If the resolved mode is `status`, or if everything is already `ready` and the intent is vague, print the report and **stop**. Do not re-install and do not open a chain of questions.

### Mode -> script routing (every mode terminates in a script subcommand)

| Mode | Route |
|------|-------|
| `status` | `semble-status.sh --section all --json` (Step 1 output; nothing further) |
| `install` | Step 3 chain: `semble-install.sh all --json` (probe: `check -> uv -> coreutils -> semble`; exit 4 = confirm) -> confirm -> `semble-install.sh all --yes --json`, or on exit 0 the report-driven `semble-install.sh coreutils --yes --json` offer -> `semble-mcp.sh detect`/`add`/`repair` -> `semble-guidance.sh install` + `semble-agents.sh apply` (Step 3.2b) -> **reload checkpoint** |
| `upgrade` | `semble-install.sh check --json` + `semble-mcp.sh detect --json` -> `semble-mcp.sh repair --yes --json` -> `semble-guidance.sh install --part all` + `semble-agents.sh apply` (the Step 3.2b block, verbatim) -> **reload checkpoint** |
| `enable` | `semble-project.sh enable --yes --json` |
| `disable` | `semble-project.sh disable --yes --json` |
| `uninstall` | `AskUserQuestion` flavour -> `semble-remove.sh <integration\|mcp\|cli> --yes --json` |
| `purge` | `semble-remove.sh purge --yes --confirm-text "purge semble code cache" --json` |
| `reindex` | `semble-project.sh reindex --json` (dry, prints the target dir) -> confirm -> `semble-project.sh reindex --yes --json` |
| `optimize` | read-only fan-out: `semble-project.sh audit --json` + `semble-cache.sh info --json` + `semble-guidance.sh status --json` + `semble-agents.sh audit --json` |
| `resume` | Step 4: `semble-state.sh phase verifying` -> `semble-project.sh smoke` -> `semble-guidance.sh install` -> `semble-agents.sh apply` -> `semble-state.sh phase ready` |

Script exit codes are uniform: `0` ok · `1` hard failure, nothing written · `2` bad usage · `3` precondition unmet (recoverable) · `4` confirmation required, nothing written.

---

## Step 3 — `install` chain (also the repair path)

State the concrete plan first — exact commands, exact paths — then run the blocks in order. Stop at the first ❌.

### 3.1 Prerequisites — one hard gate (`uv`), one soft gate (`coreutils`)

`semble-install.sh all` runs `check -> uv -> coreutils -> semble`. Two machine-level `brew install`s can appear in it and they are gated **differently** — never collapse them into one rule:

| Step | Gate | Effect on the run's exit code |
|------|------|-------------------------------|
| `brew install uv` | **hard** — exit `4`, nothing runs without an explicit confirmation (same gate as `reindex`, `purge`, `agents apply`) | drives it: `4` confirm · `3` no brew · `1` failed |
| `brew install coreutils` -> `gtimeout` | **soft** — inside `all` a missing `--yes`, a missing `brew`, a failed brew, `SEMBLE_NO_NETWORK` and `SEMBLE_DRY_RUN` all stay a *note* | none. It can never make `all` non-zero |

Only `brew` is used to obtain `uv`; the pin itself comes from `uvx`. Narrating a plan is not a gate. Default mode is **uvx-ephemeral** — no `uv tool install`, because a `semble` on `PATH` is a hazard (a bare invocation blocks).

Why the soft step still gets offered: `sc_timeout` is bounded **either way** (`.timeout.bounded` is always `true` — a real binary when one exists, the pure-bash watchdog when it does not), so a missing `gtimeout` is a degradation, not a failure. But on a machine that already has `uv`, `all` exits `0` and the note would scroll past unseen. **Therefore the coreutils decision is read from the report, never from the exit code.**

Keys to read from the probe JSON — decide on these, not on `RC` alone:

| Key | Meaning |
|-----|---------|
| `.timeout.backend` | `timeout` / `gtimeout` = a binary backs `sc_timeout`; `none` = the bash watchdog does |
| `.timeout.bounded` | always `true` — the invariant. Never report semble's shell-outs as unbounded |
| `.timeout.coreutils.status` | `present` · `installed` · `needs_confirmation` · `skipped` · `failed` |
| `.timeout.coreutils.reason` | the one clause to quote when the step did not run |
| `.brew.present` | whether `brew install coreutils` is even possible |
| `.commands` | the exact command list — print it verbatim, never retyped |

**3.1a — probe. `--yes` is absent, so nothing can be installed.**

**EXECUTE** using Bash tool:

```bash
SD="${CLAUDE_SKILL_DIR}"
[ -n "$SD" ] || SD="$(find "$HOME/.claude/plugins/cache/claude-brewcode/brewcode" -maxdepth 3 -type d -path '*/skills/*' \( -name semble-setup -o -name semble \) 2>/dev/null | sort -V | tail -1)"
bash "$SD/scripts/semble-install.sh" all --json; RC=$?
echo "RC=$RC   # 0 = uv already present | 3 = precondition | 4 = confirmation required, nothing installed"
{ [ "$RC" -e