#!/usr/bin/env python3 """ validate_rooms.py — Fleet palace taxonomy validator. Checks a wizard's mempalace.yaml against the fleet standard in rooms.yaml. Exits 0 if valid, 1 if core rooms are missing or the config is malformed. Usage: python mempalace/validate_rooms.py python mempalace/validate_rooms.py /root/wizards/bezalel/mempalace.yaml Refs: #1082, #1075 """ from __future__ import annotations import argparse import sys from pathlib import Path from typing import Any try: import yaml except ImportError: print("ERROR: PyYAML is required. Install with: pip install pyyaml", file=sys.stderr) sys.exit(2) FLEET_STANDARD = Path(__file__).parent / "rooms.yaml" def load_yaml(path: Path) -> dict[str, Any]: with path.open() as fh: return yaml.safe_load(fh) or {} def get_core_room_keys(standard: dict[str, Any]) -> list[str]: return [r["key"] for r in standard.get("core_rooms", [])] def get_wizard_room_keys(config: dict[str, Any]) -> list[str]: """Extract room keys from a wizard's mempalace.yaml. Supports two common shapes: rooms: - key: forge - key: hermes or: rooms: forge: ... hermes: ... """ rooms_field = config.get("rooms", {}) if isinstance(rooms_field, list): return [r["key"] for r in rooms_field if isinstance(r, dict) and "key" in r] if isinstance(rooms_field, dict): return list(rooms_field.keys()) return [] def validate(wizard_config_path: Path, standard_path: Path = FLEET_STANDARD) -> list[str]: """Return a list of validation errors. Empty list means valid.""" errors: list[str] = [] if not standard_path.exists(): errors.append(f"Fleet standard not found: {standard_path}") return errors if not wizard_config_path.exists(): errors.append(f"Wizard config not found: {wizard_config_path}") return errors standard = load_yaml(standard_path) config = load_yaml(wizard_config_path) core_keys = get_core_room_keys(standard) wizard_keys = get_wizard_room_keys(config) missing = [k for k in core_keys if k not in wizard_keys] for key in missing: errors.append(f"Missing required core room: '{key}'") return errors def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser( description="Validate a wizard's mempalace.yaml against the fleet room standard." ) parser.add_argument( "config", metavar="mempalace.yaml", help="Path to the wizard's mempalace.yaml", ) parser.add_argument( "--standard", default=str(FLEET_STANDARD), metavar="rooms.yaml", help="Path to the fleet rooms.yaml standard (default: mempalace/rooms.yaml)", ) args = parser.parse_args(argv) wizard_path = Path(args.config) standard_path = Path(args.standard) errors = validate(wizard_path, standard_path) if errors: print(f"[validate_rooms] FAIL: {wizard_path}", file=sys.stderr) for err in errors: print(f" ✗ {err}", file=sys.stderr) return 1 core_count = len(get_core_room_keys(load_yaml(standard_path))) print(f"[validate_rooms] OK: {wizard_path} — all {core_count} core rooms present.") return 0 if __name__ == "__main__": sys.exit(main())