# Changelog 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.12.5] - 2026-06-26 ### Summary "Dogfood denoise" — a samle-release of the Fase-3 scanner false-positive batch: `M-BUG-2/6/7/8/10`, all dogfooding finds from running config-audit on the maintainer's real `~/.claude`. The shared theme is **non-user / non-live config wrongly counted as the user's authored cascade**: installed plugins' bundled config, frozen backup copies, doc examples, and forward-compatible settings keys all produced findings the user could neither act on nor was responsible for. No new scanner, command, agent, or hook (counts stay scanners **16**, agents **7**, commands **21**, hooks **4**); all five fixes are byte-stable — the frozen v5.0.0 + SC-5 + default-output snapshots are untouched and **no fixture was re-seeded** (verified per bug: each affected fixture's findings are genuinely unchanged because the snapshot fixtures contain none of the triggering paths/tokens). **1344** tests (+37). ### Fixed - **`conflict-detector` segregates plugin-bundled config (`M-BUG-2`).** CNF compared every discovered `settings.json`/`hooks.json` pairwise regardless of origin, so it treated installed plugins' bundled configs — each plugin's own settings/hooks plus its shipped fixtures and examples under `~/.claude/plugins/` — as the user's cascade. A "conflict" between two plugins' bundled test fixtures is not user-resolvable, yet these dominated the count (dogfood **339** findings: 315 high-sev allow/deny, 18 duplicate-hook, 6 settings-key — Conflicts grade F on ~100% plugin noise). Fix: a new `isPluginBundled` predicate excludes any file whose absolute path is under `.claude/plugins/` from conflict analysis. Kept **CNF-local, not a discovery-level skip** on purpose — an active plugin's contributed `hooks.json`/`.mcp.json` legitimately lives in `plugins/cache` and other scanners need it; only conflict analysis must ignore plugin-bundled files. Same class as `M-BUG-8`. Dogfood **339→0** (the ~3 genuine user-scope local settings have no actually-conflicting keys). +3 tests (plugin-bundled exclusion, discovery-side sanity, over-exclusion guard). - **`file-discovery` skips `backups/` (`M-BUG-8`).** A directory named `backups` holds backup COPIES, not live config, so walking it during an audit produces stale findings. config-audit's own session backups (`~/.claude/config-audit/backups//files/.../CLAUDE.md`) were the canonical case: a `~/.claude`-scope audit walked 36 frozen copies as if live, polluting CPS and HKV/RUL. Fix: add `backups` to `SKIP_DIRS` (broad, name-based — consistent with `vendor`/`dist`/`.cache`). Dogfood files-under-`/backups/` **36→0**, 717 live config files retained. +3 tests. - **token estimator discounts block-level HTML comments (`M-BUG-6`).** CLAUDE.md token estimates counted block-level `` comments toward always-loaded tokens, but CC strips them before injection (preserved only inside code fences, per `code.claude.com/docs/en/memory`). Fix: new `stripInjectedHtmlComments` + `effectiveMemoryBytes` in `active-config-reader`; the CML cascade and `token-hotspots` now size CLAUDE.md from effective (stripped) bytes while raw byte figures stay honest. Block-level only — inline comments retained (conservative, verified scope). Dogfood `~/.claude` CLAUDE.md ~3386→3301 tok (~85 tok). +13 tests. - **`cache-prefix-stability` ignores code + CC-stable path vars (`M-BUG-7`).** CPS flagged `${CLAUDE_PLUGIN_ROOT}`/`${CLAUDE_PROJECT_DIR}` (CC-provided stable paths) and `{date}`/timestamp tokens shown in documentation as cache-busters. Fix: skip fenced code blocks, strip inline-code spans, and whitelist CC-stable vars before pattern-matching. Suppress-only — frozen v5.0.0 snapshots untouched (CPS yields `findings:[]` there). Dogfood **5→2** (3 doc false-positives suppressed; 2 remaining are own volatile test fixtures). +6 tests. - **`settings-validator` typo-gates unknown keys (`M-BUG-10`).** The CC settings schema is passthrough (verified against the 2.1.193 binary): it forwards unrecognized keys unchanged rather than rejecting them, so an arbitrary unknown key is forward-compatible, not an error — the finding's "silently ignored" claim was factually wrong. The only real risk is a TYPO of a real key (the intended setting then silently has no effect). Fix: flag an unknown key only when it closely matches a known key (new `levenshtein` helper; edit distance ≤2, both keys ≥4 chars); severity medium→low; honest passthrough framing in scanner + humanizer. Also refreshed `KNOWN_KEYS` with 6 binary-verified keys (`agentPushNotifEnabled`, `remoteControlAtStartup`, `skipAutoPermissionPrompt`, `skipDangerousModePermissionPrompt`, `skipWorkflowUsageWarning`, `tui`). Dogfood `~/.claude/settings.json` **6→0** (all 6 were false unknown-key findings; 0 typo flags introduced across 167 walked files). +12 tests. ## [5.12.4] - 2026-06-26 ### Summary "Rooted rules" — fixes `M-BUG-9` (dogfooding find) in `scanners/rules-validator.mjs`. The RUL "Rule path pattern matches no files" check resolved a rule's `paths:`/`globs:` glob against the outer **scan root** instead of the rule's **own project root** (the directory containing its `.claude/`), and `collectProjectFiles` carried a `depth>4` cutoff that never reached deep matching files. As a result, a live rule in a **nested repo** — e.g. a marketplace checkout under `~/.claude/plugins/marketplaces//.claude/rules/` — was wrongly flagged "never activates" (high severity), a false F-grade for any user with rules in a nested repo. Same scope-conflation family as `M-BUG-1/2` (the scanner treats a nested repo's config as scoped to the outer scan root). The fix is a no-op for the common single-repo scan (`projectRoot === targetPath`), so the frozen v5.0.0 + default-output snapshots stay byte-stable; no count change (scanners **16**, agents **7**, commands **21**). **1307** tests (+2). ### Fixed - **`rules-validator` glob base (`M-BUG-9`).** The dead-rule check now resolves each rule against its own project root: - `deriveProjectRoot(ruleAbsPath)` returns the parent of the rule's `.claude` segment. - Project files are collected and globbed **per project root** (cached), relative to that root, so a nested repo's rule matches against its own tree where its files live. This also sidesteps the old `depth>4` cutoff, because the walk now starts at the nearby project root. - User-global rules (`projectRoot === $HOME`, i.e. `~/.claude/rules/`) skip the no-match check: they scope against whatever project is active at runtime, not a fixed tree, so "matches 0 files here" is not a dead-rule signal (and this avoids a `$HOME`-wide file walk). - TDD: 2 failing tests (nested-repo false-positive + HOME guard) → fix → full suite 1307/0, frozen v5.0.0 + default-output snapshots untouched (RUL findings appear in none). Real-machine verify: the two `ktg-privat` false positives clear and a previously-hidden genuine dead rule (a false negative) surfaces in the bundled `optimal-setup` example; zero new false positives. ## [5.12.3] - 2026-06-26 ### Summary "Phantom agents" — fixes `M-BUG-3/4/5` (dogfooding finds) in `scanners/lib/active-config-reader.mjs` so that `enumerateAgents` counts only the agents Claude Code actually **registers**. Per the official subagents documentation, an agent file must carry valid `name`+`description` frontmatter, and CC scans the agents directory **recursively** while silently skipping frontmatter-less files. The reader violated all three rules: it counted every `.md` regardless of frontmatter (`M-BUG-5`), never recursed into agent subdirectories (`M-BUG-3`), and double-counted entries when the project directory equals the user directory — the case when the scope root is `$HOME` (`M-BUG-4`, the root cause, which also affected rules and output-styles). Real-machine verify: the user-agent count dropped **13→0** (all 12 user agents plus `REMEMBER.md` are frontmatter-less, so CC registers none of them) and the HOME `project` duplicate dropped **13→0**; the corrected always-loaded baseline is ≈ **53**, not 66. Agent enumeration is machine-dependent and therefore 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 (+4). ### Fixed - **`active-config-reader` agent enumeration (`M-BUG-3/4/5`).** Three surgical fixes: - `listMarkdownFiles` gains an opt-in `recursive` flag so agent enumeration descends into subdirectories the way Claude Code does (`M-BUG-3`). - `configDirs` now de-duplicates the project and user paths when they resolve to the same directory (the case when the scope root is `$HOME`), the root cause that also double-counted rules and output-styles (`M-BUG-4`). - `enumerateAgents` requires a valid `name`+`description` frontmatter block before counting a file, matching CC's actual registration rule — frontmatter-less files are silently skipped (`M-BUG-5`). - Added a `hasText` frontmatter helper. TDD: 4 failing tests (one per bug) → fix → full suite 1305/0, frozen v5.0.0 + SC-5 + default-output snapshots untouched (agent enumeration is machine-dependent and never seeded into a snapshot). ## [5.12.2] - 2026-06-24 ### Summary "Honest census" — fixes a plugin-enumeration bug (`M-BUG-1`, dogfooding find) that made the always-loaded inventory untrustworthy on two common setups: machines with **disabled plugins** and **polyrepo marketplaces**. `enumeratePlugins` walked `~/.claude/plugins/marketplaces//plugins/` and ignored both enable-state and the polyrepo cache layout, so it **over-counted phantom agents** from disabled/unenabled plugins while **missing the entire enabled polyrepo set** (whose plugins live under `cache/`, not `marketplaces//plugins/`). It now gates on `installed_plugins.json` + `enabledPlugins` and enumerates each plugin from its active `installPath`, with the marketplaces walk as fallback. Affects `manifest`, `whats-active`, the agent-listing (AGT) and `token-hotspots` for every such user. No new finding ID or scanner (count stays **16**, agents **7**, commands **21**); `--json`/`--raw` stay byte-stable and the frozen v5.0.0 + SC-5 + default-output snapshots are untouched. **1301** tests (+4). ### Fixed - **`active-config-reader` plugin enumeration (`M-BUG-1`).** `enumeratePlugins(repoPath)` now honors `enabledPlugins` (disabled plugins no longer contribute phantom agents/skills/commands) and enumerates polyrepo plugins from their active `installPath` in `installed_plugins.json` (not only `marketplaces//plugins/`). Real-machine verify: the always-loaded agent listing dropped from 114 to 104 with the phantom ghosts gone and the true enabled set present. TDD: 4 failing tests → fix → full suite 1301/0, snapshots untouched. ## [5.12.1] - 2026-06-24 ### Summary "Footgun guard" — Pattern H (stale plugin-cache versions, `token-hotspots`) recommended deleting stale version directories without warning that a currently-running session may still hold one of those versions for its whole lifetime. "Stale" is judged against `installed_plugins.json` (what NEW sessions load), so the recommendation could reproduce the exact failure that broke a live session: deleting the directory pulls the files out from under the running session, which then breaks and must `/exit` + restart. Recommendation text only — no new finding ID or scanner (count stays **16**, agents **7**, commands **21**), no token figures changed, so `--json`/`--raw` stay byte-stable and the frozen v5.0.0 + SC-5 + default-output snapshots are untouched. **1297** tests. ### Fixed - **Pattern H live-session caveat (`token-hotspots`, `plugin-cache-hygiene`).** The stale-cache cleanup recommendation now cautions against deleting a version a running session still uses, and tells affected sessions to `/exit` + restart to pick up the active version — closing the footgun that broke a live session during the C4 cache cleanup. ## [5.12.0] - 2026-06-23 ### Summary "Auto-calibration" — completes the deferred half of B8. `--context-window auto` now **probes the configured model** and calibrates SKL/CML budgets to its real context window, instead of always falling back to the conservative advisory anchor. A 1M-tier host self-calibrates without the manual `--context-window 1000000`. No new finding ID or scanner (count stays **16**, agents **7**, commands **21**); the default and explicit `--context-window` paths are unchanged, so `--json`/`--raw` stay byte-stable and the frozen v5.0.0 + SC-5 snapshots are untouched. **1296** tests. ### Added - **B8b — model→window auto-probe.** `lib/context-window.mjs` gains a pure `modelToContextWindow()` that maps a configured model id/alias to its context window: - the explicit `[1m]` tier tag wins (the running session model surfaces as e.g. `claude-opus-4-8[1m]`); - known 1M-tier families `LARGE_CONTEXT_MODEL_IDS` (`claude-fable-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6` — verified June 2026 against the platform.claude.com models overview) match by substring, so dated (`-20260528`) and provider-prefixed (`us.anthropic.…`) IDs resolve too; - the short aliases `opus` / `sonnet` / `fable` / `opusplan` resolve to 1M. - Models we cannot confirm (Haiku, older 200k-era IDs, unknown) return `null` — the caller then keeps the conservative anchor rather than guess a relaxed budget. - **New IO helper `lib/active-model.mjs` `resolveActiveModel()`** reads the configured model the way Claude Code resolves it: the shell `ANTHROPIC_MODEL` override first, otherwise the settings cascade `model` field (user `~/.claude` → project `.claude` → project-local, local wins). Reads the cascade files directly (like `isBundledSkillsDisabled`) and takes an injectable `env`, so it stays deterministic and hermetic under the test HOME. Returns `null` when no model is pinned anywhere. ### Changed - **`resolveContextWindow(arg, opts)` — the `auto` branch now probes.** It maps `opts.model` via `modelToContextWindow()`: a recognized 1M-tier model calibrates to its window (source `auto-probed`, **not** advisory); an unknown or unpinned model keeps the conservative 200k anchor and stays advisory (source `auto-unresolved`, the pre-B8b `auto` behavior). `scan-orchestrator` resolves the active model (only when the flag is `auto`) and threads it in; `posture` inherits this via `runAllScanners`. The default (no flag) and explicit `--context-window ` paths ignore `opts.model` and are unchanged. ## [5.11.0] - 2026-06-23 ### Summary "Precision polish" — the two LOW-priority calibration gaps from the hardening plan, both additive. **B7** flags an oversized SKILL.md body (`CA-SKL-003`), honestly framed as an on-demand cost. **B8** lets `CA-SKL-002` and the CML char-budget calibrate to a real context window via `--context-window`, and downgrade to advisory when the window is unknown — so the 200k anchor stops crying wolf on a 1M host. Scanner count stays **16** (both extend the existing SKL/CML scanners), agents **7**, commands **21**; `--json`/`--raw` stay byte-stable and the frozen v5.0.0 + SC-5 snapshots are untouched. **1279** tests. ### Added - **B7 — oversized skill body (`CA-SKL-003`, low).** `measureActiveSkillListing()` now measures the SKILL.md **body** below the frontmatter (the file was already read in full; only the frontmatter was parsed). A body over ~5,000 tokens (`BODY_TOKEN_THRESHOLD`) fires `CA-SKL-003`, recommending a supporting-file split and `context: fork` for heavy skills. - **Honest framing (Verifiseringsplikt):** `BODY_CALIBRATION_NOTE` marks this as an **on-demand** cost — the body loads only when the skill is invoked, **not** every turn like the always-loaded listing — and an estimate (chars/4), hence low severity. Distinct from the always-loaded listing-budget findings. - **B8 — context-window calibration (`--context-window`).** `CA-SKL-002` (skill-listing budget) and the CML char-budget threshold now calibrate to a resolved context window instead of always anchoring at 200k. `lib/context-window.mjs` gains `resolveContextWindow()` and `scaleForWindow()`: - `--context-window ` calibrates the budget to `n` (e.g. `1000000` relaxes the SKL listing budget to ~20,000 tok, so an over-200k listing is within budget and does not fire). - `--context-window auto` keeps the conservative 200k anchor but marks the result **advisory** — SKL/CML emit the finding at **info** rather than as a budget breach (model→window auto-probing is deferred to a later B8b). - No flag → the conservative 200k anchor at full severity, **byte-identical** to the pre-B8 default. - Both SKL and CML keep an untouched default branch (`window === 200k && !advisory`) for byte-stability plus a calibrated branch. The flag is wired through `scan-orchestrator` and `posture`; `runAllScanners` resolves it once and threads `{ contextWindow }` to the scanners (others ignore the third arg). - **CPS intentionally excluded:** it has no window-anchored budget (a fixed 150-line volatility heuristic), so there is nothing to calibrate. ## [5.10.0] - 2026-06-23 ### Summary "Deferral & injection hygiene" — three additive hardening levers that extend existing scanners toward a tighter always-loaded prefix. **B4** detects config that forces full MCP tool schemas into the always-loaded prefix (deferral defeated), with a CLI-over-MCP companion lever. **B5** adds a hook `additionalContext`-injection advisory plus a filter-before-Claude-reads lever. **B6** extends the cache-prefix scanner to follow `@import`s and flag volatile content in imported files. Scanner count stays **16** (all three extend existing scanners), agents **7**, commands **21**; `--json`/`--raw` stay byte-stable and the frozen v5.0.0 + SC-5 snapshots are untouched. **1257** tests. ### Added - **B4 — MCP tool-schema deferral (`CA-TOK-006`; tokens patterns 7 → 8).** By default Claude Code defers MCP tool schemas (names-only, ~120 tok; full schemas load on demand via tool search). `CA-TOK-006` detects config-file signals that force the FULL schemas into the always-loaded prefix every turn: `settings.json` `env.ENABLE_TOOL_SEARCH="false"` (high), `"ToolSearch"` in `permissions.deny` (high), a configured Haiku model (medium), and per-server `.mcp.json` `alwaysLoad:true` (CC v2.1.121+, high). Severity scales with the aggregate forced-upfront tokens (medium-confidence reasons cap at medium). New pure engine `lib/mcp-deferral.mjs` (`assessMcpDeferral`, unit-tested, no IO) shared by TOK and GAP. A feature-gap **CLI-over-MCP** lever fires only as a companion to `CA-TOK-006` (prefer `gh`/`aws`/`gcloud` over MCP for common operations — CLI adds zero context tokens until used). - **Honest scoping (Verifiseringsplikt):** triggers on config files ONLY, never `process.env` shell vars. Vertex / custom `ANTHROPIC_BASE_URL` / a runtime `/model` switch are launch state (would flap snapshots machine-dependently), so they are DISCLOSED in every finding, not triggered. Mechanism verified 2026-06-23 against code.claude.com/docs (`context-window.md`, `mcp.md#configure-tool-search` + `#exempt-a-server-from-deferral`, `costs.md`); the prefix-cache-invalidation claim was NOT-CONFIRMED in docs and is not asserted. - **B5 — hook `additionalContext`-injection advisory + filter-before lever.** HKV emits an info advisory when a hook injects unfiltered command output into `additionalContext` (it enters context every turn the hook fires). A feature-gap **filter-before-Claude-reads** companion lever cites the documented `filter-test-output.sh` pattern (filter at the hook, not after Claude reads). - **B6 — CPS `@import` volatile scan.** The cache-prefix scanner now follows `@import`s (one hop) and flags volatile content in the imported file that breaks the cached prefix — a new medium finding ("Volatile content in @imported file breaks cached prefix"), keyed on the resolved file with evidence "imported by (@ at line N)". Scoped to one hop (the IMP scanner owns deep chains); resolved files that are themselves discovered CLAUDE.md are skipped (own iteration). ### Notes - Scanner count unchanged at **16** — B4/B5/B6 all extend existing scanners (TOK / HKV + GAP / CPS). `--json`/`--raw` output remains byte-stable; frozen v5.0.0 + SC-5 snapshots untouched. ## [5.9.0] - 2026-06-23 ### Summary "Machine-wide token lens" — the three highest-impact hardening gaps toward whole-machine token tuning (the shift from *correct?* to *best-practice-tuned?*). **B1** measures the always-loaded agent listing (a new orchestrated scanner), **B2** gives the campaign ledger a machine-wide always-loaded token bill that counts the shared global layer once, and **B3** makes the token and conflict scans cache-aware so stale plugin-cache versions stop polluting them. Every new token figure carries an honesty caveat where the load mechanism is inferred or the number is an upper-bound estimate. `--json`/`--raw` stay byte-stable; frozen v5.0.0 + SC-5 snapshots untouched. Scanner count 15 → **16**, agents **7**, commands **21**; **1215** tests. ### Added - **B1 — Agent-listing budget (new orchestrated scanner `AGT`, count 15 → 16).** Claude Code injects every active agent's name+description into the system prompt on every turn; on a heavily-plugged machine this is often the dominant single always-loaded source, yet nothing measured it. New `scanners/agent-listing-scanner.mjs` + `scanners/lib/agent-listing-budget.mjs`: - `CA-AGT-001` — per-agent description over the soft bloat cap (advisory; mirrors the TOK 500-char SKILL.md heuristic — agents have no verified per-description cap, so nothing is truncated, the description is simply re-sent in full every turn). - `CA-AGT-002` — the summed agent-listing estimate exceeding the listing budget. - **Intellectual-honesty contract:** both findings are LOW and explicitly **inferred / upper-bound** — the agent-listing mechanism is undocumented (agents are absent from Claude Code's published context breakdown), the per-agent estimate is the frontmatter cap not a measurement, and the budget is a config-audit heuristic anchored on a conservative 200k window. The evidence discloses all three caveats rather than overstating certainty. - **B2 — Machine-wide always-loaded token roll-up (campaign).** The campaign ledger now carries a token bill alongside the severity roll-up. `campaign refresh-tokens` runs a live cross-repo sweep (`readActiveConfig` → `buildManifest` → ownership split) that factors out the **shared global always-loaded layer** (global CLAUDE.md + agent listing + global MCP + unscoped global rules) and counts it **once**, then adds each repo's delta — surfacing one machine-wide always-loaded total plus a ranked "most expensive repos" table. The shared layer is written once from the first readable repo (counted-once guard against the `whats-active` double-count). - **B3 — Stale plugin-cache disk-cleanup finding (`TOK`).** Stale `~/.claude/plugins/cache` versions surface as a finding (`--global`) categorized **Dead config** (not "Wasted tokens"): they occupy disk but load on zero turns. A per-finding category override (`plugin-cache-hygiene`) plus a dedicated humanizer translation keep the prose honest ("safe to delete … zero tokens per turn … housekeeping, not a performance problem"). ### Changed - **B3 — Cache-aware filtering (folds in B0): `--exclude-cache` (default ON) for token-hotspots and the orchestrator.** `~/.claude/plugins/cache` holds *both* active and stale plugin versions — `installed_plugins.json`'s `installPath` points INTO the cache — so a blunt "skip all of plugins/cache" would drop *active* config. Discovery is now **version-aware**: it reads the adjacent `installed_plugins.json`, keeps each plugin's active version dir, and drops only stale ones. Result: stale versions no longer pollute the token-hotspot ranking, and stale cached `hooks.json` no longer inflate CNF "duplicate hook" findings (verified: cache exclusion measurably drops the count). `--no-exclude-cache` restores the full walk. When `installed_plugins.json` is absent or unparseable, nothing is filtered (never silently drop live config). - **B2 — `manifest` ownership split.** `splitManifestByOwnership` separates the shared-global layer from per-repo deltas; `~/.claude.json:projects` MCP is correctly classified as a per-repo delta (its slice is keyed on the repo path), not a shared-global source. - Humanizer: a finding's `category` can now override the scanner-default impact label (`CATEGORY_TO_IMPACT`), used so a TOK finding can read as **Dead config** when that is the honest bucket. The raw `category` field is unchanged, so `--json`/`--raw` stay byte-stable. ## [5.8.0] - 2026-06-23 ### Summary "Campaign motor" — a durable, machine-wide audit **campaign** that sits ABOVE individual sessions. A single audited repo is one session; a fleet of repos is a campaign. This release adds a durable ledger (per-repo lifecycle + machine-wide roll-up), a read-only reporter and a human-approved write surface behind one `/config-audit campaign` command, a cross-repo prioritized backlog, and per-repo plan export — with execution deliberately **reusing** the existing implement/rollback engines rather than adding new machinery. The whole campaign surface is `-cli`/lib only, so the scanner count stays **15**, agents **7**, and the snapshot suite is untouched; commands 20 → **21**. ### Added - **Durable campaign ledger (`scanners/lib/campaign-ledger.mjs`).** The ledger that sits above sessions: a repo list + per-repo lifecycle (`pending → audited → planned → implemented`) + a machine-wide roll-up (counts by status and by severity, aggregated across repos). Persists to a single JSON file **outside** the plugin dir (`~/.claude/config-audit/campaign-ledger.json`, next to `sessions/`) so it survives uninstall / reinstall / upgrade. Pure transforms (`createLedger` / `addRepo` / `setRepoStatus` / `rollUp`) with `now` injected (never the clock) + a soft `validateLedger` + a thin IO shell; `schemaVersion` stamped from the start. - **Cross-repo prioritized backlog (`buildBacklog`).** One machine-wide work list — each item is a repo (the ledger tracks per-repo severity *counts*, not individual findings), ordered by a weighted severity score (`critical:1000 / high:100 / medium:10 / low:1`) with a deterministic tie-break so criticals always win. Includes only repos not yet `implemented` that have known findings. - **`/config-audit campaign` (commands 20 → 21).** A thin orchestrator that always **reports** first (read-only `campaign-cli`: status, roll-up, backlog) and, for `init` / `add` / `set-status`, proposes the change and mutates **only on explicit human approval** via `campaign-write-cli` — it never hand-edits the ledger JSON (verification duty). `add --discover ` finds git repos under a root to pick from; marking a repo `audited` attaches findings-by-severity from its session (or user-provided counts), never invented. - **Per-repo plan export (`campaign-export-cli`).** `--repo ` resolves the repo's linked session, reads its `action-plan.md`, and (under opt-in `--write`) drops a provenance-stamped, byte-faithful copy into that repo's own `docs/config-audit-plan-.md` — a durable record next to the code it changes. Advisory gates for "no session linked" / "no action plan yet". ### Changed - **Execution by reuse.** A campaign adds no execution engine of its own: the exported `docs/` file is the durable record, while `/config-audit implement` (backup + apply + verify) and `/config-audit rollback` do the work, then `campaign set-status implemented` records it. - **`knowledge-refresh` wired into the surface.** The v5.7.0 `knowledge-refresh` command shipped but was never routed; it is now in the `/config-audit` router (argument-hint + routing) and the `help` command table. - **CLAUDE.md trimmed to lean invariants.** The 19 per-scanner / per-block implementation notes moved verbatim into `docs/scanner-internals.md` ("Implementation notes"); CLAUDE.md drops 540 → 134 lines (raising its own config grade B → A). A real "Conventions" section now points at `.claude/rules/`. ### Tests - 1091 → **1168** (campaign ledger +28, read-only campaign-cli +8, campaign-write-cli +11, cross-repo backlog +12, plan-export +18). ## [5.7.0] - 2026-06-21 ### Summary "Optimization lens" — the first detector of the **«is this config optimally shaped?»** axis, on top of the existing «is it correct?» checks, plus a **living** machine-readable knowledge layer that keeps the lens's rules fresh. A config that *works* can still fit a better mechanism (a long CLAUDE.md procedure that belongs in a skill, a lifecycle instruction that belongs in a hook); this release adds a hybrid deterministic + opus motor that surfaces those as opportunities, every finding citing a provenance-stamped best-practices register entry. Scanner count 14 → **15**, commands 18 → **20**, agents 6 → **7**. ### Added - **Best-practices register (knowledge layer).** `knowledge/best-practices.json` — a provenance-stamped, schema-validated register (each entry: `id` / `claim` / `confidence` / `source` + optional `mechanism` / `lensCheck`), the first runtime-consumed file under `knowledge/` and the source of truth for the optimization lens. Loaded/validated by `scanners/lib/best-practices-register.mjs`. Only **confirmed** entries are user-facing (verification duty). - **Optimization-lens scanner (`CA-OPT`, scanner count 14 → 15).** New orchestrated scanner: `CA-OPT-001` (LOW, *Missed opportunity*) — a CLAUDE.md procedure (≥6 consecutive numbered steps) that would fit better as a skill, with recommendation + provenance from register entry `BP-MECH-003`. Deterministic and conservative (no false positives on the negative corpus). - **`/config-audit optimize` + `optimization-lens-agent` (opus, agents 6 → 7).** The prose-judgment half of the lens, for the cases the deterministic scanner deliberately skips: lifecycle phrasing → hook (`BP-MECH-001`), unscoped path-specific instruction → rule (`BP-MECH-002`), absolute "never" prohibition → permission (`BP-MECH-004`). A pre-filter (`scanners/lib/lens-prefilter.mjs`, unit-tested) does cheap recall; the opus agent is the precision gate, dropping low-confidence candidates and citing the register id + source for each kept one. Agent-driven → deliberately not byte-stable (own command, outside the snapshot suite). - **`/config-audit knowledge-refresh` (commands 19 → 20).** Keeps the register current so the lens never reads stale rules. A deterministic core (`scanners/lib/knowledge-refresh.mjs`, `assessFreshness` with an **injected** reference date — it never reads the clock) classifies each entry fresh/stale by the age of its `source.verified` stamp (default 90-day quarterly cadence); a read-only CLI (`knowledge-refresh-cli.mjs`, `--reference-date` / `--stale-after` / `--dry-run`) reports staleness without ever writing the register or hitting the network. The command layer re-verifies stale sources (WebFetch) and polls the CC changelog + Anthropic blog (WebSearch), then applies **only human-approved writes** (verification duty). Web/judgment-driven → deliberately not byte-stable. ### Notes - **Byte-stability.** The new OPT scanner's additive entry is stripped at compare time (`tests/helpers/strip-added-scanner.mjs`); the original 13-scanner `--json` / `--raw` output stays byte-stable, and only the SC-5 default-output snapshots are regenerated (additive). The two non-deterministic surfaces (`optimize`, `knowledge-refresh`) live outside the snapshot suite by design. `-cli` / lib modules are not orchestrated scanners (self-audit excludes them), so the scanner count stays **15**. Suite **1091** pass (hermetic HOME), self-audit A/A (config 94, plugin 100), readmeCheck passed. ## [5.6.0] - 2026-06-20 ### Summary "Steering-model II" — the per-mechanism **load-pattern** and **compaction-survival** model lands end-to-end across enumeration, the `manifest` / `tokens` token accounting, and a new output-style scanner. v5.5.0 shipped A+E (additive to existing scanners); this release adds the **Foundation** those features needed, plus **B** (load-pattern accounting) and **C** (the new `CA-OST` family, scanner count 13 → **14**). Every new claim is anchored to the live Claude Code docs (`code.claude.com/docs`) per the project's verification duty. ### Added - **Foundation — active-config-reader enumeration + load-pattern model.** `active-config-reader` now enumerates **rules**, **agents**, and **output styles** (alongside the existing CLAUDE.md / plugins / skills / hooks / MCP). Each source carries a `loadPattern` (`always` / `on-demand` / `external`), `survivesCompaction`, and `derivationConfidence`, derived from the published loading model. The frontmatter parser additionally reads YAML **block sequences** (`paths:` as a `- item` list), resolving a pre-existing RUL false-positive. - **C — output-style scanner (`CA-OST`, scanner count 13 → 14).** New orchestrated scanner: `CA-OST-001` (MEDIUM) a user/project custom output style missing `keep-coding-instructions: true` (defaults false) silently strips Claude Code's built-in software-engineering instructions when active (V10); `CA-OST-002` (LOW) a **plugin** output style with `force-for-plugin: true` overrides the user's selected `outputStyle` (V11); `CA-OST-003` (MEDIUM) a settings `outputStyle` matching no built-in (Default / Explanatory / Learning / Proactive) nor discovered custom style is dead config. ### Changed - **B — load-pattern accounting in `manifest` and `tokens`.** `manifest` now reports **component-level** token sources (the coarse plugin roll-up that double-counted its own skills / rules / agents is gone), tags every source with the load-pattern triple, and leads with an **always-loaded subtotal** ("≈X tokens enter context every turn before you type"). `token-hotspots` annotates each ranked hotspot with its load pattern. `manifest`'s `total` changes (de-duplicated) — the intended correctness fix. ### Notes - **Byte-stability.** `--json` / `--raw` stay byte-stable for the original 13 scanners. The frozen v5.0.0 snapshots are **preserved**: B2's additive hotspot fields and C's additive OST scanner entry are stripped at compare time (`tests/helpers/strip-hotspot-load-pattern.mjs`, `tests/helpers/strip-added-scanner.mjs`); only the SC-5 default-output snapshots are regenerated. Suite 1023 pass, self-audit A/A (config 93, plugin 100), readmeCheck passed. ## [5.5.0] - 2026-06-20 ### Summary "Steering-model I" — two additive findings on the compaction-durability / dead-config theme, extending the existing RUL, CML, and PLH scanners (scanner count stays **13**; `--json` and `--raw` remain byte-stable). Both are anchored to the live Claude Code docs (`code.claude.com/docs`) per the project's verification duty. The Foundation work (active-config-reader enumeration) and feature B are deferred to v5.6 — A and E are additive to existing scanners and do not consume it. ### Added - **RUL / CML compaction-durability findings (A)** — per the official "what survives compaction" model (`context-window.md`), only the project-root CLAUDE.md and unscoped rules are re-injected after a context compaction. Two structural findings, both severity **LOW**: a large (>50-line) **path-scoped** rule reloads only on a matching file read and is not re-injected after compaction, so a must-hold rule can silently drop mid-session (RUL); a **nested** (subdirectory) CLAUDE.md is not re-injected after compaction — only the project root is (CML). - **PLH plugin-agent ignored-frontmatter finding (E)** — plugin subagents silently ignore `hooks`/`mcpServers`/`permissionMode` frontmatter (honored only for user/project agents in `.claude/agents/`). Setting them in a plugin agent is dead config: `permissionMode` is **MEDIUM** (it implies a restriction Claude Code does not apply — false security); `hooks`/`mcpServers` are **LOW**. Additive to PLH's agent-frontmatter loop; one humanizer pattern covers the three fields. ### Known limitations - The lightweight frontmatter parser (`scanners/lib/yaml-parser.mjs`) reads inline `paths: "a, b"` but not YAML block sequences (`paths:\n - a`), so a block-sequence-scoped rule is still seen as unscoped and the RUL durability check (A) does not fire on it. Pre-existing and broader than A; to be fixed in the v5.6 Foundation work. ## [5.4.1] - 2026-06-20 ### Summary Scanner-correctness patch. Five primary-source-verified fixes to the PLH, HKV, and RUL scanners — no new scanner, no command or output-format change (scanner count stays **13**; `--json` and `--raw` remain byte-stable). All fixes are anchored to the live Claude Code docs (`code.claude.com/docs`, docs map 2026-06-19) per the project's verification duty. ### Fixed - **HKV hook events** — added `Setup`, `UserPromptExpansion`, and `PostToolBatch` to the valid-event set (verified against `hooks.md`); a valid hook using one of these was wrongly flagged "Unknown hook event … will never fire", which could lead a user to delete a working hook. The "(N total)" hint in the recommendation is now derived from the set so it cannot drift. - **HKV `post-session`** — removed from the valid-event set. The 2.1.169 changelog `post-session` hook is a **self-hosted-runner** workspace-lifecycle hook (runs after the session, before the workspace is deleted), **not** a `settings.json` hook event — it is absent from `hooks.md` (all settings.json events are PascalCase). A bogus `post-session` settings hook is now correctly flagged. - **RUL `globs` rule** — reworded the finding. Only `paths:` is documented as the rule-scoping field; whether Claude Code ever read `globs` is unverified, so the previous "deprecated/legacy" framing overclaimed. The finding (and its humanizer entry) now steer to the documented `paths:` field without asserting deprecation. The coupled fix-engine title match was updated in lockstep. - **PLH required frontmatter** — `model`/`tools`/`name`/`allowed-tools` are optional per the CC agent/command docs; a missing one is no longer flagged. - **PLH CLAUDE.md component sections** — a commands/agents/hooks section is required only for a component the plugin actually ships. ## [5.4.0] - 2026-06-19 ### Summary Plugin-hygiene & settings-validation hardening. Three additive findings extend the existing PLH and SET scanners — no new scanner file, so the scanner count stays **13**. PLH gains plugin-folder shadow detection and `skills:`-array validation; the settings validator now checks `autoMode` structure and flags it when placed in shared project settings, where Claude Code does not read it. Scanner internals only — no command, agent, or output-format changes; `--json` and `--raw` remain byte-stable. ### Added - **PLH plugin-folder shadowing** (`CA-PLH-015`) — flags a `plugin.json` component-path key in the *replaces* set (`commands`/`agents`/`outputStyles`) that points at a custom path while the default folder of that name still exists on disk, so the folder is silently ignored (dead config). Mirrors Claude Code's own warning in `/doctor`, `claude plugin list`, and the `/plugin` detail view (v2.1.140+). Severity **medium**, `category: 'plugin-hygiene'`, `details: { field, ignoredDir, customPaths }`. Deliberately excludes `skills` (adds to the default scan, never shadows) and `hooks`/`mcpServers`/`lspServers` (own merge rules); honors the explicit-address exception (a custom path resolving *into* the default folder is not flagged). - **PLH `skills:`-array validation** (`CA-PLH-016`) — validates each `plugin.json` `skills` entry (string or array) resolves to an existing directory inside the plugin root; one finding per bad entry with `problem` ∈ `non-string` / `escapes-root` / `not-found` / `not-a-directory`. Mirrors `claude plugin validate` (~2.1.145). Severity **medium**, `category: 'plugin-hygiene'`, `details: { field: 'skills', entry, problem }`. Path-escape detection is containment-based (resolve + plugin-root prefix), backed by the docs' path-traversal rule. - **SET autoMode structure + dead-config** (`CA-SET`) — `autoMode`, if present, must be an object whose only keys are `environment`/`allow`/`soft_deny`/`hard_deny`, each a **string array** (the literal `"$defaults"` is a valid entry); unknown sub-keys and wrong value types are flagged (`problem` ∈ `not-an-object` / `unknown-subkey` / `not-string-array`), severity **medium**. Separately, `autoMode` placed in **shared** project settings (`.claude/settings.json`) is flagged as dead config (`problem: 'shared-project-scope'`), severity **low** — Claude Code's classifier does not read `autoMode` from shared project settings. The two sub-checks are independent. Both verified against `code.claude.com/docs/en/auto-mode-config`. ### Internal - **Test count** — 936 → **949** (+13: PLH-015, PLH-016, and SET autoMode structure/dead-config). - **Scanner count stays 13** — all three findings extend existing PLH and SET scanners; no new scanner file, no badge/table/`countScannerShape` change. - **Byte-stability** — `json-backcompat` + `raw-backcompat` + the SC-5 default-output snapshot stay green; the three findings are additive and appear only in configs that trigger them. ## [5.3.0] - 2026-06-19 ### Summary Permission-rule & plugin-hygiene hardening. Five additive scanner findings extend the existing DIS, CML, PLH, and feature-gap scanners — no new scanner file, so the scanner count stays **13**. DIS gains forbidden-parameter and ineffective-allow-wildcard detection; CML mirrors Claude Code's own 40.0k-char "large CLAUDE.md" startup warning, context-window scaled; PLH flags two plugins that declare the same name; and feature-gap recommends `disableBundledSkills` under skill-listing pressure. The cross-plugin command-name finding is reframed from a HIGH conflict to a LOW ambiguity. Scanner internals only — no command, agent, or output-format changes; `--json` and `--raw` remain byte-stable. ### Added - **DIS forbidden-param rules** — flags `Tool(param:value)` whose key is the tool's own canonicalizing field (`command`/`file_path`/`path`/`notebook_path`/`url`); Claude Code ignores these and emits a startup warning. Severity by intent: **deny/ask = false security (medium)**, **allow = dead config (low)**. Valid forms (`Bash(npm:*)`, `WebFetch(domain:host)`, `Agent(model:opus)`) are never flagged. Predicate `forbiddenParamRule` in `permission-rules.mjs`. - **DIS ineffective allow-wildcards + `Tool(*)` deny-all** — flags unanchored tool-name globs in `permissions.allow` (`*`, `B*`, `mcp__*`) that CC silently skips (low); treats `Tool(*)` as deny-all (`Bash(*)` ≡ `Bash`) so a bare allow killed by it is reported as dead config. Valid `mcp____*` is never flagged. - **CML context-window-scaled char budget** — new `CA-CML` finding mirroring CC's startup warning *"Large CLAUDE.md will impact performance (X chars > 40.0k)"*. Anchors on the conservative 200k window; discloses the relaxed ~200,000-char figure at 1M context. Severity **medium** (token cost). Char-keyed, complementary to the 200/500-line checks. Window constants live in the shared `scanners/lib/context-window.mjs`. - **PLH plugin namespace collision** — flags 2+ discovered plugins declaring the same `name` in `plugin.json`. Namespaces collapse; CC picks an undocumented winner; the loser's commands/skills/agents go silently unreachable. Severity **medium** (dead config), `category: 'plugin-hygiene'`, COL-shaped `details.namespaces`. Keys on the declared `name`, not `basename(dir)`; name-less plugins are excluded. - **feature-gap `disableBundledSkills` lever** — conditional recommendation to set `disableBundledSkills` when the active skill listing is over budget; remediation companion to SKL `CA-SKL-002`. ### Changed - **PLH cross-plugin command-name overlap: HIGH → LOW** — reframed from "conflict" to "ambiguity". Commands are namespaced (`/name:command`) so both stay reachable; only a same-`name` plugin collision loses components (covered by the new namespace-collision finding). Now group-first (one finding per command name listing every namespace), COL-shaped `details.namespaces`. The old HIGH `Cross-plugin command name conflict` finding and its humanizer entry are removed. Scoring impact: configs with cross-plugin command overlap score slightly higher. Not a `--json`-shape break — no test or consumer asserted the old HIGH. ### Fixed - **README scanner table** — added the missing SKL row (12 → 13 rows); the prose table lagged the badge/self-audit count (the `--check-readme` gate is number-only and didn't catch it). ### Internal - **Extract skill-listing budget to shared lib** — `scanners/lib/context-window.mjs` as the single source of truth for the 200k/1M context-window constants, re-exported by `skill-listing-budget.mjs` and consumed by the new CML char-budget finding. ### Knowledge - Three backing entries added: `disableBundledSkills` as a token-efficiency lever (`prompt-cache-patterns.md`); the 40.0k-char "large CLAUDE.md" startup warning + CC 2.1.169 context-window scaling (`claude-code-capabilities.md`); and `Tool(param:value)` permission semantics — deny/ask-only matching plus the canonicalizing-field rules CC ignores (`claude-code-capabilities.md`). ### Test count - 936 tests across 56 test files (unchanged — this release covers already-tested work on `main`). ### Verification - 936/936 tests pass (`node --test 'tests/**/*.test.mjs'`). - `node scanners/self-audit.mjs --json --check-readme` → `configGrade: A`, `pluginGrade: A`, `readmeCheck.passed: true`. - README version badge updated: `version-5.2.0` → `version-5.3.0`. Scanner count stays 13; tests badge already `936+`. ## [5.2.0] - 2026-06-18 ### Summary Claude Code 2.1.114→181 compatibility + skill-listing budget release. Adds a new orchestrated scanner (SKL) for the model's skill-listing token budget, refreshes five validators to recognize the settings/hook surface shipped across CC 2.1.114–181, and eliminates a batch of false positives surfaced by an adversarial gap-review. Scanner internals only — no command, agent, or output-format changes. ### Added - **`scanners/skill-listing-scanner.mjs` (SKL)** — new orchestrated deterministic scanner (→ 13 orchestrated scanners). `CA-SKL-001` (medium): an active skill description over the verified 1,536-char listing cap (CC 2.1.105) is silently truncated in the model's skill listing. `CA-SKL-002` (low): the summed length of all active descriptions (each counted up to the cap) over the listing budget (~2% of context, CC 2.1.32) — anchored on a conservative 200k window with a note that the budget scales 5× on 1M-context models; leads with the measured sum, an estimate not telemetry. HOME-scoped (all user + plugin skills). Remediation surfaces `disableBundledSkills` / `skillOverrides` / description trimming. ### Changed - **settings-validator** — accepts CC 2.1.114–181 settings keys and `xhigh` reasoning effort. - **hook-validator** — recognizes `MessageDisplay` and post-session hook events (28 events). - **claude-md-linter** — CLAUDE.md length reframed from a HIGH "adherence cliff" to a MEDIUM token-cost finding (model-neutral, context-window aware). - **tokens** — stale "Opus 4.7" framing refreshed to model-neutral with an Opus 4.8 anchor. - **Knowledge corpus** — refreshed to the Opus 4.8 era (CC 2.1.114→181). ### Fixed - **mcp-config-validator** — no longer flags auto-injected or POSIX-style env vars; removed an invented `trust` field that does not exist in the MCP config schema. - **permissions (DIS/CNF)** — dead-allow detection and conflict matching are now parameter-aware, removing false positives on parameterized permission rules. ### Internal - **Hermetic test isolation** — byte/snapshot tests and all 12 CLI-spawning test files now isolate `HOME` via the `hermetic-home` helper, closing a leak class where the real `~/.claude` skills and config bled into fixture-scoped runs (notably SKL, which is HOME-scoped regardless of `includeGlobal`). Adversarially verified via a devil's-advocate gap-review pass. ### Test count - 792 → 875 tests across 52 → 55 test files. ### Verification - 875/875 tests pass (`node --test 'tests/**/*.test.mjs'`). - `node scanners/self-audit.mjs --json --check-readme` → `configGrade: A` (96), `pluginGrade: A` (100), `readmeCheck.passed: true`. - README badge updated: `tests-792+` → `tests-875+`. ## [5.1.0] - 2026-05-01 ### Summary Plain-language UX humanizer release. Default output of all 18 commands now leads with prose; technical IDs surface at end-of-line as references rather than headlines. Non-expert users — the bulk of the OSS audience — now read findings like "Fix soon: The same automation is set up more than once" instead of "[high] CA-CNF-001: Hook duplicate event registration". Scanner internals are unchanged; humanization is a pure output-time transform applied at the rendering layer. The `--raw` flag preserves v5.0.0 verbatim output for tooling that scrapes stderr; `--json` is unchanged from v5.0.0 and remains byte-stable for programmatic consumption. Delivered across 6 waves (Wave 0 baseline → Wave 1 humanizer module → Wave 2 test re-anchoring → Wave 3 CLI wiring → Wave 4 contract tests → Wave 5 templates/agents → Wave 6 release). ### Added - **`scanners/lib/humanizer.mjs`** — pure-function output translator: `humanizeFinding`, `humanizeFindings`, `humanizeEnvelope`, `computeRelevanceContext`. Never mutates inputs. Adds three additive fields per finding (`userImpactCategory`, `userActionLanguage`, `relevanceContext`) and replaces title/description/recommendation when a translation is available; falls through to originals otherwise. - **`scanners/lib/humanizer-data.mjs`** — TRANSLATIONS table for 13 scanner prefixes (CML, SET, HKV, RUL, MCP, IMP, CNF, COL, TOK, CPS, DIS, GAP, PLH). Three-step lookup per finding: exact title → regex pattern → `_default` → fall through to scanner original. - **`--raw` flag** threaded through every CLI: `posture.mjs`, `scan-orchestrator.mjs`, `token-hotspots-cli.mjs`, `manifest.mjs`, `whats-active.mjs`, `fix-cli.mjs`, `drift-cli.mjs`, `self-audit.mjs`. Bypasses humanizer; emits byte-stable v5.0.0 verbatim output. - **User-impact categories** (5 labels): Configuration mistake, Conflict, Wasted tokens, Missed opportunity, Dead config. Mapped from scanner prefix. - **Action-language phrases** (5 labels): Fix this now, Fix soon, Fix when convenient, Optional cleanup, FYI. Mapped from severity. - **Relevance context** (3 values): `test-fixture-no-impact`, `affects-this-machine-only`, `affects-everyone`. Computed from finding's file path — basenames matching `*.local.*` and paths containing `/tests/fixtures/` are recognized. - **Self-audit terminal humanization** — `formatSelfAudit()` routes through `humanizeEnvelope`. JSON path (`--json`) is unchanged; humanization applies only to the prose terminal render. - **Forbidden-words lint** (`tests/lint-forbidden-words.json` + runner) — 3-tier vocabulary blocklist enforced over default-mode output, ensuring humanized prose stays in plain language. - **Scenario read-test** (`tests/scenario-read-test.mjs` + 5 scenarios) — corpus-driven readability check covering broken hook, duplicate keys, stale @import, dead tool, oversized cascade. - **`tests/snapshots/v5.0.0/`** + **`tests/snapshots/v5.0.0-stderr/`** — frozen byte-equal references for SC-6 (--json) and SC-7 (--raw) backwards-compatibility tests across 8 CLIs. - **`tests/snapshots/default-output/`** — humanized-prose snapshots for SC-5 default-output stability. ### Changed - **Default output of all 18 commands** now uses plain-language descriptions. Findings group by user-impact category; titles lead with prose; technical IDs (`CA-CML-001`, `CA-TOK-005`, …) surface at end-of-line as references. - **All 21 command and agent templates** updated to render humanized output by default and pass `--raw` through when the user requests v5.0.0 verbatim mode. - **CLI flag inventory** — every CLI now accepts `--raw` (new) in addition to `--json` (existing, unchanged). `--output-file ` still writes raw v5.0.0-shape JSON regardless of mode (humanizer-bypassed, posture-specific). ### Migration - **No action required for existing automation** that consumes `--json` — the JSON envelope shape is byte-stable with v5.0.0 and humanizer fields are bypassed in `--json` and `--raw` paths. - **Tooling that scrapes stderr** from default mode (e.g., `posture.mjs`'s scorecard) needs review — default stderr now uses prose vocabulary. Pass `--raw` for byte-stable v5.0.0 verbatim stderr. - **No scanner-internal changes.** Finding IDs, severity ladders, scoring weights, and area scorecards are unchanged. Upgrades are presentation-layer only. ### Test count - 635 → 792 tests across 52 test files (+157 humanizer-tester through Waves 0–5). - New top-level tests: `json-backcompat.test.mjs`, `raw-backcompat.test.mjs`, `scenario-read-test.test.mjs`, `snapshot-default-output.test.mjs`. - New lib tests: `humanizer.test.mjs`, `humanizer-data.test.mjs`, `scoring-humanizer.test.mjs`. - New scanner tests: `posture-humanizer.test.mjs`, `scan-orchestrator-humanizer.test.mjs`, `cli-humanizer.test.mjs`. ### Out of scope (deferred to v5.1.1+) - **Posture `--output-file` humanization** — `posture.mjs` does not call `humanizeEnvelope`, so files written via `--output-file` are raw v5.0.0-shape JSON. Future revision: drop `--output-file` from command templates or add a `--humanized-json` flag. - **Knowledge cross-references** (Step 17 of plan) — not delivered per user decision (2a). - **Scoring scorecard JSON headline emission** — currently rendered prose-side only; command templates that want to skip stderr parsing would benefit. ### Verification - 792/792 tests pass (`node --test 'tests/**/*.test.mjs'`) - `node scanners/self-audit.mjs --json --check-readme` returns `configGrade: A` (97), `pluginGrade: A` (100), `readmeCheck.passed: true` - README badge updated: `tests-635+` → `tests-792+` ## [5.0.0] - 2026-05-01 ### Summary Reality-based token-optimization release. v4.0.0 shipped Opus-4.7 token surfaces aligned to a Sonnet-era cost model; v5.0.0 rebuilds the foundations against verified Opus-4.7 cost dynamics. Three pillars: honest token estimation (severity-weighted scoring, MCP estimates 15 → 500+, optional `--accurate-tokens` API calibration), new structural scanners (cache-prefix stability, dead tool grants, plugin collisions), and new diagnostic surfaces (`/config-audit manifest`, `/config-audit tokens` extended, knowledge-base rensing aligned to Opus 4.7 cache dynamics). Consolidated from `5.0.0-alpha.1` (F1-F5 token-economy round), `5.0.0-alpha.2` (M1, M2, M4-M6, F6, F7 structural gaps + README self-audit), `5.0.0-beta.1` (N1-N4, N6 new scanners + manifest CLI), and `5.0.0-rc.1` (M7, M8 knowledge rensing + N5 tokenizer calibration). ### Added - **3 new scanners (9 → 12 deterministic):** - **CPS — Cache-Prefix Stability** (`CA-CPS-NNN`): volatile content in lines 31–150 of CLAUDE.md cascade, beyond TOK Pattern A's top-30 window. Volatile-pattern set extends Pattern A with shell-exec lines (`!` prefix) and `${VAR}` substitutions. - **DIS — Disabled-In-Schema** (`CA-DIS-NNN`): tools listed in BOTH `permissions.deny` AND `permissions.allow`. Tool identity uses bare name (`Bash(npm:*)` and `Bash` are the same tool). Severity low. - **COL — Cross-Plugin Skill Collision** (`CA-COL-001`): plugin-vs-plugin same skill name → low; user-vs-plugin → medium. `details.namespaces` payload identifies conflicting sources. - **TOK extensions:** - **CA-TOK-005 MCP tool-schema budget:** per-server tiered finding (< 20 none, 20–49 low, 50–99 medium, 100+ high; null low + "tool count unknown"). Scoped to project-local `.mcp.json`. - **Pattern E — Oversized cascade:** medium when `activeConfig.claudeMd.estimatedTokens > 10_000`. - **Pattern F — Bloated SKILL.md description:** low when frontmatter `description > 500 chars` (loads every turn). Scoped to `discovery.files`. - **`/config-audit manifest`** + `scanners/manifest.mjs` CLI — single ranked table of every system-prompt token source (CLAUDE.md cascade, plugins, skills, MCP servers, hooks) sorted DESC by `estimated_tokens`. CLAUDE.md per-file tokens distributed proportional to bytes. - **`--accurate-tokens` flag** on `token-hotspots-cli.mjs` (N5): when `ANTHROPIC_API_KEY` is set, calls Anthropic's `count_tokens` for the top 3 hotspots and populates `output.calibration = { actual_tokens, source: 'count_tokens_api', sampled_hotspots: 3 }`. When absent: `calibration = { skipped: 'no-api-key' }` plus stderr warning. - **`scanners/lib/tokenizer-api.mjs`** — `count_tokens` wrapper. 5s AbortController timeout. Exponential backoff on 429 (3 retries: 1s/2s/4s). API key masked to `${key.slice(0,8)}...` in every error; HTTP body never included in errors (it may echo the key on auth failures). `maskKey()` exported. - **`--with-telemetry-recipe` flag** on the same CLI (M7): emits `telemetry_recipe_path` field pointing to `knowledge/cache-telemetry-recipe.md`. - **`knowledge/cache-telemetry-recipe.md`** (M7): manual `jq` recipe summing `cache_read_input_tokens` + `cache_creation_input_tokens` per turn from session transcripts. Hit-rate interpretation table. - **`'mcp'` kind on `estimateTokens`** (F2): active MCP servers estimate ≥ 500 tokens (base + schema overhead) instead of v4's flat 15. Optional `{toolCount}` raises to `500 + toolCount × 200`. - **MCP tool-count detection** (M1): `readActiveMcpServers` resolves count via cache → `node_modules//package.json` → `{toolCount: null, toolCountUnknown: true}` fallback. - **`additionalDirectories` settings key** (M6): added to `KNOWN_KEYS`; new low-severity finding when length > 2. - **HKV verbose hook output** (M5): low-severity finding when referenced hook script contains > 50 `console.log`/`process.stdout.write` lines (static, no execution). - **`self-audit --check-readme` flag** (F6): filesystem counts compared against README badges. Helper `checkReadmeBadges(pluginDir)`. Step 28 of v5 plan reconciled all badges. - **`scoringVersion: 'v5'`** field on `scoreByArea` output for cross-version drift detection. - **`WEIGHTS`** named export from `scanners/lib/severity.mjs` (frozen). - **`details` field on findings** (`output.mjs:finding()`): optional structured payload for scanner-specific data (used by COL). - **Plugin Hygiene** as 10th quality area (from COL). Posture JSON now reports 10 areas. - **TOK-readActiveConfig integration** (F1): one hotspot per active MCP server; `result.activeConfig` summary (claudeMd cascade tokens, mcpServerCount, pluginCount, skillCount); try/catch fallback when scope-limited. ### Changed - **F3 — `scoreByArea` is severity-weighted.** Penalty = `Σ count[s] × WEIGHTS[s]`; `passRate = max(0, 100 − penalty / max(10, findingCount × 4) × 100)`. Lows no longer crater an area's grade; criticals/highs do. `baseline-all-a` fixture remains all-A (no critical/high present). - **F7 — TOK pattern severities recalibrated** for tokens-per-turn impact: Pattern A `medium → high`, Pattern B `low → medium`, Pattern C `medium → low`. Each finding carries a `calibration_note` evidence field documenting the heuristic basis. - **`scoreByArea` deduplicates by area name** (N3 prep): TOK + CPS share "Token Efficiency"; SET + DIS share "Settings". Combined row with merged finding counts. - **M8 — knowledge rensing:** replaced "Keep CLAUDE.md under 200 lines" in `knowledge/configuration-best-practices.md` with cache-stability guidance (first 30 lines stable, volatile content below the cache threshold). Footnote explains the 200-line rule was a Sonnet-era adherence heuristic; Opus 4.7 uses prompt-cache structure as the dominant cost lever. Cross-references `knowledge/opus-4.7-patterns.md`. - **`commands/tokens.md` next-steps:** documents `--with-telemetry-recipe` as the cache-verification path. - **Scanner count: 9 → 12.** Command count: 17 → 18. Knowledge: 7 → 8. Quality areas: 8 → 10. - **`.gitignore`** — unignore rules for `tests/fixtures/**/node_modules/` so the `mcp-tool-heavy` fixture stays under version control. ### Removed - **F4 — TOK hotspot padding loop and `take` dead-code.** Hotspots may now contain fewer than 3 entries for tiny projects (the honest answer); contract still bounds at ≤ 10. - **F5 — Pattern D / `CA-TOK-004` (sonnet-era signature).** Catalogue entry removed from `knowledge/opus-4.7-patterns.md` and `commands/tokens.md`. Suppression entries for `CA-TOK-004` are now no-ops. ### Breaking changes - **F2 — MCP token estimates jump from flat 15 to ≥ 500.** Token Efficiency grades for projects with MCP servers may shift. `whats-active` totals report higher numbers. Documented in `commands/posture.md` next-steps. - **F3 — `scoreByArea` is severity-weighted.** Posture JSON consumers reading `areas[*].score` will see different values for non-clean configs. Use `result.scoringVersion === 'v5'` to detect the change. Drift comparisons across v4↔v5 baselines may show artificial deltas — re-baseline after upgrade. - **F5 — Pattern D / `CA-TOK-004` no longer emitted.** Existing exact `CA-TOK-004` suppression entries are harmless but obsolete. - **N1 suppression backward-compat — `CA-TOK-*` glob now also matches `CA-TOK-005`.** To preserve prior behavior of suppressing only patterns A/B/C, replace the glob with explicit IDs: ``` CA-TOK-001 CA-TOK-002 CA-TOK-003 ``` A one-time runtime warning for this case is a v5.0.1 candidate. - **Posture areas count: 9 → 10** (Plugin Hygiene from COL). Consumers hard-coding 9 must update. ### Migration notes - `CA-TOK-*` glob suppressions: explicit-ID list recommended if CA-TOK-005 should not be suppressed. - `CA-TOK-004` exact-ID suppression entries: safe to remove. - Drift baselines created against v4 should be re-saved post-upgrade to avoid artificial F3 weighting deltas. - Posture JSON consumers must update any hardcoded `areas.length === 8` or `=== 9` assertions to `>= 10`. ### Tests - 543 → 635 (+92): F1-F7 (alpha rounds = +43), N1-N4 + N6 (beta = +39), M7 + M8 + N5 (rc = +10). 36 test files (12 lib + 23 scanner + 1 hook). - New fixtures: `tok-active-config/`, `additional-dirs-many/`, `additional-dirs-ok/`, `large-cascade/`, `small-cascade/`, `skill-bloated/`, `skill-tight/`, `mcp-tool-heavy/` (with mocked `node_modules/`), `hooks-verbose/`, `hooks-quiet/`, `readme-desynced/`, `mcp-budget/{14,25,60,120,unknown}-tools/`, `volatile-mid-section/{volatile-line-60,volatile-line-200}/`, `denied-tools-in-schema/`, `collision-plugins/fake-home/` (plugin-a + plugin-b + plugin-c + user-level review skill). - New test files: `tests/scanners/manifest.test.mjs`, `tests/scanners/cache-prefix.test.mjs`, `tests/scanners/disabled-in-schema.test.mjs`, `tests/scanners/collision.test.mjs`, `tests/scanners/accurate-tokens.test.mjs`. ### Notes - **`mock.method` against ESM module exports does not work** (Node 18+ ESM read-only export bindings). v5 tests use `globalThis.fetch` mocking for `--accurate-tokens` instead — equivalent coverage at the actual external-dependency boundary. - **Plugin-vs-built-in collision detection is intentionally not implemented.** Step 22a research spike (`docs/v5-namespace-research.md`, gitignored) could not verify Claude Code's resolution behavior when a plugin command shares a name with a built-in. Treated as info-only; v5.0.1 candidate. - **README/CLAUDE.md badge reconciliation** done in Step 28 (this release). `self-audit --check-readme` PASSES against the filesystem. Test count counter switched from file-count to test-case count via subprocess `node --test` parse. - **`hotspot.path` exposed on file-backed hotspots** (Step 30 fix). The rc.1 `--accurate-tokens` implementation looked up `hotspot.path` but the scanner only emitted `source`. File-backed hotspots now carry `path` (absolute path); MCP-server hotspots leave it unset (they are virtual entries representing runtime tool-schema cost, not file content). ### SC-6b release-gate result (verified 2026-05-01) - **PASS — 0.85% under-estimation against real `count_tokens` API.** - Fixture: `tests/fixtures/marketplace-large/`. Top-3 hotspots = 1 file-backed (`CLAUDE.md`) + 2 MCP virtuals. MCP entries skipped per design (no readable content; their tokens are formula-based at 500 + toolCount × 200). - `CLAUDE.md` actual: 589 tokens (Anthropic `count_tokens`, `claude-opus-4-7`). Estimated: 594 tokens (byte heuristic at 4 bytes/token via `estimateTokens`). Delta: **−5 tokens, −0.85%** — well within the ±5% gate. - No tuning of `estimateTokens` heuristic required for v5.0.0. ## [5.0.0-rc.1] - 2026-05-01 ### Summary Release candidate for v5.0.0 — knowledge rensing and tokenizer calibration. Three deliverables: M8 (Sonnet-era → Opus 4.7 best-practices rewrite), M7 (cache-telemetry recipe in `knowledge/` plus an opt-in CLI flag), and N5 (`--accurate-tokens` API calibration via Anthropic's `count_tokens` endpoint). ### Added - **N5 — `--accurate-tokens` flag** on `scanners/token-hotspots-cli.mjs`. When `ANTHROPIC_API_KEY` is set, the CLI calls Anthropic's `count_tokens` endpoint for the top 3 hotspots and populates `output.calibration = { actual_tokens, source: 'count_tokens_api', sampled_hotspots: 3 }`. When the key is absent, `calibration = { skipped: 'no-api-key' }` and a stderr warning is emitted. Designed for the manual SC-6b release-gate verification, not routine use. - **`scanners/lib/tokenizer-api.mjs`** — wrapper around `count_tokens` with a 5-second AbortController timeout, exponential-backoff retry on HTTP 429 (max 3 retries: 1s, 2s, 4s), and required headers (`x-api-key`, `anthropic-version: 2023-06-01`, `content-type`). API key is masked to `${key.slice(0,8)}...` in every error message and every thrown error; non-429 HTTP errors throw status code only — response body is never included (it may echo the key on auth failures). `maskKey()` is exported for callers that need safe logging. - **M7 — `knowledge/cache-telemetry-recipe.md`** (new). Manual `jq` recipe for verifying prompt-cache hit rate from Claude Code session transcripts (`~/.claude/projects//*.jsonl`). Sums `cache_read_input_tokens` and `cache_creation_input_tokens` per turn and reports a hit-rate ratio. Recipe-form (not bundled scanner) keeps the project's "no transcript-parsing as core feature" non-goal intact while giving users a runtime escape hatch. - **M7 — `--with-telemetry-recipe` flag** on the same CLI. When passed, emits `telemetry_recipe_path` in the JSON output pointing to the recipe file. Without the flag, output is unchanged. Committed as a default deliverable, opt-in at invocation time. ### Changed - **M8 — knowledge-base rensing:** replaced the "Keep CLAUDE.md under 200 lines" rule in `knowledge/configuration-best-practices.md` with cache-stability guidance (first 30 lines stable, volatile content below the cache threshold). Added a footnote that the 200-line rule was a Sonnet-era adherence heuristic; Opus 4.7 uses prompt-cache structure as the dominant cost lever. Cross-references `knowledge/opus-4.7-patterns.md`. - **`commands/tokens.md` next-steps:** documents `--with-telemetry-recipe` as the cache-verification path after a structural fix. ### Tests - 625 → 635 (+10): `--with-telemetry-recipe` (×2), tokenizer-api unit tests (×6 — masking, body-leak protection, AbortController signal, 429 retry, header set, fetch mock happy path), `--accurate-tokens` no-key subprocess test (×1), absent-flag negative test (×1). - New file: `tests/scanners/accurate-tokens.test.mjs`. No new fixtures (re-uses `marketplace-large`). ### Notes - **SC-6b release gate is NOT closed by these commits.** Step 26's tests use mocked `globalThis.fetch` to verify the integration contract; ±5% accuracy against real `count_tokens` requires a live API key and must be verified manually before tagging v5.0.0 in Session 5. - The plan's specified `mock.method(tokenizerApi, 'callCountTokensApi', ...)` pattern collides with ESM read-only export bindings in Node 18+. Tests mock at the `globalThis.fetch` boundary instead — equivalent coverage, no module-export rebinding required. - README/CLAUDE.md badge counts and `plugin.json` version still target v4.0.0; Step 28+29 will sync those during the release wrap. - `[skip-docs]` tag on the N5 feat commit; M7 and M8 are `docs(...)` commits and don't need it. ## [5.0.0-beta.1] - 2026-05-01 ### Summary First v5.0.0 beta — new scanners. Five new finding sources land: MCP tool-schema budget (CA-TOK-005), system-prompt manifest CLI/command (`/config-audit manifest`), cache-prefix stability (CPS), disabled-tools-still-in-schema (DIS), and cross-plugin/user-vs-plugin skill collision (COL/CA-COL-001). Plugin Hygiene becomes a 10th area-scorecard column. ### Added - **N1 — `CA-TOK-005` MCP tool-schema budget:** per-server tiered finding inside the TOK scanner. Thresholds — `< 20` no finding, `20–49` low, `50–99` medium, `100+` high; `null` (manifest unparseable) low + "tool count unknown" message. Scoped to project-local `.mcp.json` to keep `/config-audit ` actionable. Recommendation links to the Step 25 cache-telemetry recipe. - **N2 — `/config-audit manifest`:** new slash command + `scanners/manifest.mjs` CLI. Renders a single ranked table of every token source (CLAUDE.md cascade, plugins, skills, MCP servers, hooks) sorted DESC by `estimated_tokens`. Reuses `readActiveConfig`; CLAUDE.md per-file tokens are distributed proportional to bytes. - **N3 — CPS scanner (`CA-CPS-NNN`):** Cache-Prefix Stability Analyzer. Walks the CLAUDE.md cascade and flags volatile content between lines 31 and 150 — beyond TOK Pattern A's top-30 territory. Volatile-pattern set extends Pattern A with shell-exec lines (`!` prefix) and `${VAR}` substitutions. Severity medium per finding. Skips lines 1–30 (Pattern A's range). - **N4 — DIS scanner (`CA-DIS-NNN`):** Disabled-In-Schema Detector. Detects tools that appear in BOTH `permissions.deny` and `permissions.allow` within the same `settings.json`. The deny list wins, so allow entries are dead config but still load every turn. Tool identity is the bare name (everything before `(`); `Bash(npm:*)` and `Bash` are treated as the same tool. Severity low. - **N6 — COL scanner (`CA-COL-001`):** Cross-Plugin Skill Collision detector. Plugin-vs-plugin same skill name → low. User-vs-plugin same skill name → medium. Findings carry `details.namespaces` array with `{source, name, path}` for every conflicting source. - **`details` field on findings:** `output.mjs:finding()` helper now passes through optional `details` for scanner-specific structured payloads (used by COL). - **"Plugin Hygiene" area** (10th in scorecard): COL contributes here. Posture JSON now reports 10 areas instead of 9. ### Changed - **`scoreByArea` deduplicates by area name:** when multiple scanners share an area (TOK + CPS → "Token Efficiency", SET + DIS → "Settings"), they produce one combined row with merged finding counts. Existing 9-area contract preserved for non-Plugin-Hygiene areas. ### Known breaking changes - **Suppression backward-compat — `CA-TOK-*` glob now also matches `CA-TOK-005`.** Existing `.config-audit-ignore` entries that suppress TOK findings via the `CA-TOK-*` glob will silently include CA-TOK-005 (MCP budget). To preserve the prior behavior of suppressing only patterns A/B/C, replace the glob with explicit IDs: ``` CA-TOK-001 CA-TOK-002 CA-TOK-003 ``` A one-time runtime warning for this case is out of scope for v5.0.0 — it is a candidate for v5.0.1. - **Plugin-vs-built-in collision is intentionally not implemented.** The Step 22a research spike could not verify Claude Code's resolution behavior when a plugin command shares a name with a built-in (`/help`, `/clear`, `/init`, `/review`, `/config`, `/cost`, `/security-review`). Treated as info-only in this release; a follow-up v5.0.1 ticket may add an opt-in check. ### Tests - 586 → 625 (+39): N1 (×7), N2 (×11), N3 (×7), N4 (×6), N6 (×8). - New fixtures: `mcp-budget/{14,25,60,120,unknown}-tools/`, `volatile-mid-section/{volatile-line-60,volatile-line-200}/`, `denied-tools-in-schema/`, `collision-plugins/fake-home/` (plugin-a + plugin-b + plugin-c + user-level review skill). ### Notes - `[skip-docs]` tag used on every feat commit — README/CLAUDE.md badge counts (scanner count, command count, test count) and the architecture sections are intentionally fenced off until Session 5 (Step 28). This keeps the v5 plan's session boundaries clean even when the Forgejo `pre-commit-docs-gate` hook would otherwise block these commits. ## [5.0.0-alpha.2] - 2026-05-01 ### Summary Second v5.0.0 alpha — structural gaps + README self-audit. TOK pattern severities recalibrated for tokens/turn impact (F7), three new findings cover settings/skills/cascade structure (M2, M4, M6), MCP tool-count detection wired (M1), HKV gains a verbose-output check (M5), and self-audit grows a `--check-readme` flag (F6). ### Added - **F7 — TOK severity recalibration:** Pattern A (cache-breaking volatile top) `medium → high`, Pattern B (redundant permissions) `low → medium`, Pattern C (deep imports) `medium → low`. Each finding now carries a `calibration_note` evidence field documenting the heuristic basis. - **M6 — `additionalDirectories` settings key:** added to `KNOWN_KEYS` so it no longer trips "unknown settings key". New low-severity finding when `additionalDirectories.length > 2`. - **M4 — TOK Pattern E:** medium-severity finding when `activeConfig.claudeMd.estimatedTokens > 10_000` — flags cascades that bleed budget every turn. - **M2 — TOK Pattern F:** low-severity finding for project-local `SKILL.md` whose frontmatter `description` exceeds 500 characters (description loads on every turn even when the body does not). Scoped to `discovery.files`; user/plugin skills out of project scope are not flagged. - **M1 — MCP tool-count detection:** `readActiveMcpServers` now resolves tool count via cache → `node_modules//package.json` → `{toolCount: null, toolCountUnknown: true}` fallback. Tool count drives `estimateTokens` per server. - **M5 — HKV verbose hook output:** new low-severity finding when a referenced hook script contains > 50 `console.log` / `process.stdout.write` lines (static heuristic, no execution). - **F6 — `self-audit --check-readme` flag:** filesystem counts (scanners, commands, agents, hooks, tests, knowledge) compared against README badge values. Helper export: `checkReadmeBadges(pluginDir)`. ### Changed - **TOK severities** (F7) — see Added. Posture aggregates that depended on Pattern A being `medium` will now reflect the higher-impact rating. - **`.gitignore`** — added unignore rules so `tests/fixtures/**/node_modules/` are tracked. Required by the `mcp-tool-heavy` fixture. ### Tests - 563 → 586 (+23): F7 table-driven (×6), M6 (×3), M4 (×2), M2 (×2), M1 (×4), M5 (×2), F6 (×4). - New fixtures: `additional-dirs-many/`, `additional-dirs-ok/`, `large-cascade/`, `small-cascade/`, `skill-bloated/`, `skill-tight/`, `mcp-tool-heavy/` (with mocked `node_modules/`), `hooks-verbose/`, `hooks-quiet/`, `readme-desynced/`. ### Notes - `result.readmeCheck.passed === true` is **not** required during alpha/beta phases. The real plugin's own check is currently red (`scanners` 10 vs README 9, `tests` 31 vs README 543) — reconciliation deferred to Session 5 Step 28 (README sync). - `[skip-docs]` tag used on every commit — README/CLAUDE.md badge counts and architecture text are intentionally fenced off until Session 5. ## [5.0.0-alpha.1] - 2026-05-01 ### Summary First v5.0.0 alpha — token-economy round, F1-F5. The TOK scanner now consumes `readActiveConfig` (per-MCP-server hotspots, claudeMd cascade tokens), severity weighting replaces flat finding counts in `scoreByArea`, and MCP servers no longer estimate at a flat 15 tokens. Pattern D (CA-TOK-004 sonnet-era signature) removed — too noisy, not actionable. ### Added - **`'mcp'` kind for `estimateTokens`** (F2): an active MCP server now estimates ≥ 500 tokens (base protocol + schema overhead) instead of the v4 flat 15. Optional `{toolCount}` raises the estimate to `500 + toolCount * 200` once Step 14 wires tool-count detection. - **TOK ↔ readActiveConfig integration** (F1): the TOK scanner emits one hotspot per active MCP server, sums their tokens into `total_estimated_tokens`, and exposes `result.activeConfig` (claudeMd cascade tokens, mcpServerCount, pluginCount, skillCount). - **`scoringVersion: 'v5'`** field on `scoreByArea` output for cross-version drift detection. - **`WEIGHTS`** named export from `scanners/lib/severity.mjs` (`Object.freeze`). ### Changed - **BREAKING (intentional, F3):** `scoreByArea` is now severity-weighted. Penalty = `Σ count[s] * WEIGHTS[s]`; `passRate = max(0, 100 - penalty / max(10, findingCount * 4) * 100)`. Lows no longer crater an area's grade; a single high or critical consumes a large fraction of budget. `baseline-all-a` fixture remains all-A (no critical/high on that fixture). - **BREAKING (intentional, F2):** MCP server token estimates jump from a flat 15 to ≥ 500. `whats-active` totals and TOK hotspots will report higher numbers for any project with active MCP servers. - **BREAKING (intentional, F5):** Pattern D / `CA-TOK-004` (sonnet-era signature) is no longer emitted. Suppression entries for `CA-TOK-004` are now no-ops; downstream tools that filter on the ID should drop it. The catalogue entry was removed from `knowledge/opus-4.7-patterns.md` and `commands/tokens.md`. - **Hotspots contract (F4):** the v4 padding loop and `take` dead-code are gone. Hotspots may now contain fewer than 3 entries for tiny projects (the honest answer); contract still bounds at ≤ 10. ### Migration notes - `CA-TOK-*` glob suppression entries continue to suppress 001-003. Existing exact `CA-TOK-004` entries are harmless but obsolete — remove them at convenience. - Posture/JSON consumers reading `areas[*].score` will see different values for non-clean configs. Use `result.scoringVersion === 'v5'` to detect. ### Tests - 543 → 563 across the alpha.1 commits (+9 severity-weighting/scoring, +4 estimateTokens 'mcp', +1 MCP caller migration, +3 readActiveConfig integration, +2 hotspots-uniqueness, +2 sonnet-era zero-finding). - New fixture `tests/fixtures/tok-active-config/` — minimal repo with `.mcp.json` (2 servers), `CLAUDE.md`, plugin skeleton. ## [4.0.0] - 2026-04-19 ### Summary Opus 4.7 era upgrade. New TOK scanner detects token-efficiency anti-patterns (cache-breaking volatile content, redundant tool permissions, deep import chains, sonnet-era minimal setups). Token Efficiency joins the quality scorecard as the 8th area. Scanner-agent and verifier-agent migrate from haiku → sonnet per global no-haiku policy. ### Added - **`token-hotspots.mjs`** scanner (CA-TOK-001..004) — 4 patterns aligned with Opus 4.7 token-cost dynamics: - CA-TOK-001 cache-breaking volatile content (timestamps/UUIDs in top 30 lines of CLAUDE.md) - CA-TOK-002 redundant tool permissions (duplicate or subset overlaps) - CA-TOK-003 deep @import chains (>2 hops on the load path) - CA-TOK-004 sonnet-era minimal setup (no skills/MCP/hooks/managed/plugins) - **`/config-audit tokens [path] [--global]`** — ranked hotspot table + per-pattern findings. - **`scanners/token-hotspots-cli.mjs`** — standalone CLI emitting `total_estimated_tokens`, `hotspots`, and per-finding output. - **Token Efficiency** as the 8th quality area in the posture scorecard (now 9 scanners total: CML/SET/HKV/RUL/MCP/IMP/CNF/GAP/TOK). - `id` field on every area in the scorecard payload (`token_efficiency`, `instruction_clarity`, etc.) for stable downstream lookup. - 13 new TOK scanner tests + 3 CLI tests + posture grade-stability test for `token_efficiency`. - Knowledge refresh: `knowledge/opus-4.7-patterns.md`, plus 2026-04 deltas (v2.1.83–v2.1.111) added to `feature-evolution.md`, `claude-code-capabilities.md`, and `hook-events-reference.md` from `research/03-claude-code-changes-config-surfaces.md`. ### Changed - **BREAKING (additive surface):** Quality areas count 7 → 8. Posture JSON consumers that hard-coded 7 areas must update. - **BREAKING (model migration):** `scanner-agent` and `verifier-agent` migrated `haiku` → `sonnet`. Latency and cost trade-offs accepted; deterministic scanner CLIs preferred over agent invocations. - Scanner count: 8 → 9 (TOK added). - Command count: 16 → 17 (`/config-audit tokens` added). - Version bump: `3.1.0` → `4.0.0`. ## [3.1.0] - 2026-04-14 ### Summary New read-only command `/config-audit whats-active` — shows exactly what Claude Code loads for a given repo, with token estimates. ### Added - **`/config-audit whats-active [path]`** — inventory of active plugins, skills, MCP servers, hooks, and CLAUDE.md cascade for a repo, with source attribution (user/project/plugin) and rough token estimates. Read-only, <2s. - `scanners/lib/active-config-reader.mjs` — pure async helper: `readActiveConfig()`, `detectGitRoot()`, `walkClaudeMdCascade()`, `readClaudeJsonProjectSlice()` (longest-prefix matching), `enumeratePlugins()`, `enumerateSkills()`, `readActiveHooks()`, `readActiveMcpServers()`, `estimateTokens()`. - `scanners/whats-active.mjs` — thin CLI shim supporting `--json`, `--output-file`, `--verbose`, `--suggest-disables`. - Optional `--suggest-disables` flag surfaces deterministic disable candidates (disabled MCP servers, zero-item plugins, unreferenced plugins, orphan skills) and invites an LLM judgment pass in the command. - 36 new tests in `tests/lib/active-config-reader.test.mjs`, plus a `rich-repo` tmpdir fixture helper. ### Changed - Version bump: `3.0.1` → `3.1.0` (minor, additive feature, no breaking changes). - Command count: 15 → 16. ## [3.0.1] - 2026-04-04 ### Summary Cross-platform fix — scanners, hooks, and lib now work correctly on Windows. ### Fixed - `file-discovery.mjs`: depth calculation, agent/command/plugin path matching now use `path.sep` - `scan-orchestrator.mjs`: fixture-path filtering now uses `path.sep` - `post-edit-verify.mjs`: rules-dir regex handles both `/` and `\` separators - `auto-backup-config.mjs`: rules-dir detection now uses `path.sep` - `import-resolver.mjs`: circular import display uses `basename()`, `/tmp` fallback replaced with `os.tmpdir()` - `string-utils.mjs`: `normalizePath` trailing separator regex handles both `/` and `\` ### Added - 4 cross-platform path tests (total 486 tests) ## [3.0.0] - 2026-04-04 ### Summary Health redesign — configuration health is now quality-only. Feature utilization removed from grades entirely. ### Changed - **Health = quality only.** 7 deterministic scanners (CML, SET, HKV, RUL, MCP, IMP, CNF) determine your grade. Feature Coverage is no longer a graded area. - **Feature recommendations are opt-in.** Unused features shown as "opportunities" via `/config-audit feature-gap`, grouped by impact (high/medium/explore), backed by Anthropic docs. No more "Feature Coverage: F" for correct minimal setups. - **Posture output redesigned.** Shows `Health: {grade} ({score}/100)` with 7 quality areas. Removed utilization %, maturity level, segment label. - **Feature-gap is interactive.** Users select recommendations to implement directly — no manual file editing required. Backup created automatically. - **avgScore bug fixed.** Grade letter and displayed score now computed from the same population (quality areas only). ### Added - `generateHealthScorecard()` in scoring.mjs — quality-only scorecard - `opportunitySummary()` in feature-gap-scanner.mjs — groups findings by impact tier - `opportunityCount` field in posture JSON output - "Official Configuration Guidance" section in knowledge base (Anthropic docs, proven impacts) - 21 new tests (total 482 across 27 test files) ### Removed - `S2-PROMPT.md` and `V2-ANNOUNCEMENT.md` — v2 development artifacts - Utilization %, maturity level, segment label from posture terminal output and reports - Feature Coverage row from area breakdown tables - "Top Actions" sourced from GAP findings (replaced by opportunities pointer) ### Backward Compatibility - JSON output preserves all legacy fields (utilization, maturity, segment) for programmatic consumers - Drift baselines unaffected — GAP findings still present in envelopes - All existing exports maintained (calculateUtilization, determineMaturityLevel, etc.) ## [2.2.0] - 2026-04-04 ### Summary UX quality fix — fixture filtering, session path migration, output polish. ### Added - Automatic test-fixture filtering in scan-orchestrator: findings from `tests/`, `examples/`, `__tests__/` excluded from grades, stored in `env.fixture_findings` - `--include-fixtures` CLI flag for scan-orchestrator and posture to override filtering - `scan-orchestrator.test.mjs` — 20 new tests for fixture filtering and `isFixturePath` - Legacy session path detection in cleanup command ### Changed - Session storage moved from `~/.config-audit/` to `~/.claude/config-audit/` (pathguard compatible) - Self-audit grade: F → A (98) after fixture filtering - Combined scanner + posture into single Bash call in default audit command - Removed "F grade is misleading" disclaimer — grades are now accurate - All CLI banners and envelope metadata updated to v2.2.0 - 461 tests (up from 441), 27 test files (up from 26) ### Removed - Manual fixture counting instruction in `config-audit.md` (orchestrator handles it) - Redundant `isFixtureOrExample` filter in `self-audit.mjs` (promoted to orchestrator) ## [2.1.0] - 2026-04-03 ### Summary UX redesign — auto-scope detection, zero questions, simplified command surface. ### Changed - `/config-audit` now runs full audit automatically (auto-detects scope from git context) - Removed mode selection prompts — scope override via `/config-audit full|repo|home|current` - Simplified from 17 to 15 commands (removed quick, report, watch; added help) - All CLI banners and envelope metadata updated to v2.1.0 ### Added - `/config-audit help` command with categorized command reference - Auto-scope detection from git context (repo vs home vs full-machine) ### Removed - `/config-audit:quick` (merged into default `/config-audit`) - `/config-audit:report` (merged into analyze output) - `/config-audit:watch` (use `/config-audit drift` instead) ## [2.0.0] - 2026-04-03 (v2.0 Complete) ### Summary Complete rewrite from LLM-only prototype to deterministic scanner-backed configuration intelligence. 7 development sessions (S1-S7), ~15,000 lines of code, 408+ tests. ### Highlights - 8 deterministic scanners (CML, SET, HKV, RUL, MCP, IMP, CNF, GAP) + PLH standalone - Feature gap analysis with 25 dimensions across 4 tiers - Auto-fix engine with 9 fix types + backup/rollback - Drift detection with baseline comparison - Suppression engine (.config-audit-ignore) - Self-audit CLI - 17 commands, 6 agents, 4 hooks - 408+ tests (zero external dependencies) ### Added (S7) - Example projects: `examples/minimal-setup/` and `examples/optimal-setup/` - Demo script: `examples/run-demo.sh` - `.config-audit-ignore` for self-audit suppressions - `V2-ANNOUNCEMENT.md` - `DEPRECATED.md` for capability-auditor skill ### Fixed (S7) - `hooks.json`: SessionStart and Stop timeout 5ms → 5000ms - `self-audit.mjs`: Suppression now enabled (was hardcoded to `suppress: false`) ### Changed (S7) - README.md: Complete rewrite for public release - CLAUDE.md: Added Suppressions section - `.gitignore`: Added `node_modules/` and `S*-PROMPT.md` ## [1.6.0] - 2026-04-03 (v2.0 S6: Unified Reports + Self-Audit + Suppressions) ### Added - **Report generator** `scanners/lib/report-generator.mjs` — unified markdown reports: generatePostureReport(), generateDriftReport(), generatePluginHealthReport(), generateFullReport() - **Suppression engine** `scanners/lib/suppression.mjs` — `.config-audit-ignore` file support with exact IDs and glob patterns (CA-SET-*), audit trail via `suppressed_findings` in envelope - **Self-audit CLI** `scanners/self-audit.mjs` — runs all scanners + plugin health on this plugin: `node self-audit.mjs [--json] [--fix]`, exit codes 0/1/2 - **PostToolUse hook** `post-edit-verify.mjs` — verifies config files after Edit/Write, blocks if new critical/high findings introduced - **New command**: `/config-audit:report` — generate unified report (posture + optional drift/plugin-health) - **Test fixture** `.config-audit-ignore` in fixable-project - 54 new tests (total 408 across 25 test files) ### Changed - `scan-orchestrator.mjs`: suppression integration — applies .config-audit-ignore after all scanners run, `--no-suppress` flag to disable - `hooks.json`: added PostToolUse event with post-edit-verify ## [1.5.0] - 2026-04-03 (v2.0 S5: Drift + Watch + Plugin Health) ### Added - **Diff engine** `scanners/lib/diff-engine.mjs` — diffEnvelopes() comparing baseline vs current, formatDiffReport() for terminal output - **Baseline manager** `scanners/lib/baseline.mjs` — save/load/list/delete named baselines in ~/.claude/config-audit/baselines/ - **Drift CLI** `scanners/drift-cli.mjs` — standalone: `node drift-cli.mjs [--save] [--baseline name] [--json] [--list]` - **Plugin health scanner** `scanners/plugin-health-scanner.mjs` (PLH) — validates plugin structure, frontmatter, cross-plugin conflicts (runs independently, not in scan-orchestrator) - **3 new commands**: - `/config-audit:drift` — compare current config against saved baseline - `/config-audit:watch` — on-demand drift check with baseline monitoring - `/config-audit:plugin-health` — audit plugin structure and cross-plugin coherence - **Test fixtures** `test-plugin/` (valid) and `broken-plugin/` (invalid) for plugin health tests - 48 new tests (total 354 across 21 test files) ## [1.4.0] - 2026-04-03 (v2.0 S4: Fix + Rollback Action Pillar) ### Added - **Fix engine** `scanners/fix-engine.mjs` — deterministic auto-fix for 9 fix types: - `json-key-add` (missing $schema), `json-key-remove` (deprecated keys), `json-key-type-fix` (type mismatches, invalid effortLevel), `json-restructure` (hooks array→object, matcher object→string), `frontmatter-rename` (globs→paths), `file-rename` (non-.md→.md) - **Rollback engine** `scanners/rollback-engine.mjs` — listBackups(), restoreBackup(), deleteBackup() with checksum verification - **Fix CLI** `scanners/fix-cli.mjs` — standalone: `node fix-cli.mjs [--apply] [--json] [--global]`, dry-run by default - **Backup lib** `scanners/lib/backup.mjs` — shared backup module with checksums and manifests - **2 new commands**: - `/config-audit:fix` — scan, plan, backup, apply, verify in one flow - `/config-audit:rollback` — list or restore from backups - **PreToolUse hook** `auto-backup-config.mjs` — auto-backup config files before Edit/Write - **Test fixture** `fixable-project/` — fixture with all 9 fixable issue types - 38 new tests (total 306 across 17 test files) ### Changed - `file-discovery.mjs`: walkRulesDir now discovers all files (not just .md) for non-.md validation - `backup-before-change.mjs`: refactored to use shared `lib/backup.mjs` (no logic duplication) - hooks.json: added PreToolUse event with auto-backup ## [1.3.0] - 2026-04-03 (v2.0 S3: Posture + Feature Gap Commands) ### Added - **Scoring module** `scanners/lib/scoring.mjs` — utilization, maturity (5 levels), segments, area scoring, scorecard generation - **Posture CLI** `scanners/posture.mjs` — standalone Node.js tool: `node posture.mjs [--json] [--global]` - **2 new commands**: - `/config-audit:posture` — quick scorecard with A-F grades, utilization%, maturity level - `/config-audit:feature-gap` — deep gap analysis with prioritized next-best-actions - **feature-gap-agent** — Opus agent for deep analysis, report generation (max 200 lines) - **Knowledge file** `gap-closure-templates.md` — 11 templates with effort/gain estimates - **HTML report template** `templates/feature-gap-report.html` — visual report with progress bars, grade badges - 64 new tests (total 268 across 14 test files) ### Changed - Tier weighting: T1 gaps count 3x, T2 count 2x, T3/T4 count 1x in utilization score - Maturity is threshold-based: highest level where ALL requirements are met ## [1.2.0] - 2026-04-03 (v2.0 S2: Advanced Scanners + Knowledge Base) ### Added - **4 advanced scanners** (zero external deps): - `mcp-config-validator.mjs` (MCP) — server types, trust levels, env vars, unknown fields - `import-resolver.mjs` (IMP) — broken @imports, circular refs, deep chains, tilde paths - `conflict-detector.mjs` (CNF) — settings conflicts, permission contradictions, hook duplicates - `feature-gap-scanner.mjs` (GAP) — 25 feature gaps across 4 tiers (Foundation/Depth/Advanced/Enterprise) - **Knowledge base** — 5 reference documents: capabilities, best practices, anti-patterns, hook events, feature evolution - **New test fixtures** — `.mcp.json` files, @import chains, `conflict-project/` fixture - 75 new tests (total 204 across 12 test files) ### Changed - Scan orchestrator runs 8 scanners (was 4) - Analyzer agent cross-references scanner findings with knowledge base ## [1.1.0] - 2026-04-03 (v2.0 S1: Scanner Foundation) ### Added - **Deterministic scanner infrastructure** — 4 Node.js scanners (zero external deps): - `claude-md-linter.mjs` (CML) — CLAUDE.md structure, length, sections, @imports, duplicates - `settings-validator.mjs` (SET) — settings.json schema, unknown/deprecated keys, type checks - `hook-validator.mjs` (HKV) — hooks.json format, script existence, event validity, timeouts - `rules-validator.mjs` (RUL) — .claude/rules/ glob matching, orphan detection, deprecated fields - **Scanner lib** — 5 shared modules: severity, output, file-discovery, yaml-parser, string-utils - **Scan orchestrator** — `scan-orchestrator.mjs` runs all scanners, outputs JSON envelope - **Test infrastructure** — 129 tests across 8 test files using node:test (zero deps) - **Test fixtures** — 4 fixture projects (healthy, broken, empty, minimal) - Finding ID format: `CA-{SCANNER}-{NNN}` (e.g. `CA-CML-001`) ### Fixed - Agent model mismatches: scanner→haiku, analyzer→sonnet, planner→opus, implementer→sonnet, verifier→haiku ### Changed - CLAUDE.md rewritten in English for public release readiness ## [1.0.0] - 2026-02-11 ### Added - Cross-platform support (macOS, Linux, Windows) ### Fixed - `stop-session-reminder.mjs`: Use `path.basename`/`path.dirname` instead of hardcoded `/` split - `backup-before-change.mjs`: Handle both `/` and `\` path separators in safe filename generation ### Removed - "Windows: hooks are 100% bash" from known gaps (was incorrect — all hooks are Node.js) ## [0.7.0] - 2026-02-07 ### Note Version reset from 1.2.0 to reflect actual maturity. Previous version was inflated — this plugin has never been externally tested. ### What exists today - 6 specialized agents (scanner, analyzer, interviewer, planner, implementer, verifier) - Full machine-wide Claude Code configuration discovery - Scope selection (current project, repo, home, full machine) - Inheritance hierarchy mapping and conflict detection - Mandatory backups before any changes - Rollback support - Syntax validation for all configuration files - Quick audit-only mode - Full optimization workflow with HITL checkpoints ### Known gaps - Testing: no automated tests - Onboarding: never verified that a new user can install and use from scratch - External verification: nobody else has ever used this