Dogfooding the four read commands (posture, tokens, manifest, whats-active) surfaced four defect classes, all in the seam between what a command template promises and what the scanner behind it actually does. M-BUG-40, fifth arm: posture wrote four temp files it could never read back. #49 closed the $$/cross-block class in four commands, but posture survived it — and so did the guard written to prevent exactly this. The guard compared each $$ path to the block that created it, so a path written once and then read via prose had no second occurrence to flag. Measured live: written from PID 21614, read attempted from PID 23772. The invariant is now blanket (no $$ in any temp path), which also caught fix.md and feature-gap.md. M-BUG-43: 6 of 7 scanners write their payload to stdout when --raw/--json is set even when --output-file was given, and the templates redirected only stderr. Measured: posture 255 182 B, whats-active 35 922 B, drift 28 316 B, manifest 23 825 B, tokens 8 768 B. fix and feature-gap never read the file they wrote, so both recovered one letter grade from a quarter-megabyte dump. tokens swallowed --json and --with-telemetry-recipe: documented, never threaded, so --json returned the humanized payload where the docs promise byte-stable v5.0.0 output. M-BUG-42: manifest's render contract asked for {load}; the payload carries loadPattern, so the Load column rendered blank for all 96 rows. Four new tests (1449 -> 1453), each verified red before the fix. The render-contract test checks {field} names against a live payload from a fixture, since a hardcoded key list would drift. Frozen v5.0.0 snapshots untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGCk9o27eWo9uXLjkZTXEq
129 lines
6.4 KiB
Markdown
129 lines
6.4 KiB
Markdown
---
|
|
name: config-audit:posture
|
|
description: Quick configuration health assessment — scorecard with A-F grades
|
|
argument-hint: "[path] [--drift] [--plugin-health]"
|
|
allowed-tools: Read, Write, Glob, Grep, Bash
|
|
model: sonnet
|
|
---
|
|
|
|
# Config-Audit: Health Assessment
|
|
|
|
Quick, deterministic configuration health scorecard. No agents needed — runs all scanners + scoring in one pass.
|
|
|
|
## What the user gets
|
|
|
|
- Health grade (A-F) with plain-language explanation
|
|
- Per-area breakdown for 10 quality areas (incl. Token Efficiency, Plugin Hygiene) with grades and actionable notes
|
|
- Opportunity count — how many features could enhance their setup (not a grade)
|
|
- Grade-appropriate next steps
|
|
|
|
## Implementation
|
|
|
|
### Step 1: Determine target and flags
|
|
|
|
Split `$ARGUMENTS` into a path and flags. Path is the first non-flag argument (default: current working directory). Resolve relative paths. Recognized flags:
|
|
|
|
- `--raw` — pass-through to the scanner; produces v5.0.0 verbatim output (bypasses the humanizer). Power-user mode for byte-stable diffs and machine consumption.
|
|
- `--drift` — append a "Configuration Drift" section (see Step 5).
|
|
- `--plugin-health` — append a "Plugin Health" section (see Step 5).
|
|
|
|
Tell the user:
|
|
|
|
```
|
|
## Configuration Health
|
|
|
|
Running quick assessment{if path != cwd: " on `{path}`"}...
|
|
```
|
|
|
|
### Step 2: Run posture scanner
|
|
|
|
Run silently — JSON goes to a file, the humanized scorecard prints to stderr (default mode). The humanized stderr scorecard already includes the grade headline and area-score lines in plain language, so render those directly rather than re-deriving prose tables.
|
|
|
|
```bash
|
|
RAW_FLAG=""
|
|
if echo "$ARGUMENTS" | grep -q -- "--raw"; then RAW_FLAG="--raw"; fi
|
|
node ${CLAUDE_PLUGIN_ROOT}/scanners/posture.mjs <target-path> --output-file /tmp/config-audit-posture.json $RAW_FLAG >/dev/null 2>/tmp/config-audit-posture-stderr.txt; echo $?
|
|
```
|
|
|
|
Both paths are fixed literals, repeated literally in every later step: each
|
|
```bash fence is its own process, so a `$$`-derived path could never be named
|
|
again. `>/dev/null` is required, not cosmetic — with `--raw` the scanner writes
|
|
the full envelope to stdout *as well as* the file (`posture.mjs:101`), which is
|
|
255 KB on a real repo.
|
|
|
|
If exit code is non-zero, tell the user: "Assessment couldn't complete. Check that the path exists and contains Claude Code configuration files."
|
|
|
|
If `--raw` was passed, treat the captured stderr as v5.0.0-shape verbatim text and present it as-is in a code block; skip the humanized rendering steps below.
|
|
|
|
### Step 3: Read and interpret results
|
|
|
|
Use the Read tool on `/tmp/config-audit-posture.json`. Extract:
|
|
|
|
- `overallGrade`, `opportunityCount`
|
|
- `areas[]` — each with `name`, `grade`, `score`, `findingCount`
|
|
- `scannerEnvelope.scanners[].findings[]` — when surfacing individual findings, prefer the humanizer-provided fields: `userImpactCategory` (e.g., "Configuration mistake", "Wasted tokens"), `userActionLanguage` (e.g., "Fix this now", "Fix soon", "Optional cleanup"), and `relevanceContext` ("affects-everyone", "affects-this-machine-only", "test-fixture-no-impact"). These let you group and prioritize without hardcoded severity-to-prose mappings.
|
|
|
|
Also use the Read tool on `/tmp/config-audit-posture-stderr.txt` — its body is the humanized scorecard (grade headline, area-score block, opportunity hint). You can present it verbatim or interleave its lines with the JSON-driven table.
|
|
|
|
### Step 4: Present the scorecard
|
|
|
|
```markdown
|
|
**Health: {overallGrade}** | (area count: take it from the humanized scorecard's
|
|
"N areas reviewed" line — do NOT use `areas.length`, which counts Feature
|
|
Coverage; the table below excludes it, so the two would disagree)
|
|
|
|
{Use the headline line from the humanized stderr scorecard — it carries grade-context prose already (e.g., " Health: A (97/100) — Healthy setup, only minor polish needed"). Do not re-derive an A/B/C/D prose table here; the humanizer owns that vocabulary.}
|
|
|
|
### Area Scores
|
|
|
|
| Area | Grade | Score | Findings | |
|
|
|------|-------|-------|----------|-|
|
|
{for each area EXCEPT Feature Coverage:}
|
|
| {name} | {grade} | {score}/100 | {findingCount} | {plain-language note: A="Excellent", B="Good", C="Needs work", D/F="Issues found"} |
|
|
|
|
{if opportunityCount > 0:}
|
|
{opportunityCount} feature opportunities available — run `/config-audit feature-gap` for context-aware recommendations.
|
|
|
|
### What's next
|
|
```
|
|
|
|
Group "what's next" suggestions by `userActionLanguage` from the humanized findings:
|
|
|
|
- Findings tagged "Fix this now" / "Fix soon" → suggest `/config-audit fix` first, then `/config-audit plan`.
|
|
- Findings tagged "Fix when convenient" / "Optional cleanup" → suggest `/config-audit feature-gap` and routine maintenance.
|
|
- No high-urgency findings → suggest `/config-audit feature-gap` for opportunities and re-running posture after major config changes.
|
|
|
|
Avoid hardcoded grade-to-prose ladders here — the humanized scorecard headline already supplies grade context, and `userActionLanguage` supplies finding-level urgency.
|
|
|
|
### Step 5: Optional sections
|
|
|
|
**If `--drift` flag is present:**
|
|
|
|
Run drift comparison silently:
|
|
```bash
|
|
node ${CLAUDE_PLUGIN_ROOT}/scanners/drift-cli.mjs <target-path> --output-file /tmp/config-audit-posture-drift.json 2>/dev/null; echo $?
|
|
```
|
|
|
|
Use the Read tool on `/tmp/config-audit-posture-drift.json` and append a "Configuration Drift" section showing what changed since the last baseline. Both scanners report to stderr in default mode, which `2>/dev/null` discards — the payload is the only readable output.
|
|
|
|
**If `--plugin-health` flag is present:**
|
|
|
|
Run plugin health scanner silently:
|
|
```bash
|
|
node ${CLAUDE_PLUGIN_ROOT}/scanners/plugin-health-scanner.mjs <target-path> --output-file /tmp/config-audit-posture-plh.json 2>/dev/null; echo $?
|
|
```
|
|
|
|
Use the Read tool on `/tmp/config-audit-posture-plh.json` and append a "Plugin Health" section, using its `plugins[]` rows for per-plugin grades.
|
|
|
|
**If both flags:** Use `scanners/lib/report-generator.mjs` to produce a unified markdown report.
|
|
|
|
### Step 6: Save to session (if active)
|
|
|
|
If a config-audit session exists, save results:
|
|
```bash
|
|
node ${CLAUDE_PLUGIN_ROOT}/scanners/posture.mjs <target-path> --json --output-file ~/.claude/config-audit/sessions/<session-id>/posture.json >/dev/null 2>/dev/null
|
|
```
|
|
|
|
This is a second scan on purpose: the session file stores the raw v5.0.0 shape,
|
|
while step 2 wrote the humanized one. `>/dev/null` matters most here — `--json`
|
|
sends the same envelope to stdout regardless of `--output-file`.
|