chore(release): v6.0.0 — "Prose is not a contract" (MAJOR: finding IDs name the check)

MAJOR, because M-BUG-28 changed what a finding ID means: the {NNN} in
CA-{SCANNER}-{NNN} names the CHECK, not its emission position. IDs are
therefore not unique per finding -- one check failing in three files emits
three findings sharing an ID -- and any consumer keying on `id` alone must
move to (id, file, line).

The release theme is a class of defect rather than a feature area. Three
sweeps (Q1, Q2, Q_AUDIT) kept surfacing the same shape: a command template
stated an invariant in prose, code on the other side depended on it, and
nothing checked that the two still agreed. Q1 put the write-scope gate in
code (measured: 9 writers under scanners/, 1 imported the gate). Q2 checks
each template's argv against the CLI that receives it (--stale-after 30
reached its CLI as one argument under zsh and was ignored while the command
reported success). Q_AUDIT measured and ranked the third instance --
data contracts hand-built by the model and parsed by engines that know one
frozen example -- with the recovery path on top, deliberately not yet fixed.

Measured this session, not carried forward:
  37 commits since v5.13.0 (git log v5.13.0..HEAD --oneline | wc -l)
  1752 tests, 0 failing, post-bump
  self-audit --check-readme: PASS, readmeCheck.passed, 0 mismatches
  check-versions.mjs: 0 ERROR (1 WARN = the unreleased bump this cut closes)
  scanners 16, agents 7, commands 21, hooks 4, knowledge 8

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKeJyEVn9GuBcBQbE6EYwm
This commit is contained in:
Kjell Tore Guttormsen 2026-08-18 20:57:58 +02:00
commit b35ff449e8
3 changed files with 30 additions and 2 deletions

View file

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

View file

@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [6.0.0] - 2026-08-18
### Summary
"Prose is not a contract" — a MAJOR release whose theme is a *class* of defect rather than a feature
area. Across three sweeps (Q1, Q2, Q_AUDIT) the same shape kept surfacing: a command template stated
an invariant in prose, code on the other side depended on it, and nothing checked that the two still
agreed. The write-scope **gate** was policy paraphrased in five templates while exactly one writer
imported it. The **argv** a template built was never checked against the CLI receiving it —
`--stale-after 30` arrived as a single argument, matched no flag, and the command reported success
about a threshold the user had just overridden. And the **data contracts** — backup manifests,
`state.yaml`, `scope.yaml` — are hand-built by the model and parsed by engines that know one frozen
example. The first two are now enforced in code and tests; the third is measured and ranked in
`docs/q-audit-prose-invariants.md`, with the recovery path (`rollback`) at the top as the surface
that runs precisely when the user is already in trouble.
**Breaking — a finding ID's `{NNN}` names the check, not its emission position (`M-BUG-28`).**
IDs are therefore not unique per finding: one check failing in three files emits three findings
sharing an ID, and `(id, file, line)` is the instance key. Any consumer keying on `id` alone must
move to the triple. `scanners/lib/finding-codes.mjs` is now the single authority — an undeclared or
missing code **throws**, with no counter fallback, because a fallback lets a half-converted scanner
ship IDs that look valid. Retired numbers are never reissued. Frozen `v5.0.0` baselines mask IDs
rather than re-deriving them.
**37** commits since 5.13.0. **1752** tests, 0 failing. GAP dimensions **25 → 24** (one `/doctor`
duplicate retired). No component-count change: scanners **16**, agents **7**, commands **21**,
hooks **4**, knowledge entries **8**.
### Fixed
- **`M-BUG-45``/config-audit knowledge-refresh --stale-after N` was silently dead under zsh.** The
command built `STALE_AFTER="--stale-after 30"` and expanded it unquoted, relying on the shell to

View file

@ -8,7 +8,7 @@ Know if your configuration is correct. Find what could improve it. Fix it automa
*AI-generated: all code produced by Claude Code through dialog-driven development. Every change is human-directed, reviewed, and validated before commit. Per Anthropic Consumer Terms §4, ownership of outputs is assigned to the user; this plugin is licensed MIT.*
![Version](https://img.shields.io/badge/version-5.13.0-blue)
![Version](https://img.shields.io/badge/version-6.0.0-blue)
![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)
![Scanners](https://img.shields.io/badge/scanners-16-cyan)
![License](https://img.shields.io/badge/license-MIT-lightgrey)
@ -809,6 +809,7 @@ Full detail in [CHANGELOG.md](CHANGELOG.md). Highlights per release:
| Version | Date | Highlights |
|---------|------|-----------|
| **6.0.0** | 2026-08-18 | "Prose is not a contract" — a MAJOR release whose subject is a *class* of defect rather than a feature area. Three sweeps (Q1, Q2, Q_AUDIT) kept surfacing the same shape: a command template stated an invariant in prose, code on the other side depended on it, and nothing checked that the two still agreed. **Breaking (`M-BUG-28`):** a finding ID's `{NNN}` names the **check**, not its emission position — so IDs are **not unique per finding** (one check failing in three files emits three findings sharing an ID) and any consumer keying on `id` alone must move to `(id, file, line)`. `finding-codes.mjs` is the single authority and **throws** on an undeclared code, with no counter fallback — a fallback lets a half-converted scanner ship IDs that look valid. **Q1 — the write gate runs in code:** `write-scope.mjs` existed for four releases while exactly *one* writer imported it and five templates paraphrased the policy; measured, 9 files under `scanners/` write to disk and 1 imported the gate. Every writer must now import it or hold an `EXEMPT` entry naming **where the bytes land** — four of them write the plugin's own bookkeeping and must stay ungated, because a gate that fires on every run gets switched off. **Q2 — the argv is checked against the CLI that receives it:** `--stale-after 30` reached its CLI as one argument under zsh, matched no flag, and the command reported "✓ all 14 entries re-verified within the last 90 days" about a threshold the user had just overridden. The probe builds argv from each template's **own text**, and proves itself per CLI by first being seen rejecting a flag that cannot exist. **Q_AUDIT — the third instance, measured not fixed:** data contracts (`state.yaml`, backup manifests) are hand-built by the model and parsed by engines knowing one frozen example; ranked R1R9 with the **recovery path** on top — `rollback` has no CLI entry at all and runs as model prose that pre-renders "(checksum verified)". **Added:** `optimize --subtract --apply` (the subtraction axis can now remove what it proposes — validated against the ORIGINAL content, applied in **descending** line order, coverage asserted from the backup manifest before a byte changes, and the load-bearing floor re-checked in the engine so a hand-built approval cannot route around it); `--for-model <name>` (`BP-PROMPT-001`), an annotation on existing candidates that can never widen the set, reporting `recognized` separately from `matchedCount` so a typo'd model name cannot read as a clean config; a cross-repo write disclosure before approval (`M-BUG-41`); and model/effort routing as a **lever**, not a 25th GAP dimension — a dimension would move every user's utilization score. **Removed:** the `No autoMode classifier` GAP dimension as a `/doctor` duplicate (**25 → 24**); its title lived in **four** tables, not the two the removal was scoped against. **Fixed:** the CLI-argument class across all 14 CLIs, shell state assumed to survive between fenced blocks (**20 places across 9 files**), stdout discarded against a pipe, a nonexistent target path graded instead of erroring, and `drift` crashing on a moved finding humanized as if it were flat. **1752** tests, 0 failing; frozen `v5.0.0` untouched. No component-count change (scanners **16**, agents **7**, commands **21**, hooks **4**). |
| **5.13.0** | 2026-07-31 | "Pipeline hardening" — the batch release of everything found by dogfooding the plugin against the maintainer's real machine and by walking the `analyze → plan → implement → rollback` pipeline end-to-end on a throwaway repo copy: one new lens mode plus **14 bugs** (`M-BUG-11``M-BUG-20`, `M-BUG-22``M-BUG-25`). **Added — `optimize --subtract` (`BP-SUB-001`):** the subtraction axis, asking what no longer earns its always-loaded rent. Opt-in, proposes only, and the only lens that removes config — so a **load-bearing block is never a candidate**, decided in code (`scanners/lib/floor-exclusion.mjs`) *before* the judge runs, never in prose. Verified against a hand-built ground truth written before any classifier existed: **zero load-bearing blocks proposed**, 11/18 groups, ~756 tok ≈ 18% of a ~4300-token file. **Fixed — `rollback` (`M-BUG-22/23/24/25`):** nothing agreed on where a backup lives; `listBackups()` returned 9 phantom test backups and 0 of 4 real ones, and `restoreBackup` returned `{restored:[],failed:[]}` — a **success-shaped no-op** — because `parseManifest` knew only one of the two manifest spellings in use. Canonical root now, legacy kept readable, unparseable manifests **throw**. **`M-BUG-19`:** `globToRegex` corrupted mid-pattern `/**/`, flagging live rules dead. **`M-BUG-18`/`M-BUG-20`:** the subagent harness won't write report-shaped `.md` (analyze now persists the returned report), and parallel agents clobbered the shared log with `Write` (pinned to Bash `>>`). **`M-BUG-11`/`M-BUG-13`:** `optimize` and `feature-gap` scanned vendored plugin config a user cannot act on — `optimize` candidates **454→45**, `feature-gap` **~0 (masked) → 18** opportunities. **`M-BUG-12`/`M-BUG-14`/`M-BUG-15`/`M-BUG-16`/`M-BUG-17`:** plain-language output that contradicted its own evidence (posture's `--output-file` never humanized; four finding types with no humanizer entry). Known, deliberately unfixed: `rollback` cannot delete files `implement` *created* — it now reports them (`createdNotRemoved`) instead of failing silently; automatic deletion of user files gets its own design. No count change (scanners **16**, agents **7**, commands **21**, hooks **4**); frozen v5.0.0 untouched, SC-5 regenerated once for two humanized titles. **1398** tests (+54). |
| **5.12.5** | 2026-06-26 | "Dogfood denoise" — a samle-release of the Fase-3 scanner false-positive batch (`M-BUG-2/6/7/8/10`, all dogfooding finds on the maintainer's real machine). Five scanners stop counting non-user / non-live config as the user's: **CNF** (`M-BUG-2`) excludes files under `.claude/plugins/` from conflict analysis (`isPluginBundled`) — installed plugins' bundled settings/hooks/fixtures are not a user-resolvable cascade (dogfood **339→0**, Conflicts was an F on pure plugin noise). **file-discovery** (`M-BUG-8`) adds `backups` to `SKIP_DIRS` — a `backups/` tree holds frozen copies, never live config (dogfood files-under-`/backups/` **36→0**, 717 live retained). **token estimator** (`M-BUG-6`) strips block-level `<!-- -->` HTML comments from CLAUDE.md sizing — CC strips them before injection, so they were never always-loaded tokens (dogfood ~3386→3301, ~85 tok). **CPS** (`M-BUG-7`) skips fenced/inline code and whitelists CC-stable path vars (`${CLAUDE_PLUGIN_ROOT}`/`${CLAUDE_PROJECT_DIR}`) before cache-buster matching (dogfood **5→2**). **SET** (`M-BUG-10`) typo-gates the unknown-settings-key finding — the CC schema is passthrough (verified against the 2.1.193 binary), so an unknown key is forward-compatible, not an error; it now flags only a near-miss of a known key (levenshtein ≤2), severity medium→low, +6 binary-verified `KNOWN_KEYS` (dogfood **6→0**). No count change (scanners **16**, agents **7**, commands **21**, hooks **4**); all five are byte-stable — frozen v5.0.0 + SC-5 + default-output snapshots untouched, no re-seed (each fixture's findings are genuinely unchanged). **1344** tests (+37). |
| **5.12.4** | 2026-06-26 | "Rooted rules" — fixes `M-BUG-9` (dogfooding find) in `scanners/rules-validator.mjs`: the RUL "Rule path pattern matches no files" check now resolves a rule's `paths:`/`globs:` pattern against the rule's **own project root** (the dir containing its `.claude/`), not the outer scan root. Previously `countGlobMatches` globbed against the scan target and `collectProjectFiles`' `depth>4` cutoff never reached deep matching files, so a live rule in a **nested repo** (e.g. a marketplace checkout under `~/.claude`) was wrongly flagged "never activates" (high) — a false F-grade for anyone with rules in a nested repo. The fix derives each rule's project root, collects+globs per root (cached), and skips the check for user-global rules (`root === $HOME`), which scope against the active project at runtime. Same scope-conflation family as `M-BUG-1/2`. No count change (scanners **16**, agents **7**, commands **21**); the fix is a no-op when `projectRoot === targetPath` (the common single-repo scan), so frozen v5.0.0 + default-output snapshots stay byte-stable. **1307** tests (+2 TDD: nested-repo false-positive + HOME guard). |