config-audit/commands/posture.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

6.4 KiB

name description argument-hint allowed-tools model
config-audit:posture Quick configuration health assessment — scorecard with A-F grades [path] [--drift] [--plugin-health] Read, Write, Glob, Grep, Bash 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.

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

**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:

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:

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:

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.