Dependencies
This skill requires Python 3.8+ and standard library only. No external packages needed.
To install this skill's dependencies:
pip-compile ./requirements.in
pip install -r ./requirements.txt
See ../../requirements.txt for the dependency lockfile (currently empty — standard library only).
Plugin Auditor
Performs comprehensive validation of a Claude Code plugin against structure standards, naming conventions, component requirements, and security best practices.
Step 1: Locate the Plugin
Establish the plugin root:
- Look for
../../../../.claude-plugin/plugin.json-- this is the definitive marker - If user didn't specify a path, check current directory and common locations
- Confirm with user if ambiguous
Step 2: Run Component Validation Scripts
Run the scripts bundled in this plugin (all in scripts/):
# Validate agent files
python ${CLAUDE_PLUGIN_ROOT}/scripts/validate_agent.py agents/my-agent.md
# Validate hooks.json schema
python ${CLAUDE_PLUGIN_ROOT}/scripts/validate_hook_schema.py hooks/hooks.json
# Lint hook scripts
python ${CLAUDE_PLUGIN_ROOT}/scripts/hook_linter.py hooks/
Checks performed: frontmatter structure, required fields (name/description/model/color),
<example> blocks in agent descriptions, hook event names, matcher + hooks array structure.
Validation report format:
## Plugin Validation Report
### Plugin: [name] | Location: [path]
### Summary: [PASS/FAIL with stats]
### Critical Issues ([count]) -- file path + issue + fix
### Warnings ([count]) -- file path + recommendation
### Component Summary -- counts of each type
### Positive Findings
### Overall Assessment: [PASS/FAIL + reasoning]
Step 2b: Auto-Fix Claude Code Load Errors
Before deep validation, run the load-error fixer to catch issues that prevent
Claude Code from loading the plugin at all. These are silent failures — the plugin
simply doesn't load with no useful error until you run /doctor.
python ${CLAUDE_PLUGIN_ROOT}/scripts/fix_plugin_load_errors.py <plugin_root>
What it fixes automatically:
| Issue | Symptom in /doctor | Root cause |
|---|---|---|
plugin.json has skills/agents/hooks/commands field (any value — array, boolean true, object) |
Invalid input |
Validator rejects these entirely — auto-discovery handles them; "hooks": true is a common mistake |
hooks.json is {} (empty object) |
expected record, received undefined |
Must be {"hooks": {}} |
hooks.json is [] (array) |
expected object received array |
Must be an object |
hooks.json flat format {"EventName":{...}} |
expected record, received undefined |
Must be nested under "hooks" key |
hooks.json/lsp.json/.mcp.json has literal \n chars |
Unrecognized token '' |
Python json.dump wrote escaped newlines; file must have real newlines |
SKILL.md has comment lines before --- |
skill fails to load | Frontmatter parser requires --- as the very first line |
Correct hooks.json format:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [{ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/script.py || python ${CLAUDE_PLUGIN_ROOT}/hooks/script.py" }]
}
]
}
}
Note: use python3 ... || python ... — not bare python — so hooks work on both macOS/Linux and Windows.
Empty hooks (no hooks needed):
{ "hooks": {} }
IMPORTANT — Cache coverage: Claude Code scans ALL cached versions under
~/.claude/plugins/cache/, not just the active installPath. Fixing source files
and reinstalling is the only reliable fix. Use uvx plugin-add to reinstall.
Step 2c: Detect and Fix Symlink Stand-Ins
Git checks out symlinks as plain-text "stand-in" files when core.symlinks=false (common on
Windows without Developer Mode, or when cloned without the setting). Stand-ins look like real files
but contain only a relative path (e.g. ../../../scripts/execute.py). They are functionally broken
— the bridge installer will copy the path string, not the actual script.
Run the bulk scanner from the link-checker plugin:
python plugins/dev-utils/scripts/bulk_symlink_fixer.py plugins/<plugin-name>
The scanner detects both:
- text-file stand-ins: small plain-text files (
< 512 bytes) whose content looks like a relative path - broken symlinks: real symlinks whose target no longer exists
Important — the fixer has a silent failure mode. symlink_manager.py always exits 0, so
bulk_symlink_fixer.py prints "✓ Fixed" even when the source doesn't exist. Always verify manually:
# Confirm symlinks resolved (count should match expectations)
find plugins/<plugin-name> -type l | wc -l
# Confirm no text-file stand-ins remain for scripts paths (critical)
find plugins/<plugin-name>/skills -path "*/scripts/*" -type f ! -type l
Two categories of stand-ins:
Valid stand-ins (target exists) — the fixer converts these automatically. If it fails silently, convert manually:
# Read the path out of the stand-in, unlink the file, recreate as symlink content = Path(standin).read_text().strip() Path(standin).unlink() Path(standin).symlink_to(content)Wrong-path stand-ins (target missing) — common cause: an extra subdirectory in the path (e.g.
references/architecture/architecture.mdwhen the file is atreferences/architecture.md). Correct the relative path before creating the symlink. Check what actually exists at the pluginreferences/root and recalculate the depth.
Correct symlink pattern (must match all standard skills):
skills/<skill>/scripts/execute.py → ../../../scripts/<canonical_name>.py
skills/<skill>/references/architecture.md → ../../../references/architecture.md
The symlink filename and the target filename may differ (e.g. execute.py → exploration_optimizer_execute.py)
— that is intentional and valid.
6 known missing-source stand-ins in exploration-cycle-plugin (leave as-is until source files
are created at plugins/exploration-cycle-plugin/references/):
agent-loop-patterns.md, exploration-output-standards.md
Step 3: Run Component-Specific Scripts
Run targeted scripts for detailed checks:
Validate each agent file:
python ${CLAUDE_PLUGIN_ROOT}/scripts/validate_agent.py agents/my-agent.md
Checks: frontmatter structure, required fields (name/description/model/color), name format
(3-50 chars, lowercase + hyphens), description has <example> blocks, system prompt
length (minimum 20 chars, recommended 500-3,000).
Validate hooks.json schema:
python ${CLAUDE_PLUGIN_ROOT}/scripts/validate_hook_schema.py hooks/hooks.json
Checks: JSON syntax, valid event names, each hook has matcher + hooks array,
hook type is command or prompt, command hooks reference existing scripts with
${CLAUDE_PLUGIN_ROOT}.
Test a hook script directly:
python ${CLAUDE_PLUGIN_ROOT}/scripts/test_hook.py \
--hook hooks/scripts/validate.py \
--event PreToolUse \
--input '{"tool_name": "Write", "tool_input": {"file_path": "src/app.py"}}'
Lint hook scripts for common issues:
python ${CLAUDE_PLUGIN_ROOT}/scripts/hook_linter.py hooks/
Step 3b: Self-Evolution Policy Compliance Check
Every plugin must comply with the continuous self-evolution policy at
.agent/rules/self-evolution-policy.md. Check each of the following:
Required files (flag WARN if missing):
# Check for self-evolution profile (required by Phase 0 before any autonomous edit)
ls plugins/<plugin>/references/self-evolution-profile.md
# Check for map-debt working queue (required by friction-driven evolution policy)
ls plugins/<plugin>/references/map-debt.md
# Check for evolution log (created by Phase 7 on first fix)
ls plugins/<plugin>/references/evolution-log.md
Evals schema (flag CRITICAL if wrong):
# All evals.json must use should_trigger boolean schema — NOT legacy expected_behavior
grep -r "expected_behavior\|expected_output\|\"expected\":" plugins/<plugin>/skills/*/evals/evals.json
# Any match = wrong schema → fix immediately
SKILL.md line count (flag WARN if over 500):
wc -l plugins/<plugin>/skills/*/SKILL.md | sort -rn | head -10
Stale skill references (flag WARN):
# os-skill-improvement is methodology-only — any SKILL.md directing agents to invoke it
# for active improvement should reference os-improvement-loop instead
grep -rn "os-skill-improvement" plugins/<plugin>/skills/*/SKILL.md
File-level-symlinks-only check (flag CRITICAL if violated):
# Only file-level symlinks permitted — no directory symlinks
find plugins/<plugin> -type l | while read l; do
if [ -d "$l" ]; then echo "DIRECTORY SYMLINK (violates file-level-symlinks-only rule): $l"; fi
done
Step 4: Manual Checks
For issues the scripts may not catch:
Plugin structure check:
# Manifest must be here (not in root)
ls .claude-plugin/plugin.json
# Components must be at root (not in .claude-plugin/)
ls commands/ agents/ skills/ hooks/
# Validate JSON & author format (author MUST be an object with name/email, not a string)
jq . .claude-plugin/plugin.json
Manifest lint rules:
authorMUST be an object:{"name": "...", "email": "..."}. A string value causesInvalid input: expected object, received stringduring/plugin install.- No duplicate top-level keys in
plugin.json(e.g. duplicateagentsorcommands).
Security scan:
# Check for hardcoded credentials
grep -rn "password\|api_key\|secret\|token" --include="*.md" --include="*.json" --include="*.sh" .
${CLAUDE_PLUGIN_ROOT} portability:
# Ensure no hardcoded paths in hook commands or MCP config
grep -rn "/Users/\|/home/" --include="*.json" --include="*.sh" .
Hook script quality (for any .py files wired as hooks):
- Does the hook command use
python3 ... || python ...for cross-platform compatibility? - Does
main()have an early-exit project-type guard (e.g.if not (project_root / "context").exists(): return) so it skips silently in projects that haven't initialized the plugin?
Naming conventions:
- Plugin name: kebab-case (
my-plugin, notMyPluginormy_plugin) - Command files: kebab-case
.md - Agent files: kebab-case
.mddescribing role - Skill directories: kebab-case
- Script files: kebab-case with extension (
.sh,.py,.js)
Skill quality (run skill-reviewer for each skill):
"Review my skill at skills/skill-name/SKILL.md"
Step 4b: Marketplace Source Path Audit
If the repo contains a .claude-plugin/marketplace.json, run this check to ensure
every plugin entry points to a directory that actually exists. Missing directories
silently fail during /plugin install with no helpful error message.
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/audit_marketplace_sources.py <repo_root>
# or from the repo root:
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/audit_marketplace_sources.py .
Common causes of missing paths:
- Plugin directory renamed but
marketplace.jsonnot updated (e.g.obsidian-integration→obsidian-wiki-engine) - Plugin entry added to marketplace before the plugin directory was created
- Plugin directory deleted but marketplace entry not removed
Fix: Either update the source path to match the real directory name, or remove the entry.
Step 5: Report and Remediate
Severity levels:
- Critical -- plugin won't work or is insecure. Fix immediately. (e.g., invalid JSON, string author instead of object, duplicate keys, hardcoded credentials, missing required fields)
- Warning -- degrades quality or usability. Fix before distribution. (e.g., missing README, vague skill descriptions, no
<example>blocks in agents) - Minor -- best practice improvement. Fix when convenient.
Fix critical issues first, then re-validate:
# Re-run validation after fixes
"Validate my plugin at <path>"
Keep running until: 0 critical issues, warnings addressed or documented.
Standards & References
- Compliance rules to check directly: no cross-plugin script execution (a plugin never imports or runs another plugin's Python code); hub-and-spoke shared scripts (a script used by 2+ skills in the same plugin lives at the plugin root, not duplicated per-skill); file-level symlinks only (never directory symlinks, never duplicated copies); full self-containment (an installed skill has zero runtime dependency on the source repo or another plugin). A plugin that duplicates a shared script instead of symlinking it, or depends on another plugin's code, is structurally non-compliant.
Standards Reference
.claude-plugin/plugin.json minimal valid:
{
"name": "plugin-name",
"author": { "name": "Author Name", "email": "email@example.com" }
}
.claude-plugin/plugin.json recommended:
{
"name": "plugin-name",
"version": "0.1.0",
"description": "What the plugin does",
"author": { "name": "Author Name", "email": "email@example.com" }
}
Agent description pattern (must have <example> blocks):
description: |
Use this agent when user asks to "do X", "run Y", or mentions Z.
<example>
Context: user just finished creating a plugin
user: "I've set up my plugin"
assistant: "Let me validate the structure."
</example>
Skill description pattern (third-person, anti-undertrigger):
description: >
This skill should be used when the user asks to "X", "Y", or "Z".
Use this skill even when the user doesn't explicitly say "Z" --
mentions of [related concept] should also trigger this.
Next Actions
- Fix gaps: Run
create-skill,create-command, orcreate-hookto add missing components - Improve skills: Run
skill-revieweron each skill for trigger optimization - Upgrade to L5: Run
audit-plugin-l5for advanced red-team structural audit - Distribute: Push to GitHub — users install via
plugin_add.py richfrem/agent-plugins-skills