forked from Timmy_Foundation/the-nexus
feat(bezalel): MemPalace ecosystem — validation, audit, sync, auto-revert, Evennia integration
This commit is contained in:
123
scripts/validate_mempalace_taxonomy.py
Normal file
123
scripts/validate_mempalace_taxonomy.py
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user