Dogfooding `/config-audit` (the router) against the repo, fasit written before any run (docs/router-fasit.local.md, untouched). Every claim below is measured behaviour, not a reading of the source. 1. Bare `<target-path>` inside the step-3 fence is a shell REDIRECTION, not an argument. Measured in zsh: both CLIs failed before starting, no output file was written, and the echoed status was 1 — inside the band the router's own gate calls "continue normally". Quoting makes an unsubstituted placeholder reach argv, so it fails in the CLI where the exit code means something. Swept the whole class: 30 sites across 12 further command files, since a defect in one file is a class until the opposite is measured. New guard: command-placeholder-shell-safety.test.mjs. 2. The orchestrator's exit code was discarded. Two commands on one line share a single trailing `echo $?`, which reports only the last: measured, an orchestrator exit 3 echoed as posture's 0, so the "3 -> stop" gate could never fire. Both statuses are now captured and echoed. 3. "Running 12 configuration scanners" — the orchestrator registers 16. The new test binds the narrated count to the registry so the next scanner added cannot re-stale it silently. 4. The Area Breakdown table hardcoded 7 rows; posture emits 9 quality areas. Token Efficiency (a B on this repo) and Plugin Hygiene never reached the user. Rows added, and the row set is now asserted against lib/scoring.mjs. Label aligned: "MCP Servers" -> "MCP", as posture emits it. 5. Step 6 rendered "the headline line from the humanized stderr scorecard" and forbade deriving a replacement — while step 3 sent posture's stderr to /dev/null, as UX rule 2 requires, and the prose is absent from the JSON payload (measured). The slot could only be improvised. posture's stderr now goes to a file in the session dir, as commands/posture.md already did; the user still never sees raw scanner output. Also: `grep -q -- "--raw"` matched any argument CONTAINING --raw (measured on `--rawdog` and on a path with --raw in it) — anchored to whole arguments. SCOPE_FLAGS renamed SCOPE_FLAG, since zsh does not word-split and the plural invited the M-BUG-45 shape. command-shell-state-shape.test.mjs only recognised line-initial assignments, so it reported the idiomatic `node …; STATUS=$?` capture as never assigned. Widened to assignments after a separator; verified it still fails on a real cross-block reference before trusting it. Suite 1477 -> 1483, frozen v5.0.0 snapshots untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDAwy1ZXRpZxht1wyCeSbF
7.1 KiB
| name | description | argument-hint | allowed-tools | model |
|---|---|---|---|---|
| config-audit:whats-active | Show which plugins, skills, MCP servers, hooks, and CLAUDE.md files are active for a repo — with token estimates | [path] [--json] [--verbose] [--suggest-disables] | Read, Glob, Bash | sonnet |
Config-Audit: What's Active
Show a complete, read-only inventory of everything Claude Code loads for a given repo — plugins, skills, MCP servers, hooks, CLAUDE.md cascade — with source attribution and rough token estimates. Helps identify candidates for disabling without guessing.
UX Rules (MANDATORY — from .claude/rules/ux-rules.md)
- Never show raw JSON or stderr output. Always use
--output-file+2>/dev/null. - Narrate before acting. Tell the user what you're about to do.
- Read, don't dump. Read the JSON file and render formatted tables.
- End with context-sensitive next steps.
Implementation
Step 1: Parse $ARGUMENTS
Split $ARGUMENTS into a path and flags. Path is the first non-flag argument. Default to . (current working directory). Recognized flags:
--json— emit raw JSON instead of rendered tables (power-user mode)--raw— pass-through to the scanner; accepted for CLI surface consistency.whats-activeis an inventory-only output (no findings prose), so--rawis a no-op here, but the flag is still threaded through for uniform behaviour across the toolchain.--verbose— include per-file byte/line detail--suggest-disables— append deterministic disable-candidates + LLM-judgment pass
Step 2: Run the CLI silently
Tell the user: "Reading active configuration for <path>..."
RAW_FLAG=""
if echo "$ARGUMENTS" | grep -q -- "--raw"; then RAW_FLAG="--raw"; fi
# Set each to the flag itself when the user asked for it, otherwise leave empty.
# A placeholder in square brackets does not start with a dash, so the scanner's
# arg loop would take it as the TARGET PATH instead of a flag.
VERBOSE_FLAG="" # --verbose
SUGGEST_FLAG="" # --suggest-disables
node ${CLAUDE_PLUGIN_ROOT}/scanners/whats-active.mjs "<path>" --output-file /tmp/config-audit-whats-active.json $VERBOSE_FLAG $SUGGEST_FLAG $RAW_FLAG >/dev/null 2>/dev/null; echo $?
Exit code handling:
0→ continue3→ tell user: "Couldn't read configuration. Check that the path exists and is a directory." Stop.
Step 3: If --json was requested, cat the file and stop
cat /tmp/config-audit-whats-active.json
Do NOT render tables in JSON mode.
Step 4: Read JSON and render
Use the Read tool on /tmp/config-audit-whats-active.json. Extract:
meta.repoPath,meta.durationMs,meta.gitRoot,meta.projectKeytotals.estimatedTokens.grandTotal(and subtotals)claudeMd.files[]— render cascade tableplugins[]— render plugin tableskills[]— render skills tablemcpServers[]— render MCP table (disabled shown italic)hooks[]— render hooks table
Render as markdown:
**Active configuration for `<repoPath>`** — ~{grandTotal} tokens loaded at startup
{if gitRoot != repoPath: "Git root: `<gitRoot>`"}
{if projectKey: "`.claude.json` project slice: `<projectKey>`"}
### CLAUDE.md cascade ({claudeMd.files.length} files, ~{claudeMd.estimatedTokens} tokens)
| Scope | Path | Bytes | Lines |
|-------|------|-------|-------|
| {scope} | `<path>` | {bytes} | {lines} |
| ... | ... | ... | ... |
### Plugins ({plugins.length}, ~{plugins subtotal} tokens)
| Plugin | Version | Commands | Agents | Skills | Hooks | Rules | Tokens |
|--------|---------|----------|--------|--------|-------|-------|--------|
| {name} | {version} | {commands} | {agents} | {skills} | {hooks} | {rules} | ~{estimatedTokens} |
### Skills ({skills.length}, ~{skills subtotal} tokens)
| Skill | Source | Tokens |
|-------|--------|--------|
| {name} | {source}{if pluginName: ` (${pluginName})`} | ~{estimatedTokens} |
### MCP Servers ({mcpServers.length}, ~{mcpServers subtotal} tokens)
| Server | Source | Status | Command |
|--------|--------|--------|---------|
| {name} | {source} | {enabled ? "enabled" : "*disabled*"} | `{command}` |
### Hooks ({hooks.length}, ~{hooks subtotal} tokens)
| Event | Matcher | Source |
|-------|---------|--------|
| {event} | {matcher or "-"} | {source} |
### Settings cascade
| Scope | Path | Keys |
|-------|------|------|
| user | `<path>` | {keyCount} |
| project | `<path>` | {keyCount} |
| local | `<path>` | {keyCount or "(missing)"} |
### Totals
| Category | Items | Estimated tokens |
|----------|-------|------------------|
| CLAUDE.md | {claudeMdFiles} | ~{claudeMd} |
| Plugins | {plugins} | ~{plugins} |
| Skills | {skills} | ~{skills} |
| MCP servers | {mcpServers} | ~{mcpServers} |
| Hooks | {hooks} | ~{hooks} |
| **Grand total** | — | **~{grandTotal}** |
_Estimates assume ~4 chars/token (Claude ballpark). Real token count varies ±15%._
Step 5: If --verbose, add per-file detail
For each CLAUDE.md file, skill, and plugin, include a nested "Details" list with bytes, lines, and full path.
Step 6: If --suggest-disables, show candidates
First show deterministic signals from suggestDisables.candidates[]:
### Disable candidates (deterministic)
| Kind | Name | Reason | Confidence |
|------|------|--------|------------|
| {kind} | {name} | {reason} | {confidence} |
Then run LLM judgment — check git log --oneline -20 and project manifests (package.json/Cargo.toml/etc.) to propose up to 3 additional candidates. For each candidate, you MUST:
- Name the specific redundancy
- Name the signal the user should check to confirm
Do NOT suggest items you can't name concrete redundancy for. If you can't find 3 strong candidates, return fewer or zero.
Step 7: Cleanup and next steps
rm -f /tmp/config-audit-whats-active.json
### What's next
- **`/config-audit posture`** — check configuration health (A-F grades per area)
- **`/config-audit feature-gap`** — context-aware recommendations for features you aren't using
- **Disable a plugin:** edit `~/.claude/settings.json` → `enabledPlugins` (remove the entry)
- **Disable an MCP server:** edit `~/.claude.json` → `projects.<path>.disabledMcpjsonServers`
- **Re-run with flags:** `/config-audit whats-active --verbose` (details) or `--suggest-disables` (pruning help)
Scope and limits
- Read-only. This command never writes to configuration files — no mkdir, no edits, no deletes.
- Single repo. Scans one repo path per invocation. Cross-repo rollups are out of scope.
- Ballpark token counts. Estimates are deterministic but not calibrated against Claude's tokenizer. Use them to compare categories, not to predict exact billing.
- No runtime queries. We inspect config files only — we do not connect to MCP servers or invoke hooks.
Error handling
| Condition | Action |
|---|---|
| Exit code 3 | Tell user path is invalid, suggest checking path exists |
| JSON parse fails (shouldn't happen — CLI writes valid JSON) | Tell user to re-run, mention this as a bug to report |
| No plugins, no CLAUDE.md, no hooks found | Still render with zeroes; suggest /config-audit feature-gap for setup help |