Files
gh-fcakyon-claude-codex-set…/hooks/scripts/validate_plugin_structure.py
2025-11-29 18:26:37 +08:00

115 lines
4.2 KiB
Python
Executable File

#!/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())