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
8.4 KiB
| name | description | argument-hint | allowed-tools | model |
|---|---|---|---|---|
| config-audit:tokens | Show ranked token hotspots and prompt-cache pattern findings — what's costing the most per turn and how to reduce it | [path] [--global] | Read, Bash | sonnet |
Config-Audit: Token Hotspots
Show the configuration sources that contribute the most tokens per turn, ranked by estimated tokens, with prompt-cache-aware recommendations for reducing cache misses, schema bloat, and deep import chains.
Complementary to /config-audit whats-active:
whats-active= inventory view (what loads).tokens= action view (what to trim and why).
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:
--global— also include the user-level~/.claude/cascade--no-exclude-cache— include stale~/.claude/plugins/cacheversions in the ranking. By default they are excluded (cache-aware filtering, default ON): the cache holds superseded plugin versions that load on zero turns, and counting them used to crowd the top-10 with dead config. The active version of each plugin (perinstalled_plugins.json) is always kept — only stale versions are filtered. Use--no-exclude-cacheto see the full on-disk walk.--json— emit raw JSON instead of rendered tables (power-user mode; bypasses the humanizer for byte-stable v5.0.0 output)--raw— pass-through to the scanner; produces v5.0.0 verbatim JSON (bypasses the humanizer). Use when piping into v5.0.0-baseline diff tooling.--with-telemetry-recipe— includetelemetry_recipe_pathin the JSON output, pointing toknowledge/cache-telemetry-recipe.md. Use this when you want to verify a structural fix actually improved cache hit rate (manual jq recipe, opt-in)
Step 2: Run the CLI silently
Tell the user: "Analysing token hotspots for <path>..."
Default mode (no --json, no --raw) emits a humanized JSON envelope: each finding carries userImpactCategory, userActionLanguage, and relevanceContext in addition to the v5.0.0 fields. Pass --raw through verbatim if the user requested it.
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 CLI's arg
# loop would take it as the TARGET PATH instead of a flag.
GLOBAL_FLAG="" # --global
CACHE_FLAG="" # --no-exclude-cache
JSON_FLAG="" # --json
TELEMETRY_FLAG="" # --with-telemetry-recipe
node ${CLAUDE_PLUGIN_ROOT}/scanners/token-hotspots-cli.mjs "<path>" --output-file /tmp/config-audit-tokens.json $GLOBAL_FLAG $CACHE_FLAG $JSON_FLAG $TELEMETRY_FLAG $RAW_FLAG >/dev/null 2>/dev/null; echo $?
--json and --with-telemetry-recipe must be threaded here, not just
documented: the CLI is what turns them on. --json/--raw make the payload
byte-stable v5.0.0 (the humanizer is skipped, token-hotspots-cli.mjs:127), and
--with-telemetry-recipe is what adds telemetry_recipe_path. >/dev/null is
required because those two modes also print the payload to stdout even with
--output-file set (token-hotspots-cli.mjs:137).
Exit code handling:
0→ continue3→ tell user: "Couldn't analyse tokens. 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-tokens.json
Do NOT render tables in JSON mode.
Step 4: Read JSON and render
Use the Read tool on /tmp/config-audit-tokens.json. Extract:
total_estimated_tokens— top-line numberhotspots[]— top 10 ranked sources; each carries a load pattern (loadPattern∈ always / on-demand / external, plussurvivesCompaction/derivationConfidence)findings[]— prompt-cache pattern findings; each finding in default mode carries humanizer fields (userImpactCategory,userActionLanguage,relevanceContext) alongside the v5.0.0 fieldscounts— severity breakdown
A hotspot's load pattern matters as much as its size: an always-loaded source (CLAUDE.md, MCP tool schemas) is paid on every turn, an on-demand one (skill body, path-scoped rule) only when invoked/matched, and an external one (hooks, harness-config files like settings.json/.mcp.json) costs no per-turn context tokens at all. A big always-loaded hotspot is the most worth trimming.
The stale plugin-cache finding is different from the rest. All other TOK findings are about per-turn token cost. The "Old plugin versions are sitting on disk" finding (category plugin-cache-hygiene, impact Dead config, --global only) is a pure disk-cleanup item with zero live-context impact — the listed versions are never loaded. Render it as housekeeping, not a token problem: don't conflate its disk bytes with the per-turn token numbers above it.
Render as markdown. Group findings by userImpactCategory (e.g., "Wasted tokens" vs "Configuration mistake") rather than re-deriving severity prose; lead each line with userActionLanguage ("Fix this now", "Fix soon", "Optional cleanup", etc.) so the urgency phrasing stays consistent with the rest of the toolchain. The humanizer already replaced jargon-heavy title/description/recommendation strings with plain-language equivalents — render them verbatim.
**Token hotspots for `<path>`** — ~{total_estimated_tokens} estimated tokens loaded per turn
### Top hotspots (ranked by estimated tokens)
| Rank | Source | Tokens | Load | Recommendations |
|------|--------|--------|------|-----------------|
| {rank} | `{source}` | ~{estimated_tokens} | {loadPattern} | {recommendations joined as `· ` bullets} |
_Load column: **always** (every turn) / **on-demand** (on invoke/match) / **external** (out-of-context). Append `°` when `derivationConfidence` is `inferred`._
### Findings, grouped by impact
{Group findings[] by their userImpactCategory. Within each group, sort by userActionLanguage urgency (Fix this now → Fix soon → Fix when convenient → Optional cleanup → FYI), then render:}
- **{userActionLanguage}** — {title} ({id})
- {description}
- **Fix:** {recommendation}
- _{relevanceContext}_ when not "affects-everyone" (mention the scope so the user knows whether a fix touches shared config or just their machine)
### Severity summary
| Severity | Count |
|----------|-------|
| critical | {counts.critical} |
| high | {counts.high} |
| medium | {counts.medium} |
| low | {counts.low} |
| info | {counts.info} |
_Estimates assume ~4 chars/token (Claude ballpark). Real token count varies ±20%._
Step 5: Cleanup and next steps
rm -f /tmp/config-audit-tokens.json
### What's next
- **`/config-audit whats-active`** — full inventory of what loads (plugins, skills, MCP, hooks)
- **`/config-audit posture`** — overall health scorecard (Token Efficiency is the 8th area)
- **`/config-audit fix`** — auto-fix deterministic issues (where applicable)
- See `knowledge/prompt-cache-patterns.md` for the full pattern catalogue (CA-TOK-001 … 003)
- **Verify cache hit rate after a fix:** rerun with `--with-telemetry-recipe` to surface the path to `knowledge/cache-telemetry-recipe.md` — a copy-paste `jq` recipe that reads cache hit rate from your session transcripts. Opt-in. The TOK scanner is structural; this recipe is the runtime escape hatch.
Scope and limits
- Read-only. Inspects config files; never writes.
- Single repo. Scans one path per invocation.
- Structural only. Hotspots are deterministic byte→token estimates from disk; runtime cache hit-rate is out of scope.
- Heuristic estimates. ~4 chars/token for markdown, ~3.5 for JSON. Real counts vary ±20%.
Error handling
| Condition | Action |
|---|---|
| Exit code 3 | Tell user path is invalid, suggest checking path exists |
| JSON parse fails | Tell user to re-run, mention as a bug to report |
| Empty hotspots | Suggest adding a CLAUDE.md or running /config-audit feature-gap first |