conventions/scripts/cli/lint_yaml.py
Natalie 59656b5b93 feat(conventions): apiVersion+semver versioning, run lint:yaml CLI, rename infra_manifest
Add document apiVersion (conventions/v1) + per-convention semver + updated date to
the schema and all seed conventions; manifest files carry their own apiVersion
(infra/v1). New ./run (symlink -> scripts/cli/run) with lint:yaml validating every
programming_*/<name>.yaml against the schema (name==filename, scope==dir). Rename
infra-manifest.yaml -> infra_manifest.yaml for name match. 4/4 valid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 08:36:10 -04:00

55 lines
1.9 KiB
Python
Executable file

#!/usr/bin/env python3
"""lint:yaml — validate every convention against convention.yaml.schema.
Checks each programming_<scope>/<name>.yaml: parses, validates against the
meta-schema, and enforces name==filename and scope==directory. Run from the
@conventions repo root (the `run` wrapper cd's there).
"""
import glob
import sys
try:
import yaml
import jsonschema
except ImportError as e:
sys.exit(f"lint:yaml needs pyyaml + jsonschema ({e}). pip install pyyaml jsonschema")
def main() -> int:
try:
schema = yaml.safe_load(open("convention.yaml.schema"))
except FileNotFoundError:
return _fail("convention.yaml.schema not found — run from the @conventions root")
files = sorted(glob.glob("programming_*/*.yaml"))
if not files:
return _fail("no convention files found under programming_*/")
failed = 0
for f in files:
scope_dir = f.split("/")[0].removeprefix("programming_")
stem = f.split("/")[-1].removesuffix(".yaml")
try:
doc = yaml.safe_load(open(f))
jsonschema.validate(doc, schema)
if doc.get("name") != stem:
failed += 1; print(f"FAIL {f}: name '{doc.get('name')}' != filename '{stem}'"); continue
if doc.get("scope") != scope_dir:
failed += 1; print(f"FAIL {f}: scope '{doc.get('scope')}' != dir '{scope_dir}'"); continue
print(f"ok {f} ({doc['name']} v{doc.get('version', '?')}, {doc.get('status')})")
except yaml.YAMLError as e:
failed += 1; print(f"FAIL {f}: YAML parse: {e}")
except jsonschema.ValidationError as e:
failed += 1; print(f"FAIL {f}: {e.message}")
print(f"\n{len(files) - failed}/{len(files)} valid")
return 1 if failed else 0
def _fail(msg: str) -> int:
print(f"FAIL: {msg}")
return 1
if __name__ == "__main__":
sys.exit(main())