import json import hashlib import shutil import tempfile import unittest import zipfile from pathlib import Path from tools.packages import PackageError, check_package, package_archive ROOT = Path(__file__).resolve().parents[1] class PackageTests(unittest.TestCase): def test_migrated_role_guidance_has_one_declared_role_step(self): expected = {"codebase-design": ("planner", "planning"), "grill-me": ("planner", "planning"), "grilling": ("planner", "planning"), "to-questionnaire": ("planner", "planning"), "diagnosing-bugs": ("implementer", "implementation"), "domain-modeling": ("implementer", "implementation"), "migrate-to-shoehorn": ("implementer", "implementation"), "pr": ("implementer", "implementation"), "prototype": ("implementer", "implementation"), "scaffold-exercises": ("implementer", "implementation"), "shared-abstraction-refactor": ("implementer", "implementation"), "split-large-files": ("implementer", "implementation"), "tdd": ("implementer", "implementation"), "writing-for-agents": ("implementer", "implementation"), "code-review": ("reviewer", "review"), "c4-diff": ("reviewer", "review"), "research": ("planner", "planning"), "improve-codebase-architecture": ("planner", "planning")} for name, role_step in expected.items(): with self.subTest(name=name): package = ROOT / "packages" / name manifest = json.loads((package / "relayview-skill.json").read_text()) self.assertEqual((manifest["role"], manifest["step"]), role_step) check_package(package) def test_release_package_and_starter_are_valid(self): for path in sorted((ROOT / "packages").iterdir()) + [ROOT / "examples/starter-planner"]: check_package(path) with tempfile.TemporaryDirectory() as directory: first = package_archive(path, Path(directory) / "one.zip") second = package_archive(path, Path(directory) / "two.zip") self.assertEqual(first, second) def test_maintained_guidance_covers_distinct_role_steps(self): expected = { "planner-scope": ("planner", "planning", "scope.md", "Plan"), "implementer-handoff": ("implementer", "implementation", "handoff.md", "documentation"), } for name, (role, step, resource, marker) in expected.items(): with self.subTest(name=name): package = ROOT / "packages" / name manifest = json.loads((package / "relayview-skill.json").read_text()) self.assertEqual((manifest["role"], manifest["step"]), (role, step)) self.assertEqual(manifest["source"], {"kind": "first-party", "reference": "v0.2.0"}) self.assertIn(resource, [item["path"] for item in manifest["files"]]) self.assertIn(marker, (package / "SKILL.md").read_text() + (package / resource).read_text()) def test_user_fork_is_distinct_under_same_role_limits(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "implementer-handoff" shutil.copytree(ROOT / "packages/implementer-handoff", package) original = check_package(package) resource = package / "handoff.md" resource.write_text(resource.read_text() + "\n- Prefer the team's concise PR summary format.\n") manifest_path = package / "relayview-skill.json" manifest = json.loads(manifest_path.read_text()) manifest["source"] = {"kind": "user", "reference": "team-fork-1"} next(item for item in manifest["files"] if item["path"] == "handoff.md")["sha256"] = hashlib.sha256(resource.read_bytes()).hexdigest() manifest_path.write_text(json.dumps(manifest)) self.assertNotEqual(check_package(package), original) self.assertEqual((manifest["role"], manifest["step"]), ("implementer", "implementation")) def test_rejects_scripts_and_undeclared_files(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) (package / "helper.py").write_text("print('unsafe')\n") with self.assertRaises(PackageError): check_package(package) def test_rejects_permission_expansion(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) manifest = package / "relayview-skill.json" data = json.loads(manifest.read_text()) data["tools"] = ["shell"] manifest.write_text(json.dumps(data)) with self.assertRaises(PackageError): check_package(package) def test_rejects_role_mismatch(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) manifest = package / "relayview-skill.json" data = json.loads(manifest.read_text()) data["role"] = "reviewer" manifest.write_text(json.dumps(data)) with self.assertRaises(PackageError): check_package(package) def test_rejects_changed_resource(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) (package / "scope.md").write_text("changed\n") with self.assertRaises(PackageError): check_package(package) def test_rejects_frontmatter_permission_request(self): with tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) skill = package / "SKILL.md" skill.write_text(skill.read_text().replace("metadata:\n", "tools: [shell]\nmetadata:\n")) manifest = package / "relayview-skill.json" data = json.loads(manifest.read_text()) next(item for item in data["files"] if item["path"] == "SKILL.md")["sha256"] = hashlib.sha256(skill.read_bytes()).hexdigest() manifest.write_text(json.dumps(data)) with self.assertRaises(PackageError): check_package(package) def test_rejects_owner_incompatible_frontmatter_values(self): for old, new in ( ("metadata:\n", "disable-model-invocation: yes\nmetadata:\n"), ("description: Check assigned issue scope, neighboring work, and verification before drafting a Plan.", "description: "), ): with self.subTest(new=new), tempfile.TemporaryDirectory() as directory: package = Path(directory) / "planner-scope" shutil.copytree(ROOT / "packages/planner-scope", package) skill = package / "SKILL.md" skill.write_text(skill.read_text().replace(old, new)) manifest = package / "relayview-skill.json" data = json.loads(manifest.read_text()) next(item for item in data["files"] if item["path"] == "SKILL.md")["sha256"] = hashlib.sha256(skill.read_bytes()).hexdigest() manifest.write_text(json.dumps(data)) with self.assertRaises(PackageError): check_package(package) def test_archive_carries_license_and_notice(self): with tempfile.TemporaryDirectory() as directory: archive_path = Path(directory) / "planner-scope.zip" package_archive(ROOT / "packages/planner-scope", archive_path) with zipfile.ZipFile(archive_path) as archive: self.assertEqual(archive.read("LICENSE"), (ROOT / "LICENSE").read_bytes()) self.assertEqual(archive.read("NOTICE.md"), (ROOT / "NOTICE.md").read_bytes()) if __name__ == "__main__": unittest.main()