Initial commit
This commit is contained in:
114
hooks/scripts/validate_plugin_structure.py
Executable file
114
hooks/scripts/validate_plugin_structure.py
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate overall plugin structure and organization.
|
||||
|
||||
Checks:
|
||||
- skills/ directory exists if skills are mentioned
|
||||
- agents/, commands/, hooks/ directories follow naming conventions
|
||||
- No invalid file/directory names
|
||||
- Required metadata files present
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def validate_plugin_structure():
|
||||
"""Check plugin directory structure and naming."""
|
||||
errors = []
|
||||
plugin_root = Path(os.environ.get("CLAUDE_PLUGIN_ROOT", "."))
|
||||
|
||||
# Check for valid component directories
|
||||
valid_dirs = {"skills", "agents", "commands", "hooks", ".claude-plugin"}
|
||||
|
||||
for item in plugin_root.iterdir():
|
||||
if item.is_dir() and item.name.startswith("."):
|
||||
continue # Skip hidden dirs
|
||||
|
||||
if item.is_dir() and item.name not in valid_dirs:
|
||||
# Check if it's a generated dir like __pycache__
|
||||
if item.name.startswith("__"):
|
||||
continue
|
||||
# Check if it's a valid plugin component
|
||||
if not re.match(r"^[a-z0-9_-]+$", item.name):
|
||||
errors.append(f"Invalid directory name: {item.name} (use lowercase, hyphens, underscores)")
|
||||
|
||||
# Check skills structure if skills directory exists
|
||||
skills_dir = plugin_root / "skills"
|
||||
if skills_dir.exists():
|
||||
for skill_path in skills_dir.iterdir():
|
||||
if not skill_path.is_dir():
|
||||
continue
|
||||
|
||||
# Check SKILL.md exists
|
||||
skill_md = skill_path / "SKILL.md"
|
||||
if not skill_md.exists():
|
||||
errors.append(f"skills/{skill_path.name}/: Missing SKILL.md file")
|
||||
|
||||
# Check skill directory name format
|
||||
if not re.match(r"^[a-z0-9-]+$", skill_path.name):
|
||||
errors.append(f"skills/{skill_path.name}/: Invalid directory name (use lowercase with hyphens only)")
|
||||
|
||||
# Check agents directory if exists
|
||||
agents_dir = plugin_root / "agents"
|
||||
if agents_dir.exists():
|
||||
for agent_file in agents_dir.iterdir():
|
||||
if agent_file.is_file() and agent_file.suffix == ".md":
|
||||
# Agent files should use kebab-case
|
||||
name = agent_file.stem
|
||||
if not re.match(r"^[a-z0-9-]+$", name):
|
||||
errors.append(
|
||||
f"agents/{agent_file.name}: Invalid agent name (use kebab-case: lowercase with hyphens)"
|
||||
)
|
||||
|
||||
# Check commands directory if exists
|
||||
commands_dir = plugin_root / "commands"
|
||||
if commands_dir.exists():
|
||||
for cmd_file in commands_dir.iterdir():
|
||||
if cmd_file.is_file() and cmd_file.suffix == ".md":
|
||||
# Command files should use kebab-case
|
||||
name = cmd_file.stem
|
||||
if not re.match(r"^[a-z0-9-]+$", name):
|
||||
errors.append(
|
||||
f"commands/{cmd_file.name}: Invalid command name (use kebab-case: lowercase with hyphens)"
|
||||
)
|
||||
|
||||
# Check hooks directory if exists
|
||||
hooks_dir = plugin_root / "hooks"
|
||||
if hooks_dir.exists():
|
||||
hooks_json = hooks_dir / "hooks.json"
|
||||
if not hooks_json.exists():
|
||||
errors.append("hooks/: Missing hooks.json file")
|
||||
|
||||
# Check scripts directory
|
||||
scripts_dir = hooks_dir / "scripts"
|
||||
if scripts_dir.exists():
|
||||
for script in scripts_dir.iterdir():
|
||||
if script.is_file():
|
||||
# Scripts should be executable
|
||||
if not os.access(script, os.X_OK):
|
||||
# Note: We don't error here, just validate naming
|
||||
pass
|
||||
|
||||
# Check script naming
|
||||
if script.suffix in {".py", ".sh"}:
|
||||
name = script.stem
|
||||
if not re.match(r"^[a-z0-9_]+$", name):
|
||||
errors.append(
|
||||
f"hooks/scripts/{script.name}: Invalid script name "
|
||||
"(use snake_case: lowercase with underscores)"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print("❌ Plugin Structure Validation Failed:")
|
||||
for error in errors:
|
||||
print(f" • {error}")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(validate_plugin_structure())
|
||||
Reference in New Issue
Block a user