process.exit() terminates immediately, but Node writes stdout asynchronously when stdout is a pipe — everything still buffered is dropped. scan-orchestrator measured 246 854 bytes to a file against 65 536 to a pipe (131 072 on another run; the cut point is a flush race), so every machine consumer that pipes the envelope got truncated, unparseable JSON. The failure reads like a corrupt file, not like a cut-off, which is what made it survive this long. Reported by org-ops, whose census pipes our output. Closes the class rather than the one CLI where it was visible. campaign-cli, campaign-export-cli, campaign-write-cli, knowledge-refresh-cli, drift-cli and fix-cli all exited the same way on their success paths and were green only because their payloads fit the pipe buffer today; size is not correctness. All 38 sites across 14 files now set process.exitCode and return, which is the pattern self-audit.mjs already used. Two contracts needed care rather than substitution: fail() is a never-returns guard at ~25 call sites, so it throws a CliUsageError the top-level catch renders with the identical "Error: " prefix and exit code 3; the path guards needed an explicit return so main() stops instead of running on. Exit codes and stderr text are unchanged, and the frozen v5.0.0 snapshots are untouched. The class sweep is landed as a test, not as fourteen edits — it caught one site this commit had missed. Suite 1443/0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8sS1DuDV6bUJcyumLwbvj
1496 lines
118 KiB
Markdown
1496 lines
118 KiB
Markdown
# 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).
|
||
|
||
## [Unreleased]
|
||
|
||
### Fixed
|
||
- **`M-BUG-39` — every scanner CLI could truncate its own output when piped.** `process.exit()`
|
||
terminates immediately, but Node writes stdout **asynchronously** when stdout is a pipe, so whatever
|
||
is still buffered is discarded. `scan-orchestrator.mjs` measured **246 854 bytes to a file vs
|
||
65 536 to a pipe** (and 131 072 on another run — the cut point is a nondeterministic flush race),
|
||
handing any machine consumer truncated, unparseable JSON that reads like a corrupt file rather than
|
||
a cut-off. Reported by `org-ops`, whose census pipes our envelope. The whole class is closed, not
|
||
just the CLI where it was observable: `campaign-cli`, `campaign-export-cli`, `campaign-write-cli`,
|
||
`knowledge-refresh-cli`, `drift-cli` and `fix-cli` all exited the same way on their success paths
|
||
and were green only because their payloads happen to fit the pipe buffer today. All 38 sites across
|
||
14 files now set `process.exitCode` and return, letting Node exit once stdout drains — the pattern
|
||
`self-audit.mjs` and llm-security's orchestrator already used. `fail()` throws instead of exiting so
|
||
it keeps its never-returns contract; exit codes and `Error:`/`Fatal:` stderr text are unchanged.
|
||
Guarded by `tests/scanners/cli-pipe-integrity.test.mjs`: one behavioural test that pipes a >128 KB
|
||
envelope, one class sweep over `scanners/*.mjs`.
|
||
- **`M-BUG-36` — `/config-audit drift --list` showed nothing.** `drift-cli.mjs` accepted
|
||
`--output-file` but list mode ignored it, and the listing itself goes to **stderr**, which
|
||
`commands/drift.md` discards with `2>/dev/null` (ux-rules rule 2). The command received **0 bytes**
|
||
and could render no baselines at all. List mode now honours `--output-file`; `--raw`/`--json` stdout
|
||
is unchanged and byte-stable. Fourth instance of the stderr-only class after `M-BUG-33`.
|
||
- **`M-BUG-37` — `/config-audit feature-gap` promised a backup it never made.** Step 6's
|
||
"Create backup" ran `fix-cli.mjs <path> --json`, but fix-cli is **dry-run by default**: no backup was
|
||
written and `backupId` came back `null`, after which the command edited the user's configuration
|
||
believing it could be restored. Passing `--apply` would have been worse — it executes unrelated
|
||
auto-fixes the user never selected. The step now copies the files itself and states plainly that
|
||
plain copies are restored by copying them back, not by `/config-audit rollback` (`M-BUG-31` class).
|
||
- **`M-BUG-38` — `fix-cli.mjs` sent users to a script that does not exist.** After applying fixes it
|
||
printed `Rollback: node scanners/rollback-cli.mjs <id>`; there is no `rollback-cli.mjs` — only
|
||
`rollback-engine.mjs`, driven by `/config-audit rollback`. A dead reference in the one message a
|
||
user reaches for after a bad fix. Now points at the command.
|
||
- **`M-BUG-21` (fourth arm) — command templates fed bracketed placeholder flags to the arg loops.**
|
||
Five templates (`config-audit.md`, `discover.md`, `fix.md`, `tokens.md`, `whats-active.md`) carried
|
||
literal `[--global]` / `[--full-machine]` / `[--verbose]` inside executable bash blocks. A bracketed
|
||
placeholder does not start with `-`, so every scanner's `else if (!args[i].startsWith('-'))` branch
|
||
takes it as the **scan target** — silently scanning a path that does not exist. Replaced with empty
|
||
shell variables that expand to nothing when the flag does not apply.
|
||
- **`/config-audit interview` and `analyze` never said which session they act on.** Both referenced
|
||
`{session-id}` with no resolution rule, while every other session-aware command globs
|
||
`sessions/*/state.yaml` and takes the most recent. Two runs could write to two different sessions.
|
||
Both now resolve the session explicitly and exit when none exists.
|
||
- **`/config-audit interview` could rewind a finished session.** The mandated state write had no bound,
|
||
so running the optional interview against a session that had already reached `implement` reset
|
||
`current_phase` and re-added phases. It now appends `interview` only if absent and leaves the
|
||
furthest phase reached intact.
|
||
- **`/config-audit cleanup` interpolated an unvalidated id into `rm -rf`.** An empty or malformed
|
||
`{session-id}` expands the path to `sessions//`, deleting **every** session. The id must now match
|
||
`^[0-9]{8}_[0-9]{6}$` or come verbatim from the directory listing; anything else is refused and
|
||
reported.
|
||
- **`/config-audit status` advertised a command that does not exist.** It documented
|
||
`/config-audit resume {session-id}`; there is no `resume` command. Replaced with how session
|
||
selection actually works. A test now fails on any `/config-audit <word>` reference in `commands/`
|
||
without a matching file.
|
||
- **`/config-audit status all` was documented but never parsed.** The flag-parse step knew only
|
||
`--raw`. It now parses `all` and routes to the all-sessions table.
|
||
|
||
### Fixed (previously)
|
||
- **`M-BUG-21` (third arm) — `plugin-health-scanner.mjs` swallowed unknown flags, and the wrong
|
||
target looked *green*.** The same `else if (!args[i].startsWith('-')) targetPath = args[i]` loop:
|
||
`--output-file /tmp/x.json` was dropped and `/tmp/x.json` became the scan target. Where `drift`
|
||
produced phantom drift, this produced a **reassuring** answer — a non-existent path discovers no
|
||
plugins, so the scanner reported `No plugins found` (info) and exit `0`. Unknown options and a
|
||
value-less `--output-file` now exit `3`.
|
||
- **`M-BUG-33` — `/config-audit plugin-health` read zero bytes.** The scanner had no `--output-file`
|
||
(ux-rules rule 2) and its default-mode report goes to **stderr**, which `commands/plugin-health.md`
|
||
discards with `2>/dev/null` before telling the agent to "read stdout output (JSON)". The command's
|
||
default path could not produce the report it documents. `--output-file` now writes a humanized
|
||
payload; `--raw`/`--json` stdout is unchanged and byte-stable.
|
||
- **`M-BUG-34` — the report's per-plugin table and Cross-Plugin section were unbuildable.** Per-plugin
|
||
data (`commandCount`, `agentCount`) and the grade formula never left `scan()` — the only grade code,
|
||
`formatPluginHealthReport`, had no caller — and cross-plugin findings were flattened into `findings`
|
||
behind a `category: 'plugin-hygiene'` they share with per-plugin findings. The command mandated both,
|
||
so it had to fabricate them. The payload now carries `plugins[]` (name, declaredName, counts, score,
|
||
grade via the shared `pluginGrade`) and `cross_plugin_findings[]` (also marked `crossPlugin: true`),
|
||
via a new `scanDetailed()`; `scan()`'s frozen v5.0.0 envelope is untouched.
|
||
- **`M-BUG-35` — `.claude-plugin/marketplace.json` was reported as an unknown file.** It is the
|
||
documented, required location for a marketplace catalog, and a marketplace entry with
|
||
`"source": "./"` makes the repo root its own plugin — such a repo legitimately carries both files.
|
||
Genuinely unexpected files in `.claude-plugin/` are still flagged.
|
||
- **`commands/posture.md` discarded both optional scanners' output.** Its `--drift` and
|
||
`--plugin-health` sections ran `drift-cli.mjs` / `plugin-health-scanner.mjs` in default mode under
|
||
`2>/dev/null` and read stdout, which is empty in that mode. Both calls now use `--output-file`.
|
||
- **`M-BUG-21` — `drift-cli.mjs` had no `--output-file`, and its argument loop turned the missing
|
||
flag into a wrong scan target.** The loop ended in `else if (!arg.startsWith('-')) targetPath = arg`
|
||
with no unknown-flag branch, so an unrecognised flag was dropped silently and its *value* fell
|
||
through to the scan target: `drift-cli.mjs . --output-file /tmp/x.json` scanned `/tmp/x.json`, a
|
||
path that does not exist, and reported the resulting near-empty scan as drift — permanently, and
|
||
without a warning. The same silence was destructive for `--save --name` with the value omitted:
|
||
`--name` was ignored, the name stayed `default`, and an existing baseline was **overwritten**.
|
||
Unknown options and value-less `--name`/`--baseline`/`--output-file` now exit `3`.
|
||
- **`M-BUG-21` (second arm) — `/config-audit drift` captured nothing at all.** `commands/drift.md`
|
||
ran the CLI under `2>/dev/null` and told the agent to "read stdout", but the default-mode report,
|
||
the `--save` confirmation, and the `--list` output all go to **stderr**. All three modes returned
|
||
empty. `--output-file` now writes the diff (humanized in default mode, raw under `--json`/`--raw`,
|
||
matching `posture.mjs`), and the command reads that file; `--save` passes `--json` for its
|
||
confirmation.
|
||
- **`M-BUG-27` — `drift` compared against baselines anchored to a different directory and called it
|
||
"improving".** `diff-engine` never checked the baseline's stored `target_path` against the current
|
||
scan target. Diffing a repo against a baseline saved elsewhere marked every baseline finding
|
||
"resolved" and every current finding "new" — a 100% phantom diff surfacing as a *reassuring* trend,
|
||
on the **default** baseline. The CLI now warns on stderr in every mode and carries
|
||
`_baselineAnchor {matches, baselineTarget, currentTarget}` in the default-mode payload, so a caller
|
||
running under `2>/dev/null` can still see it. `--json`/`--raw` stdout stays v5.0.0-shaped and the
|
||
frozen `drift.json` snapshot is untouched.
|
||
|
||
- **`M-BUG-21` (third arm) — `fix-cli.mjs` had the same unvalidated argument loop, where it moves the
|
||
*write* target.** An unrecognised flag was dropped and its value became the scan target, so
|
||
`fix-cli.mjs <repo> --output-file /tmp/x.json` silently audited `/tmp/x.json`; with `--apply` the
|
||
same slip relocates what gets written. Unknown options and value-less `--output-file` now exit `3`.
|
||
`--dry-run` — documented in `commands/fix.md`'s `argument-hint` but never implemented — is now
|
||
accepted instead of silently dropped, and `--output-file` writes the fix payload to disk so
|
||
`commands/fix.md` can read a file rather than parse stdout it runs under `2>/dev/null`.
|
||
- **`M-BUG-31` — `fix` promised a mandatory backup it did not always take.** `fix-cli.mjs` excluded
|
||
`file-rename` from the backup set, so a rule file whose only defect was its extension was renamed
|
||
with **no** backup entry — while the command told the user "every fix creates a backup first" and
|
||
handed back a `backupId` that could not restore it. The source file is now backed up like any other.
|
||
- **`M-BUG-32` — verification re-scanned a different scope than the fix run.** `verifyFixes` hardcoded
|
||
`includeGlobal: false`. After a `--global` run every user-scope finding fell out of the re-scan and
|
||
was therefore counted as *verified*: a clean "fixed" report for files nothing had touched
|
||
(reproduced against an untouched `~/.claude/CLAUDE.md`). It now inherits the run's scope, and
|
||
`commands/fix.md` passes `--global` to every step instead of only the display scan.
|
||
- **`M-BUG-29` — two fixes on one file were applied in an order that guaranteed failure.** A rule file
|
||
with both `globs:` and a non-`.md` extension had the rename applied first; the frontmatter fix then
|
||
failed with `ENOENT`. Renames now sort after every other fix.
|
||
- **`M-BUG-30` — critical fixes sorted last.** `severityOrder[s] || 4` maps `critical` (weight `0`) to
|
||
`4`, the opposite of the documented "critical first" contract. The old test used the same falsy
|
||
fallback, so it agreed with the bug. Now `?? 4`.
|
||
- **A failed fix no longer exits `0`.** `fix-cli.mjs` returns `2` when any planned fix failed, matching
|
||
the `0/1/2 = PASS/WARNING/FAIL`, `3 = error` convention the other scanners follow.
|
||
|
||
**1420** tests (+10). No count change (scanners **16**, agents **7**, commands **21**, hooks **4**).
|
||
|
||
## [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
|
||
"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/<ts>/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/<mkt>/.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/<mkt>/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/<mkt>/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/<mkt>/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 <n>` 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 <n>` 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 <file> (@<path> 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 <root>` 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 <path>` 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-<session>.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 <repo> 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__<server>__*` 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 <path>` 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/<pkg>/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/<slug>/*.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 <path>` 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/<pkg>/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 <path> [--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 <path> [--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 <path> [--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
|