fix(plh): require CLAUDE.md commands/agents/hooks section only for shipped components
plugin-health flagged "CLAUDE.md missing <commands|agents|hooks> section" regardless of whether the plugin actually had that component — e.g. graceful-handoff (no commands/ or agents/ dir) got two spurious medium findings. Same over-report class as the model-field fix. Now gated on component presence (pluginShipsComponent): a section is required only if the plugin ships that component (commands/ or agents/ with .md, or hooks/hooks.json). Across the 5 stable plugins this drops 12 spurious findings to 3 legitimate ones (graceful-handoff hooks, ai-psychosis commands+hooks). New fixture plugin-section-coverage proves both directions. Found via the marketplace-wide review. Suite 950/950, self-audit A/A, scanner count 13. tests badge 949 -> 950. 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:
parent
a5cfc331fd
commit
292352eff8
6 changed files with 56 additions and 1 deletions
|
|
@ -19,6 +19,19 @@ const SCANNER = 'PLH';
|
|||
|
||||
const REQUIRED_PLUGIN_JSON_FIELDS = ['name', 'description', 'version'];
|
||||
const RECOMMENDED_CLAUDE_MD_SECTIONS = ['commands', 'agents', 'hooks'];
|
||||
|
||||
// A CLAUDE.md need only document the component types the plugin actually ships. Mirrors the
|
||||
// optional-frontmatter rule: do not demand docs for commands/agents/hooks that do not exist.
|
||||
async function pluginShipsComponent(pluginDir, section) {
|
||||
if (section === 'hooks') {
|
||||
try { await readFile(join(pluginDir, 'hooks', 'hooks.json'), 'utf-8'); return true; }
|
||||
catch { return false; }
|
||||
}
|
||||
try {
|
||||
const entries = await readdir(join(pluginDir, section));
|
||||
return entries.some(f => f.endsWith('.md'));
|
||||
} catch { return false; }
|
||||
}
|
||||
// Keys as they appear after yaml-parser normalizeKey (hyphens → underscores).
|
||||
// Field requirements are pinned to the primary docs, NOT to "every field a plugin could set":
|
||||
// - Commands/skills (code.claude.com/docs slash-commands): "All fields are optional. Only
|
||||
|
|
@ -302,6 +315,9 @@ async function scanSinglePlugin(pluginDir) {
|
|||
const lower = content.toLowerCase();
|
||||
|
||||
for (const section of RECOMMENDED_CLAUDE_MD_SECTIONS) {
|
||||
// Only require a section for a component the plugin actually ships (mirrors the
|
||||
// optional-frontmatter rule — no docs demanded for absent commands/agents/hooks).
|
||||
if (!(await pluginShipsComponent(pluginDir, section))) continue;
|
||||
// Look for markdown table header or section header
|
||||
const hasSection = lower.includes(`## ${section}`) ||
|
||||
lower.includes(`| ${section}`) ||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue