Implement component registry for PA session awareness
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>
This commit is contained in:
155
automation/validate-registry.py
Executable file
155
automation/validate-registry.py
Executable file
@@ -0,0 +1,155 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user