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>
55 lines
1.9 KiB
Python
Executable file
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())
|