CA-PLH-015 shipped (7abc5a1). Records the Verifiseringsplikt correction:
replaces-only field set (commands/agents/outputStyles); skills excluded
(adds-to-default), hooks/mcpServers/lspServers excluded (own merge rules).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
17 KiB
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, tagv5.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 badgescanners-13). All three approved features extend existing scanners (PLH, SET) — no new scanner file, so the badge, the scanner table, andcountScannerShapeneed no change. (An early grep ofexport async function scancounted 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.skillsexcluded — it adds to the defaultskills/scan (both load, never a shadow);hooks/mcpServers/lspServersexcluded — own merge rules, not a folder-shadow. Experimentalthemes/monitorsomitted (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.jsoncomponent-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 listedcommands/agents/skills/hooks— corrected above.) - Where:
scanners/plugin-health-scanner.mjs(per-plugin loop; the scanner already readsplugin.jsonbut only checksname/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 acommands/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)
- What: when
plugin.jsonhas askills:array, validate each entry resolves to an existing directory inside the plugin root. Flag: non-string entry, missing path, path that is a file not a directory, path escaping the plugin root. Mirrorsclaude plugin validate(~2.1.145; the CC error suggests the parent directory when an entry points at a file). - Where:
scanners/plugin-health-scanner.mjs(readparsed.skillsafter 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 forvalid-dir; fixture with noskills:key → not flagged.
Feature 3 — SET autoMode structure + dead-config (CA-SET-NNN)
- What (two sub-checks):
- Structure validation —
autoMode, if present, must be an object whose only keys areenvironment,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. - Dead-config —
autoModeplaced 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.
- Structure validation —
- Where:
scanners/settings-validator.mjs(autoModeis already inKNOWN_KEYS:21but 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
userImpactCategorymapping (dead config / conflict / configuration mistake) — verify--json/--rawstay byte-stable for existing findings (json-backcompat+raw-backcompatgreen); 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)
- Bump
.claude-plugin/plugin.json5.3.0 → 5.4.0. (Version lives ONLY here.) - 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. - 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). - Knowledge: apply the 3 backing entries (list below).
- Marketplace cross-repo (
../catalog/, separate repo): bump the config-auditref/version inmarketplace.json+ README after the plugin tag is pushed. Same push-window rules, separate push. Cross-repo edit requires operator GO. - 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).
- Commit
release: v5.4.0 — <one-line>, tagv5.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-readmeis 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):
knowledge/claude-code-capabilities.md— plugin component-path keys shadow default folders; CC warns in/doctor&claude plugin list(~2.1.140/142). BacksCA-PLH-015.knowledge/claude-code-capabilities.md—claude plugin validaterequiresskills:entries to be directories (~2.1.145). BacksCA-PLH-016.knowledge/claude-code-capabilities.mdorconfiguration-best-practices.md—autoModeschema (environment/allow/soft_deny/hard_denystring arrays,"$defaults"literal) and the "not read from shared project settings" rule. BacksCA-SETautoMode 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 aplugin.jsoncomponent-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 listwarning. Severity medium (dead config),category: 'plugin-hygiene'. - PLH
skills:-array validation (CA-PLH-016) — validates each entry in aplugin.jsonskills:array resolves to a directory inside the plugin; flags non-string entries, missing paths, file-not-directory, and path-escape. Mirrorsclaude plugin validate. Severity medium,category: 'plugin-hygiene'. - SET autoMode structure + dead-config (
CA-SET-NNN) — validatesautoModecontains only the four known sub-keys (environment/allow/soft_deny/hard_deny), each a string array, and flagsautoModeplaced 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
/doctorwarning), and validates thatskills:-array entries resolve to real directories (mirroringclaude plugin validate); the settings validator now checks the structure ofautoModeand flags it when placed in shared project settings, where Claude Code ignores it. Scanner count stays 13 (all extend existing scanners).--json/--rawremain 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).