Components: - state/component-registry.json: Registry with all skills, commands, agents, workflows - automation/generate-registry.py: Auto-generate from directory scan - automation/validate-registry.py: Check for drift and TODO placeholders - system-instructions.json: Added component-lifecycle process Registry includes: - 6 skills with routing triggers - 10 commands with aliases - 12 agents with model info - 10 workflows with triggers - 2 delegation helpers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
156 lines
4.6 KiB
Python
Executable File
156 lines
4.6 KiB
Python
Executable File
#!/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()
|