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
218 lines
12 KiB
Markdown
218 lines
12 KiB
Markdown
---
|
|
name: config-audit
|
|
description: Claude Code Configuration Intelligence - audit, analyze, and optimize your configuration
|
|
argument-hint: "[posture|tokens|manifest|feature-gap|optimize|fix|rollback|plan|implement|help|discover|analyze|interview|drift|plugin-health|whats-active|campaign|knowledge-refresh|status|cleanup]"
|
|
allowed-tools: Read, Write, Glob, Grep, Bash, Agent, AskUserQuestion
|
|
model: opus
|
|
---
|
|
|
|
# Config-Audit: Claude Code Configuration Intelligence
|
|
|
|
Analyze, report on, and optimize your Claude Code configuration.
|
|
|
|
## Router Logic
|
|
|
|
If a subcommand is provided, route to it:
|
|
- `posture` → `/config-audit:posture`
|
|
- `tokens` → `/config-audit:tokens`
|
|
- `manifest` → `/config-audit:manifest`
|
|
- `feature-gap` → `/config-audit:feature-gap`
|
|
- `optimize` → `/config-audit:optimize`
|
|
- `fix` → `/config-audit:fix`
|
|
- `rollback` → `/config-audit:rollback`
|
|
- `plan` → `/config-audit:plan`
|
|
- `implement` → `/config-audit:implement`
|
|
- `help` → `/config-audit:help`
|
|
- `discover` → `/config-audit:discover`
|
|
- `analyze` → `/config-audit:analyze`
|
|
- `interview` → `/config-audit:interview`
|
|
- `drift` → `/config-audit:drift`
|
|
- `plugin-health` → `/config-audit:plugin-health`
|
|
- `whats-active` → `/config-audit:whats-active`
|
|
- `campaign` → `/config-audit:campaign`
|
|
- `knowledge-refresh` → `/config-audit:knowledge-refresh`
|
|
- `status` → `/config-audit:status`
|
|
- `cleanup` → `/config-audit:cleanup`
|
|
|
|
If a scope override is provided (`current`, `repo`, `home`, `full`), use it as the scope type (see Scope Resolution below).
|
|
|
|
If no subcommand and no scope override: **run the default audit** (see below).
|
|
|
|
## UX Rules (MANDATORY — apply to every step)
|
|
|
|
1. **Narrate before acting.** Before each step, tell the user what you're about to do and why, in plain language.
|
|
2. **Never show raw output.** All scanner Bash commands MUST use `--output-file <path>` AND `2>/dev/null`. The user should NEVER see JSON, stderr progress lines, or exit codes.
|
|
3. **Handle exit codes silently.** Append `; echo $?` to scanner commands. Exit codes 0/1/2 are all expected (PASS/WARNING/FAIL). Only exit code 3 is a real error — tell user: "Scanner encountered an unexpected error. Try `/config-audit posture` for a quick check instead."
|
|
4. **Explain, don't dump.** When presenting findings, add plain-language context. "Grade B" alone means nothing — say "Grade B — your CLAUDE.md files are well-structured with minor improvements possible."
|
|
5. **Separate signal from noise.** If findings exist in `tests/fixtures/` or `examples/` directories, count them separately and exclude from the main count: "Found 37 findings (66 additional in test fixtures, excluded)."
|
|
6. **Context-sensitive next steps.** Don't just list commands — explain what each does and why the user might want it based on their specific results.
|
|
|
|
## Default Audit (no arguments)
|
|
|
|
### Step 1: Auto-detect scope and greet the user
|
|
|
|
If the user provided a scope override (`/config-audit full`, `/config-audit repo`, etc.), use that.
|
|
|
|
Otherwise, auto-detect:
|
|
1. Run `git rev-parse --show-toplevel 2>/dev/null` via Bash
|
|
2. If it succeeds and pwd is inside the repo → **repo** scope (use the git root path)
|
|
3. If pwd is `$HOME` → **home** scope
|
|
4. Otherwise → **current** directory scope
|
|
|
|
Show the user what's happening:
|
|
|
|
```
|
|
## Config-Audit
|
|
|
|
Analyzing your Claude Code configuration...
|
|
|
|
**Scope:** {Repository|Home directory|Current directory} — `{path}`
|
|
**What this checks:** CLAUDE.md quality, settings validation, hook safety, rules correctness, MCP server config, import chains, conflicts, and feature coverage.
|
|
```
|
|
|
|
### Step 2: Initialize session
|
|
|
|
1. Generate session ID: `YYYYMMDD_HHmmss` format
|
|
2. Create session directory and findings subdirectory:
|
|
|
|
```bash
|
|
mkdir -p ~/.claude/config-audit/sessions/{session-id}/findings
|
|
```
|
|
|
|
This is a silent infrastructure step — do NOT show output to the user.
|
|
|
|
### Step 3: Run scanners and posture assessment
|
|
|
|
Tell the user: **"Running 16 configuration scanners..."**
|
|
|
|
Run both scanners and posture in a single Bash command. Default mode runs the humanizer, so each finding in `scan-results.json` carries `userImpactCategory`, `userActionLanguage`, and `relevanceContext` alongside the v5.0.0 fields. If the user passed `--raw`, thread it through to both CLIs to get v5.0.0 verbatim output.
|
|
|
|
```bash
|
|
RAW_FLAG=""
|
|
if echo "$ARGUMENTS" | grep -qE -- '(^| )--raw( |$)'; then RAW_FLAG="--raw"; fi
|
|
# Set to the ONE scope flag the detected scope calls for, otherwise leave empty.
|
|
# Exactly one token: zsh does not word-split an unquoted expansion, so a variable
|
|
# holding "--flag value" would reach argv as a single unrecognised argument.
|
|
# A placeholder in square brackets does not start with a dash either, so both
|
|
# CLIs' arg loops would take it as the TARGET PATH instead of a flag.
|
|
SCOPE_FLAG="" # e.g. SCOPE_FLAG="--full-machine" or SCOPE_FLAG="--global"
|
|
node ${CLAUDE_PLUGIN_ROOT}/scanners/scan-orchestrator.mjs "<target-path>" --output-file ~/.claude/config-audit/sessions/{session-id}/findings/scan-results.json $SCOPE_FLAG $RAW_FLAG >/dev/null 2>/dev/null; ORCH_STATUS=$?
|
|
node ${CLAUDE_PLUGIN_ROOT}/scanners/posture.mjs "<target-path>" --output-file ~/.claude/config-audit/sessions/{session-id}/posture.json $SCOPE_FLAG $RAW_FLAG >/dev/null 2>~/.claude/config-audit/sessions/{session-id}/posture-stderr.txt; POSTURE_STATUS=$?
|
|
echo "$ORCH_STATUS $POSTURE_STATUS"
|
|
```
|
|
|
|
Use `--full-machine` for `full` scope, `--global` for `home` scope. For `repo` and `current`, pass the resolved path directly.
|
|
|
|
Two exit codes are echoed — the orchestrator's first, posture's second. They must be read **independently**; a single trailing `echo $?` would report only the last command, hiding an orchestrator failure behind posture's success.
|
|
|
|
- both in `0`, `1`, `2` → continue normally
|
|
- **either** is `3` → tell user: "Scanner encountered an unexpected error. Try `/config-audit posture` for a quick check instead." and stop.
|
|
|
|
Posture's stderr goes to a **file**, not `/dev/null`: it carries the humanized scorecard headline that step 6 renders, and that headline exists nowhere in the JSON payload. Writing it to a file keeps UX rule 2 intact (the user still never sees raw scanner output) while leaving the text readable.
|
|
|
|
### Step 4: Analyze results
|
|
|
|
Tell the user: **"Scanners complete. Preparing your results..."**
|
|
|
|
Read all three output files using the Read tool:
|
|
- `~/.claude/config-audit/sessions/{session-id}/findings/scan-results.json`
|
|
- `~/.claude/config-audit/sessions/{session-id}/posture.json`
|
|
- `~/.claude/config-audit/sessions/{session-id}/posture-stderr.txt` — the humanized scorecard. Take the `Health: {grade} ({score}/100) — {prose}` headline from it; that prose is not in either JSON payload.
|
|
|
|
Extract these metrics from the JSON:
|
|
|
|
**From posture.json:**
|
|
- `overallGrade` — the health grade (A-F)
|
|
- `opportunityCount` — number of unused features detected
|
|
- `areas[]` — per-area grades and finding counts (use only quality areas, exclude Feature Coverage)
|
|
|
|
**From scan-results.json:**
|
|
- `aggregate.total_findings` — total findings (test fixture findings are already excluded automatically)
|
|
- `fixture_findings` array (if present) — count of findings excluded from test/example directories
|
|
- Count findings by severity from `aggregate.counts` (critical, high, medium, low, info)
|
|
- Count findings where `autoFixable: true`
|
|
- Note total `files_scanned` across scanners
|
|
|
|
### Step 5: Update state
|
|
|
|
Write session state (silent — no user output):
|
|
|
|
```yaml
|
|
session_id: "{session-id}"
|
|
current_phase: "analyze"
|
|
completed_phases: ["discover", "analyze"]
|
|
next_phase: "plan"
|
|
updated_at: "{ISO timestamp}"
|
|
scope_type: "{repo|home|current|full}"
|
|
target_path: "{resolved path}"
|
|
```
|
|
|
|
Write to: `~/.claude/config-audit/sessions/{session-id}/state.yaml`
|
|
|
|
### Step 6: Display results
|
|
|
|
Present results using this template. The humanizer has already replaced jargon-heavy `title`/`description`/`recommendation` strings on every finding with plain-language equivalents — render them verbatim. Lead urgency phrasing with `userActionLanguage` ("Fix this now", "Fix soon", "Fix when convenient", "Optional cleanup", "FYI") and group "What you can do next" suggestions by that field. Do not re-derive an A/B/C/D/F-to-prose ladder here; the humanized stderr scorecard headline already supplies the grade context, and `userActionLanguage` supplies finding-level urgency.
|
|
|
|
```markdown
|
|
### Results
|
|
|
|
**Health: {overallGrade}** | {qualityAreaCount} areas scanned
|
|
|
|
{Use the `Health: …` headline read from `posture-stderr.txt` in step 4 — it carries grade-context prose already. Avoid hardcoding a separate per-grade prose ladder. If that file is missing or empty, say the grade plainly without inventing prose for it.}
|
|
|
|
Scanned {files_scanned} files | {real_finding_count} findings ({severity_breakdown})
|
|
{If test_fixture_count > 0: "({test_fixture_count} additional findings in test fixtures were excluded.)"}
|
|
{If fixable_count > 0: "{fixable_count} of these can be auto-fixed."}
|
|
|
|
### Area Breakdown
|
|
|
|
| Area | Grade | Findings | |
|
|
|------|-------|----------|-|
|
|
| CLAUDE.md | {grade} | {count} | {one-phrase status} |
|
|
| Settings | {grade} | {count} | {status} |
|
|
| Hooks | {grade} | {count} | {status} |
|
|
| Rules | {grade} | {count} | {status} |
|
|
| MCP | {grade} | {count} | {status} |
|
|
| Imports | {grade} | {count} | {status} |
|
|
| Conflicts | {grade} | {count} | {status} |
|
|
| Token Efficiency | {grade} | {count} | {status} |
|
|
| Plugin Hygiene | {grade} | {count} | {status} |
|
|
|
|
{For the status column, use the humanized title from the most-severe finding in that area, or a one-phrase plain-language summary. Findings carry userImpactCategory which already groups by impact bucket — use that vocabulary, not raw scanner names.}
|
|
|
|
{If opportunityCount > 0:}
|
|
{opportunityCount} feature opportunities available — run `/config-audit feature-gap` for context-aware recommendations.
|
|
|
|
### What you can do next
|
|
|
|
Group suggestions by `userActionLanguage` from the humanized findings:
|
|
|
|
{If any finding has userActionLanguage "Fix this now" or "Fix soon":}
|
|
- **`/config-audit fix`** — auto-fix what's possible (backup created first, one-command rollback). The remaining items go into a prioritized plan.
|
|
- **`/config-audit plan`** — produce a prioritized action plan for the items that need manual attention.
|
|
|
|
{If most findings are "Fix when convenient" or "Optional cleanup":}
|
|
- **`/config-audit feature-gap`** — see which features could enhance your setup; pick what you want and implement on the spot.
|
|
- **`/config-audit fix`** — auto-fix anything deterministic; the rest is genuinely optional.
|
|
|
|
{If only "FYI" findings:}
|
|
- **`/config-audit feature-gap`** — explore opportunities; nothing is urgent.
|
|
|
|
Session saved to: `~/.claude/config-audit/sessions/{session-id}/`
|
|
```
|
|
|
|
## Scope Resolution
|
|
|
|
| Scope | What gets scanned |
|
|
|-------|-------------------|
|
|
| `current` | Current directory + parent CLAUDE.md files up to root + `~/.claude/` |
|
|
| `repo` | Git repository root + `~/.claude/` |
|
|
| `home` | `~/.claude/` global configuration only |
|
|
| `full` | Everything: `~/.claude/`, managed paths, all dev dirs under $HOME |
|
|
|
|
## Error Handling
|
|
|
|
- If scanner fails (exit 3), tell the user in plain language and suggest `/config-audit posture` as fallback
|
|
- If path doesn't exist, tell the user: "That path doesn't exist. Run `/config-audit` without arguments to auto-detect."
|
|
- If git command fails for auto-detect, silently fall back to `current` scope
|
|
- If no CLAUDE.md found anywhere, explain: "No CLAUDE.md found. This is the main configuration file for Claude Code — creating one is the single highest-impact thing you can do. Run `/config-audit feature-gap` to see what's recommended."
|