124 lines
3.3 KiB
Python
124 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Validate a wizard's mempalace.yaml against the fleet taxonomy standard.
|
|
|
|
Usage:
|
|
python validate_mempalace_taxonomy.py /path/to/mempalace.yaml
|
|
python validate_mempalace_taxonomy.py --ci /path/to/mempalace.yaml
|
|
|
|
Exit codes:
|
|
0 = valid
|
|
1 = missing required rooms or other violations
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import yaml
|
|
except ImportError:
|
|
print("ERROR: PyYAML not installed. Run: pip install pyyaml")
|
|
sys.exit(1)
|
|
|
|
|
|
REQUIRED_ROOMS = {
|
|
"forge",
|
|
"hermes",
|
|
"nexus",
|
|
"issues",
|
|
"experiments",
|
|
}
|
|
|
|
|
|
def load_standard():
|
|
# Try to find the fleet standard in the-nexus clone or local path
|
|
candidates = [
|
|
Path(__file__).parent.parent / "mempalace_taxonomy.yaml",
|
|
Path("/tmp/nexus_clone/docs/mempalace_taxonomy.yaml"),
|
|
Path(__file__).parent.parent.parent / "the-nexus" / "docs" / "mempalace_taxonomy.yaml",
|
|
]
|
|
for c in candidates:
|
|
if c.exists():
|
|
with open(c) as f:
|
|
return yaml.safe_load(f)
|
|
return None
|
|
|
|
|
|
def validate(path: Path):
|
|
errors = []
|
|
warnings = []
|
|
|
|
if not path.exists():
|
|
errors.append(f"File not found: {path}")
|
|
return errors, warnings
|
|
|
|
with open(path) as f:
|
|
data = yaml.safe_load(f)
|
|
|
|
if not data:
|
|
errors.append("Empty or invalid YAML")
|
|
return errors, warnings
|
|
|
|
rooms = data.get("rooms", data.get("wings", {}).get("bezalel", {}).get("rooms", []))
|
|
if isinstance(rooms, list) and rooms and isinstance(rooms[0], dict):
|
|
room_names = {r.get("name") for r in rooms if isinstance(r, dict)}
|
|
elif isinstance(rooms, dict):
|
|
room_names = set(rooms.keys())
|
|
else:
|
|
room_names = set()
|
|
|
|
missing = REQUIRED_ROOMS - room_names
|
|
if missing:
|
|
errors.append(f"Missing required rooms: {', '.join(sorted(missing))}")
|
|
|
|
# Check for duplicate room names
|
|
if len(room_names) < len(list(rooms) if isinstance(rooms, list) else rooms):
|
|
errors.append("Duplicate room names detected")
|
|
|
|
# Check for empty keywords
|
|
if isinstance(rooms, list):
|
|
for r in rooms:
|
|
if isinstance(r, dict):
|
|
kw = r.get("keywords", [])
|
|
if not kw:
|
|
warnings.append(f"Room '{r.get('name')}' has no keywords")
|
|
|
|
standard = load_standard()
|
|
if standard:
|
|
std_optional = set(standard.get("optional_rooms", {}).keys())
|
|
unknown = room_names - REQUIRED_ROOMS - std_optional
|
|
if unknown:
|
|
warnings.append(f"Non-standard rooms (OK but not in fleet spec): {', '.join(sorted(unknown))}")
|
|
|
|
return errors, warnings
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
parser = argparse.ArgumentParser(description="Validate MemPalace taxonomy")
|
|
parser.add_argument("config", help="Path to mempalace.yaml")
|
|
parser.add_argument("--ci", action="store_true", help="CI mode: fail on warnings too")
|
|
args = parser.parse_args()
|
|
|
|
errors, warnings = validate(Path(args.config))
|
|
|
|
if warnings:
|
|
for w in warnings:
|
|
print(f"WARNING: {w}")
|
|
|
|
if errors:
|
|
for e in errors:
|
|
print(f"ERROR: {e}")
|
|
sys.exit(1)
|
|
|
|
if args.ci and warnings:
|
|
print("Validation failed in CI mode (warnings treated as errors)")
|
|
sys.exit(1)
|
|
|
|
print("OK: Taxonomy validation passed")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|