Setup Status
Read-only cross-analysis over every setup skill in the brewcode suite. It answers one question:
what is already set up in this project, what drifted, what was never installed — and hands back the
exact command to run for each row.
Every probe is read-only. No file is created, edited or deleted by the report itself;
allowed-tools carries no Write, no Edit, no Agent. Each probe is an existence check, a cmp,
or a one-line grep. The single exception is Phase 1c's opt-in one-key merge into a settings.json,
which happens only after the user picks it — see Why it does not run the setups.
Prompt contract
Position 1 of $ARGUMENTS is a free-form prompt (RU/EN). This skill has no modes to select —
report is the only behavior — so the prompt carries exactly one optional decision: which plugin or
skill to filter to.
- There are no flags and no destructive path — nothing to strip.
- Extract a plugin name (
brewcode, brewtools, brewdoc) or a skill name (semble-setup,
docsync, task board, ...) from the prompt if one is present; that becomes the filter. Prose
naming neither is unrecognised text, not an error -> full report.
- Empty arguments -> full cross-plugin report. Nothing here is chosen by asking.
AskUserQuestion fires at most ONCE per run, and only for Phase 1c's task-tools offer — the one
outcome-changing choice this skill has. The filter is never asked about.
- Prose that is not a plugin/skill name is still input: extract the name from it, never treat the
first word as a positional filter.
Then print this block ONCE, right before the report (Phase 4) — the only possible mutation is the
Phase 1c env key, and it comes after the report:
PLAN — brewcode:setup-status
INPUT: <arguments verbatim, or "(empty)">
MODE: report — <full cross-plugin | filtered: <plugin/skill>>
SCOPE: <resolved plugin(s)/skill(s) in scope>
DO: <2-5 imperative bullets: resolve plugin roots, probe artifacts, read stamps, classify;
add "offer to enable the task-graph tools" when Phase 1c's verdict is `off`>
RESULT: <the report the user ends up holding; name the settings.json write when one is offered>
Labels are literal; values follow the conversation language.
Why it does not run the setups
Each setup skill is an interactive generator: it fans out subagents, analyses the repo and asks the
user real questions. Running two of them back-to-back in one session degrades both — the context
fills with the first one's analysis, and the second one's questions get answered against stale
findings. So the correct flow is:
This skill reports. The user runs each setup by hand, ideally one per fresh session.
There is no --run, no --fix, no auto mode, and no plan to add one. If the user asks for one, say
this paragraph and print the run-list instead.
One carve-out, and it is not a setup. Phase 1c's CLAUDE_CODE_ENABLE_TODO_TOOLS offer merges
ONE key into ONE settings.json. It spawns no subagent, analyses nothing, generates nothing, and
asks exactly one question — none of the properties that make batching setups harmful apply to it.
The promise above is about the eleven setups and stays whole: this skill still runs none of them.
Outside that single env key, this skill creates, edits and deletes nothing.
Arguments: $ARGUMENTS — empty = full report. A plugin name (brewcode, brewtools, brewdoc)
filters to that plugin's rows. A skill name (semble-setup, docsync, task board) filters to
that one row and prints its detection rule in full. Unrecognised text = full report.
Two questions, two signals
Every setup artifact in a project carries a content_version — the release in which its own content
last actually changed — alongside a version — the plugin release that produced this install. The
field contract (version, content_version, generated_by, last_updated, doc_type, and which
carrier each file type uses) lives in ONE place:
references/artifact-metadata.md. This skill consumes it and
never restates it.
| Signal |
The question it answers |
Read from |
| content_version stamp — the headline |
has this artifact's own content moved since it was installed here? |
the artifact's own content_version field, compared against the plugin's current content_version for that same artifact |
| owner stamp — the third signal |
did the setup that OWNS this path actually write it? |
the artifact's own generated_by, compared against the roster row's <plugin>:<skill> |
cmp vs the plugin asset — corroborating |
was this file actually re-copied after the plugin update? |
byte equality against $BC / $BT / $BD |
version — which plugin release produced this install — stays readable on every row and every
verdict; it answers a real question, just not this skill's headline one, since it bumps on every
plugin release whether or not this artifact's own content changed, which is exactly what was
producing false stale reports before content_version existed. Report content_version first,
version beside it as provenance, the owner when it disagrees, the byte verdict last.
None answers another's question. A hand-edited installed hook still carries the X.Y.Z
content_version stamp it was copied with, so the stamp cannot see body drift — cmp can. And cmp
is meaningless for a generated artifact, which is AI-authored per project and never byte-equal to any
asset — only the stamp reaches those. The owner is orthogonal to both: a file written by the WRONG
setup can be at the current content_version and byte-perfect, and nothing but generated_by will
say so.
Of the five fields in the contract, this skill reads three — content_version (headline),
generated_by, and version (secondary, displayed but never decisive). Why last_updated and
doc_type are deliberately unread is argued once, in Phase 2a; do not add a reader for them without
an actionable verdict to attach.
Two stamping moments, and the difference matters when reading a row:
| Artifact kind |
Stamped |
Consequence |
byte-copied asset (.mjs, .sh, semble-first.md) |
baked at release by bump-version.sh |
the installed copy stays byte-identical to the plugin asset, so cmp keeps working alongside the stamp |
generated artifact (emitted SKILL.md, team.md, config.json) |
substituted at install |
no cmp partner exists; the stamp is the only version signal |
byte-copied asset the install then FILLS (memory-sync's references/hard-sync.md) |
baked at release, then frozen |
it ships as mechanism a and is cpd verbatim, but the generator's own Phase 3 writes project-specific tables into it afterwards, so the installed copy is legitimately never byte-equal and its stamp is never refreshed. Neither signal reaches it — no cmp, no stamp read |
pristine template baseline (.template-baseline/) |
not stamped at all |
it holds the raw template, placeholders unresolved, on purpose — never read a version out of it |
The Roster — SINGLE SOURCE OF TRUTH
Every fact this skill knows about a setup lives in this ONE table. Adding a future setup = adding
ONE row. Nothing else in this file, and no script, encodes the roster.
| # |
Skill (command) |
Plugin |
Anchor artifact |
Secondary artifacts |
content_version stamp — carrier & how to read (headline; version rides along at the same carrier as provenance) |
cmp corroboration |
| 1 |
/brewcode:teams-setup |
brewcode |
.claude/teams/*/team.md |
.claude/teams/*/trace.jsonl, .claude/teams/*/trace-ops.sh |
team.md header table: the | Content Version | X.Y.Z | row of the Field/Value block is the headline, beside | Version | (provenance), | Generated by | and | Last update |. Generated, substituted at install. The Agents table also carries a per-agent trailing Version column — upgrade rewrites only the rows it touches, so a roster may legitimately mix versions; that per-agent column stays version, not content_version — it exists to show which write touched each agent, not content drift. The header's Content Version row is the headline (content_version of the last real change to team.md's own template); if it is behind the plugin's current content_version for that template, say so in found. Confirmed wired — scripts/detect-mode.sh:36 self-locates CONTENT_VERSION off teams-setup/SKILL.md's own brewcode-meta: marker and SKILL.md:417 writes it into the header row; verify-team.sh:166 fails a surviving {CONTENT_VERSION} token. Compared against skills/teams-setup/SKILL.md (the STAMPS source), never against the release version. A team.md with no Content Version row is a pre-5.6 install -> stale (legacy stamp) |
trace-ops.sh vs $BC/skills/teams-setup/scripts/trace-ops.sh (byte-copied, meta line baked at release). Absence signal kept: complete team with no trace-ops.sh = pre-standard install whose agents cannot trace -> stale. Remedy check: upgrade's U4 rewrites the three header rows from the Phase 1 scalars, and C4 directs upgrade to re-cp the tracer ("Re-copy it in UPGRADE too (cp is idempotent) so a team created by an older version gains it") — so both the stamp and this absence clear. Per-agent Version cells move only for agents the run actually touched, which is why a mixed roster stays installed |
| 2 |
/brewcode:semble-setup |
brewcode |
.claude/rules/semble-first.md |
.claude/hooks/semble-session.mjs, semble-prefetch.mjs, semble-stats.mjs, semble-reminder.mjs, semble-subagent.mjs, .claude/semble/state.json |
frontmatter content_version: of .claude/rules/semble-first.md — headline; version: rides along at the same carrier as provenance. It is a pure byte-copy: the template carries baked doc_type: llm + version + generated_by and deliberately no last_updated, and the installer does NOT restamp on copy — so the installed rule stays byte-identical to the template and cmp must read SAME. DIFFERS here means a hand-edit or a rule never re-copied after the plugin update, never a stamping artefact |
all FIVE live hooks vs $BC/skills/semble-setup/assets/*.mjs (semble-explore.mjs is retired for good, superseded by semble-subagent.mjs; if that file is still in .claude/hooks/ the install predates the migration - report it in found and prescribe upgrade, never DIFFERS), plus the rule vs assets/semble-first.md.template — and NOTHING else. The repo-root .sembleignore is byte-copied but never byte-STABLE: it is carved out of the cmp set (its # brewcode-meta: stamp IS still read — see the row-2 carve-out below). .sembleignore sits at the REPO ROOT, not under .claude/, and is absent from installs predating it: report that presence check in found, never as a cmp verdict. .claude/semble/state.json is runtime state — never a stamp source, and its approvedVersion is the semble package version, not ours. Wiring is a separate signal from bytes, and it is the one that catches a v1-shaped repo: .claude/settings.json can list a hook that no longer exists, or list five of the six the current version wants, while every file on disk is byte-current. semble-status.sh reads it (guidance.hooks: retired[], staleEntries, wiredCount/wantCount) and downgrades its own ready to partial for any of the three; report the same way — retired hooks on disk, stale settings entries, or wiredCount < wantCount is stale, prescribing install, no matter how current the stamps are. wantCount: 0 means the counts were not reported at all and is never a defect. This row's setup carries its OWN version signal, and it now asks the same question this dashboard does: semble-guidance.sh computes rule.content_version (the installed rule's frontmatter content_version:) beside rule.templateContentVersion (the plugin template's content_version:, read straight off assets/semble-first.md.template — the same file this row's STAMPS source names), and semble-status.sh:791-800 drops ready -> partial on exactly that pair, with nextStep: Run /brewcode:semble-setup upgrade. version rides along informational-only there, as here. So the two agree by construction; a disagreement is a finding worth naming, not a known gap. A partial from semble-setup status whose nextStep is resume is NOT a version signal — that ladder means warm/smoke unproven (an offline install/enable legitimately exits 3 with "status":"skipped") and it never bears on this row's stamp verdict. Both stamps empty (a pre-5.0 unstamped rule) is deliberately NOT stale there; this dashboard's LEGACY-NONE still covers it. Remedy check: upgrade unconditionally re-runs semble-guidance.sh install --part all (SKILL.md ### upgrade), which is the ONLY writer of the rule's stamp — so it does clear BEHIND. It also cps the five hooks with no user_modified guard (install_hook_files), so hook DIFFERS always clears. semble-first.md is the exception: install_managed re-syncs it only when the sole delta is the metadata block, and a real prose hand-edit is SKIPPED with a diff -u to stderr. Say so in found — that one needs --force, which is not a skill mode. .sembleignore takes the same skip branch inside the installer, but this dashboard never reaches that verdict for it: it is not cmpd at all, so never prescribe --force on .sembleignore — see the carve-out |
| 3 |
/brewcode:superreview-setup |
brewcode |
.claude/skills/superreview/SKILL.md |
.claude/skills/superreview/references/agent-prompt.md, .../report-template.md, .../scope.md, .claude/skills/superreview/.template-baseline/ |
frontmatter content_version: of the emitted .claude/skills/superreview/SKILL.md — headline; version: rides along at the same carrier as provenance — substituted at install from {PLUGIN_VERSION} / {CONTENT_VERSION}. Confirmed wired — generate.sh _content_version() (:207-216) self-locates it off superreview-setup/SKILL.md's own marker and HARD-FAILS rather than stamping a placeholder; :279 substitutes {CONTENT_VERSION}, _restamp_meta and validate (:861) both require a quoted X.Y.Z. Compared against skills/superreview-setup/SKILL.md (the STAMPS source). An emitted SKILL.md with no content_version: is a pre-5.6 install -> stale (legacy stamp). Never read either version out of .template-baseline/ — that dir is the pristine template and its stamps are the unresolved {PLUGIN_VERSION}/{CONTENT_VERSION} tokens by design, which is precisely why upgrade reports IDENTICAL across a version bump instead of a phantom diff. A placeholder in the emitted file means substitution never completed -> partial. Remedy check: upgrade restamps FIVE live files unconditionally — SKILL.md + references/{agent-prompt,report-template,scope}.md + the per-stack ref (generate.sh _restamp_meta loop) — and prints a RESTAMP: line for each even when the delta report says IDENTICAL, which is the normal outcome of a plain version bump. So BEHIND clears |
the 4 baseline copies vs the plugin templates (mapping below) — answers "did the plugin's templates move since this project was tailored", which the emitted stamp cannot. Baseline dir absent -> pre-baseline install, report it in found, do not call it a version. A baseline DIFFERS is NOT cleared by upgrade alone: upgrade only stages the new templates and prints the promote command (rm -rf <baseline> && mv <staging>/.template <baseline> && rm -rf <staging>), which the user runs after porting the delta. Name both halves in the remedy |
| 4 |
/brewtools:task-board-setup |
brewtools |
.claude/features/board.md |
.claude/agents/task-tracker.md, .claude/skills/task-board/SKILL.md, .claude/skills/task-spec/SKILL.md, .claude/rules/tasks.md, .claude/features/PROGRESS.md |
frontmatter content_version: of the anchor itself — headline; version: rides along at the same carrier as provenance — board.md opens with the key block, substituted at install from {PLUGIN_VERSION} / {CONTENT_VERSION}. Nine artifacts carry the block: the anchor + the 5 secondaries above, plus TRACKER.md, INDEX.md, backlog/README.md. TASK_TEMPLATE.md is deliberately UNSTAMPED — its frontmatter is copied into every task card — so its lack of a stamp is never a defect. Confirmed wired — {CONTENT_VERSION} is self-located off task-board-setup/SKILL.md's own line-1 brewcode-meta: marker (stamped by bump-version.sh, kind marker), resolved beside {PLUGIN_VERSION} in the same "Resolving..." bash block, and substituted into all nine templates. Remedy check: upgrade step U5b (references/10-upgrade.md:282) restamps the quartet on all nine unconditionally, and :109/:111 make it run even on the commonest path, where every content row is SKIP and the version stamp is the only thing out of date. So BEHIND clears |
none copied verbatim. Absence signal kept: board present but .claude/skills/task-spec/SKILL.md missing = install predates the spec+design layer -> stale, the documented upgrade path was never run |
| 5 |
/brewtools:think-short-setup |
brewtools |
.claude/hooks/think-short-session.mjs (project) or ~/.claude/hooks/think-short-session.mjs (global) |
in the same dir: think-short-prompt-counter.mjs, think-short-subagent.mjs, think-short-prompt.md; plus a think-short reference in the matching settings.json |
the content_version= token in the // brewcode-meta: line right after the shebang of think-short-session.mjs is the headline (baked at release by bump-version.sh, alongside version= as provenance). think-short-prompt.md carries the same marker pair as an HTML comment on line 1, not frontmatter. There is no JSON carrier on this row. Confirmed wired — all 4 assets are STAMPED_FILES in bump-version.sh, which now stamps content_version into every kind it handles. Remedy check: upgrade re-emits all four assets from the current plugin version, keeping the disabled state (SKILL.md mode table), so both the stamp and any DIFFERS clear |
all 4 vs $BT/skills/think-short-setup/assets/ |
| 6 |
/brewtools:agent-deadline-setup |
brewtools |
.claude/hooks/agent-deadline-guard.mjs (or the ~/.claude twin) |
agent-deadline-cleanup.mjs beside it, .claude/agent-deadline.json, agent-deadline in settings.json |
the content_version= token in agent-deadline-guard.mjs's // brewcode-meta: line (baked at release) is the headline, version= beside it as provenance. .claude/agent-deadline.json carries the same quartet (version, content_version, generated_by, last_updated), copied from assets/INSTALL.md's own header marker at every write — read it too, it is the only carrier that moves when the user runs enable/disable. Confirmed wired — both INSTALL.md (its own content_version= marker, stamped by bump-version.sh) and the JSON write (reads that marker, stamps all 4 keys, verifies with a read-back) are in place. Remedy check: upgrade replays the install at the SAME budget (defaultMinutes/byAgentType/hardStopRatio read back out of the config), re-copying both hooks and rewriting the JSON trio; a disabled setup stays disabled |
both .mjs vs $BT/skills/agent-deadline-setup/assets/ |
| 7 |
/brewtools:agent-router-setup |
brewtools |
.claude/hooks/agent-router.mjs |
.claude/brewtools/agent-router.json, agent-router.mjs referenced in .claude/settings.json |
the content_version= token in agent-router.mjs's // brewcode-meta: line (baked at release) is the headline, version= beside it as provenance; .claude/brewtools/agent-router.json carries the same quartet (version, content_version, generated_by, last_updated), copied from assets/INSTALL.md's own header marker and rewritten by install/upgrade/enable/disable/level — read it too, it is the only carrier that moves on those. Confirmed wired — INSTALL.md (its own content_version= marker, stamped by bump-version.sh) and the JSON write both stamp all 4 keys with a read-back. Remedy check: upgrade reads level back out of the config and replays the install at that level — fresh agent-router.mjs, behavior values preserved, metadata re-stamped — so it never asks a question and never changes a setting |
the hook vs $BT/skills/agent-router-setup/assets/agent-router.mjs |
| 8 |
/brewtools:manager-setup |
brewtools |
.claude/brewtools/manager/state.json |
.claude/brewtools/manager/hardmode-guard.mjs, a hardmode-guard.mjs PreToolUse entry in .claude/settings.local.json |
top-level "content_version" in the RAW state.json is the headline — confirmed wired: writeState() in $BT/hooks/lib/manager-state.mjs stamps it from that module's OWN brewcode-meta: marker (resolveContentVersion(), :91-98) and DELETES the key rather than inventing one when the marker cannot be read (:273-280), so it is compared against hooks/lib/manager-state.mjs (the STAMPS source). When the key is absent, read the RAW file's "version" as the fallback headline — read the file, never resolveState's merged view, or a defaulted key would let an old state file inherit the current version and hide the staleness. writeState() resolves that version from brewtools/.claude-plugin/plugin.json and falls back to that module's OWN baked brewcode-meta line (pluginVersion(), :69-81), never a literal. DEFAULT_STATE deliberately carries no version — it is the answer to "no state file exists", and a version there would be a fake stamp; a project with no state file therefore resolves with no version key at all, which is the missing signal, not a version. Second precedence, only when the primary key is absent: the content_version= token in the copied hardmode-guard.mjs's // brewcode-meta: line (baked at release by bump-version.sh, compared against hooks/hardmode-guard.mjs). The precedence holds because the primary carrier MOVES: upgrade calls writeState('project', {}, cwd) with an EMPTY partial, and writeState stamps version/content_version/generated_by/last_updated on every write while hard, level, mode and every unknown key merge through from the existing file. Reading the guard's meta line first would answer a question the state file already answers more precisely. A second, rarer way the key goes absent, and the reason the fallback is load-bearing rather than historical: when pluginVersion() cannot resolve, writeState DELETES version rather than stamping unknown (:242-252) — including a version inherited from the older file it is merging over. So an absent key on a freshly written state file is a resolver failure on a current install, NOT a legacy one; the guard's line beneath it is the answer, and re-running enable/disable/upgrade will not put it back. references/artifact-metadata.md records why this one writer omits instead of aborting |
the copied guard vs $BT/hooks/hardmode-guard.mjs — install AND upgrade both overwrite it every run, so DIFFERS means exactly "neither was re-run since the plugin update", and upgrade clears it together with the stamp |
| 9 |
/brewdoc:memory-sync-setup |
brewdoc |
.claude/skills/memory-sync/SKILL.md |
references/memory-guide.md, references/agent-audit.md, references/hard-sync.md under it |
frontmatter content_version: of the emitted SKILL.md is the headline — confirmed wired: generate.sh resolve_content_version() (:38-44) self-locates it off memory-sync-setup/SKILL.md's own marker, :84 HARD-FAILS on unknown, and it is written into the frontmatter quartet (:193) and refreshed by restamp (:489). Compared against skills/memory-sync-setup/SKILL.md (the STAMPS source). Read version: (quoted), carrying the brewdoc plugin version, as the fallback headline on a pre-5.6 anchor that carries no content_version:. Empty version: + a last line starting <!-- memory-sync template v = a legacy stamp; empty with no tail stamp = unstamped. What was retired from generate.sh is the hardcoded per-template counter (VERSION=1.0.0), not the VERSION= variable; a literal there would be the defect. A skill-specific surface_files: key TRAILS the four standard ones; ignore it. The two byte-copied references are ahead of the anchor: memory-guide.md and agent-audit.md ARE in bump-version.sh's STAMPED_FILES (kind md), so they already carry a real content_version= token in their line-1 HTML comment — read it there as corroboration, but never promote a secondary's content_version to the row's headline |
the anchor is generated and has no cmp partner; TWO of the three references have one and hard-sync.md does NOT. generate.sh:398 cps all three verbatim (EMITTED_REFS, :38) and they are mechanism-a byte copies stamped at release with a line-1 HTML-comment brewcode-meta: (now including content_version=), so exactly two pair up: .claude/skills/memory-sync/references/memory-guide.md and agent-audit.md vs $BD/skills/memory-sync-setup/references/<same name>. DIFFERS there = a SELF-SYNC hand-edit or references never re-copied after the plugin update. hard-sync.md is byte-copied but never byte-STABLE — carved out of the cmp set and out of the STAMPS heredoc alike; see the carve-out note below. For a deeper diff of the generated anchor, OFFER (never run) generate.sh status, see below |
| 10 |
/brewdoc:docsync-setup |
brewdoc |
.claude/docsync/config.json |
.claude/docsync/state-<session_id>.json, .claude/hooks/docsync-track.mjs, docsync-watch.mjs, docsync-gate.mjs, docsync in .claude/settings.json |
top-level "content_version" of .claude/docsync/config.json is the headline — confirmed wired: install reads docsync-setup/SKILL.md's own line-10 marker into CV and writes it (docsync-setup/SKILL.md:231-257), and upgrade (:390-398) and enable/disable (:468-479) re-stamp the same quartet while leaving threshold_days/exclude verbatim. Compared against skills/docsync-setup/SKILL.md (the STAMPS source). Read "version" as the fallback headline on a pre-5.6 config that carries no content_version key. config.json present but carrying no version key either = a pre-standard install = stale (legacy, unstamped), never missing. Corroborating only: the content_version= token in the // brewcode-meta: line of .claude/hooks/docsync-track.mjs, which catches a hook set that was never re-copied. State is runtime and never carries a stamp — it is now ONE FILE PER SESSION, state-<session_id>.json (docsync-track.mjs:60-64, falling back to state.json when no session id), so probe the glob and never treat several of them, or none, as a defect |
the 3 hooks vs $BD/skills/docsync-setup/assets/ |
| 11 |
/brewtools:agent-return-setup |
brewtools |
.claude/hooks/agent-return-guard.mjs (or the ~/.claude twin) |
agent-return-contract.mjs and agent-return-budget.mjs beside it, .claude/agent-return.json, agent-return in settings.json |
the content_version= token in agent-return-guard.mjs's // brewcode-meta: line (baked at release) is the headline, version= beside it as provenance. .claude/agent-return.json carries the same quartet (version, content_version, generated_by, last_updated) written by every mode that touches it (install, upgrade, enable, disable — INSTALL.md:208, :536), copied from INSTALL.md's own header marker — read it too, it is the only carrier that moves on an enable/disable. Confirmed wired — INSTALL.md's own marker (stamped by bump-version.sh) and the JSON write (stamps all 4 keys, verifies with a read-back) are in place. Only TWO of the three .mjs are ever registered — agent-return-budget.mjs is a shared module imported by both hooks, so it is a file-presence check and must NEVER appear in settings.json; a settings ref to it is a defect. 1/3 or 2/3 hook files is partial, not stale: ESM resolution precedes evaluation, so a missing sibling makes BOTH hooks exit 1 with a hook-error banner on every subagent spawn and return. Remedy check: upgrade (INSTALL.md:455-486) reads passTokens/fileTokens back out of the config and replays the install for that scope — all three files re-copied, both settings entries re-merged, the JSON trio re-stamped; a disabled setup stays disabled |
all THREE .mjs vs $BT/skills/agent-return-setup/assets/ |
$BC / $BT / $BD = the resolved plugin roots from Phase 0.
Rows 8 and 10 share one precedence rule: the setup's own generated JSON is the preferred
carrier, and the byte-copied companion's brewcode-meta: line is the documented fallback for as
long as that JSON goes unstamped. Read the JSON first; fall through silently; never report LEGACY
on a row whose fallback carrier answered.
Row 2 — the .sembleignore carve-out. Like hard-sync.md it is shipped as mechanism a
(STAMPED_FILES, kind marker) and copied verbatim by install_managed, so it LOOKS like a cmp
partner. It is not one. install_ignore_apply (semble-guidance.sh:564-585, reached through the
install_ignore dispatcher at :618-620) runs install_candidates after install_managed
has copied the template, and that function appends a # --- brewcode:semble measured candidates --- block measured from THIS repo (:500-555) — every line commented out, proposals only. So a
HEALTHY, freshly-installed .sembleignore differs from assets/sembleignore.template by
construction, exactly like row 9. Fed to Phase 2b it prints DIFFERS beside a CURRENT stamp,
which Phase 3 rule 9 turns into stale (bytes drifted) — and row 2's only two readings of that
(metadata-only re-sync or prose hand-edit needing --force) are both wrong. --force there
takes a backup and overwrites the user's own uncommented exclusions. Never cmp it, never
prescribe --force for it, never name it in a remedy for byte drift.
Unlike row 9, its STAMP is still read. The installer's comparison strips both the
# brewcode-meta: line and the whole candidates block from both sides (sg_strip_metaline,
:401-411), so a template update still writes through on the metadata-only branch and the stamp
genuinely moves. Phase 2a on .sembleignore is valid; Phase 2b on it is not. The two carve-outs
differ exactly there: hard-sync.md loses BOTH signals, .sembleignore loses only cmp.
unchanged from the installer does NOT mean byte-equal to the template. install_ignore_apply
snapshots the file around both halves and collapses a clean net-zero run — re-sync then a
byte-identical re-append — back to ignore: up to date (:576-584; the dry-run path predicts the
same verdict by simulating into a temp dir, install_ignore_dry:591-616). So a back-to-back re-run
over an unchanged repo reports ignore: up to date — while the file on disk still differs from
assets/sembleignore.template by the whole candidates block. Do not read the installer's
unchanged as licence to cmp the pair; the two answer different questions.
A re-run over a repo whose FILE SET moved does report changed, because the scan re-measures and
the block genuinely changes. That is real byte movement, still not a drift signal, and still not
something cmp against the template can express.
Row 9 — why the dashboard parses instead of executing. bash "$BD/skills/memory-sync-setup/scripts/generate.sh" status
prints a richer verdict (STAMP_FORMAT, META_*, DRIFTS, VERDICT) and is genuinely read-only —
its status_report only reads and echoes, and resolve_root only cds. It is still the wrong
default here: it re-scans the whole memory surface (real work, not a probe), it exit 1s when the
cwd is not a repo root, and it returns a private verdict vocabulary that would have to be
translated into this skill's states — which puts roster knowledge inside a foreign script and
breaks "no script encodes the roster". So: read the frontmatter with the Phase 2a block like every
other row, and put generate.sh status in the Command column as something the USER may run
when row 9 is not green.
Row 9 — the hard-sync.md carve-out. It is shipped as mechanism a (STAMPED_FILES row,
kind md) and cpd verbatim, so it LOOKS like a cmp partner. It is not one, and listing it as
one made this dashboard report drift on every correctly-installed project. The emitted
references/hard-sync.md carries two of the generator's twelve BLOCK placeholders —
{PATHS_PRECISION_TABLE} and {OBVIOUS_VS_DOMAIN_TABLE} — which the generator's own Phase 3
fills with project-specific tables via Edit, and generate.sh validate FAILS while either is
unfilled. So a HEALTHY install differs from the plugin source by construction: cmp DIFFERS is
the success state, not drift, and no mode can ever clear it without destroying the user's tables.
Its baked stamp is frozen at the release that emitted it for the same reason — refresh_refs
refuses to re-copy a file whose content differs — so feeding it to Phase 2a would print a
permanent BEHIND. Never cmp it, never stamp-read it, never name it in a remedy. The row's
version signal is the anchor's frontmatter and nothing else.
The two references that DO pair up are cleared by upgrade, and only by it.
generate.sh:514 refresh_refs() (reached from restamp, which is the mandatory last step of
upgrade — memory-sync-setup/SKILL.md mode upgrade step 3) re-copies a reference only when
that is provably lossless, and prints which case fired:
| Line |
Condition |
Clears a cmp DIFFERS? |
REF OK: |
already byte-identical |
n/a — the row was SAME |
REF RESTORED: |
the file was absent; nothing local to lose |
yes |
REF RECOPIED: |
the ONLY difference is the brewcode-meta: release-stamp line |
yes — this is the plugin-update case |
REF DIFFERS: |
real content differs (hand-edit, filled BLOCKs, prose moved in a newer release) |
no — the file is left untouched, by design |
A REF DIFFERS: on memory-guide.md or agent-audit.md after an upgrade therefore means a
genuine local edit: report stale (bytes drifted), say upgrade will NOT overwrite it, and tell
the user to diff against $BD/skills/memory-sync-setup/references/<name> and port by hand. Do not
prescribe a mode that would silently discard their edit — there is none.
Every secondary must be EXCLUSIVE to its row. A shared artifact — .claude/agents/*.md,
.claude/agents/intent-guard.md (superreview and teams both emit it), any hand-written agent —
is not evidence that THIS setup ran, and listing one makes Phase 3 rule 3 report a partial
install in every project that merely has an agent file. If a setup owns no exclusive secondary,
leave the cell empty and let the anchor decide.
Row 3 baseline mapping (the only non-obvious cmp pairing):
| Project baseline copy |
Plugin template |
.claude/skills/superreview/.template-baseline/SKILL.md |
$BC/skills/superreview-setup/references/SKILL.md.template |
.../.template-baseline/references/agent-prompt.md |
$BC/skills/superreview-setup/references/agent-prompt.md |
.../.template-baseline/references/report-template.md |
$BC/skills/superreview-setup/references/report-template.md |
.../.template-baseline/references/scope.md |
$BC/skills/superreview-setup/references/scope.md.template |
.../.template-baseline/references/<stack>.md |
$BC/skills/superreview-setup/references/<stack>.md |
<stack> is the one per-stack reference the install picked — go.md, java-kotlin.md,
python.md or typescript-react.md. It is substituted and baseline-copied exactly like the
other four; only its filename varies. Absent from an install that predates it: report the other
four and say the per-stack ref is missing, never DIFFERS.
NOT setups — never appear in the report
Recurring tools, not one-time installs. They are correct to run repeatedly and have no installed
state to report: brewcode:agents, skills, rules, convention, e2e;
brewtools:text-optimize, text-human, secrets-scan, ssh, deploy, plugin-update,
provider-switch; brewdoc:md-to-pdf, my-claude, publish.
Phase 0 — Resolve plugin roots
A plugin that is not installed makes every one of its rows n/a — never missing. Do not assume
all four are present.
The plugin version is the number every stamp is compared against, so resolve it with the SAME
precedence the brewcode SessionStart hook uses (brewcode/hooks/session-start.mjs, parseVersion):
the cache directory basename first, .claude-plugin/plugin.json .version second. One
precedence, two consumers — do not invent a third.
EXECUTE using Bash tool:
for p in brewcode brewdoc brewtools brewui; do
r=$({ ls -d "$HOME/.claude/plugins/cache/claude-brewcode/$p"/*/ 2>/dev/null || true; } | sort -V | tail -1 | sed 's:/*$::')
if [ -n "$r" ] && [ -d "$r" ]; then
v=$(basename "$r")
case "$v" in
[0-9]*.[0-9]*.[0-9]*) : ;;
*) v=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$r/.claude-plugin/plugin.json" 2>/dev/null | head -1 | sed 's/.*:[[:space:]]*"//; s/"$//' || true) ;;
esac
echo "$p ROOT=$r VERSION=${v:-unknown}"
else echo "$p ROOT=none VERSION=none"; fi
done
echo "PROJECT=$PWD"
test -d "$PWD/.claude" && echo "DOTCLAUDE=yes" || echo "DOTCLAUDE=no"
echo "OK"
STOP if FAILED — cannot resolve the cache; report it and stop rather than calling everything
missing. All four roots none also means stop: nothing installed, nothing to report.
Bind $BC, $BT, $BD and their versions from the output. DOTCLAUDE=no is a legitimate answer —
every row is missing, print the table anyway. VERSION=unknown on a plugin that IS installed means
no comparison is possible for its rows: report installed (plugin version unresolved) and say why.
Phase 1 — Probe artifacts
One generic block, fed from the roster. Paste the anchor + secondary paths of the rows in scope
(after the $ARGUMENTS filter) into the heredoc — relative to the project root, one per line, a
trailing / for a directory, globs allowed.
EXECUTE using Bash tool:
cd "$PWD" || exit 1
while IFS= read -r rel; do
[ -z "$rel" ] && continue
case "$rel" in
*/) [ -d "$rel" ] && echo "DIR $rel" || echo "MISS $rel" ;;
*"*"*) n=$({ find . -path "./$rel" 2>/dev/null || true; } | wc -l | tr -d ' '); n=${n:-0}; [ "$n" -gt 0 ] && echo "GLOB $rel ($n)" || echo "MISS $rel" ;;
*) if [ -f "$rel" ]; then echo "FILE $rel"
elif [ -f "$rel.disabled" ]; then echo "PARK $rel.disabled"
else echo "MISS $rel"; fi ;;
esac
done <<'PATHS'
.claude/teams/*/team.md
.claude/teams/*/trace-ops.sh
.claude/rules/semble-first.md
PATHS
echo "OK"
PARK is present, not missing. Five setups disable by renaming their entry file to
<name>.disabled (Phase 1b), and the body stays byte-identical — so a PARK line never feeds
Phase 3's missing or partial rules, and the file it names is still a readable version stamp.
Then the `setting
…(truncated)
1---2name: setup-status3description: Reports which brewcode setup skills are installed, stale, partial or missing in this project, compares the version each installed artifact was generated under against the installed plugin, and prints the exact command to run for each. Triggers: setup status, what is installed, what version is installed, что установлено.4---56<instructions>78# Setup Status910Read-only cross-analysis over every **setup skill** in the brewcode suite. It answers one question:11*what is already set up in this project, what drifted, what was never installed* — and hands back the12exact command to run for each row.1314**Every probe is read-only.** No file is created, edited or deleted by the report itself;15`allowed-tools` carries no `Write`, no `Edit`, no `Agent`. Each probe is an existence check, a `cmp`,16or a one-line grep. The single exception is Phase 1c's opt-in one-key merge into a `settings.json`,17which happens only after the user picks it — see *Why it does not run the setups*.1819## Prompt contract2021Position 1 of `$ARGUMENTS` is a **free-form prompt** (RU/EN). This skill has no modes to select —22report is the only behavior — so the prompt carries exactly one optional decision: which plugin or23skill to filter to.24251. There are no flags and no destructive path — nothing to strip.262. Extract a plugin name (`brewcode`, `brewtools`, `brewdoc`) or a skill name (`semble-setup`,27 `docsync`, `task board`, ...) from the prompt if one is present; that becomes the filter. Prose28 naming neither is unrecognised text, not an error -> full report.293. Empty arguments -> full cross-plugin report. Nothing here is chosen by asking.304. `AskUserQuestion` fires at most ONCE per run, and only for Phase 1c's task-tools offer — the one31 outcome-changing choice this skill has. The filter is never asked about.325. Prose that is not a plugin/skill name is still input: extract the name from it, never treat the33 first word as a positional filter.3435Then print this block ONCE, right before the report (Phase 4) — the only possible mutation is the36Phase 1c env key, and it comes after the report:3738```39PLAN — brewcode:setup-status40INPUT: <arguments verbatim, or "(empty)">41MODE: report — <full cross-plugin | filtered: <plugin/skill>>42SCOPE: <resolved plugin(s)/skill(s) in scope>43DO: <2-5 imperative bullets: resolve plugin roots, probe artifacts, read stamps, classify;44 add "offer to enable the task-graph tools" when Phase 1c's verdict is `off`>45RESULT: <the report the user ends up holding; name the settings.json write when one is offered>46```4748Labels are literal; values follow the conversation language.4950## Why it does not run the setups5152Each setup skill is an interactive generator: it fans out subagents, analyses the repo and asks the53user real questions. Running two of them back-to-back in one session degrades both — the context54fills with the first one's analysis, and the second one's questions get answered against stale55findings. So the correct flow is:5657> **This skill reports. The user runs each setup by hand, ideally one per fresh session.**5859There is no `--run`, no `--fix`, no auto mode, and no plan to add one. If the user asks for one, say60this paragraph and print the run-list instead.6162> **One carve-out, and it is not a setup.** Phase 1c's `CLAUDE_CODE_ENABLE_TODO_TOOLS` offer merges63> ONE key into ONE `settings.json`. It spawns no subagent, analyses nothing, generates nothing, and64> asks exactly one question — none of the properties that make batching setups harmful apply to it.65> The promise above is about the eleven setups and stays whole: this skill still runs none of them.6667Outside that single env key, this skill creates, edits and deletes nothing.6869**Arguments:** `$ARGUMENTS` — empty = full report. A plugin name (`brewcode`, `brewtools`, `brewdoc`)70filters to that plugin's rows. A skill name (`semble-setup`, `docsync`, `task board`) filters to71that one row and prints its detection rule in full. Unrecognised text = full report.7273---7475## Two questions, two signals7677Every setup artifact in a project carries a `content_version` — the release in which its own content78last actually changed — alongside a `version` — the plugin release that produced this install. The79field contract (`version`, `content_version`, `generated_by`, `last_updated`, `doc_type`, and which80carrier each file type uses) lives in ONE place:81[`references/artifact-metadata.md`](references/artifact-metadata.md). This skill consumes it and82never restates it.8384| Signal | The question it answers | Read from |85|--------|------------------------|-----------|86| **content_version stamp** — the headline | *has this artifact's own content moved since it was installed here?* | the artifact's own `content_version` field, compared against the plugin's current `content_version` for that same artifact |87| **owner stamp** — the third signal | *did the setup that OWNS this path actually write it?* | the artifact's own `generated_by`, compared against the roster row's `<plugin>:<skill>` |88| **`cmp` vs the plugin asset** — corroborating | *was this file actually re-copied after the plugin update?* | byte equality against `$BC` / `$BT` / `$BD` |8990`version` — which plugin release produced this install — stays readable on every row and every91verdict; it answers a real question, just not this skill's headline one, since it bumps on every92plugin release whether or not this artifact's own content changed, which is exactly what was93producing false `stale` reports before `content_version` existed. Report `content_version` first,94`version` beside it as provenance, the owner when it disagrees, the byte verdict last.9596None answers another's question. A hand-edited installed hook still carries the `X.Y.Z`97`content_version` stamp it was copied with, so the stamp cannot see body drift — `cmp` can. And `cmp`98is meaningless for a generated artifact, which is AI-authored per project and never byte-equal to any99asset — only the stamp reaches those. The owner is orthogonal to both: a file written by the WRONG100setup can be at the current `content_version` and byte-perfect, and nothing but `generated_by` will101say so.102103Of the five fields in the contract, this skill reads three — `content_version` (headline),104`generated_by`, and `version` (secondary, displayed but never decisive). Why `last_updated` and105`doc_type` are deliberately unread is argued once, in Phase 2a; do not add a reader for them without106an actionable verdict to attach.107108Two stamping moments, and the difference matters when reading a row:109110| Artifact kind | Stamped | Consequence |111|---------------|---------|-------------|112| byte-copied asset (`.mjs`, `.sh`, `semble-first.md`) | **baked at release** by `bump-version.sh` | the installed copy stays byte-identical to the plugin asset, so `cmp` keeps working alongside the stamp |113| generated artifact (emitted `SKILL.md`, `team.md`, `config.json`) | **substituted at install** | no `cmp` partner exists; the stamp is the only version signal |114| byte-copied asset the install then FILLS (`memory-sync`'s `references/hard-sync.md`) | **baked at release**, then frozen | it ships as mechanism `a` and is `cp`d verbatim, but the generator's own Phase 3 writes project-specific tables into it afterwards, so the installed copy is legitimately never byte-equal and its stamp is never refreshed. **Neither signal reaches it** — no `cmp`, no stamp read |115| pristine template baseline (`.template-baseline/`) | **not stamped at all** | it holds the raw template, placeholders unresolved, on purpose — never read a version out of it |116117---118119## The Roster — SINGLE SOURCE OF TRUTH120121Every fact this skill knows about a setup lives in this ONE table. Adding a future setup = adding122ONE row. Nothing else in this file, and no script, encodes the roster.123124| # | Skill (command) | Plugin | Anchor artifact | Secondary artifacts | `content_version` stamp — carrier & how to read (headline; `version` rides along at the same carrier as provenance) | `cmp` corroboration |125|---|-----------------|--------|-----------------|---------------------|--------------------------------------|---------------------|126| 1 | `/brewcode:teams-setup` | brewcode | `.claude/teams/*/team.md` | `.claude/teams/*/trace.jsonl`, `.claude/teams/*/trace-ops.sh` | `team.md` header table: the `\| Content Version \| X.Y.Z \|` row of the `Field/Value` block is the headline, beside `\| Version \|` (provenance), `\| Generated by \|` and `\| Last update \|`. Generated, substituted at install. **The Agents table also carries a per-agent trailing `Version` column** — `upgrade` rewrites only the rows it touches, so a roster may legitimately mix versions; that per-agent column stays `version`, not `content_version` — it exists to show which write touched each agent, not content drift. The header's `Content Version` row is the headline (content_version of the last real change to `team.md`'s own template); if it is behind the plugin's current `content_version` for that template, say so in *found*. **Confirmed wired** — `scripts/detect-mode.sh:36` self-locates `CONTENT_VERSION` off `teams-setup/SKILL.md`'s own `brewcode-meta:` marker and `SKILL.md:417` writes it into the header row; `verify-team.sh:166` fails a surviving `{CONTENT_VERSION}` token. Compared against `skills/teams-setup/SKILL.md` (the `STAMPS` source), never against the release version. A `team.md` with no `Content Version` row is a pre-5.6 install -> `stale (legacy stamp)` | `trace-ops.sh` vs `$BC/skills/teams-setup/scripts/trace-ops.sh` (byte-copied, meta line baked at release). **Absence signal kept:** complete team with no `trace-ops.sh` = pre-standard install whose agents cannot trace -> `stale`. **Remedy check:** `upgrade`'s U4 rewrites the three header rows from the Phase 1 scalars, and C4 directs `upgrade` to re-`cp` the tracer ("Re-copy it in UPGRADE too (`cp` is idempotent) so a team created by an older version gains it") — so both the stamp and this absence clear. Per-agent `Version` cells move only for agents the run actually touched, which is why a mixed roster stays `installed` |127| 2 | `/brewcode:semble-setup` | brewcode | `.claude/rules/semble-first.md` | `.claude/hooks/semble-session.mjs`, `semble-prefetch.mjs`, `semble-stats.mjs`, `semble-reminder.mjs`, `semble-subagent.mjs`, `.claude/semble/state.json` | frontmatter `content_version:` of `.claude/rules/semble-first.md` — headline; `version:` rides along at the same carrier as provenance. It is a **pure byte-copy**: the template carries baked `doc_type: llm` + `version` + `generated_by` and deliberately no `last_updated`, and the installer does NOT restamp on copy — so the installed rule stays byte-identical to the template and `cmp` must read `SAME`. `DIFFERS` here means a hand-edit or a rule never re-copied after the plugin update, never a stamping artefact | **all FIVE live hooks** vs `$BC/skills/semble-setup/assets/*.mjs` (`semble-explore.mjs` is retired for good, superseded by `semble-subagent.mjs`; if that file is still in `.claude/hooks/` the install predates the migration - report it in *found* and prescribe `upgrade`, never `DIFFERS`), plus the rule vs `assets/semble-first.md.template` — and NOTHING else. **The repo-root `.sembleignore` is byte-copied but never byte-STABLE: it is carved out of the `cmp` set** (its `# brewcode-meta:` stamp IS still read — see the row-2 carve-out below). `.sembleignore` sits at the REPO ROOT, not under `.claude/`, and is absent from installs predating it: report that presence check in *found*, never as a `cmp` verdict. `.claude/semble/state.json` is runtime state — never a stamp source, and its `approvedVersion` is the semble **package** version, not ours. **Wiring is a separate signal from bytes, and it is the one that catches a v1-shaped repo:** `.claude/settings.json` can list a hook that no longer exists, or list five of the six the current version wants, while every file on disk is byte-current. `semble-status.sh` reads it (`guidance.hooks`: `retired[]`, `staleEntries`, `wiredCount`/`wantCount`) and downgrades its own `ready` to `partial` for any of the three; report the same way — retired hooks on disk, stale settings entries, or `wiredCount < wantCount` is `stale`, prescribing `install`, no matter how current the stamps are. `wantCount: 0` means the counts were not reported at all and is never a defect. **This row's setup carries its OWN version signal, and it now asks the same question this dashboard does:** `semble-guidance.sh` computes `rule.content_version` (the installed rule's frontmatter `content_version:`) beside `rule.templateContentVersion` (the plugin template's `content_version:`, read straight off `assets/semble-first.md.template` — the same file this row's `STAMPS` source names), and `semble-status.sh:791-800` drops `ready` -> `partial` on exactly that pair, with `nextStep: Run /brewcode:semble-setup upgrade`. `version` rides along informational-only there, as here. So the two agree by construction; a disagreement is a finding worth naming, not a known gap. **A `partial` from `semble-setup status` whose `nextStep` is `resume` is NOT a version signal** — that ladder means warm/smoke unproven (an offline `install`/`enable` legitimately exits 3 with `"status":"skipped"`) and it never bears on this row's stamp verdict. Both stamps empty (a pre-5.0 unstamped rule) is deliberately NOT stale there; this dashboard's `LEGACY-NONE` still covers it. **Remedy check:** `upgrade` unconditionally re-runs `semble-guidance.sh install --part all` (SKILL.md `### upgrade`), which is the ONLY writer of the rule's stamp — so it does clear `BEHIND`. It also `cp`s the five hooks with no user_modified guard (`install_hook_files`), so hook `DIFFERS` always clears. **`semble-first.md` is the exception:** `install_managed` re-syncs it only when the sole delta is the metadata block, and a real prose hand-edit is SKIPPED with a `diff -u` to stderr. Say so in *found* — that one needs `--force`, which is not a skill mode. `.sembleignore` takes the same skip branch inside the installer, but this dashboard never reaches that verdict for it: it is not `cmp`d at all, so **never prescribe `--force` on `.sembleignore`** — see the carve-out |128| 3 | `/brewcode:superreview-setup` | brewcode | `.claude/skills/superreview/SKILL.md` | `.claude/skills/superreview/references/agent-prompt.md`, `.../report-template.md`, `.../scope.md`, `.claude/skills/superreview/.template-baseline/` | frontmatter `content_version:` of the **emitted** `.claude/skills/superreview/SKILL.md` — headline; `version:` rides along at the same carrier as provenance — substituted at install from `{PLUGIN_VERSION}` / `{CONTENT_VERSION}`. **Confirmed wired** — `generate.sh` `_content_version()` (`:207-216`) self-locates it off `superreview-setup/SKILL.md`'s own marker and HARD-FAILS rather than stamping a placeholder; `:279` substitutes `{CONTENT_VERSION}`, `_restamp_meta` and `validate` (`:861`) both require a quoted `X.Y.Z`. Compared against `skills/superreview-setup/SKILL.md` (the `STAMPS` source). An emitted `SKILL.md` with no `content_version:` is a pre-5.6 install -> `stale (legacy stamp)`. **Never read either version out of `.template-baseline/`** — that dir is the pristine template and its stamps are the unresolved `{PLUGIN_VERSION}`/`{CONTENT_VERSION}` tokens by design, which is precisely why `upgrade` reports IDENTICAL across a version bump instead of a phantom diff. A placeholder in the *emitted* file means substitution never completed -> `partial`. **Remedy check:** `upgrade` restamps FIVE live files unconditionally — `SKILL.md` + `references/{agent-prompt,report-template,scope}.md` + the per-stack ref (`generate.sh` `_restamp_meta` loop) — and prints a `RESTAMP:` line for each even when the delta report says `IDENTICAL`, which is the normal outcome of a plain version bump. So `BEHIND` clears | the 4 baseline copies vs the plugin templates (mapping below) — answers "did the plugin's templates move since this project was tailored", which the emitted stamp cannot. Baseline dir absent -> pre-baseline install, report it in *found*, do not call it a version. **A baseline `DIFFERS` is NOT cleared by `upgrade` alone:** `upgrade` only stages the new templates and prints the promote command (`rm -rf <baseline> && mv <staging>/.template <baseline> && rm -rf <staging>`), which the user runs after porting the delta. Name both halves in the remedy |129| 4 | `/brewtools:task-board-setup` | brewtools | `.claude/features/board.md` | `.claude/agents/task-tracker.md`, `.claude/skills/task-board/SKILL.md`, `.claude/skills/task-spec/SKILL.md`, `.claude/rules/tasks.md`, `.claude/features/PROGRESS.md` | frontmatter `content_version:` of the anchor itself — headline; `version:` rides along at the same carrier as provenance — `board.md` opens with the key block, substituted at install from `{PLUGIN_VERSION}` / `{CONTENT_VERSION}`. Nine artifacts carry the block: the anchor + the 5 secondaries above, plus `TRACKER.md`, `INDEX.md`, `backlog/README.md`. `TASK_TEMPLATE.md` is deliberately UNSTAMPED — its frontmatter is copied into every task card — so its lack of a stamp is never a defect. **Confirmed wired** — `{CONTENT_VERSION}` is self-located off `task-board-setup/SKILL.md`'s own line-1 `brewcode-meta:` marker (stamped by `bump-version.sh`, kind `marker`), resolved beside `{PLUGIN_VERSION}` in the same "Resolving..." bash block, and substituted into all nine templates. **Remedy check:** `upgrade` step `U5b` (`references/10-upgrade.md:282`) restamps the quartet on all nine unconditionally, and `:109`/`:111` make it run even on the commonest path, where every content row is `SKIP` and the version stamp is the only thing out of date. So `BEHIND` clears | none copied verbatim. **Absence signal kept:** board present but `.claude/skills/task-spec/SKILL.md` missing = install predates the spec+design layer -> `stale`, the documented upgrade path was never run |130| 5 | `/brewtools:think-short-setup` | brewtools | `.claude/hooks/think-short-session.mjs` (project) or `~/.claude/hooks/think-short-session.mjs` (global) | in the same dir: `think-short-prompt-counter.mjs`, `think-short-subagent.mjs`, `think-short-prompt.md`; plus a `think-short` reference in the matching `settings.json` | the `content_version=` token in the `// brewcode-meta:` line right after the shebang of `think-short-session.mjs` is the headline (baked at release by `bump-version.sh`, alongside `version=` as provenance). `think-short-prompt.md` carries the same marker pair as an HTML comment on line 1, not frontmatter. There is no JSON carrier on this row. **Confirmed wired** — all 4 assets are `STAMPED_FILES` in `bump-version.sh`, which now stamps `content_version` into every kind it handles. **Remedy check:** `upgrade` re-emits all four assets from the current plugin version, keeping the disabled state (SKILL.md mode table), so both the stamp and any `DIFFERS` clear | all 4 vs `$BT/skills/think-short-setup/assets/` |131| 6 | `/brewtools:agent-deadline-setup` | brewtools | `.claude/hooks/agent-deadline-guard.mjs` (or the `~/.claude` twin) | `agent-deadline-cleanup.mjs` beside it, `.claude/agent-deadline.json`, `agent-deadline` in `settings.json` | the `content_version=` token in `agent-deadline-guard.mjs`'s `// brewcode-meta:` line (baked at release) is the headline, `version=` beside it as provenance. `.claude/agent-deadline.json` carries the same quartet (`version`, `content_version`, `generated_by`, `last_updated`), copied from `assets/INSTALL.md`'s own header marker at every write — read it too, it is the only carrier that moves when the user runs `enable`/`disable`. **Confirmed wired** — both `INSTALL.md` (its own `content_version=` marker, stamped by `bump-version.sh`) and the JSON write (reads that marker, stamps all 4 keys, verifies with a read-back) are in place. **Remedy check:** `upgrade` replays the install at the SAME budget (`defaultMinutes`/`byAgentType`/`hardStopRatio` read back out of the config), re-copying both hooks and rewriting the JSON trio; a disabled setup stays disabled | both `.mjs` vs `$BT/skills/agent-deadline-setup/assets/` |132| 7 | `/brewtools:agent-router-setup` | brewtools | `.claude/hooks/agent-router.mjs` | `.claude/brewtools/agent-router.json`, `agent-router.mjs` referenced in `.claude/settings.json` | the `content_version=` token in `agent-router.mjs`'s `// brewcode-meta:` line (baked at release) is the headline, `version=` beside it as provenance; `.claude/brewtools/agent-router.json` carries the same quartet (`version`, `content_version`, `generated_by`, `last_updated`), copied from `assets/INSTALL.md`'s own header marker and rewritten by `install`/`upgrade`/`enable`/`disable`/`level` — read it too, it is the only carrier that moves on those. **Confirmed wired** — `INSTALL.md` (its own `content_version=` marker, stamped by `bump-version.sh`) and the JSON write both stamp all 4 keys with a read-back. **Remedy check:** `upgrade` reads `level` back out of the config and replays the install at that level — fresh `agent-router.mjs`, behavior values preserved, metadata re-stamped — so it never asks a question and never changes a setting | the hook vs `$BT/skills/agent-router-setup/assets/agent-router.mjs` |133| 8 | `/brewtools:manager-setup` | brewtools | `.claude/brewtools/manager/state.json` | `.claude/brewtools/manager/hardmode-guard.mjs`, a `hardmode-guard.mjs` PreToolUse entry in `.claude/settings.local.json` | top-level `"content_version"` in the RAW `state.json` is the headline — **confirmed wired**: `writeState()` in `$BT/hooks/lib/manager-state.mjs` stamps it from that module's OWN `brewcode-meta:` marker (`resolveContentVersion()`, `:91-98`) and DELETES the key rather than inventing one when the marker cannot be read (`:273-280`), so it is compared against `hooks/lib/manager-state.mjs` (the `STAMPS` source). When the key is absent, read the RAW file's `"version"` as the fallback headline — read the file, never `resolveState`'s merged view, or a defaulted key would let an old state file inherit the current version and hide the staleness. `writeState()` resolves that `version` from `brewtools/.claude-plugin/plugin.json` and falls back to that module's OWN baked `brewcode-meta` line (`pluginVersion()`, `:69-81`), never a literal. `DEFAULT_STATE` deliberately carries no version — it is the answer to "no state file exists", and a version there would be a fake stamp; a project with no state file therefore resolves with no version key at all, which is the `missing` signal, not a version. Second precedence, only when the primary key is absent: the `content_version=` token in the copied `hardmode-guard.mjs`'s `// brewcode-meta:` line (baked at release by `bump-version.sh`, compared against `hooks/hardmode-guard.mjs`). **The precedence holds because the primary carrier MOVES:** `upgrade` calls `writeState('project', {}, cwd)` with an EMPTY partial, and `writeState` stamps `version`/`content_version`/`generated_by`/`last_updated` on every write while `hard`, `level`, `mode` and every unknown key merge through from the existing file. Reading the guard's meta line first would answer a question the state file already answers more precisely. **A second, rarer way the key goes absent, and the reason the fallback is load-bearing rather than historical:** when `pluginVersion()` cannot resolve, `writeState` DELETES `version` rather than stamping `unknown` (`:242-252`) — including a `version` inherited from the older file it is merging over. So an absent key on a freshly written state file is a resolver failure on a current install, NOT a legacy one; the guard's line beneath it is the answer, and re-running `enable`/`disable`/`upgrade` will not put it back. `references/artifact-metadata.md` records why this one writer omits instead of aborting | the copied guard vs `$BT/hooks/hardmode-guard.mjs` — `install` AND `upgrade` both overwrite it every run, so `DIFFERS` means exactly "neither was re-run since the plugin update", and `upgrade` clears it together with the stamp |134| 9 | `/brewdoc:memory-sync-setup` | brewdoc | `.claude/skills/memory-sync/SKILL.md` | `references/memory-guide.md`, `references/agent-audit.md`, `references/hard-sync.md` under it | frontmatter `content_version:` of the emitted `SKILL.md` is the headline — **confirmed wired**: `generate.sh` `resolve_content_version()` (`:38-44`) self-locates it off `memory-sync-setup/SKILL.md`'s own marker, `:84` HARD-FAILS on `unknown`, and it is written into the frontmatter quartet (`:193`) and refreshed by `restamp` (`:489`). Compared against `skills/memory-sync-setup/SKILL.md` (the `STAMPS` source). Read `version:` (quoted), carrying the **brewdoc plugin version**, as the fallback headline on a pre-5.6 anchor that carries no `content_version:`. Empty `version:` + a last line starting `<!-- memory-sync template v` = a **legacy stamp**; empty with no tail stamp = **unstamped**. What was retired from `generate.sh` is the hardcoded per-template counter (`VERSION=1.0.0`), not the `VERSION=` variable; a literal there would be the defect. A skill-specific `surface_files:` key TRAILS the four standard ones; ignore it. **The two byte-copied references are ahead of the anchor:** `memory-guide.md` and `agent-audit.md` ARE in `bump-version.sh`'s `STAMPED_FILES` (kind `md`), so they already carry a real `content_version=` token in their line-1 HTML comment — read it there as corroboration, but never promote a secondary's content_version to the row's headline | **the anchor is generated and has no `cmp` partner; TWO of the three references have one and `hard-sync.md` does NOT.** `generate.sh:398` `cp`s all three verbatim (`EMITTED_REFS`, `:38`) and they are mechanism-`a` byte copies stamped at release with a line-1 HTML-comment `brewcode-meta:` (now including `content_version=`), so exactly two pair up: `.claude/skills/memory-sync/references/memory-guide.md` and `agent-audit.md` vs `$BD/skills/memory-sync-setup/references/<same name>`. `DIFFERS` there = a SELF-SYNC hand-edit or references never re-copied after the plugin update. **`hard-sync.md` is byte-copied but never byte-STABLE — carved out of the `cmp` set and out of the `STAMPS` heredoc alike**; see the carve-out note below. For a deeper diff of the generated anchor, OFFER (never run) `generate.sh status`, see below |135| 10 | `/brewdoc:docsync-setup` | brewdoc | `.claude/docsync/config.json` | `.claude/docsync/state-<session_id>.json`, `.claude/hooks/docsync-track.mjs`, `docsync-watch.mjs`, `docsync-gate.mjs`, `docsync` in `.claude/settings.json` | top-level `"content_version"` of `.claude/docsync/config.json` is the headline — **confirmed wired**: install reads `docsync-setup/SKILL.md`'s own line-10 marker into `CV` and writes it (`docsync-setup/SKILL.md:231-257`), and `upgrade` (`:390-398`) and `enable`/`disable` (`:468-479`) re-stamp the same quartet while leaving `threshold_days`/`exclude` verbatim. Compared against `skills/docsync-setup/SKILL.md` (the `STAMPS` source). Read `"version"` as the fallback headline on a pre-5.6 config that carries no `content_version` key. **`config.json` present but carrying no `version` key either = a pre-standard install = `stale (legacy, unstamped)`**, never `missing`. Corroborating only: the `content_version=` token in the `// brewcode-meta:` line of `.claude/hooks/docsync-track.mjs`, which catches a hook set that was never re-copied. State is runtime and never carries a stamp — it is now ONE FILE PER SESSION, `state-<session_id>.json` (`docsync-track.mjs:60-64`, falling back to `state.json` when no session id), so probe the glob and never treat several of them, or none, as a defect | the 3 hooks vs `$BD/skills/docsync-setup/assets/` |136| 11 | `/brewtools:agent-return-setup` | brewtools | `.claude/hooks/agent-return-guard.mjs` (or the `~/.claude` twin) | `agent-return-contract.mjs` and `agent-return-budget.mjs` beside it, `.claude/agent-return.json`, `agent-return` in `settings.json` | the `content_version=` token in `agent-return-guard.mjs`'s `// brewcode-meta:` line (baked at release) is the headline, `version=` beside it as provenance. `.claude/agent-return.json` carries the same quartet (`version`, `content_version`, `generated_by`, `last_updated`) written by every mode that touches it (`install`, `upgrade`, `enable`, `disable` — `INSTALL.md:208`, `:536`), copied from `INSTALL.md`'s own header marker — read it too, it is the only carrier that moves on an `enable`/`disable`. **Confirmed wired** — `INSTALL.md`'s own marker (stamped by `bump-version.sh`) and the JSON write (stamps all 4 keys, verifies with a read-back) are in place. **Only TWO of the three `.mjs` are ever registered** — `agent-return-budget.mjs` is a shared module imported by both hooks, so it is a file-presence check and must NEVER appear in `settings.json`; a settings ref to it is a defect. `1/3` or `2/3` hook files is `partial`, not `stale`: ESM resolution precedes evaluation, so a missing sibling makes BOTH hooks exit 1 with a hook-error banner on every subagent spawn and return. **Remedy check:** `upgrade` (`INSTALL.md:455-486`) reads `passTokens`/`fileTokens` back out of the config and replays the install for that scope — all three files re-copied, both settings entries re-merged, the JSON trio re-stamped; a disabled setup stays disabled | all THREE `.mjs` vs `$BT/skills/agent-return-setup/assets/` |137138`$BC` / `$BT` / `$BD` = the resolved plugin roots from Phase 0.139140> **Rows 8 and 10 share one precedence rule:** the setup's own generated JSON is the preferred141> carrier, and the byte-copied companion's `brewcode-meta:` line is the documented fallback for as142> long as that JSON goes unstamped. Read the JSON first; fall through silently; never report `LEGACY`143> on a row whose fallback carrier answered.144145> **Row 2 — the `.sembleignore` carve-out.** Like `hard-sync.md` it is shipped as mechanism `a`146> (`STAMPED_FILES`, kind `marker`) and copied verbatim by `install_managed`, so it LOOKS like a `cmp`147> partner. It is not one. `install_ignore_apply` (`semble-guidance.sh:564-585`, reached through the148> `install_ignore` dispatcher at `:618-620`) runs `install_candidates` **after** `install_managed`149> has copied the template, and that function appends a `# --- brewcode:semble measured candidates150> ---` block measured from THIS repo (`:500-555`) — every line commented out, proposals only. So a151> HEALTHY, freshly-installed `.sembleignore` differs from `assets/sembleignore.template` **by152> construction**, exactly like row 9. Fed to Phase 2b it prints `DIFFERS` beside a `CURRENT` stamp,153> which Phase 3 rule 9 turns into `stale (bytes drifted)` — and row 2's only two readings of that154> (`metadata-only re-sync` or `prose hand-edit needing --force`) are both wrong. **`--force` there155> takes a backup and overwrites the user's own uncommented exclusions.** Never `cmp` it, never156> prescribe `--force` for it, never name it in a remedy for byte drift.157>158> **Unlike row 9, its STAMP is still read.** The installer's comparison strips both the159> `# brewcode-meta:` line and the whole candidates block from both sides (`sg_strip_metaline`,160> `:401-411`), so a template update still writes through on the metadata-only branch and the stamp161> genuinely moves. Phase 2a on `.sembleignore` is valid; Phase 2b on it is not. The two carve-outs162> differ exactly there: `hard-sync.md` loses BOTH signals, `.sembleignore` loses only `cmp`.163>164> **`unchanged` from the installer does NOT mean byte-equal to the template.** `install_ignore_apply`165> snapshots the file around both halves and collapses a clean net-zero run — re-sync then a166> byte-identical re-append — back to `ignore: up to date` (`:576-584`; the dry-run path predicts the167> same verdict by simulating into a temp dir, `install_ignore_dry:591-616`). So a back-to-back re-run168> over an unchanged repo reports `ignore: up to date` — while the file on disk still differs from169> `assets/sembleignore.template` by the whole candidates block. Do not read the installer's170> `unchanged` as licence to `cmp` the pair; the two answer different questions.171>172> A re-run over a repo whose FILE SET moved does report `changed`, because the scan re-measures and173> the block genuinely changes. That is real byte movement, still not a drift signal, and still not174> something `cmp` against the template can express.175176> **Row 9 — why the dashboard parses instead of executing.** `bash "$BD/skills/memory-sync-setup/scripts/generate.sh" status`177> prints a richer verdict (`STAMP_FORMAT`, `META_*`, `DRIFTS`, `VERDICT`) and is genuinely read-only —178> its `status_report` only reads and echoes, and `resolve_root` only `cd`s. It is still the wrong179> default here: it re-scans the whole memory surface (real work, not a probe), it `exit 1`s when the180> cwd is not a repo root, and it returns a private verdict vocabulary that would have to be181> translated into this skill's states — which puts roster knowledge inside a foreign script and182> breaks "no script encodes the roster". So: read the frontmatter with the Phase 2a block like every183> other row, and put `generate.sh status` in the **Command** column as something the USER may run184> when row 9 is not green.185186> **Row 9 — the `hard-sync.md` carve-out.** It is shipped as mechanism `a` (`STAMPED_FILES` row,187> kind `md`) and `cp`d verbatim, so it LOOKS like a `cmp` partner. It is not one, and listing it as188> one made this dashboard report drift on every correctly-installed project. The emitted189> `references/hard-sync.md` carries two of the generator's twelve BLOCK placeholders —190> `{PATHS_PRECISION_TABLE}` and `{OBVIOUS_VS_DOMAIN_TABLE}` — which the generator's own Phase 3191> fills with project-specific tables via `Edit`, and `generate.sh validate` FAILS while either is192> unfilled. So a HEALTHY install differs from the plugin source by construction: `cmp` `DIFFERS` is193> the success state, not drift, and no mode can ever clear it without destroying the user's tables.194> Its baked stamp is frozen at the release that emitted it for the same reason — `refresh_refs`195> refuses to re-copy a file whose content differs — so feeding it to Phase 2a would print a196> permanent `BEHIND`. **Never `cmp` it, never stamp-read it, never name it in a remedy.** The row's197> version signal is the anchor's frontmatter and nothing else.198>199> The two references that DO pair up are cleared by `upgrade`, and only by it.200> `generate.sh:514 refresh_refs()` (reached from `restamp`, which is the mandatory last step of201> `upgrade` — `memory-sync-setup/SKILL.md` mode `upgrade` step 3) re-copies a reference **only when202> that is provably lossless**, and prints which case fired:203>204> | Line | Condition | Clears a `cmp` `DIFFERS`? |205> |------|-----------|---------------------------|206> | `REF OK:` | already byte-identical | n/a — the row was `SAME` |207> | `REF RESTORED:` | the file was absent; nothing local to lose | yes |208> | `REF RECOPIED:` | the ONLY difference is the `brewcode-meta:` release-stamp line | yes — this is the plugin-update case |209> | `REF DIFFERS:` | real content differs (hand-edit, filled BLOCKs, prose moved in a newer release) | **no — the file is left untouched, by design** |210>211> A `REF DIFFERS:` on `memory-guide.md` or `agent-audit.md` after an `upgrade` therefore means a212> genuine local edit: report `stale (bytes drifted)`, say `upgrade` will NOT overwrite it, and tell213> the user to diff against `$BD/skills/memory-sync-setup/references/<name>` and port by hand. Do not214> prescribe a mode that would silently discard their edit — there is none.215216> **Every secondary must be EXCLUSIVE to its row.** A shared artifact — `.claude/agents/*.md`,217> `.claude/agents/intent-guard.md` (superreview *and* teams both emit it), any hand-written agent —218> is not evidence that THIS setup ran, and listing one makes Phase 3 rule 3 report a `partial`219> install in every project that merely has an agent file. If a setup owns no exclusive secondary,220> leave the cell empty and let the anchor decide.221222**Row 3 baseline mapping** (the only non-obvious `cmp` pairing):223224| Project baseline copy | Plugin template |225|-----------------------|-----------------|226| `.claude/skills/superreview/.template-baseline/SKILL.md` | `$BC/skills/superreview-setup/references/SKILL.md.template` |227| `.../.template-baseline/references/agent-prompt.md` | `$BC/skills/superreview-setup/references/agent-prompt.md` |228| `.../.template-baseline/references/report-template.md` | `$BC/skills/superreview-setup/references/report-template.md` |229| `.../.template-baseline/references/scope.md` | `$BC/skills/superreview-setup/references/scope.md.template` |230| `.../.template-baseline/references/<stack>.md` | `$BC/skills/superreview-setup/references/<stack>.md` |231232`<stack>` is the one per-stack reference the install picked — `go.md`, `java-kotlin.md`,233`python.md` or `typescript-react.md`. It is substituted and baseline-copied exactly like the234other four; only its filename varies. Absent from an install that predates it: report the other235four and say the per-stack ref is missing, never `DIFFERS`.236237### NOT setups — never appear in the report238239Recurring tools, not one-time installs. They are correct to run repeatedly and have no installed240state to report: `brewcode:agents`, `skills`, `rules`, `convention`, `e2e`;241`brewtools:text-optimize`, `text-human`, `secrets-scan`, `ssh`, `deploy`, `plugin-update`,242`provider-switch`; `brewdoc:md-to-pdf`, `my-claude`, `publish`.243244---245246## Phase 0 — Resolve plugin roots247248A plugin that is not installed makes every one of its rows `n/a` — never `missing`. Do not assume249all four are present.250251The plugin version is the number every stamp is compared against, so resolve it with the SAME252precedence the brewcode SessionStart hook uses (`brewcode/hooks/session-start.mjs`, `parseVersion`):253**the cache directory basename first, `.claude-plugin/plugin.json` `.version` second.** One254precedence, two consumers — do not invent a third.255256**EXECUTE** using Bash tool:257258```bash259for p in brewcode brewdoc brewtools brewui; do260 r=$({ ls -d "$HOME/.claude/plugins/cache/claude-brewcode/$p"/*/ 2>/dev/null || true; } | sort -V | tail -1 | sed 's:/*$::')261 if [ -n "$r" ] && [ -d "$r" ]; then262 v=$(basename "$r")263 case "$v" in264 [0-9]*.[0-9]*.[0-9]*) : ;;265 *) v=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$r/.claude-plugin/plugin.json" 2>/dev/null | head -1 | sed 's/.*:[[:space:]]*"//; s/"$//' || true) ;;266 esac267 echo "$p ROOT=$r VERSION=${v:-unknown}"268 else echo "$p ROOT=none VERSION=none"; fi269done270echo "PROJECT=$PWD"271test -d "$PWD/.claude" && echo "DOTCLAUDE=yes" || echo "DOTCLAUDE=no"272echo "OK"273```274275> **STOP if FAILED** — cannot resolve the cache; report it and stop rather than calling everything276> `missing`. All four roots `none` also means stop: nothing installed, nothing to report.277278Bind `$BC`, `$BT`, `$BD` and their versions from the output. `DOTCLAUDE=no` is a legitimate answer —279every row is `missing`, print the table anyway. `VERSION=unknown` on a plugin that IS installed means280no comparison is possible for its rows: report `installed (plugin version unresolved)` and say why.281282## Phase 1 — Probe artifacts283284One generic block, fed from the roster. Paste the anchor + secondary paths of the rows in scope285(after the `$ARGUMENTS` filter) into the heredoc — relative to the project root, one per line, a286trailing `/` for a directory, globs allowed.287288**EXECUTE** using Bash tool:289290```bash291cd "$PWD" || exit 1292while IFS= read -r rel; do293 [ -z "$rel" ] && continue294 case "$rel" in295 */) [ -d "$rel" ] && echo "DIR $rel" || echo "MISS $rel" ;;296 *"*"*) n=$({ find . -path "./$rel" 2>/dev/null || true; } | wc -l | tr -d ' '); n=${n:-0}; [ "$n" -gt 0 ] && echo "GLOB $rel ($n)" || echo "MISS $rel" ;;297 *) if [ -f "$rel" ]; then echo "FILE $rel"298 elif [ -f "$rel.disabled" ]; then echo "PARK $rel.disabled"299 else echo "MISS $rel"; fi ;;300 esac301done <<'PATHS'302.claude/teams/*/team.md303.claude/teams/*/trace-ops.sh304.claude/rules/semble-first.md305PATHS306echo "OK"307```308309`PARK` is **present**, not missing. Five setups `disable` by renaming their entry file to310`<name>.disabled` (Phase 1b), and the body stays byte-identical — so a `PARK` line never feeds311Phase 3's `missing` or `partial` rules, and the file it names is still a readable version stamp.312313Then the `setting314315…(truncated)