The Human answers a prototype's design question by using it as the software's user will, so the prototype method now has the Designer prepare the scenario itself and give the Human the real command, syntax and directory a user would use, with any reset command offered for that purpose alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
189 lines
10 KiB
Python
189 lines
10 KiB
Python
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_designer_release_modes_and_source(self):
|
|
automatic = "issue-designer-default"
|
|
manual = (
|
|
"designer-wayfinder", "designer-grilling", "designer-prototype",
|
|
"designer-research", "designer-domain-modeling", "designer-triage",
|
|
"designer-to-spec", "designer-to-tickets",
|
|
)
|
|
for name in (automatic, *manual):
|
|
with self.subTest(name=name):
|
|
package = ROOT / "packages" / name
|
|
skill = (package / "SKILL.md").read_text()
|
|
frontmatter = skill.split("---", 2)[1]
|
|
self.assertEqual("disable-model-invocation: true" in frontmatter, name in manual)
|
|
version = {automatic: "0.6.1", "designer-prototype": "0.6.2"}.get(name, "0.6.0")
|
|
self.assertIn(f"relayview-version: {version}", frontmatter)
|
|
manifest = json.loads((package / "relayview-skill.json").read_text())
|
|
self.assertEqual(manifest["source"], {"kind": "first-party", "reference": f"v{version}"})
|
|
check_package(package)
|
|
|
|
def test_tdd_is_available_to_owner_started_implementers(self):
|
|
package = ROOT / "packages/tdd-auto"
|
|
skill = (package / "SKILL.md").read_text()
|
|
frontmatter = skill.split("---", 2)[1]
|
|
self.assertIn("name: tdd-auto", frontmatter)
|
|
self.assertNotIn("disable-model-invocation: true", frontmatter)
|
|
self.assertIn("relayview-version: 0.3.1", frontmatter)
|
|
manifest = json.loads((package / "relayview-skill.json").read_text())
|
|
self.assertEqual(manifest["source"], {"kind": "first-party", "reference": "v0.3.1"})
|
|
check_package(package)
|
|
self.assertIn("disable-model-invocation: true", (ROOT / "packages/tdd/SKILL.md").read_text())
|
|
|
|
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"),
|
|
**{name: ("designer", "design") for name in (
|
|
"issue-designer-default", "designer-wayfinder", "designer-grilling", "designer-prototype",
|
|
"designer-research", "designer-domain-modeling", "designer-triage",
|
|
"designer-to-spec", "designer-to-tickets")}}
|
|
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()
|