feat(plh): flag plugin.json paths that shadow default folders (CA-PLH-015)

PLH now flags a plugin.json component-path key (commands/agents/outputStyles)
that replaces a default folder still present on disk — Claude Code stops
scanning that folder, so its contents are silently ignored (dead config).
Mirrors CC's /doctor & `claude plugin list` warning (v2.1.140+).

Field set pinned to the docs' "replaces" category only (Verifiseringsplikt,
code.claude.com/docs path-behavior-rules): skills is excluded (adds to the
default skills/ scan — both load) as are hooks/mcpServers/lspServers (own
merge rules); a custom path that addresses the default folder is not flagged.

Tests +5 (936->941). Scanner count unchanged (13). --json/--raw byte-stable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
This commit is contained in:
Kjell Tore Guttormsen 2026-06-19 21:46:22 +02:00
commit 7abc5a1dcb
10 changed files with 217 additions and 1 deletions

View file

@ -181,6 +181,26 @@ severe) signal, so the command check stays silent there to avoid a redundant `"d
The earlier HIGH `Cross-plugin command name conflict` finding (basename-keyed, "only one wins")
is gone, along with its now-inaccurate humanizer entry.
### PLH scanner — plugin-folder shadowing (`CA-PLH-015`)
Per-plugin check (in `scanSinglePlugin`, right after the required-field loop): a `plugin.json`
component-path key that **replaces** its default folder while that folder still exists on disk →
the folder is silently ignored (dead config). Severity **MEDIUM**, `category: 'plugin-hygiene'`,
`details: { field, ignoredDir, customPaths }`. Mirrors Claude Code's own warning in `/doctor`,
`claude plugin list`, and the `/plugin` detail view (v2.1.140+).
The field set is **primary-source-pinned** to the *replaces* category only —
`SHADOWING_PATH_FIELDS` = `commands`/`agents`/`outputStyles` (defaults `commands/`, `agents/`,
`output-styles/`). Deliberately excluded: **`skills`** (per
`code.claude.com/docs/.../path-behavior-rules` it *adds to* the default `skills/` scan — both
load, never a shadow), and **`hooks`/`mcpServers`/`lspServers`** (own merge rules, not a
folder-shadow). Experimental `themes`/`monitors` are omitted because the docs warn their manifest
schema may change between releases. The check also honors the doc's explicit-address exception: a
custom path that resolves *into* the default folder (`"commands": ["./commands/x.md"]`) is not
flagged, because Claude Code keeps scanning the folder in that case (`addressesDefaultDir`
predicate). The v5.4.0 plan originally listed `commands/agents/skills/hooks`; that set was
corrected here against the live docs (Verifiseringsplikt).
## Gotchas
- Session directories accumulate — use `/config-audit cleanup` to manage