Lifecycle router for
semble_code— a semantic repository-search MCP server (semble0.5.5, pinned) registered at user scope withalwaysLoad: true, indexing the corpus named bySEMBLE_CONTENT_ARGSinto one shared cache root. This skill decides the mode, prints the state before touching anything, and delegates every mutation to the scripts underscripts/. 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 installed 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. |
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.
Before Step 0 executes, resolve the prompt against known context and print one PLAN. If checkpoint
state is unknown, label the mode provisional and include the Step 1 status probe in DO. Step 2 refines
from that probe with one short update, never a second PLAN. status 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:
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(orclaude 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
SDin 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:
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
readywith--strictexiting 0. An erroredguidanceprobe therefore always downgrades the verdict topartial(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--stricton a healthy repo.cacheandagentsstay lenient by design — each witnesses one optional fact and carries its own error for the reader.
Keep this JSON as the immutable Before snapshot. Step 2 prints Detection/Before after final routing; the initial PLAN already preceded Step 0/1 execution. Never refresh Before after a mutation.
Step 2 — Resolve the mode
Read references/intent-routing.md and apply its 5-step algorithm to $ARGUMENTS literally. The load-bearing parts:
- Empty / whitespace-only input ->
status, read-only, no questions. Never a mutation. phase == "awaiting_reload"and no mode named ->resume(checked before scoring).- Highest count of distinct matched keywords wins.
- Ties: destructive involved -> ask;
statusinvolved ->status; two mutating modes -> first keyword; all zero ->status, two plausible modes in Detection, one concrete Next Step. - Bundle known choices into one
AskUserQuestion(max 4 questions): routing/removal, MCP scope/repair, machine prerequisites, reindex, pin change, or concrete managed-file replacement. New destructive or permission decisions discovered later require a separate explicit gate unless already authorized. Theuv(3.1b) andcoreutils(3.1d) offers remain mutually exclusive; never treat a backup or--yesas user consent.
Refine the provisional mode if Step 1 changes it; report the reason. Print Detection/Before now, before mutations, using the saved Step 1 JSON. Do not repeat the initial PLAN; routine choices need no question.
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 |
independently check prerequisites + MCP -> repair only when needed and approved -> always run project guidance/agents after successful preflight -> reload checkpoint when registration changed or already awaiting reload |
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 |
Exit codes: 0 ok · 1 hard failure (earlier writes may survive) · 2 bad usage · 3 recoverable
precondition/conflict/skipped verification · 4 confirmation required. Read each script's JSON and final
status; exit 1 is never a universal rollback guarantee. Record actual changed/failed/skipped surfaces.
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 installs 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:
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" -eq 0 ] || [ "$RC" -eq 3 ] || [ "$RC" -eq 4 ]; } && echo "✅" || echo "❌ FAILED"
STOP if ❌ — exit 1/2 means nothing was installed; report the raw output.
RC=3is not a failure: a precondition is unmet (nobrew) — print the manual fallbackcurl -LsSf https://astral.sh/uv/install.sh | shwithout running it, and stop.RC=0meansuv/uvxare already onPATHand the pin resolved: skip 3.1b/3.1c and go to 3.1d —RC=0does not mean there is nothing to confirm.
3.1b — ask, only on RC=4. Print the .commands array from the probe JSON verbatim — that is the exact list that would run. On a machine with neither uv nor a timeout binary it is all three lines; the coreutils line is absent when a binary already backs sc_timeout:
brew install uv
brew install coreutils
uvx --from 'semble[mcp]==0.5.5' semble --version
Then one AskUserQuestion: "Run these Homebrew installs now?" — options Install (runs exactly the commands printed above) / Cancel (nothing runs; install stops and the manual fallback curl -LsSf https://astral.sh/uv/install.sh | sh is printed, not run). Say plainly that brew install writes to the machine, outside this project, and that coreutils is the optional half: it only upgrades sc_timeout from its bash watchdog to gtimeout. On Cancel: emit the report with Actions -> skipped: brew install uv (declined) and end the invocation.
Bundle this with known MCP scope/repair choices from Step 1. It covers both installs, so skip 3.1d after 3.1c; a newly discovered required permission/replacement gate follows Step 2 rule 5.
3.1c — apply, only after an explicit Install.
EXECUTE using Bash tool:
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 --yes --json; RC=$?
echo "RC=$RC"
{ [ "$RC" -eq 0 ] || [ "$RC" -eq 3 ]; } && echo "✅" || echo "❌ FAILED"
STOP if ❌ — exit 1/2 means the install failed; report the raw output.
RC=3is still not a failure:brewis missing — print the manual fallbackcurl -LsSf https://astral.sh/uv/install.sh | shwithout running it, and stop. Read.timeout.coreutils.statusfrom this run too and put it inActions(installed-> changed, anything else -> skipped with itsreason). It never changes the verdict. Then go to 3.2 — 3.1d does not run on this path.
3.1d — the coreutils offer, only when 3.1b did not fire (RC=0). Take the FIRST matching row and act; there is no fallthrough:
| # | Condition (from the 3.1a JSON) | Action |
|---|---|---|
| 1 | .timeout.backend != "none" |
Nothing to install. One line: timeout: <backend> <path> — sc_timeout is bound by a binary. -> 3.2 |
| 2 | .timeout.backend == "none" and .timeout.coreutils.status == "needs_confirmation" and .brew.present == true |
Include the optional offer in the bundled question; apply only if approved. -> 3.1e |
| 3 | .timeout.backend == "none" and .brew.present == false |
One line: no brew — gtimeout cannot be installed; sc_timeout keeps its bash watchdog (still bounded). -> 3.2 |
| 4 | anything else (skipped / failed / dry / no-network) |
One line quoting .timeout.coreutils.reason. -> 3.2 |
Row 2 prints the brew install coreutils line from .commands verbatim and asks: "Install coreutils for gtimeout? Optional — sc_timeout is already bounded by a bash watchdog; gtimeout just makes the bound a real binary." — options Install / Skip. Skip is a first-class answer: record Actions -> skipped: brew install coreutils (declined) and go to 3.2.
Rows 1, 3 and 4 never ask and never block. This step cannot fail the install chain: whatever happens, sc_timeout stays bounded.
3.1e — apply coreutils, only after an explicit Install. Soft by construction — with --yes this subcommand exits 0 on every path, including a failed brew.
EXECUTE using Bash tool:
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" coreutils --yes --json; RC=$?
echo "RC=$RC"
[ "$RC" -eq 0 ] && echo "✅" || echo "❌ FAILED"
Not a stop — continue to 3.2 either way.
.timeout.coreutils.statusis the outcome:installed->Actions -> changed,failed/skipped->Actions -> skippedwith itsreason. A ❌ here is reported and nothing else; theinstallchain carries on and the verdict is unaffected.
3.2 Register the MCP server
semble-mcp.sh detect returns exactly one state; the response is fixed. Read references/mcp-and-cache.md for the full matrix before acting on anything other than absent.
| State | Action |
|---|---|
absent |
semble-mcp.sh add --scope user --yes |
correct |
no MCP mutation — add still writes the project checkpoint itself (see below); no extra step |
stale_args |
show the before/after diff, confirm once, semble-mcp.sh repair --yes |
wrong_scope |
use the bundled migrate-to-user/keep decision; repair only if migration was approved |
duplicate |
show every scope repair removes and its user-scoped replacement; migrate only if approved. Keeping another scope skips repair |
upstream_unpinned |
never auto-remove the upstream semble server. Report the conflict; removal is an explicit user choice |
malformed |
STOP. Never write. Report the exact file and parser offset; the user fixes it by hand |
Precedence when several apply: malformed > duplicate > wrong_scope > stale_args > upstream_unpinned > correct > absent.
EXECUTE using Bash tool:
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)"
MCP_ACTION=skip # add for absent/correct; repair only for the approved concrete transition
if [ "$MCP_ACTION" = add ]; then
bash "$SD/scripts/semble-mcp.sh" add --scope user --yes --json; RC=$?
elif [ "$MCP_ACTION" = repair ]; then
bash "$SD/scripts/semble-mcp.sh" repair --yes --json; RC=$?
else echo "skipped: MCP registration kept by user decision"; RC=0; fi
echo "RC=$RC"
[ "$RC" -eq 0 ] && echo "✅" || echo "❌ FAILED"
STOP if ❌ — report surviving state/config changes.
addwrites its checkpoint before config; it triesadd-jsonfirst, thenadd. Both failing can leaveprereq_readyplus failure notes.repairrestores MCP config backups on failure; neither path promises no prior state write. Set MCP_ACTION from the matrix, not by guessing.repairalways targetsuser, removing every existing semble_code scope; keeping local/project registration leaves it unchanged and reports the gap.
correct:addwrites/refreshes the project checkpoint even when registration is unchanged. Absent/prereq_ready/error->awaiting_reload;verifying/ready/disabledretain phase while refreshing the three installer-owned fields (cacheRoot,repoHash,resumePrompt). Itsnotenames the resulting phase; a second project needs no remembered follow-up.MCP_ACTION=skipstops after the report: do not wire the project or claim a reload checkpoint for a declined transition.
3.2b Wire the guidance, permissions and agents — install does this itself
Everything that does not need a live MCP server is wired now, not left hostage to the user coming back for resume. This is the same block as Step 4.2 and every step in it is idempotent, so resume re-runs it harmlessly and repairs any drift it finds.
The three nudge hooks fire at awaiting_reload, with "Verification has not finished (phase=awaiting_reload)
— the first call rebuilds the index and may take minutes". Only enabled:false, phase disabled/error/
prereq_ready, or completed without mcp silence them. Wiring before 3.3 avoids a registered-but-unhooked project.
What is not done here and cannot be: the smoke query (Step 4.1) — the server does not exist for this session — and therefore phase ready.
EXECUTE using Bash tool:
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)"
RC=0
if bash "$SD/scripts/semble-guidance.sh" install --part all --json; then
ARC=0; bash "$SD/scripts/semble-agents.sh" apply --scope project --yes --json || ARC=$?
echo "agents apply RC=$ARC # 3 = reported conflict, not a failure"
{ [ "$ARC" -eq 0 ] || [ "$ARC" -eq 3 ]; } || RC=1
else RC=1; fi
[ "$RC" -eq 0 ] && echo "✅" || echo "❌ FAILED"
STOP if ❌ — report which step failed and what it left behind. The MCP registration from 3.2 stands either way; do not roll it back.
agents applyexiting3is a reported outcome, not a failure. Recordguidanceandagentsin Actions; they are marked complete on the state file in Step 4.3, afterresumehas confirmed them.
skipped: rule: user_modified requires review, not automatic replacement. Read the printed diff -u:
| The diff shows | Do |
|---|---|
corpus / --content / "Not in this corpus" drift, possibly mixed with local additions |
Show the exact file, template diff and local keys/prose that replacement would discard. Preserve it unless that concrete full replacement is explicitly approved; otherwise report the stale fact and proposed targeted correction |
| only local additions (extra frontmatter, project prose) | keep them; record rule: user_modified, kept; name any independently confirmed stale section |
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)"
APPROVED_REPLACEMENT=0 # set to 1 only for this exact approved full-file replacement
if [ "$APPROVED_REPLACEMENT" = 1 ]; then
bash "$SD/scripts/semble-guidance.sh" install --part rule --force --json
else echo "skipped: rule: user_modified, kept; replacement not approved"; fi
--forcereplaces the whole template byte for byte, including frontmatter/local prose; backup:<rule>.bak.<epoch>. A backup does not authorize data loss. Use only the concrete approval above; recordrule: overwritten (backup <path>)when it runs. The same policy covers.sembleignore.The rule is copied verbatim and is never stamped at install time.
doc_type,versionandgenerated_byare baked into the plugin's own template by.claude/scripts/bump-version.shat release; there is nolast_updated, and nothing is substituted here. That is what letssetup-statuscmpthe installed rule against the plugin asset and read them as identical (brewcode/skills/setup-status/references/artifact-metadata.md, mechanisma).The managed/user_modified verdict still ignores the four metadata keys on both sides, so a rule installed by an older version of this skill — which did stamp
last_updated— is not mistaken for a user edit. Such a rule is re-synced to the plugin bytes without--forceand without a backup and reported asrule: re-synced <path> (metadata only)— record it in Actions as a re-sync, not an overwrite.
Size budget — HARD, enforced by test J9. The rule carries
paths: ["**/*"], so it is auto-loaded into every request of every session in the installed repo forever: a word here is not a word, it is a per-prompt tax. Cap: ≤ 45 lines and ≤ 2000 bytes including frontmatter (it stood at 111 lines / 4634 bytes until 5.5.1, ~3x over). Facts only, and each fact in its shortest correct form — a table row, not a paragraph; no worked examples beyond the singlesearchJSON; no rationale, no measurement narrative, no justification of the doctrine. Prose that explains why the split is what it is belongs inreferences/engine-landscape.md, not in the rule. Adding a fact means displacing one, not appending: if the budget is exceeded the answer is to cut, never to raise the cap. The same budget governs any hand-compression of an already-installed rule.
3.3 Reload checkpoint — install STOPS here
The server does not exist for this session. Do not attempt a smoke query, do not claim success, do not continue. Print the Next Step exactly as references/output-contract.md requires:
Reload Claude Code (new session), then run: /brewcode:semble-setup resume
Checkpoint: <abs projectRoot>/.claude/semble/state.json
Then emit the full report and end the invocation.
Step 4 — resume (runs in the NEW session)
Re-run Step 1 first. If .mcp.state is not correct, go back to Step 3 instead of verifying.
4.0 Enter verifying — before anything is verified
ready is reachable only from verifying; there is no awaiting_reload -> ready edge and there must not be one, because phase is what semble-session.mjs and semble-prefetch.mjs read to decide whether to advertise the MCP tools. Writing verifying first is what makes the claim honest: a resume that dies half-way leaves the file saying verification started and never finished, which is the truth, instead of awaiting_reload (as if nothing happened) or ready (a verification that never ran).
phase verifying also self-heals a missing state file — it is created at prereq_ready and walked prereq_ready -> awaiting_reload -> verifying through the legal chain. That is the case where the MCP was already registered at user scope by an earlier project, so add reported unchanged and no checkpoint was ever written.
EXECUTE using Bash tool:
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-state.sh" phase verifying --json; RC=$?
echo "RC=$RC"
[ "$RC" -eq 0 ] && echo "✅" || echo "❌ FAILED"
STOP if ❌ — the only way this refuses is an illegal source phase (
prereq_ready: the MCP was never registered, so go back to Step 3) or an unparseable state file. Report the message verbatim; nothing was written.
4.1 Smoke query — slow on a cold cache
The first run downloads the embedding model. Tell the user that before starting, then run this block with the Bash tool timeout raised to 600000 ms.
smoke shells out to uvx --from 'semble[mcp]==0.5.5' semble search — the CLI, not the MCP server — with the registered content set and shared root, so it builds and queries the same index-code-config-docs variant without needing the server to be loaded in this session. That is why resume is fully scriptable: claude -p "/brewcode:semble-setup resume" in a fresh process completes end to end. The reload checkpoint exists so that Claude gets the two MCP tools, not because anything here is unverifiable before a restart.
EXECUTE using Bash tool:
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)"
OUT="$(bash "$SD/scripts/semble-project.sh" smoke --json)"; RC=$?
printf '%s\n' "$OUT"
STATUS="$(printf '%s' "$OUT" | grep -o '"status":"[a-z]*"' | head -1 | cut -d'"' -f4 || true)"
echo "RC=$RC status=${STATUS:-unknown} # 3 + skipped = verified nothing, not a failure"
if [ "$RC" -eq 0 ]; then echo "✅"
elif [ "$RC" -eq 3 ] && [ "$STATUS" = "skipped" ]; then echo "⏭️ SKIPPED"
else echo "❌ FAILED"; fi
STOP if ❌ — verification failed; the state stays at
errorwith a note. Report the raw output. ⏭️ SKIPPED is not ❌ —RC=3with"status":"skipped"(SEMBLE_NO_NETWORK=1,SEMBLE_DRY_RUN=1, nouvx) means the query never ran: nothing is broken and nothing is proven. Reportskipped (<reason>), setSMOKE_OK=0for Step 4.3 and continue.
4.2 Guidance, permissions and agents
install --part all writes the semble-first rule (never blind-overwriting a user-edited file — the --force rule of Step 3.2b applies here too), writes <repo>/.sembleignore under that same managed-file policy (--part ignore; it keeps generated trees such as .claude/tmp/ and .claude/reports/ out of the index — on this workspace that was 32% of all indexed files), and refreshes the <!-- BEGIN brewcode:semble --> block in CLAUDE.md and then reconciles the search doctrine around it. Scope: root CLAUDE.md only, outside the semble markers. Whole lines only, never a clause, because the confirmed instance mixes a true fact with a false one in one sentence. Whole file backed up to a timestamped .bak first. Idempotent — a second run finds nothing.
| Line matches | Action |
|---|---|
| denies semantic search outright | removed even when the line also scopes the tool — a denial is false regardless of what else it says |
| puts grep/Bash/rg first | removed, unless the same line scopes the tool (see the row below) |
| a search-titled heading the removal leaves empty | removed with it |
| merely mentions a search tool | reported and left untouched |
| scopes the tool to exact identifiers / regexes / paths / exhaustive enumeration (the skill's own correct guidance) | never touched, not even reported — this exemption shields the first-tier match only, never a denial |
Removal is whole lines only, echoed verbatim in the report so re-adding the true half is a paste.
This has no measured effect on tool choice — an A/B on the hypothesis that a contradicting CLAUDE.md line suppresses semble adoption came back negative: with the directive present, 5/14 searches used semble (36%); with it removed, 4/18 (22%); Fisher exact two-sided p = 0.45, n = 32, and the "clean" arm was nominally worse. The scan is kept anyway, on honest ground: a false statement in CLAUDE.md — this repo's own copy said "Search via the Bash tool" — is worth removing because it is false, not because removing it converts anyone.
Then it copies the five hook files into .claude/hooks/ (semble-session.mjs, semble-prefetch.mjs, semble-stats.mjs, semble-reminder.mjs, semble-subagent.mjs), deletes the one retired hook (semble-explore.mjs, superseded for good by semble-subagent.mjs) and merges the six settings entries they wire — SessionStart, UserPromptSubmit, PostToolUse and PostToolUseFailure on the stats matcher, PreToolUse on Bash|Grep, and an unmatched SubagentStart (an absent matcher matches every agent type), each with "timeout": 5 (seconds) — plus the two exact permission entries. The merge is a reconcile, not an append: a v1-shaped settings.json has its two PreToolUse rows collapsed into the single Bash|Grep row and its SubagentStart/Explore row replaced by the unmatched semble-subagent.mjs row — purged by the stale-triple filter, then re-pushed by the want loop, never left side by side — while the retired .claude/semble/.reminder-ts ignore line and marker file are dropped (the restored reminder fires on every eligible search — N = state.reminderEvery, DEF 1, so the counter in .claude/semble/reminder.json throttles nothing until a user raises N; it stays project-global and persisted across sessions, because at N>1 a per-session counter would burn the first N-1 eligible searches of every session in silence; already covered by the directory ignore). The .gitignore line install writes is the DIRECTORY .claude/semble/, not a single marker: state.json and telemetry.jsonl (verbatim commands + distilled prompt text, trimmed at 2 MB) live there too. Both narrower lines it supersedes — .reminder-ts and .prefetch-ts — are stripped by install and by remove. --part ignore also MEASURES the repo (semble-project.sh candidates: byte-identical duplicate trees, and directories or single files carrying a disproportionate share of the corpus, with exact chunk counts once an index exists) and writes what it found into a delimited block at the end of .sembleignore, commented out. Nothing is excluded until the user uncomments a line: a wrong exclusion removes code from the index silently, which is the worse error, so the scan proposes and the user decides. Re-running only ever adds paths it has never proposed, and the block is stripped before the managed-file compare, so an annotated file still reads managed. Measured: .codex/ at 13.3% + RELEASE-NOTES.md at 5.5% here, data/ at 26.5% on a second repo where nothing in the generic template matched anything. Every step is idempotent. Then the project agents are audited and patched — project scope only; global agents are never touched by install/resume.
EXECUTE using Bash tool:
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)"
RC=0
if bash "$SD/scripts/semble-guidance.sh" install --part all --json \
&& bash "$SD/scripts/semble-agents.sh" audit --scope project --json; then
ARC=0; bash "$SD/scripts/semble-agents.sh" apply --scope project --yes --json || ARC=$?
echo "agents apply RC=$ARC # 3 = reported conflict, not a failure"
{ [ "$ARC" -eq 0 ] || [ "$ARC" -eq 3 ]; } || RC=1
else RC=1; fi
[ "$RC" -eq 0 ] && echo "✅" || echo "❌ FAILED"
STOP if ❌ — report which of the three steps failed and what it left behind.
agents applyexiting3is a reported outcome, not a failure: those files are left byte-identical, named in the report, and the run continues (same handling as the recoverable3/4codes elsewhere in this skill). RecordSMOKE_OKfrom Step 4.1 before running 4.3.
4.3 Close the state
Only steps that actually ran may be marked complete. warm and smoke are marked only when Step 4.1 exited 0 with a real result — a "status":"skipped" smoke (SEMBLE_NO_NETWORK=1, SEMBLE_DRY_RUN=1, or no uvx) verified nothing, so those two stay incomplete and the gap is reported in Actions -> skipped.
complete takes the whole list in one call. phase ready is legal here because Step 4.0 already moved the file to verifying — that hop is mandatory, never skipped, and ready has no other legal source. (phase ready self-heals a missing file the same way 4.0 does, so a state file deleted mid-resume still closes out.) The last line prints what the state file actually holds, read back after the writes — never the shell variable.
Both semble-state.sh writes also refresh the installer-owned fields — resumePrompt, cacheRoot, repoHash — from the current constants, so a state file written by an older version of this skill stops advertising a command that no longer exists. enabled, phase and completed are never reset as a side effect.
EXECUTE using Bash tool:
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)"
SMOKE_OK=1 # set to 0 when Step 4.1 reported "status":"skipped" — never guess
STEPS="prereq mcp permissions guidance agents"
[ "$SMOKE_OK" = "1" ] && STEPS="$STEPS warm smoke"
RC=0
bash "$SD/scripts/semble-state.sh" complete $STEPS || RC=1
if [ "$RC" -eq 0 ] && [ "$SMOKE_OK" = 1 ]; then
bash "$SD/scripts/semble-state.sh" phase ready || RC=1
fi
RECORDED="$(bash "$SD/scripts/semble-state.sh" get completed 2>/dev/null || true)"
echo "recorded: ${RECORDED:-NOTHING — no state file was written}"
[ "$RC" -eq 0 ] && echo "✅" || echo "❌ FAILED"
STOP if ❌ — report the illegal phase/unknown step/parser error and actual surviving writes; quote
recorded:. A successful earlier state write is not rolled back by a later failure. Reportrecorded:verbatim — it is the state file read back. Never restate$STEPSas if it were the outcome. WithSMOKE_OK=0, retainverifying; Current Status ispartial - warm and smoke not recorded in state.completed. Status names each missing member:warmalone ->partial - smoke not recorded in state.completed. Next Step:/brewcode:semble-setup resumeonce the skip reason is gone.
Step 5 — The other modes
Use the initial PLAN and Step 1 snapshot, then the selected block; do not reprint the PLAN.
enable / disable
disable deletes nothing: state.enabled=false, phase -> disabled; the MCP registration, the rule, the CLAUDE.md block, the hooks and the whole cache stay. The hooks read enabled and go silent.
EXECUTE using Bash tool:
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)"
ACTION=disable # or: enable
OUT="$(bash "$SD/scripts/semble-project.sh" "$ACTION" --yes --json)"; RC=$?
printf '%s\n' "$OUT"
STATUS="$(printf '%s' "$OUT" | grep -o '"status":"[a-z]*"' | head -1 | cut -d'"' -f4 || true)"
echo "RC=$RC status=${STATUS:-unknown} # 3 + skipped = warm never ran, phase left at verifying"
if [ "$RC" -eq 0 ]; then echo "✅"
elif [ "$RC" -eq 3 ] && [ "$STATUS" = "skipped" ]; then echo "⏭️ SKIPPED"
else echo "❌ FAILED"; fi
STOP if ❌ — report the raw output and surviving state/guidance/config changes; no rollback is implied. ⏭️ SKIPPED is not ❌ —
enablewith a skipped warm (offline, dry run, nouvx) exits3with"status":"skipped": guidance, agents and permissions are done, but no successful search proves readiness, so the phase stops atverifying— deliberately neitherreadynorerror. Report theskipped:lines verbatim and tell the user to re-run/brewcode:semble-setup resumeonce the reason is gone.statusreports the same gap aspartial - warm and smoke not recorded in state.completed.
reindex
There is no per-repo rebuild CLI. Run it without --yes first: it exits 4, changes nothing, and prints the exact <shared root>/<64-hex>/index-code-config-docs variant it would replace, plus a verified pre-0.5.5 combined index only when present. Show those paths and the size, ask the single confirmation permitted by rule 5d, then re-run with --yes. The implementation builds in a private staging root, proves a real query, and swaps only the selected variant; sibling content variants survive. A warm-only request ("warm", "прогрей") uses semble-project.sh warm and deletes nothing.
EXECUTE using Bash tool:
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-project.sh" reindex --json; RC=$?
echo "RC=$RC # 4 = confirmation required, nothing deleted"
[ "$RC" -eq 4 ] || [ "$RC" -eq 0 ] && echo "✅" || echo "❌ FAILED"
STOP if ❌ — the cache variant could not be resolved; never fall back to a broader delete.
After the user confirms, re-run the same command with --yes (Bash tool timeout 600000 ms — the rebuild warms the index).
optimize — read-only by default
Fan out the four audits, then report findings and offer concrete actions. It mutates nothing on its own; each recommendation names the mode that would apply it.
EXECUTE using Bash tool:
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)"
RC=0
bash "$SD/scripts/semble-project.sh" audit --json || RC=1
bash "$SD/scripts/semble-cache.sh" info --json || RC=1
bash "$SD/scripts/semble-guidance.sh" status --json || RC=1
bash "$SD/scripts/semble-agents.sh" audit --scope project --json || RC=1
[ "$RC" -eq 0 ] && echo "✅" || echo "❌ FAILED"
STOP if ❌ — report which audit failed; nothing was changed either way.
upgrade
After successful preflight, run both halves: a correct MCP at approved 0.5.5 is unchanged;
otherwise show the exact pin/scope/args transition and repair only when approved. Do not jump to the
reload checkpoint before the project half. Never @latest or unpinned --from semble[mcp].
Historical 0.5.4 -> 0.5.5: combined bare index is legacy; resume builds the selected variant;
reindex removes the old one only after metadata matching and verified replacement.
EXECUTE using Bash tool:
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)"
IRC=0; bash "$SD/scripts/semble-install.sh" check --json || IRC=$?
MRC=0; bash "$SD/scripts/semble-mcp.sh" detect --json || MRC=$?
echo "prerequisites RC=$IRC | MCP detect RC=$MRC"
if [ "$IRC" -eq 0 ] && [ "$MRC" -eq 0 ]; then echo "✅"
elif [ "$IRC" -eq 3 ] && [ "$MRC" -eq 0 ]; then echo "⏭️ PRECONDITION — uv/uvx absent"
else echo "❌ FAILED"; exit 1; fi
STOP if ❌ — neither probe may hide the other's error; malformed MCP state also stops.
IRC=3-> report prerequisites and use Step 3.1's explicit machine gate before retrying upgrade. Applysemble-mcp.sh repair --yes --jsononly for the approved concrete transition.
The project half is unconditional and runs even when the pin is unchanged — it is the only thing that moves this install's version stamp. Re-run the Step 3.2b block verbatim: semble-guidance.sh install --part all re-copies the rule, .sembleignore and the five live hooks from the plugin's assets (a byte-copy: identical files report unchanged, a file whose only delta is the release stamp takes the metadata-only re-sync branch, a hand-edited one is skipped and diffed to stderr), deletes the retired semble-explore.mjs if the install predates the migration, and re-merges the settings entries and permissions.
EXECUTE the Step 3.2b block, including its user-modified-file policy. It does not depend on pin drift.
After both halves, use Step 3.3 only if registration changed or state remains awaiting_reload;
otherwise report the post-write status and any remaining verification gap.
STOP if ❌ — report which half failed and what it left behind; the MCP registration stands either way.
Without this block
upgradecould never clear astaleverdict.setup-statusreads this install's version out of the frontmatter of.claude/rules/semble-first.md, andsemble-guidance.sh installis the only writer of that file — anupgradethat skipped it reported success and left the stamp exactly where it was, so the nextstatusprintedstaleagain forever.
skipped: rule: user_modifiedcan leave the stamp stale by design: keep the edit and apply Step 3.2b's concrete replacement gate; generic upgrade approval never authorizes--forceor loss of local keys/prose.Stray cache root, installs that ran a pre-5.0.1 prefetch hook. That hook spawned
semble searchwithoutSEMBLE_CACHE_LOCATION, so the child used semble's own default root —~/Library/Caches/sembleon macOS,$XDG_CACHE_HOME/sembleelsewhere — and built a SECOND copy of every index the MCP server had already built undersemble-code(measured: 62 MB here). The hook now passes the registered root, so nothing writes there any more, but the old copy is not deleted by any mode: it sits outside the project and this skill does not remove machine-level directories on its own. Report it and hand the user the command —du -sh ~/Library/Caches/sembleto size it,rm -rf ~/Library/Caches/sembleto drop it. The registered shared root issemble-code; that single missing suffix is the whole bug. A pre-0.5.5semble-docs/RESERVED-FOR-DOCS.txtmarker is only legacy residue;purgeremoves that root only when the marker is its sole entry.
uninstall — four flavours, always an explicit choice
| Flavour | Rule / hooks / CLAUDE.md | MCP | Agent frontmatter | Cache | uv tool |
|---|---|---|---|---|---|
integration |
removed | kept | semble entries removed | kept | kept |
mcp |
kept | removed | kept | kept | kept |
cli |
kept | kept | kept | kept | uv tool uninstall semble |
purge |
removed | removed | removed | shared root removed | confirmation |
state.json and .claude/semble/ are deleted by integration and purge, kept by mcp and cli. Use the one AskUserQuestion here, listing per option exactly what is deleted and what survives.
EXECUTE using Bash tool:
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)"
FLAVOUR=integration # or: mcp | cli — chosen by the user, never guessed
bash "$SD/scripts/semble-remove.sh" "$FLAVOUR" --yes --json && echo "✅" || echo "❌ FAILED"
STOP if ❌ — report exactly which surface was left behind so the user can finish by hand.
purge — destructive
Requires --yes and the literal --confirm-text "purge semble code cache". Run it without those first: it exits 4, deletes nothing, and prints every directory it would remove. Show that list, get the typed confirmation, then run the block. The upstream semble server (if any) is still never removed automatically.
EXECUTE using Bash tool:
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-remove.sh" purge --yes --confirm-text "purge semble code cache" --json && echo "✅" || echo "❌ FAILED"
STOP if ❌ — report what survived. Never retry a purge with a broader target.
Step 6 — Report
Re-run Step 1 (semble-status.sh --section all --json) after the last write and emit the full report from references/output-contract.md — all six sections, in order, empty ones printing none:
Detection · Before (the Step 1 snapshot, not refreshed) · Actions · Verification · Current Status (the post-mutation .verdict) · Next Step (.nextStep).
commands: lists every command actually run, verbatim, including the failures. The uncovered: line is printed on every invocation.
When phase == awaiting_reload, Next Step is exactly the two-line reload text in Step 3.3 — no paraphrase.