15 KiB
Setup Status
A read-only dashboard over every setup skill in the brewcode suite (brewcode, brewtools, brewdoc). It probes the current project, reports what is installed, stale, half-installed or missing, and hands back the exact command to run for each row.
/brewcode:setup-status # full cross-plugin report
/brewcode:setup-status brewtools # only brewtools rows
/brewcode:setup-status semble-setup # one row, with its detection rule spelled out
/brewcode:setup-status что установлено # free text works (RU + EN); answers in your language
It never runs a setup
Every setup skill is an interactive generator: it fans out subagents, analyses the repo and asks real questions. Two of them in one session degrade each other — the second answers against the first one's stale analysis. So this skill reports and stops, and you run each setup by hand, one per fresh session.
allowed-tools is [Read, Bash, Glob, Grep]. No Write, no Edit, no Agent. Not a policy — a
capability. There is no --run, no --fix, no auto mode.
What it covers
Eleven setups. Everything else in the suite (text-optimize, secrets-scan, agents, rules,
md-to-pdf, …) is a recurring tool with no installed state and never appears in the report.
| Setup | Anchor it looks for | Where its version stamp lives |
|---|---|---|
/brewcode:teams-setup |
.claude/teams/*/team.md |
the | Version | row of team.md's header table |
/brewcode:semble-setup |
.claude/rules/semble-first.md |
frontmatter version: of that rule |
/brewcode:superreview-setup |
.claude/skills/superreview/SKILL.md |
frontmatter version: of the emitted skill — never .template-baseline/ |
/brewtools:task-board-setup |
.claude/features/board.md |
frontmatter version: of the anchor itself — board.md opens with the four-key block |
/brewtools:think-short-setup |
.claude/hooks/think-short-session.mjs (or the ~/.claude twin) |
// brewcode-meta: line after the shebang |
/brewtools:agent-deadline-setup |
.claude/hooks/agent-deadline-guard.mjs (or the twin) |
// brewcode-meta: line after the shebang |
/brewtools:agent-router-setup |
.claude/hooks/agent-router.mjs |
// brewcode-meta: line after the shebang |
/brewtools:manager-setup |
.claude/brewtools/manager/state.json |
top-level "version", falling back to the copied guard's meta line |
/brewdoc:memory-sync-setup |
.claude/skills/memory-sync/SKILL.md |
frontmatter version: of the emitted skill |
/brewdoc:docsync-setup |
.claude/docsync/config.json |
top-level "version" |
/brewtools:agent-return-setup |
.claude/hooks/agent-return-guard.mjs (or the twin) |
// brewcode-meta: line after the shebang, with .claude/agent-return.json's "version" as the stamp that moves on enable/disable |
States
| State | Means |
|---|---|
missing |
anchor and every secondary artifact absent — never installed here. The anchor is decisive: a shared file such as a stray .claude/agents/*.md never counts as evidence |
disabled |
installed, then switched off on purpose (disable) — a config flag flipped, or the entry file parked as <name>.disabled. Reported as inactive with its real version, never as broken and never as missing, and never queued in the run-list |
partial |
some artifacts present, some gone — or a version stamp left as an unresolved {PLACEHOLDER}, meaning the generator never finished substituting |
installed |
stamp equals the installed plugin version, and every byte-copied file still matches its asset |
stale (X.Y.Z -> A.B.C) |
the stamp is a plugin version behind |
stale (legacy stamp) |
the artifact predates the metadata standard and carries no version at all |
stale (bytes drifted) |
right version, wrong bytes — a copied file was hand-edited or never re-copied |
n/a |
that plugin is not installed |
How staleness is decided
Two signals, answering two different questions. No mtime heuristics, no guessing.
| Signal | Question | How |
|---|---|---|
| version stamp (headline) | which plugin version produced what is installed here? | every artifact a setup writes carries version and generated_by, plus last_updated everywhere except .mjs/.sh stamps and doc_type in .md frontmatter only — never in JSON. The field contract lives in references/artifact-metadata.md. Carriers: YAML frontmatter for .md, top-level keys for .json, a brewcode-meta: comment after the shebang for .mjs / .sh, a | Version | header row for team.md |
| owner stamp | did the setup that owns this path actually write it? | generated_by vs the row's own <plugin>:<skill>. A mismatch is partial and names both skills; a missing generated_by beside a real version is stale (legacy stamp) |
cmp vs the plugin asset (corroborating) |
was this file actually re-copied after the plugin update? | byte equality on the copied files — semble's rule + its 5 hooks (not .sembleignore), think-short's 4, agent-deadline's 2, agent-return's 3, agent-router's 1, the manager guard, docsync's 3, two of memory-sync's 3 references, and trace-ops.sh |
Of the contract's four fields this skill reads exactly two. last_updated is not read: it is a date,
and no state in the vocabulary below is defined by one — an old date on an old stamp is the stale
the version already reports, and on a current stamp it only means the release did not touch the file.
doc_type is not read either: it is user-owned, and a re-install deliberately preserves whatever the
repo chose, so a difference is the spec working.
No signal replaces another. A hand-edited hook still carries the stamp it was copied with, so the
stamp cannot see body drift. cmp is meaningless for generated artifacts — an emitted SKILL.md or
team.md is AI-authored per project and never byte-equal to anything — so only the stamp reaches
those. And a file written by the wrong setup can be at the current version AND byte-perfect; only
generated_by catches it.
Stamps land at two different moments, which is why both signals stay honest: byte-copied assets are
stamped at release by bump-version.sh, so the installed copy stays byte-identical to the plugin
asset; generated artifacts are stamped at install, when the generator substitutes the version it
is running as. .template-baseline/ is stamped at neither — it is the raw template, placeholders
unresolved by design, and no version is ever read from it.
Two byte-copied files are nevertheless never byte-STABLE, because the install writes to them again
after the copy — so cmp is wrong for both, and a healthy project is the case that proves it.
| File | Written after the copy by | Signals it keeps |
|---|---|---|
memory-sync's references/hard-sync.md |
the generator's Phase 3 fills two project-specific tables | neither — its stamp is never refreshed either, so the row's version comes from the emitted SKILL.md alone |
the repo-root .sembleignore |
install_candidates appends a measured-candidates block (commented-out proposals, per repo) |
the # brewcode-meta: stamp only — the installer strips that line AND the block before deciding, so the stamp still moves on a template update |
cmp DIFFERS on either is the healthy state, and reporting it as drift is exactly the false alarm
this dashboard exists to avoid. For .sembleignore the alarm was also dangerous: the remedy the row
used to prescribe, semble-guidance.sh --force, backs up and overwrites the user's own uncommented
exclusions. Neither file may appear in a cmp pair.
Two absence signals survive as extra stale triggers: a deployed board with no
.claude/skills/task-spec/ predates the spec+design layer, and a complete team with no
trace-ops.sh is a pre-standard install whose agents cannot trace.
Two rules that stop false alarms
Anchor MISS is decisive, and every secondary must be EXCLUSIVE to its row. A shared file — any
hand-written .claude/agents/*.md, or intent-guard.md, which both superreview-setup and
teams-setup can emit — is not evidence that this setup ran. Listing one made teams-setup report a
broken partial install in every project that merely had an agent file, and jump to the top of the
run-list. teams-setup therefore claims .claude/teams/*/trace.jsonl and trace-ops.sh;
superreview-setup claims none of the shared agent. A setup with no exclusive secondary is decided
by its anchor alone.
disabled is evaluated first — ahead of missing, partial and stale. All eleven setups leave a
real off-switch on disk, each probed directly, in one of two mechanisms:
| Setup | Mechanism | Off-switch | Disabled when |
|---|---|---|---|
| teams | entry-file parking | .claude/agents/<member>.md.disabled |
every roster member of team.md is parked. intent-guard is never parked — it is shared with superreview-setup |
| semble | config flag | .claude/semble/state.json |
.enabled is false |
| superreview | entry-file parking | .claude/skills/superreview/SKILL.md.disabled |
present, SKILL.md gone. references/ stays readable |
| task-board | entry-file parking | any of task-tracker.md, task-board/SKILL.md, task-spec/SKILL.md, rules/tasks.md as .disabled |
every deployed one of the four is parked; .claude/features/** untouched |
| think-short | entry-file parking | hooks dir (project or ~/.claude) |
think-short-prompt.md.disabled present, think-short-prompt.md gone |
| manager | config flag | .claude/brewtools/manager/state.json |
.hard is not true — disarmed wall, not a broken one |
| agent-deadline | config flag | .claude/agent-deadline.json (or the ~/.claude twin) |
"enabled": false or the key absent — the guard reads cfg.enabled !== true, so a key-less config is inert. Opt-in, the inverse of the two rows below |
| agent-return | config flag | .claude/agent-return.json (or the ~/.claude twin) |
"enabled": false or the key absent — the shared module gates on CONFIG.enabled === true, so an absent, key-less or unparsable config injects no contract and sizes no return. Opt-in, same polarity as agent-deadline. A malformed project config falls back to the global one |
| agent-router | config flag | .claude/brewtools/agent-router.json |
"enabled": false. An absent key means enabled — the hook defaults enabled: true and only a literal false flips it |
| memory-sync | entry-file parking | .claude/skills/memory-sync/SKILL.md.disabled |
present, SKILL.md gone. The 3 references and every self-synced hand-edit stay |
| docsync | config flag | .claude/docsync/config.json |
"enabled": false. An absent key means enabled — all three hooks read c.enabled !== false, for back-compat |
Parking works because Claude Code discovers a project agent only as .claude/agents/<n>.md, a
project skill only as <dir>/SKILL.md, and auto-loads a rule only as .claude/rules/*.md.
Withholding that one filename is the whole switch; the body is byte-identical and nothing is deleted.
That is exactly why the order matters. On a parked install the anchor itself is renamed away, so
missing ("never installed here") would fire first and partial ("repair this") second — for
something you switched off on purpose. Both probes and the stamp reader treat a .disabled twin as
present: the stamp is read out of the parked file, so a disabled row reports its real version, not
--. Inversely, semble or docsync at enabled:false has every file byte-identical and must not be
called installed — the mechanism is inert.
A row-1 or row-4 toggle caught halfway (some artifacts live, some parked) is partial, named as
such; re-running the same verb finishes it. And upgrade is never the offer for a disabled row —
task-board-setup, memory-sync and superreview all refuse to operate on a parked install and
say enable first.
Output
A headline count first — how many setups are behind the installed plugin:
4 of 11 setups are behind the installed plugin (2 stale by version, 1 legacy stamp, 1 drifted bytes).
Then one table (skill, state, version, what was found, command), then an ordered run-list. Writing
X.Y.Z for the artifact's own stamp and A.B.C for the installed plugin, the version column reads
A.B.C when current, X.Y.Z -> A.B.C when behind, legacy -> A.B.C when unstamped, and
A.B.C = A.B.C on a bytes-drifted row so nobody hunts for a version difference that does not exist.
No literal version number is ever carried in from this file — the report prints what Phase 0
resolved.
Every command must be able to CLEAR the verdict it follows. Each roster row records the code that
proves its upgrade restamps — the failure this guards against is an upgrade that refreshes
content and leaves the stamp alone, so status says stale, upgrade says success, and the next
status says stale again. A mode is never offered as the fix for its own failure, and where no
mode can clear a finding (a hand-edited semble-first.md, a hand-edited memory-sync reference) the
report says so and hands back the diff to port by hand.
The command is ready to paste, and for stale / partial it carries a concrete fine-tune prompt:
/brewtools:task-board-setup upgrade "retrofit the spec + design layer onto the deployed board, keep every task id"
A bare upgrade with no prompt is not acceptable output. Commands use the canonical modes:
status · install · upgrade · enable · disable · uninstall · purge.
Run order: partial first (broken installs), then stale, then missing. disabled, installed
and n/a rows stay out of the list — a switched-off mechanism is a choice, not a defect. The
run-list always closes with the reason nothing was run for you, so the stance is visible in the
report and not just in the source.
Roster self-check
The roster is ONE table in SKILL.md — adding a future setup is one row, nothing else. On every run
the skill lists the *-setup dirs actually present in the installed plugins and compares. A setup it
does not know about produces a warning above the table, never a silent edit:
WARNING: brewtools:foo-setup is installed but not in this skill's roster — its state was NOT checked.
Files
| Path | What |
|---|---|
SKILL.md |
the roster table (single source of truth), the probe blocks, classification and output contract |
references/artifact-metadata.md |
the artifact metadata + versioning standard — the field contract this skill consumes and never restates |
README.md |
this file |
No scripts and no assets: every probe is a generic inline block fed from the roster, so a new row never needs a code change. The stamp reader dispatches on file extension, so a new carrier of an existing type costs nothing either.
The one thing a new row DOES cost is two literals. The stamp reader's STAMPS heredoc enumerates
all 21 carrier lines for all eleven rows — it is never a sample the model expands, because an expansion
that stops short reports nothing about the rows it skipped and cannot go red. Two exit 1 assertions
hold it to the roster: total lines must be 21, and the scanned plugin's group must be 3 / 15 / 3.
Adding a row means adding its lines and raising both counts in the same edit.
Documentation
Full docs: setup-status