config-audit/docs/v5.4.0-release-plan.md
Kjell Tore Guttormsen d77c18aa53 docs(plan): mark v5.4.0 Feature 3 DONE — Session B complete [skip-docs]
CA-SET autoMode shipped (3633571). All 3 Session B features built+pushed;
suite 936->949. Premise #3 confirmed against primary source (per-file-scope
gate passed). Next: Session C release (needs operator GO).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
2026-06-19 22:15:26 +02:00

258 lines
18 KiB
Markdown

# v5.4.0 Release — Multi-Session Plan
_Status: PLANNED. No version bump yet. `plugin.json` = 5.3.0. All work below is gated on
explicit operator GO per session. Plans live next to STATE per the continuity system.
Mirrors the structure of `docs/v5.3.0-release-plan.md`._
## Goal
Release **v5.4.0** (minor, backward-compatible): three **net-new additive scanner findings**
that extend the plugin-hygiene (PLH) and settings (SET) surfaces, mirroring real Claude Code
warnings/validation that config-audit does not yet detect. Unlike v5.3.0 (which documented work
already on `main`), **the features here do not exist yet** — Session B is the core build, not a
conditional. No bugs/false-positives are involved; all three are enhancements with confirmed CC
premises.
**Theme:** *plugin-hygiene & settings-validation hardening.*
## Pre-verified facts (do NOT re-derive)
- Last release: **v5.3.0** = commit `fe686b6`, tag `v5.3.0`, 2026-06-19. `git rev-list --count
v5.3.0..HEAD` = **0** → working tree is at the tag; v5.4.0 builds net-new code.
- Suite at HEAD: **936/936** (clean tree, unchanged since the v5.3.0 release). self-audit at
v5.3.0: configGrade A 97, pluginGrade A 100, readmeCheck.passed.
- **Scanner count = 13** (authoritative source `countScannerShape`, `scanners/self-audit.mjs:56`;
README badge `scanners-13`). All three approved features **extend existing scanners** (PLH, SET)
— **no new scanner file**, so the badge, the scanner table, and `countScannerShape` need **no
change**. (An early grep of `export async function scan` counted 15 shapes — that set includes
lib/non-badge shapes like CNF/IMP/SAL and is NOT the badge count. Do not bump the badge.)
- PLH currently emits `CA-PLH-001`..`CA-PLH-014` (014 = namespace collision, 013 = command-name
ambiguity, both shipped v5.3.0). Next free: `CA-PLH-015`, `CA-PLH-016`.
- Edit-target files (v5.3.0 release pattern): `.claude-plugin/plugin.json`, `README.md`
(badges + What's New + version-history row + TOC), `CHANGELOG.md` (`## [5.4.0]` above `[5.3.0]`),
`docs/cc-2.1.x-gap-matrix.md`, the two target scanners, their tests, and 3 knowledge entries.
- **CC premises primary-source verified** (code.claude.com/docs, 2026-06-19) — see Session A
reconciliation. The matrix is NOT ground truth; row 175 was found to be framed backwards.
---
## Session A — Reconciliation & scope decision (read/plan; this session) ✅ DONE
**No code changes.** Re-reconcile the gap-matrix against HEAD, verify each candidate is still open
AND that its CC premise is real, classify ship/defer/wontfix, get operator GO on the ship-list,
draft the changelog. Output recorded below + a `## v5.4.0 reconciliation` block appended to
`docs/cc-2.1.x-gap-matrix.md`.
**Verifisering (testbar):**
- Every v5.4 candidate has a recorded code-state verdict (IMPLEMENTED/OPEN/PARTIAL) with file:line.
- Every candidate has a CC-premise verdict (CONFIRMED/REFUTED/UNVERIFIABLE) with a source.
- Ship-list has a recorded operator GO before any build. ✅
- The refuted premise (#3) is corrected in the gap-matrix. ✅
---
## Session B — Implement the 3 approved features (CORE build)
**One feature per chunk. Iron Law: failing test FIRST (/tdd), then minimal implementation.**
Re-confirm each feature's exact CC behavior against primary docs at the start of its chunk
(Verifiseringsplikt — premises were verified in Session A but the exact field lists / warning text
must be pinned before coding). Checkpoint STATE + this plan after each feature.
### Feature 1 — PLH shadow-folder (`CA-PLH-015`) ✅ DONE (commit `7abc5a1`)
- **Verifiseringsplikt correction (2026-06-19):** the field set was pinned against
`code.claude.com/docs/.../path-behavior-rules`. Only the **replaces** category truly shadows:
shipped set = `commands`/`agents`/`outputStyles`. **`skills` excluded** — it *adds to* the
default `skills/` scan (both load, never a shadow); **`hooks`/`mcpServers`/`lspServers`
excluded** — own merge rules, not a folder-shadow. Experimental `themes`/`monitors` omitted
(docs warn their schema may change). Explicit-address exception honored
(`"commands": ["./commands/x.md"]` not flagged). +5 tests (936→941), suite green, self-audit
A/A, count 13, gitleaks clean. Pushed.
- **What:** flag when a `plugin.json` component-path key (`commands`/`agents`/`outputStyles`)
points at a **custom path** while the **default folder of that name also exists** in the plugin,
so the default is silently ignored. Mirrors CC's warning in `/doctor` & `claude plugin list`
(~2.1.140). _(Original plan listed `commands/agents/skills/hooks` — corrected above.)_
- **Where:** `scanners/plugin-health-scanner.mjs` (per-plugin loop; the scanner already reads
`plugin.json` but only checks `name`/`description`/`version`). Add component-path-key parsing.
- **Severity:** MEDIUM — silently-shadowed components = dead config (a whole command/skill/agent
set goes unreachable). `category: 'plugin-hygiene'`.
- **Tests:** fixture plugin with both `"commands": "custom/"` AND a `commands/` folder → flagged;
fixture with only one of the two → not flagged; fixture with no component keys → not flagged.
### Feature 2 — PLH `skills:`-array validation (`CA-PLH-016`) ✅ DONE (commit `76d5eda`)
- **Verifiseringsplikt correction (2026-06-19):** the plan's claim that the CC error "suggests the
parent directory when an entry points at a file" is **not** in the primary docs — dropped. The
finding asserts only the four primary-source-verified conditions. Escape backed by docs'
path-traversal rule (*"Installed plugins cannot reference files outside their directory … such as
`../shared-utils`"*). string|array normalized, so a non-string top-level value (e.g. `42`) is
caught as one non-string entry. +3 tests (941→944), suite green, self-audit A/A, count 13.
- **What:** when `plugin.json` has a `skills:` field (string|array), validate each entry resolves to
an existing **directory inside the plugin root**. One finding per bad entry, `problem` ∈
{`non-string`, `escapes-root`, `not-found`, `not-a-directory`}. Mirrors `claude plugin validate`.
- **Where:** `scanners/plugin-health-scanner.mjs` (read `parsed.skills` after the parse-success
guard).
- **Severity:** MEDIUM (broken/partly-broken plugin manifest). `category: 'plugin-hygiene'`.
- **Tests:** fixture `skills: ["valid-dir", "points-to-file.md", "missing", "../escape"]` → one
finding per bad entry, none for `valid-dir`; fixture with no `skills:` key → not flagged.
### Feature 3 — SET autoMode structure + dead-config (`CA-SET-NNN`) ✅ DONE (commit `3633571`)
- **Premise CONFIRMED against primary source** (`code.claude.com/docs/en/auto-mode-config`) — the
plan was correct this time (unlike #1/#2). 4 sub-keys verbatim; `"$defaults"` valid; exact scope
exclusion quote confirmed. **Per-file-scope gate PASSED:** `ConfigFile` already carries `scope`
(`classifyScope` → `'project'` for shared `.claude/settings.json`), so the dead-config sub-check
shipped (no fallback to structure-only needed). SET is in the orchestrator → **SC-5 re-checked,
byte-equal** (snapshot fixture has no autoMode). +5 tests (944→949), suite green, self-audit A/A,
count 13, gitleaks clean. Fixtures force-added (`.claude/` gitignored).
- **What (two sub-checks):**
1. **Structure validation** — `autoMode`, if present, must be an object whose only keys are
`environment`, `allow`, `soft_deny`, `hard_deny`, each a **string array** (entries are prose
rules; `"$defaults"` is a valid literal). Flag unknown sub-keys and wrong value types.
2. **Dead-config** — `autoMode` placed in **shared project settings** (`.claude/settings.json`)
is **ignored by CC** ("Not read from shared project settings" — official docs). Flag it as
dead config; valid scopes are user (`~/.claude/settings.json`), local
(`.claude/settings.local.json`), and managed.
- **Where:** `scanners/settings-validator.mjs` (`autoMode` is already in `KNOWN_KEYS:21` but has no
TYPE_CHECKS / nested validation). The dead-config sub-check needs the settings file's **scope**;
confirm the scanner already knows per-file scope (it validates user/project/local files).
- **Severity:** structure = LOW/MEDIUM (malformed config); dead-config = LOW (dead config).
- **Tests:** valid autoMode in user settings → clean; unknown sub-key / non-array value → flagged;
autoMode in shared `.claude/settings.json` → dead-config finding.
**Per-feature gates (all must pass before that feature's commit):**
- New tests RED→GREEN; full suite N/N green.
- `node scanners/self-audit.mjs --json --check-readme` → configGrade ≥ A, pluginGrade ≥ A,
readmeCheck.passed, scanner count still 13.
- Any snapshot/fixture touch → SC-5 re-seed + contamination-grep clean; gitleaks clean.
- **docs-gate** (`feat:` commits): README **and** CLAUDE.md each get ≥3 substantive lines
documenting the new finding — OR `[skip-docs]`. Prefer real docs (each finding deserves a
CLAUDE.md "Scanner" note + README mention). Stage docs in a separate Bash call before commit.
- Humanizer: each new finding needs a `userImpactCategory` mapping (dead config / conflict /
configuration mistake) — verify `--json`/`--raw` stay byte-stable for *existing* findings
(`json-backcompat` + `raw-backcompat` green); new findings are additive.
**Scanner count stays 13.** No badge/table/`countScannerShape` change (no new scanner file).
---
## Session C — Release v5.4.0 (version sync + docs + tag + push)
1. **Bump** `.claude-plugin/plugin.json` 5.3.0 → 5.4.0. (Version lives ONLY here.)
2. **README:** `version-` badge → 5.4.0; `tests-` badge → exact suite count after Session B
(will rise — new tests added). Replace "What's New in v5.3.0" → **v5.4.0** (+ TOC anchor).
Insert a new version-history row above `**5.3.0**`. **Scanner count stays 13.**
3. **CHANGELOG.md:** insert `## [5.4.0] - <date>` above `## [5.3.0]` (Added / Test count /
Verification — mirror the 5.3.0 block; this release is Added-only + Internal if any).
4. **Knowledge:** apply the 3 backing entries (list below).
5. **Marketplace cross-repo (`../catalog/`, separate repo):** bump the config-audit `ref`/version
in `marketplace.json` + README **after** the plugin tag is pushed. Same push-window rules,
separate push. **Cross-repo edit requires operator GO.**
6. **Gates:** suite N/N; self-audit configGrade A, pluginGrade A, readmeCheck.passed, count 13;
SC-5 byte-equal or re-seeded + contamination-grep clean; gitleaks clean (both repos).
7. **Commit** `release: v5.4.0 — <one-line>`, **tag** `v5.4.0`, **push** (in push window).
**Verifisering (testbar):** `git tag --list v5.4.0` returns it; README `version-` badge ==
`plugin.json` version; `self-audit --check-readme` passed + badge tests == suite count;
`git log -1` subject starts `release: v5.4.0`; marketplace entry shows 5.4.0.
---
## Key assumptions (test, don't trust)
- **5.4.0 is non-breaking.** Three additive findings; no envelope/JSON-shape change. Verify
`json-backcompat` + `raw-backcompat` + SC-5 snapshot stay green; new findings appear only in
configs that trigger them. If anything changes the JSON shape or existing-finding output → it is
a Breaking note, not Added.
- **settings-validator knows per-file scope.** Feature 3's dead-config sub-check needs to know a
settings file is the *shared project* one. **Test this assumption first in Feature 3's chunk** —
if the scanner doesn't already carry scope per file, either thread it through or drop the
dead-config sub-check to structure-only (still ships).
- **docs-gate blocks `feat:` commits** lacking ≥3 substantive lines in BOTH README and CLAUDE —
proven 2026-06-19. (`release:`/`fix:`/`chore:`/`docs:` pass freely.)
- **self-audit `--check-readme` is number-only** (badge vs filesystem) — it does NOT catch prose
gaps. Manually eyeball the scanner table + "What's New" (the SKL-row gap slipped exactly here).
## Knowledge-touch list (Session C step 4)
Three short backing entries (mirrors v5.3.0's 3-entry pattern; grep-verify each is absent first):
1. `knowledge/claude-code-capabilities.md` — plugin component-path keys shadow default folders;
CC warns in `/doctor` & `claude plugin list` (~2.1.140/142). Backs `CA-PLH-015`.
2. `knowledge/claude-code-capabilities.md` — `claude plugin validate` requires `skills:` entries to
be directories (~2.1.145). Backs `CA-PLH-016`.
3. `knowledge/claude-code-capabilities.md` *or* `configuration-best-practices.md` — `autoMode`
schema (`environment`/`allow`/`soft_deny`/`hard_deny` string arrays, `"$defaults"` literal) and
the "not read from shared project settings" rule. Backs `CA-SET` autoMode finding.
Everything else in the matrix (env-var rows, model-lineup nuance, hook-output fields, nested
.claude doc) → **defer** to rolling knowledge maintenance.
## Out of scope (do not start without separate GO)
- **#2 acceptEdits-on-shell/build-config** (deferred — needs the exact CC special-cased file-category
list primary-source-confirmed before it's buildable). Candidate for a later release.
- **#6 nested-.claude closest-wins** (deferred — a NEW scanner: ancestor-chain walker + grouping +
badge bump 13→14. Heaviest item; natural headline for its own release, e.g. v5.5.0).
- **#3 Read-deny/Glob-Grep** — **wontfix** (premise refuted by primary source; see reconciliation).
- Any scanner refactor not required by the 3 approved features; marketplace-wide changes beyond the
config-audit catalog entry.
---
## v5.4.0 scope decision (Session A output, 2026-06-19)
### Operator GO (recorded verbatim)
> **"Option A"** — ship #1 PLH shadow-folder + #5 PLH skills:-array + #4 autoMode structure/
> dead-config. No new scanner (badge stays 13). #2 and #6 deferred, #3 wontfix.
**Ship-list (3 features):** `CA-PLH-015` shadow-folder · `CA-PLH-016` skills:-array · `CA-SET-NNN`
autoMode structure + dead-config. All extend existing scanners.
### Reconciliation result (6 candidates vs HEAD, code-state + CC-premise)
| # | Candidate (matrix row) | Code-state @HEAD | CC premise | Verdict |
|---|---|---|---|---|
| 1 | PLH shadow-folder (164) | OPEN — `plugin-health-scanner` parses only name/desc/version | CONFIRMED (~2.1.140; `/doctor`+`plugin list` warning) | **ship-5.4** |
| 5 | PLH `skills:`-array dirs (165) | OPEN — does not read `parsed.skills` | CONFIRMED (~2.1.145 `claude plugin validate`) | **ship-5.4** |
| 4 | autoMode.hard_deny structure (179) | OPEN — key in `KNOWN_KEYS:21`, no TYPE_CHECKS/nested val | CONFIRMED (primary source: 4 string-array sub-keys; not read from shared project settings) | **ship-5.4** |
| 2 | acceptEdits-writes shell/build (176) | OPEN — 0 matches in `scanners/` | CONFIRMED (~2.1.160) but exact file-category list unpinned | **defer** (needs primary-source field list) |
| 6 | nested-.claude closest-wins (139/166) | OPEN — no scanner; CML walks only the CLAUDE.md cascade | CONFIRMED (~2.1.178) | **defer** (NEW scanner, badge bump) |
| 3 | Read-deny hides Glob/Grep (175) | PARTIAL — hint in `permission-rules.mjs:158-159`, but `dominates()` treats Read/Glob/Grep as distinct | **REFUTED** — docs: "Claude makes a best-effort attempt to apply `Read` rules to … Grep and Glob"; Read-deny already covers them | **wontfix** |
**Matrix correction (Verifiseringsplikt):** row 175's framing was **backwards**. CC's permissions
doc states Read deny rules already apply (best-effort) to Glob/Grep, so a "Read-deny is bypassable"
finding would be a false positive — the same failure mode as the invented MCP `trust` field. The
only real Read-deny bypass is via Bash subprocesses (a python/node script that opens files), which
is already documented behavior, not a config mistake to flag. The Windows-path half (CC normalizes
`C:\` → `/c/`) is real but narrow/low-value and is folded into "defer rolling maintenance".
### Draft CHANGELOG narrative (finalize after Session B)
**Added**
- **PLH plugin-folder shadowing** (`CA-PLH-015`) — flags a `plugin.json` component-path key
(`commands`/`agents`/`skills`/`hooks`) that points at a custom path while the default folder of
that name also exists, so one silently shadows the other. Mirrors Claude Code's `/doctor` &
`claude plugin list` warning. Severity **medium** (dead config), `category: 'plugin-hygiene'`.
- **PLH `skills:`-array validation** (`CA-PLH-016`) — validates each entry in a `plugin.json`
`skills:` array resolves to a directory inside the plugin; flags non-string entries, missing
paths, file-not-directory, and path-escape. Mirrors `claude plugin validate`. Severity
**medium**, `category: 'plugin-hygiene'`.
- **SET autoMode structure + dead-config** (`CA-SET-NNN`) — validates `autoMode` contains only
the four known sub-keys (`environment`/`allow`/`soft_deny`/`hard_deny`), each a string array,
and flags `autoMode` placed in shared project settings (`.claude/settings.json`), which Claude
Code ignores. Structure = low/medium, dead-config = low.
**Internal / Test count / Verification** — fill in Session C (mirror the 5.3.0 block).
Scanner count stays **13** (all extend existing PLH/SET scanners). `--json`/`--raw` byte-stable.
### What's New in v5.4.0 (draft prose)
> **v5.4.0 — plugin-hygiene & settings-validation hardening.** Three additive findings extend the
> plugin and settings surfaces: PLH now flags a plugin.json component-path key that silently shadows
> the default folder of the same name (mirroring Claude Code's `/doctor` warning), and validates
> that `skills:`-array entries resolve to real directories (mirroring `claude plugin validate`); the
> settings validator now checks the structure of `autoMode` and flags it when placed in shared
> project settings, where Claude Code ignores it. Scanner count stays **13** (all extend existing
> scanners). `--json`/`--raw` remain byte-stable.
### Version & non-breaking confirmation
- **5.4.0** (minor). Additive findings only; no envelope/JSON-shape change. `json-backcompat` +
`raw-backcompat` + SC-5 snapshot green at HEAD; new findings are additive (appear only in configs
that trigger them).