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