chore(release): v5.13.0 — pipeline hardening batch (M-BUG-11..25 + --subtract)

Batch release of everything since v5.12.5: one new lens mode and 14 real bugs,
all of them dogfooding finds — either from running the plugin against the
maintainer's real machine, or from walking analyze -> plan -> implement ->
rollback end-to-end on a throwaway repo copy.

Minor, not patch. STATE recorded this batch as "fix: only"; the log says
otherwise — e9921d3 ships `optimize --subtract`, a user-facing opt-in flag, so
semver requires a minor. Verified by reading `git log v5.12.5..HEAD` rather than
trusting the note: 11 fix, 1 feat, 8 docs.

Consequence: the planned v5.13 work (model routing, effort awareness, dead
references) now targets v5.14. docs/v5.13-model-routing-effort-deadref-plan.md
keeps its filename so existing references resolve, and says so at the top —
leaving a doc named for a version that shipped something else is exactly the
dead-reference class that plan is about.

Gates, all re-run against ground truth before writing anything:
- node --test 'tests/**/*.test.mjs' -> 1398 pass / 0 fail
- scanners/self-audit.mjs --check-readme -> passed (tests badge 1344 -> 1398;
  the path in STATE said scripts/, which does not exist)
- catalog check-versions.mjs -> 0 ERROR

Counts unchanged: scanners 16, agents 7, commands 21, hooks 4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AA5LT1UaDzctNkMi414qzA
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 17:33:48 +02:00
commit c60f849d2e
4 changed files with 153 additions and 3 deletions

View file

@ -1,7 +1,7 @@
{
"name": "config-audit",
"description": "Multi-agent workflow for analyzing, reporting, and optimizing Claude Code configuration across your entire machine",
"version": "5.12.5",
"version": "5.13.0",
"author": {
"name": "Kjell Tore Guttormsen"
},

View file

@ -5,6 +5,151 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [5.13.0] - 2026-07-31
### Summary
"Pipeline hardening" — the batch release of everything found by dogfooding the plugin against the
maintainer's real machine and by walking the `analyze → plan → implement → rollback` pipeline
end-to-end on a throwaway repo copy: **one new lens mode** (`optimize --subtract`) and **14 bugs**
(`M-BUG-11``M-BUG-20`, `M-BUG-22``M-BUG-25`), every one of them a real defect a user could hit.
The minor bump is carried by `--subtract` alone; the other 14 are fixes. Two themes run through them:
**agent-facing commands were scanning config the user cannot act on** (plugin-bundled and vendored
copies masking real findings), and **new finding types kept shipping without their matching humanizer
entry**, so plain-language output contradicted the finding's own evidence. The rollback chunk found the
worst class in the repo: a `restoreBackup` that returned `{restored: [], failed: []}` — a *success-shaped
no-op* — because nothing agreed on where a backup lives or what its manifest looks like.
No count change (scanners **16**, agents **7**, commands **21**, hooks **4**). Frozen `v5.0.0` snapshots
untouched throughout; the SC-5 default-output snapshot was regenerated once, for two humanized titles
only (`M-BUG-15`). **1398** tests (+54).
**Known and deliberately not fixed in this release:** `rollback` still cannot delete files that
`implement` *created* — a backup cannot hold a file that never existed. It no longer fails silently
(manifests carry a `created:` list, `restoreBackup` returns `createdNotRemoved`, and `rollback.md`
requires the report), but automatic deletion of user files is destructive and gets its own design.
`drift-cli.mjs` still lacks `--output-file` (`M-BUG-21`).
### Added
- **`optimize --subtract` — the subtraction axis (`BP-SUB-001`).** Every command so far asked an
addition question: what to add, what to move, what it costs. Nothing asked what no longer earns its
always-loaded rent. `--subtract` adds that as a fourth `lensCheck` on the existing hybrid motor — a
mode, not a 22nd command or a 17th scanner, because the measured payoff (~18% of one file) justifies
a mode and no more. It is **opt-in and proposes only**.
It is also the only lens that proposes *removing* config, so it carries a guarantee the others don't
need: **a load-bearing block is never a candidate.** Precision is asymmetric — a missed dead line
costs a few tokens per turn, a wrongly deleted one costs a broken script or a wrong remote — so the
floor is decided in code (`scanners/lib/floor-exclusion.mjs`) *before* the opus judge sees anything,
never in prose. That ordering is an invariant, not an implementation detail.
Granularity is the leaf block, with two structural exceptions: a paragraph ending in `:` merges with
the list it introduces, and an ordered list is a contract whose steps inherit floor from any sibling.
Unordered lists deliberately do **not** inherit — a load-bearing bullet and a disposable one routinely
share a list.
Verified against a hand-built ground truth written *before* any classifier existed, with the
comparison machine-checked rather than read by eye: **zero load-bearing blocks proposed**, 11/18
deletable groups surfaced, ~756 tok ≈ 18% of a ~4300-token file — inside the pre-registered band. The
gate is re-runnable via `scripts/dogfood-subtraction-gate.local.mjs`. Three bugs the dogfood run
exposed are now covered by fixtures: JS `\b` is ASCII-only so `/\bunngå\b/` never matched (every
Norwegian keyword ending in `æ/ø/å` was silently dead); a bare `word/word` is not a path
("pros/cons" vetoed the largest deletable block); "mid-sentence" must key on a preceding lowercase
letter, or `**bold labels:**` read as entities and cost 4 of 11 groups.
`BP-SUB-001` is grounded entirely in the Anthropic steering blog already cited by
`BP-MECH-001..004` and asserts nothing from the talk that motivated the feature.
### Fixed
- **`rollback` — the backup path contract the engine and the commands disagreed on
(`M-BUG-22`/`M-BUG-23`/`M-BUG-24`/`M-BUG-25`).** Pipeline step 4 dogfood: `/config-audit rollback`
could not see a single one of the four real backups on this machine, and reported "Backup not found"
for one sitting right there. Four defects, one root — nothing agreed on where a backup lives or what
its manifest looks like.
**`M-BUG-22` (high):** `lib/backup.mjs` resolved `~/.config-audit/backups` (pre-v2.2.0) while every
command, agent and doc uses `~/.claude/config-audit/backups`. The auto-backup hook and `fix-cli` wrote
to the first, `implement` to the second, `rollback` read only the first — so `listBackups()` returned
**9 phantom backups from the test suite** and **0 of the 4 real ones**. The canonical root is now
`~/.claude/config-audit/backups`, with the legacy root kept **readable** (`legacy: true`) so older
backups stay listable and restorable.
**`M-BUG-25` (high, the worst failure mode in the file):** `parseManifest` understood only the
engine's quoted `original_path:` spelling, but `implement` hand-builds its manifest with
`- backup:`/`original:`/`sha256:`. Every implement-made backup parsed to zero files and
`restoreBackup` returned `{restored: [], failed: []}` — success-shaped, and silent. Both formats parse
now, and a manifest with unparseable entries **throws** instead of pretending to succeed.
**`M-BUG-23`:** both session hooks watched `~/.config-audit/sessions`, which does not exist — "check
for active sessions" had never fired once. It fires now.
**`M-BUG-24`:** the suite called `createBackup()` against the developer's real home, leaving nine stray
backups there while `cleanupOldBackups()` deletes past ten. The root is overridable via
`CONFIG_AUDIT_BACKUP_ROOT` / `CONFIG_AUDIT_LEGACY_BACKUP_ROOT`, and both test files use it — any new
test touching `createBackup()` must too.
Verified against backup `20260717_032636` on a throwaway copy, through the previously broken engine
path: 3/3 files restored **byte-exact** (sha256 match), zero writes outside the copy, backup dir
unmodified.
- **`rules-validator``globToRegex` corrupted mid-pattern `/**/` globs (`M-BUG-19`).** The
`?``[^/]` replacement ran *after* the `{{GLOBSTAR_SLASH}}` placeholder was restored to `(?:/.+/|/)`,
corrupting the group opener `(?:` into `([^/]:`. Every rule pattern containing a mid-pattern `/**/`
silently matched only the zero-dir branch, so live rules were flagged "matches no files" (`CA-RUL`).
Found by dogfooding `/config-audit implement` on a throwaway repo copy: the implementer agent's
correct `posts/**/post.md` rule was flagged dead. Fixture outcomes byte-identical.
- **`analyze` persists the agent-returned report (`M-BUG-18`).** The Claude Code subagent harness
instructs spawned agents *not* to write report/summary/findings/analysis `.md` files — the parent
reads the final text message. Verified live: `analyzer-agent` skipped `Write` entirely, so
`analysis-report.md` never landed on disk and the plan/interview/status phases found nothing to read.
New orchestrator-writes contract: the agent returns the complete report as its final message and the
`analyze` command saves it verbatim before presenting the summary. The harness note is **file-type
specific** — `plan` was dogfooded afterwards and writes `action-plan.md` without friction, so the same
fix is *not* needed there.
- **`implement` pins `>>` append discipline on the shared log (`M-BUG-20`).** `implement.md` spawns
implementer agents in parallel batches, all appending to the same `implementation-log.md`. Dogfooding
showed agents satisfying "append result to:" with a full-file `Write` — the last writer clobbered 4 of
6 entries. Both contracts now pin the mechanism: append with a Bash `>>` heredoc, never the Write/Edit
tool on a shared log.
- **`optimize` lens scopes out plugin-bundled CLAUDE.md + keys candidates by absolute path
(`M-BUG-11`).** The lens CLI fed its precision-gate agent every CLAUDE.md discovery returned, including
the 256 files under `~/.claude/plugins/` — vendored copies across every cached version plus their
fixtures and examples. `optimize --global` produced 454 candidates across 92 "files", ~250 of them from
plugin-internal files a user cannot act on (the plugin overwrites them on update). Second defect:
candidates were keyed by `relPath || absPath`, and `relPath` collides across scopes — a repo-root
`CLAUDE.md` and `~/.claude/CLAUDE.md` both key to `CLAUDE.md`, so the two files that actually matter
merged into one indistinguishable bucket and the agent's `Read(file)` would resolve the wrong one.
Dogfood: candidates **454→45**, distinct files **92→11**, repo vs user-global now distinct.
- **`feature-gap` scopes presence checks to authored config and reads the settings cascade
(`M-BUG-13`).** The GAP scanner's 25 presence checks ran over the full `includeGlobal` discovery, so
this plugin's own `examples/optimal-setup` (vendored across plugin-cache versions) satisfied every
tier-3 check — masking real feature gaps to **GAP=0 on any target**. And the real
`~/.claude/settings.json` was invisible to the settings-key checks (the `includeGlobal` gotcha plus the
`maxFiles` cap), which would have flipped `statusLine`/`autoMode` into false positives the moment the
maskers were removed. Both halves are fixed together: `isAuthoredConfig` excludes plugin-bundled and
nested `examples/`/`tests/fixtures/` config, and `readSettingsCascade` reads user→project→local
directly. Empty target: ~0 (masked) → **18** humanized opportunities.
- **`posture --output-file` humanizes findings in default mode (`M-BUG-12`).** `feature-gap.md` and
`posture.md` both read findings from `posture.mjs --output-file` and group on the humanizer fields,
but `posture.mjs` only humanized the stderr scorecard — its `--output-file` JSON wrote the raw
v5.0.0-shape result, so every finding's humanizer fields were `undefined` and both commands silently
degraded to the raw tier-fallback. v5.1.0 plain-language output was dead for `feature-gap` and for
`posture`'s finding-level grouping. The payload is now humanized in default mode (applied to
`result.scannerEnvelope`, which is where posture nests it); `--json`/`--raw` stay raw.
- **AGT findings humanize to "Wasted tokens", not "Other" (`M-BUG-17`).** The agent-listing scanner emits
an always-loaded per-turn token cost — "the dominant single always-loaded source" — but
`SCANNER_TO_CATEGORY` had no AGT entry, so its findings fell through to the `Other` fallback, a bucket
that isn't even in the analyzer-agent's category list. All 16 orchestrator scanner prefixes are now
covered by the category map, closing the class.
- **On-demand copy for the oversized skill-body finding (`M-BUG-16`).** The v5.11 B7 finding measures a
skill *body*, which loads only when the skill is invoked — but with no `SKL.static` entry it fell
through to `SKL._default` ("using more of the listing budget than it should"), so the humanized title
claimed a listing-budget cost and directly contradicted its own humanized evidence ("loads on demand
only … NOT every turn").
- **Honest absence-state copy for two GAP enhancement findings (`M-BUG-15`).** The "No path-scoped rules"
and "No subagent isolation" checks fire on an *empty* collection too, but the humanized titles
presupposed the feature exists — "Your subagents share Claude's main work folder" appeared in the same
report as "You haven't set up any specialized helper agents yet". A user cannot simultaneously have no
subagents and have subagents that lack isolation. Both titles now use the house "You haven't set up X
yet" framing, which is honest for the zero-state *and* the has-but-unconfigured state. Fixed in the
humanizer, not by gating the scanner — a presence gate would have moved the frozen v5.0.0
marketplace-medium baseline.
- **Size-neutral copy for "CLAUDE.md not modular" (`M-BUG-14`).** The check is a pure presence check with
no length gate, but the copy claimed the file is "one big block" and that splitting makes it "easier on
the loading time" — an unconditional size overclaim that simply lies for a ~625-token CLAUDE.md. Copy
softened to the honest structural framing; no length gate added, which would have made it the only
size-gated check among its siblings.
## [5.12.5] - 2026-06-26
### Summary

View file

@ -6,13 +6,13 @@
*AI-generated: all code produced by Claude Code through dialog-driven development. [Full disclosure →](../../README.md#ai-generated-code-disclosure)*
![Version](https://img.shields.io/badge/version-5.12.5-blue)
![Version](https://img.shields.io/badge/version-5.13.0-blue)
![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)
![Scanners](https://img.shields.io/badge/scanners-16-cyan)
![Commands](https://img.shields.io/badge/commands-21-green)
![Agents](https://img.shields.io/badge/agents-7-orange)
![Hooks](https://img.shields.io/badge/hooks-4-red)
![Tests](https://img.shields.io/badge/tests-1344-brightgreen)
![Tests](https://img.shields.io/badge/tests-1398-brightgreen)
![License](https://img.shields.io/badge/license-MIT-lightgrey)
A Claude Code plugin that checks configuration health, suggests context-aware improvements, and auto-fixes issues — `CLAUDE.md`, `settings.json`, hooks, rules, MCP servers, `@imports`, and plugins. 16 deterministic scanners across 10 quality areas, context-aware feature recommendations, auto-fix with backup/rollback, a prompt-cache-aware Token Hotspots scanner with optional API-calibrated `--accurate-tokens` mode, plus cache-prefix stability, dead-tool, cross-plugin collision, output-style, and always-loaded agent-listing-budget detection. Zero external dependencies.
@ -689,6 +689,7 @@ This plugin is cautious by design — configuration files are important, and a b
| Version | Date | Highlights |
|---------|------|-----------|
| **5.13.0** | 2026-07-31 | "Pipeline hardening" — the batch release of everything found by dogfooding the plugin against the maintainer's real machine and by walking the `analyze → plan → implement → rollback` pipeline end-to-end on a throwaway repo copy: one new lens mode plus **14 bugs** (`M-BUG-11``M-BUG-20`, `M-BUG-22``M-BUG-25`). **Added — `optimize --subtract` (`BP-SUB-001`):** the subtraction axis, asking what no longer earns its always-loaded rent. Opt-in, proposes only, and the only lens that removes config — so a **load-bearing block is never a candidate**, decided in code (`scanners/lib/floor-exclusion.mjs`) *before* the judge runs, never in prose. Verified against a hand-built ground truth written before any classifier existed: **zero load-bearing blocks proposed**, 11/18 groups, ~756 tok ≈ 18% of a ~4300-token file. **Fixed — `rollback` (`M-BUG-22/23/24/25`):** nothing agreed on where a backup lives; `listBackups()` returned 9 phantom test backups and 0 of 4 real ones, and `restoreBackup` returned `{restored:[],failed:[]}` — a **success-shaped no-op** — because `parseManifest` knew only one of the two manifest spellings in use. Canonical root now, legacy kept readable, unparseable manifests **throw**. **`M-BUG-19`:** `globToRegex` corrupted mid-pattern `/**/`, flagging live rules dead. **`M-BUG-18`/`M-BUG-20`:** the subagent harness won't write report-shaped `.md` (analyze now persists the returned report), and parallel agents clobbered the shared log with `Write` (pinned to Bash `>>`). **`M-BUG-11`/`M-BUG-13`:** `optimize` and `feature-gap` scanned vendored plugin config a user cannot act on — `optimize` candidates **454→45**, `feature-gap` **~0 (masked) → 18** opportunities. **`M-BUG-12`/`M-BUG-14`/`M-BUG-15`/`M-BUG-16`/`M-BUG-17`:** plain-language output that contradicted its own evidence (posture's `--output-file` never humanized; four finding types with no humanizer entry). Known, deliberately unfixed: `rollback` cannot delete files `implement` *created* — it now reports them (`createdNotRemoved`) instead of failing silently; automatic deletion of user files gets its own design. No count change (scanners **16**, agents **7**, commands **21**, hooks **4**); frozen v5.0.0 untouched, SC-5 regenerated once for two humanized titles. **1398** tests (+54). |
| **5.12.5** | 2026-06-26 | "Dogfood denoise" — a samle-release of the Fase-3 scanner false-positive batch (`M-BUG-2/6/7/8/10`, all dogfooding finds on the maintainer's real machine). Five scanners stop counting non-user / non-live config as the user's: **CNF** (`M-BUG-2`) excludes files under `.claude/plugins/` from conflict analysis (`isPluginBundled`) — installed plugins' bundled settings/hooks/fixtures are not a user-resolvable cascade (dogfood **339→0**, Conflicts was an F on pure plugin noise). **file-discovery** (`M-BUG-8`) adds `backups` to `SKIP_DIRS` — a `backups/` tree holds frozen copies, never live config (dogfood files-under-`/backups/` **36→0**, 717 live retained). **token estimator** (`M-BUG-6`) strips block-level `<!-- -->` HTML comments from CLAUDE.md sizing — CC strips them before injection, so they were never always-loaded tokens (dogfood ~3386→3301, ~85 tok). **CPS** (`M-BUG-7`) skips fenced/inline code and whitelists CC-stable path vars (`${CLAUDE_PLUGIN_ROOT}`/`${CLAUDE_PROJECT_DIR}`) before cache-buster matching (dogfood **5→2**). **SET** (`M-BUG-10`) typo-gates the unknown-settings-key finding — the CC schema is passthrough (verified against the 2.1.193 binary), so an unknown key is forward-compatible, not an error; it now flags only a near-miss of a known key (levenshtein ≤2), severity medium→low, +6 binary-verified `KNOWN_KEYS` (dogfood **6→0**). No count change (scanners **16**, agents **7**, commands **21**, hooks **4**); all five are byte-stable — frozen v5.0.0 + SC-5 + default-output snapshots untouched, no re-seed (each fixture's findings are genuinely unchanged). **1344** tests (+37). |
| **5.12.4** | 2026-06-26 | "Rooted rules" — fixes `M-BUG-9` (dogfooding find) in `scanners/rules-validator.mjs`: the RUL "Rule path pattern matches no files" check now resolves a rule's `paths:`/`globs:` pattern against the rule's **own project root** (the dir containing its `.claude/`), not the outer scan root. Previously `countGlobMatches` globbed against the scan target and `collectProjectFiles`' `depth>4` cutoff never reached deep matching files, so a live rule in a **nested repo** (e.g. a marketplace checkout under `~/.claude`) was wrongly flagged "never activates" (high) — a false F-grade for anyone with rules in a nested repo. The fix derives each rule's project root, collects+globs per root (cached), and skips the check for user-global rules (`root === $HOME`), which scope against the active project at runtime. Same scope-conflation family as `M-BUG-1/2`. No count change (scanners **16**, agents **7**, commands **21**); the fix is a no-op when `projectRoot === targetPath` (the common single-repo scan), so frozen v5.0.0 + default-output snapshots stay byte-stable. **1307** tests (+2 TDD: nested-repo false-positive + HOME guard). |
| **5.12.3** | 2026-06-26 | "Phantom agents" — fixes `M-BUG-3/4/5` (dogfooding finds) in `scanners/lib/active-config-reader.mjs`: `enumerateAgents` now counts only the agents Claude Code actually **registers**. Per the official subagents doc, an agent needs valid `name`+`description` frontmatter, and CC scans recursively and silently skips frontmatter-less files. The reader previously (`M-BUG-5`) counted every `.md` regardless of frontmatter, (`M-BUG-3`) never recursed into agent subdirs, and (`M-BUG-4`) double-counted when the project dir equals the user dir (scanning `$HOME` — root cause, also affecting rules/output-styles). Real-machine verify: user-agent count **13→0** (all 12 user agents + `REMEMBER.md` are frontmatter-less → CC registers none), HOME `project`-dup **13→0**; corrected always-loaded baseline ≈ **53** (was 66). Agent enumeration is machine-dependent and absent from the frozen snapshots, so the v5.0.0 + SC-5 + default-output snapshots stay byte-stable; no count change (scanners **16**, agents **7**, commands **21**). **1305** tests. |

View file

@ -1,5 +1,9 @@
# v5.13 Plan — Model Routing, Effort Awareness, Dead References
> **Target version is now v5.14.** The `5.13.0` slot was consumed by the pipeline-hardening batch
> (`optimize --subtract` is a feature, so that release could not be a patch). The filename is kept so
> existing references resolve; only the target moved.
Derived from an external video analysis ("The Model Isn't the Moat", 2026-07) cross-checked
against primary sources and against what config-audit already encodes. Every claim acted on
here was verified against Anthropic's own docs; video-only claims are explicitly rejected below.