#!/usr/bin/env python3 """ Validate Component Registry Checks that the registry is in sync with actual component files. Usage: python3 validate-registry.py Exit codes: 0 - All valid 1 - Warnings (stale entries) 2 - Errors (missing entries or TODO placeholders) """ import json import sys from pathlib import Path CLAUDE_DIR = Path.home() / ".claude" REGISTRY_PATH = CLAUDE_DIR / "state" / "component-registry.json" # Scan paths SCAN_PATHS = { "skills": (CLAUDE_DIR / "skills", "*/SKILL.md"), "commands": (CLAUDE_DIR / "commands", "**/*.md"), "agents": (CLAUDE_DIR / "agents", "*.md"), "workflows": (CLAUDE_DIR / "workflows", "**/*.yaml"), } def get_actual_components() -> dict: """Get actual components from filesystem.""" actual = { "skills": set(), "commands": set(), "agents": set(), "workflows": set(), } # Skills skills_dir = SCAN_PATHS["skills"][0] if skills_dir.exists(): for skill_dir in skills_dir.iterdir(): if skill_dir.is_dir() and (skill_dir / "SKILL.md").exists(): actual["skills"].add(skill_dir.name) # Commands commands_dir = SCAN_PATHS["commands"][0] if commands_dir.exists(): for cmd_file in commands_dir.rglob("*.md"): rel_path = cmd_file.relative_to(commands_dir) cmd_name = "/" + str(rel_path).replace(".md", "").replace("/", ":") actual["commands"].add(cmd_name) # Agents agents_dir = SCAN_PATHS["agents"][0] if agents_dir.exists(): for agent_file in agents_dir.glob("*.md"): actual["agents"].add(agent_file.stem) # Workflows workflows_dir = SCAN_PATHS["workflows"][0] if workflows_dir.exists(): for wf_file in workflows_dir.rglob("*.yaml"): rel_path = wf_file.relative_to(workflows_dir) actual["workflows"].add(str(rel_path).replace(".yaml", "")) for wf_file in workflows_dir.rglob("*.yml"): rel_path = wf_file.relative_to(workflows_dir) actual["workflows"].add(str(rel_path).replace(".yml", "")) return actual def validate_registry() -> int: """Validate the registry against actual components.""" print("Registry Validation") print("=" * 40) if not REGISTRY_PATH.exists(): print("✗ Registry file not found!") print(f" Run: python3 generate-registry.py") return 2 # Load registry with open(REGISTRY_PATH) as f: registry = json.load(f) # Get actual components actual = get_actual_components() errors = 0 warnings = 0 for component_type in ["skills", "commands", "agents", "workflows"]: registered = set(registry.get(component_type, {}).keys()) registered_active = { k for k, v in registry.get(component_type, {}).items() if v.get("status") != "removed" } actual_set = actual[component_type] # Check for missing in registry missing = actual_set - registered if missing: print(f"✗ {component_type}: {len(missing)} missing from registry") for name in sorted(missing): print(f" + {name}") errors += len(missing) # Check for stale entries stale = registered_active - actual_set if stale: print(f"⚠ {component_type}: {len(stale)} stale entries") for name in sorted(stale): print(f" - {name}") warnings += len(stale) # Check for TODO placeholders for name, data in registry.get(component_type, {}).items(): if data.get("status") == "removed": continue if data.get("description") == "TODO": print(f"⚠ {component_type}/{name}: description is TODO") warnings += 1 if "triggers" in data and data["triggers"] == ["TODO"]: print(f"⚠ {component_type}/{name}: triggers is TODO") warnings += 1 # Success message if all good if not missing and not stale: count = len(actual_set) print(f"✓ {component_type}: {count} components, all present") print("=" * 40) if errors > 0: print(f"\n✗ {errors} error(s), {warnings} warning(s)") print(" Run: python3 generate-registry.py") return 2 elif warnings > 0: print(f"\n⚠ {warnings} warning(s)") print(" Consider updating registry with manual hints") return 1 else: print("\n✓ Registry is valid") return 0 def main(): sys.exit(validate_registry()) if __name__ == "__main__": main()