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") # The supplied template/export examples establish empty requirement arrays. # Nonempty registry requirements need the actual app schema, not free text. requirements = p.get("requirements", {}) if not isinstance(requirements, dict): errs.append("package.requirements must be an object") else: for field in ("apps", "capabilities"): if requirements.get(field) != []: errs.append(f"package.requirements.{field}: this portable generator supports [] only; put manual setup in skills until registry requirements are verified") 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])