# 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] - ` 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 — `, **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).