Publish instruction skill starter release
Check instruction packages / check (push) Failing after 1m19s

This commit is contained in:
admin committed 2026-10-05 10:13:10 -04:00
commit cfc38c60ba
14 files changed
+396

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
name: Check instruction packages
on:
push:
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python -m unittest discover -s tests -v
- run: python -m tools.packages check packages/planner-scope
- run: python -m tools.packages check examples/starter-planner
- run: python -m tools.packages pack packages/planner-scope dist/planner-scope.zip
- run: python -m tools.packages pack examples/starter-planner dist/starter-planner.zip
- uses: actions/upload-artifact@v4
with:
name: instruction-packages
path: dist/*.zip
+3
View File
@@ -0,0 +1,3 @@
dist/
__pycache__/
*.pyc
+5
View File
@@ -0,0 +1,5 @@
# Contributions
The MVP repository does not accept external upstream code contributions or pull requests. No CLA or DCO is required. You may fork, modify, and redistribute these MIT-licensed packages under the license's notice condition. Maintain your fork under its own source identity and release process.
Report a suspected defect in a named maintained release through the RelayView issue tracker. Publication of an example or fork does not make it a supported first-party package.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 RelayView contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+7
View File
@@ -0,0 +1,7 @@
# Notices
The instruction packages and authoring tools in this repository are licensed under the MIT License in [LICENSE](LICENSE). Include that license and copyright notice when redistributing a package or fork.
This release has no third-party runtime dependencies, vendored assets, or third-party package notices. Python's standard library runs the authoring checks and is not bundled into a skill package.
The RelayView owner, its private prompts, and its separate open trust modules are outside this repository and its MIT grant.
+32
View File
@@ -0,0 +1,32 @@
# RelayView skills
Public source for RelayView-maintained instruction-only role skills and an illustrative author starter. The proprietary RelayView owner controls workflow stages, role tools, verification, and Human acceptance. A skill guides one assigned role during one step; it does not add tools, credentials, network access, or workflow authority.
## Packages and support
| Directory | Status | Role / step | Version |
| --- | --- | --- | --- |
| `packages/planner-scope` | Maintained default | Planner / planning | 0.1.0 |
| `examples/starter-planner` | Illustrative starter; not a production default | Planner / planning | 0.1.0 |
Only named releases of maintained defaults receive RelayView compatibility testing and maintenance. User forks are maintained by their publishers. This release uses the instruction package schema version 1 admitted by RelayView's Community MVP. It contains only `SKILL.md`, declared `.md`/`.txt` resources, and `relayview-skill.json` within each package. Scripts, executable files, undeclared files, and requested permissions fail checks and owner admission. There is no fixed release calendar: maintained packages are versioned when their content or compatibility changes. A breaking owner contract requires a new compatible maintained release before the product depends on it.
## Fork the starter
Clone this repository, copy `examples/starter-planner` to a new directory named for your skill, and edit its `SKILL.md` and `local-guidance.md`. Set the frontmatter `name` to the directory name and give it a distinct version. Keep `source.kind` as `user` and set `source.reference` to your fork's release identifier. Update each `files[].sha256` with the SHA-256 of the exact file bytes. Then run:
```sh
python -m unittest discover -s tests -v
python -m tools.packages check examples/starter-planner
python -m tools.packages pack examples/starter-planner dist/starter-planner.zip
```
Replace `examples/starter-planner` in the last two commands with your package directory. The `pack` command writes a reproducible ZIP and prints its SHA-256. The check returns the owner's package identity digest. Both use only Python 3's standard library and this repository's source. Include `LICENSE` and `NOTICE.md` when redistributing the package.
To import a user fork, place its closed package directory under the owner's configured trusted root, use Settings → Skills to import the relative `SKILL.md` path under a new unique ID, and assign it to the matching future role step. Saving a new assignment takes effect after owner restart; existing sessions retain their captured bytes. The Settings catalog displays source, version, digest, role, step, and pending versus effective assignment. The owner's admission remains authoritative even when these local checks pass.
## Release identity
A release tag pins public source. CI runs package checks and archives from that source. RelayView's bundled copy records the source tag and commit plus each package identity and file hash, then verifies the bytes in its own focused test. A bundled update creates a new release identity; it does not overwrite a user fork.
The MIT license covers this repository only. The contribution policy is in [CONTRIBUTING.md](CONTRIBUTING.md); dependency notices are in [NOTICE.md](NOTICE.md).
+11
View File
@@ -0,0 +1,11 @@
---
name: starter-planner
description: Illustrative Planner instruction package for a user-maintained fork.
metadata:
relayview-display-name: Starter planner
relayview-version: 0.1.0
---
# Starter Planner
Use [local-guidance.md](local-guidance.md) to document this organization's planning convention. Replace this example with task-specific guidance before assigning it to a role.
@@ -0,0 +1,3 @@
# Local guidance
For each Plan, name the outcome, assumptions, verification, and handoff boundary. Edit this file to fit your own workflow.
@@ -0,0 +1,19 @@
{
"schemaVersion": 1,
"source": {
"kind": "user",
"reference": "v0.1.0"
},
"role": "planner",
"step": "planning",
"files": [
{
"path": "SKILL.md",
"sha256": "e0dfc733d4af0866b87993c7efc898a9a8dabbbd46f30777683ba85b6f80bad2"
},
{
"path": "local-guidance.md",
"sha256": "73ef6f48af48b7bf24a1ad88c7c906eafc28fe38201b87a860e0ecda927e1e34"
}
]
}
+13
View File
@@ -0,0 +1,13 @@
---
name: planner-scope
description: Check a Planner's issue scope and evidence before drafting a Plan.
metadata:
relayview-display-name: Planner scope
relayview-version: 0.1.0
---
# Planner scope
When drafting the assigned Plan, identify the requested behavior, its boundary, dependencies, and the evidence needed to verify it. Read the task's authoritative project context and relevant neighboring work before deciding scope. Use the [scope guide](scope.md) to keep assumptions and open decisions visible in the Plan.
Work within the Planner's assigned tools and read access. Send the Plan through the owner's existing planning interface. Instructions here provide guidance; owner-controlled workflow stages and checks remain the source of execution authority.
@@ -0,0 +1,19 @@
{
"schemaVersion": 1,
"source": {
"kind": "first-party",
"reference": "v0.1.0"
},
"role": "planner",
"step": "planning",
"files": [
{
"path": "SKILL.md",
"sha256": "5c52e8c6183472c03f039475c6df9ce1fadf7aecd06a846ad74d7a741f5f95e0"
},
{
"path": "scope.md",
"sha256": "a59376ba22a7c1eacf5af03533908badb36a00154adc894b5454bf4dbcb850ec"
}
]
}
+7
View File
@@ -0,0 +1,7 @@
# Scope guide
- State the user-visible outcome and the exact part of the system affected.
- Name prerequisites and neighboring work that change the boundary.
- Distinguish verified current behavior from a proposal.
- Identify one observable success path and one meaningful failure or recovery path.
- Record uncertainty as an open decision; do not turn silence into approval.
+75
View File
@@ -0,0 +1,75 @@
import json
import hashlib
import shutil
import tempfile
import unittest
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_release_package_and_starter_are_valid(self):
for path in (ROOT / "packages/planner-scope", 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_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)
if __name__ == "__main__":
unittest.main()
+159
View File
@@ -0,0 +1,159 @@
"""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")
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")
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:
for file in sorted(path.rglob("*")):
if file.is_file():
name = f"{path.name}/{file.relative_to(path).as_posix()}"
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()