config-audit/commands/whats-active.md
Kjell Tore Guttormsen 0b763f25c1 fix(commands): router dogfood — five seam defects, plus the placeholder class
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
2026-08-09 21:18:05 +02:00

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)

  1. Never show raw JSON or stderr output. Always use --output-file + 2>/dev/null.
  2. Narrate before acting. Tell the user what you're about to do.
  3. Read, don't dump. Read the JSON file and render formatted tables.
  4. 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-active is an inventory-only output (no findings prose), so --raw is 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 → continue
  • 3 → 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.projectKey
  • totals.estimatedTokens.grandTotal (and subtotals)
  • claudeMd.files[] — render cascade table
  • plugins[] — render plugin table
  • skills[] — render skills table
  • mcpServers[] — 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:

  1. Name the specific redundancy
  2. 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