-
v5.13.0 Stable
released this
2026-07-31 15:34:09 +00:00 | 40 commits to main since this releaseSummary
"Pipeline hardening" — the batch release of everything found by dogfooding the plugin against the
maintainer's real machine and by walking theanalyze → plan → implement → rollbackpipeline
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
--subtractalone; 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: arestoreBackupthat 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.0snapshots
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:
rollbackstill cannot delete files that
implementcreated — a backup cannot hold a file that never existed. It no longer fails silently
(manifests carry acreated:list,restoreBackupreturnscreatedNotRemoved, androllback.md
requires the report), but automatic deletion of user files is destructive and gets its own design.
drift-cli.mjsstill 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.--subtractadds that as a fourthlensCheckon 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 viascripts/dogfood-subtraction-gate.local.mjs. Three bugs the dogfood run
exposed are now covered by fixtures: JS\bis ASCII-only so/\bunngå\b/never matched (every
Norwegian keyword ending inæ/ø/åwas silently dead); a bareword/wordis 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-001is grounded entirely in the Anthropic steering blog already cited by
BP-MECH-001..004and 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.mjsresolved~/.config-audit/backups(pre-v2.2.0) while every
command, agent and doc uses~/.claude/config-audit/backups. The auto-backup hook andfix-cliwrote
to the first,implementto the second,rollbackread only the first — solistBackups()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):parseManifestunderstood only the
engine's quotedoriginal_path:spelling, butimplementhand-builds its manifest with
- backup:/original:/sha256:. Every implement-made backup parsed to zero files and
restoreBackupreturned{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 calledcreateBackup()against the developer's real home, leaving nine stray
backups there whilecleanupOldBackups()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 touchingcreateBackup()must too.
Verified against backup20260717_032636on 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—globToRegexcorrupted 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 implementon a throwaway repo copy: the implementer agent's
correctposts/**/post.mdrule was flagged dead. Fixture outcomes byte-identical.analyzepersists the agent-returned report (M-BUG-18). The Claude Code subagent harness
instructs spawned agents not to write report/summary/findings/analysis.mdfiles — the parent
reads the final text message. Verified live:analyzer-agentskippedWriteentirely, so
analysis-report.mdnever 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
analyzecommand saves it verbatim before presenting the summary. The harness note is file-type
specific —planwas dogfooded afterwards and writesaction-plan.mdwithout friction, so the same
fix is not needed there.implementpins>>append discipline on the shared log (M-BUG-20).implement.mdspawns
implementer agents in parallel batches, all appending to the sameimplementation-log.md. Dogfooding
showed agents satisfying "append result to:" with a full-fileWrite— 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.optimizelens 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 --globalproduced 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 byrelPath || absPath, andrelPathcollides across scopes — a repo-root
CLAUDE.mdand~/.claude/CLAUDE.mdboth key toCLAUDE.md, so the two files that actually matter
merged into one indistinguishable bucket and the agent'sRead(file)would resolve the wrong one.
Dogfood: candidates 454→45, distinct files 92→11, repo vs user-global now distinct.feature-gapscopes presence checks to authored config and reads the settings cascade
(M-BUG-13). The GAP scanner's 25 presence checks ran over the fullincludeGlobaldiscovery, so
this plugin's ownexamples/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.jsonwas invisible to the settings-key checks (theincludeGlobalgotcha plus the
maxFilescap), which would have flippedstatusLine/autoModeinto false positives the moment the
maskers were removed. Both halves are fixed together:isAuthoredConfigexcludes plugin-bundled and
nestedexamples//tests/fixtures/config, andreadSettingsCascadereads user→project→local
directly. Empty target: ~0 (masked) → 18 humanized opportunities.posture --output-filehumanizes findings in default mode (M-BUG-12).feature-gap.mdand
posture.mdboth read findings fromposture.mjs --output-fileand group on the humanizer fields,
butposture.mjsonly humanized the stderr scorecard — its--output-fileJSON wrote the raw
v5.0.0-shape result, so every finding's humanizer fields wereundefinedand both commands silently
degraded to the raw tier-fallback. v5.1.0 plain-language output was dead forfeature-gapand 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/--rawstay 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_CATEGORYhad no AGT entry, so its findings fell through to theOtherfallback, 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 noSKL.staticentry it fell
through toSKL._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.
Downloads
-
Source code (ZIP)
2 downloads
-
Source code (TAR.GZ)
1 download