51 lines
2.8 KiB
Python
51 lines
2.8 KiB
Python
import json, re, sys
|
|
|
|
def parse_fm(text):
|
|
try:
|
|
import yaml # pip install pyyaml (recommended: matches real YAML parsing)
|
|
return yaml.safe_load(text) or {}
|
|
except ImportError:
|
|
out = {}
|
|
for line in text.split("\n"):
|
|
if ":" in line and not line.startswith(" "):
|
|
k, v = line.split(":", 1); v = v.strip()
|
|
if len(v) > 1 and v[0] == v[-1] and v[0] in "\"'": v = v[1:-1]
|
|
out[k.strip()] = v
|
|
return out
|
|
|
|
def main(path):
|
|
errs = []
|
|
d = json.load(open(path))
|
|
if d.get("format") != "openmaus.package": errs.append("format must be 'openmaus.package'")
|
|
if d.get("version") != 2: errs.append("version must be 2")
|
|
p = d.get("package", {})
|
|
for k in ["id","release","name","tagline","summary","category","author","license","outcomes","requirements","team","agents"]:
|
|
if k not in p: errs.append(f"package.{k} missing")
|
|
kebab = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
|
|
if not kebab.match(p.get("id","")): errs.append("package.id must be kebab-case")
|
|
if not re.match(r"^\d+\.\d+\.\d+$", p.get("release","")): errs.append("release must be semver")
|
|
entries = {e.get("name"): e for e in p.get("skills",{}).get("entries",[])}
|
|
for name, e in entries.items():
|
|
m = re.match(r"^---\n(.*?)\n---", e.get("instructions",""), re.S)
|
|
if not m: errs.append(f"skill {name}: instructions need --- frontmatter ---"); continue
|
|
fm = parse_fm(m.group(1))
|
|
if fm.get("name") != e.get("name"): errs.append(f"skill {name}: frontmatter name differs")
|
|
if fm.get("description") != e.get("description"): errs.append(f"skill {name}: frontmatter description differs from entry description")
|
|
keys = set()
|
|
for a in p.get("agents", []):
|
|
keys.add(a.get("key"))
|
|
for k in ["key","name"]:
|
|
if k not in a: errs.append(f"agent missing {k}")
|
|
for s in a.get("skills", []):
|
|
if s not in entries: errs.append(f"agent {a.get('key')}: unknown skill {s}")
|
|
for r in p.get("routines", []):
|
|
if r.get("agent") not in keys: errs.append(f"routine {r.get('key')}: unknown agent {r.get('agent')}")
|
|
if r.get("enabledAfterInstall") is not False: errs.append(f"routine {r.get('key')}: enabledAfterInstall must be false")
|
|
if "schedule" not in r or "expression" not in r["schedule"]: errs.append(f"routine {r.get('key')}: schedule.expression missing")
|
|
blob = json.dumps(d).lower()
|
|
for pat in [r"password\s*[:=]", r"api[_-]?key\s*[:=]", r"secret\s*[:=]", r"bearer\s+[a-z0-9]", r"sk-[a-z0-9]{10,}"]:
|
|
if re.search(pat, blob): errs.append(f"possible secret matches /{pat}/ - remove it")
|
|
print("OK" if not errs else "\n".join("ERROR: "+e for e in errs))
|
|
sys.exit(1 if errs else 0)
|
|
|
|
main(sys.argv[1])
|