"""Dependency-free checks and reproducible archives for instruction packages.""" import argparse import hashlib import json import re import stat import struct import zipfile from pathlib import Path class PackageError(ValueError): pass def _fail(message): raise PackageError(message) def _text(data, label): try: value = data.decode("utf-8") except UnicodeDecodeError: _fail(f"{label}: invalid UTF-8") if not value or value.startswith("\ufeff") or "\x00" in value: _fail(f"{label}: invalid text") return value def _frame(value): return struct.pack(">I", len(value)) + value def check_package(path): path = Path(path) if path.is_symlink() or not path.is_dir(): _fail(f"{path}: package must be a directory") sidecar = path / "relayview-skill.json" if sidecar.is_symlink() or not sidecar.is_file() or sidecar.stat().st_size > 65536: _fail(f"{path}: missing or invalid sidecar") raw_manifest = sidecar.read_bytes() try: manifest = json.loads(_text(raw_manifest, str(sidecar))) except (ValueError, TypeError) as error: _fail(f"{sidecar}: invalid JSON: {error}") if not isinstance(manifest, dict) or set(manifest) != {"schemaVersion", "source", "role", "step", "files"}: _fail(f"{path}: unsupported manifest fields") source = manifest["source"] if not isinstance(source, dict) or set(source) != {"kind", "reference"}: _fail(f"{path}: invalid source") if source["kind"] not in ("first-party", "user") or not isinstance(source["reference"], str) or not re.fullmatch(r"[A-Za-z0-9._:-]{1,128}", source["reference"]): _fail(f"{path}: invalid source identity") if manifest["schemaVersion"] != 1 or (manifest["role"], manifest["step"]) not in (("planner", "planning"), ("implementer", "implementation"), ("reviewer", "review")): _fail(f"{path}: incompatible role or step") files = manifest["files"] if not isinstance(files, list) or not 1 <= len(files) <= 16: _fail(f"{path}: invalid file list") declared = {} for item in files: if not isinstance(item, dict) or set(item) != {"path", "sha256"}: _fail(f"{path}: invalid file entry") name, digest = item["path"], item["sha256"] if not isinstance(name, str) or len(name) > 256 or "\\" in name or any(part in ("", ".", "..") for part in name.split("/")) or name.startswith("/"): _fail(f"{path}: invalid file path") if name != "SKILL.md" and not name.endswith((".md", ".txt")): _fail(f"{path}: unsupported package content") if name in declared or not isinstance(digest, str) or not re.fullmatch(r"[a-f0-9]{64}", digest): _fail(f"{path}: duplicate file or invalid digest") declared[name] = digest if "SKILL.md" not in declared: _fail(f"{path}: SKILL.md is required") observed = {} total = len(raw_manifest) entry_count = 0 for file in path.rglob("*"): entry_count += 1 relative = file.relative_to(path).as_posix() if entry_count > 64 or len(file.relative_to(path).parts) > 8 or file.is_symlink(): _fail(f"{path}: package entry exceeds bounds or is a link") if file.is_dir(): continue if not file.is_file() or file.stat().st_mode & 0o111: _fail(f"{path}: executable or unsupported file") if relative == "relayview-skill.json": continue if relative not in declared: _fail(f"{path}: undeclared file {relative}") raw = file.read_bytes() total += len(raw) if not 1 <= len(raw) <= 1000000 or total > 131072: _fail(f"{path}: package exceeds text bounds") _text(raw, relative) if hashlib.sha256(raw).hexdigest() != declared[relative]: _fail(f"{path}: changed file {relative}") observed[relative] = raw if set(observed) != set(declared): _fail(f"{path}: missing declared file") skill = _text(observed["SKILL.md"], "SKILL.md") match = re.match(r"\A---\r?\n(.*?)\r?\n---(?:\r?\n|\Z)", skill, re.S) if not match: _fail(f"{path}: invalid SKILL.md frontmatter") frontmatter = match.group(1) top_keys = [] nested_keys = [] in_metadata = False for line in frontmatter.splitlines(): if line.startswith(" "): if not in_metadata or not re.fullmatch(r" [a-z-]+: .+", line): _fail(f"{path}: unsupported skill metadata") nested_keys.append(line[2:].split(":", 1)[0]) elif re.match(r"^[a-z-]+:", line): key = line.split(":", 1)[0] top_keys.append(key) in_metadata = key == "metadata" else: _fail(f"{path}: unsupported skill metadata") if len(top_keys) != len(set(top_keys)) or set(top_keys) - {"name", "description", "metadata", "disable-model-invocation"} or len(nested_keys) != len(set(nested_keys)) or set(nested_keys) != {"relayview-display-name", "relayview-version"}: _fail(f"{path}: unsupported skill metadata") values = dict(line.split(": ", 1) for line in frontmatter.splitlines() if not line.startswith(" ") and ": " in line) if "disable-model-invocation" in values and values["disable-model-invocation"] not in ("true", "false"): _fail(f"{path}: disable-model-invocation must be a boolean") expected_name = path.name if not re.search(rf"(?m)^name: {re.escape(expected_name)}$", frontmatter) or not re.search(r"(?m)^description: .+$", frontmatter) or not re.search(r"(?m)^ relayview-display-name: .+$", frontmatter) or not re.search(r"(?m)^ relayview-version: [0-9]+\.[0-9]+\.[0-9]+$", frontmatter): _fail(f"{path}: missing or incompatible skill metadata") if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", expected_name) or len(expected_name) > 64 or len(values["description"]) > 1024: _fail(f"{path}: skill name or description exceeds owner bounds") display_name = next(line.split(": ", 1)[1] for line in frontmatter.splitlines() if line.startswith(" relayview-display-name: ")) if len(display_name) > 128: _fail(f"{path}: display name exceeds owner bounds") digest = hashlib.sha256(b"relayview-instruction-package-v1\0" + _frame(raw_manifest)) for name in sorted(declared): digest.update(_frame(name.encode("utf-8"))) digest.update(_frame(observed[name])) return digest.hexdigest() def package_archive(path, destination): path, destination = Path(path), Path(destination) check_package(path) destination.parent.mkdir(parents=True, exist_ok=True) with zipfile.ZipFile(destination, "w", compression=zipfile.ZIP_STORED) as archive: root = Path(__file__).resolve().parents[1] files = [(name, root / name) for name in ("LICENSE", "NOTICE.md")] files += [(f"{path.name}/{file.relative_to(path).as_posix()}", file) for file in sorted(path.rglob("*")) if file.is_file()] for name, file in files: info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0)) info.external_attr = 0o100644 << 16 archive.writestr(info, file.read_bytes()) return hashlib.sha256(destination.read_bytes()).hexdigest() def main(): parser = argparse.ArgumentParser() parser.add_argument("action", choices=("check", "pack")) parser.add_argument("path", type=Path) parser.add_argument("destination", type=Path, nargs="?") args = parser.parse_args() if args.action == "pack": if args.destination is None: parser.error("pack requires a destination") print(package_archive(args.path, args.destination)) else: print(check_package(args.path)) if __name__ == "__main__": main()