Add skill packaging invariants and a path-filtered skill workflow

Three invariants the deterministic suite did not pin: the version
triple (SKILL.md, plugin.json, marketplace.json), the 500-line SKILL.md
budget, and probe.py MIN_CLI_VERSION never exceeding the package
version. skill.yml gives skill-only changes a seconds-fast gate on top
of the full Tests workflow.
This commit is contained in:
Victor Kuznetsov committed 2026-09-06 12:39:22 -07:00
1 parent 8ba2666a30
commit f0b425ac71
2 files changed
+74

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
name: Skill
# Fast path-filtered gate for agent-skill packaging changes. The full
# Tests workflow still runs on the same pushes; this answers skill-only
# pull requests in seconds by running just the deterministic skill suites.
on:
push:
branches: [main]
paths:
- "skills/**"
- ".claude-plugin/**"
- "docs/agent-skill.md"
- "tests/test_agent_skill.py"
- "tests/test_skill_evals.py"
pull_request:
paths:
- "skills/**"
- ".claude-plugin/**"
- "docs/agent-skill.md"
- "tests/test_agent_skill.py"
- "tests/test_skill_evals.py"
permissions:
contents: read
concurrency:
group: skill-${{ github.ref }}
cancel-in-progress: true
jobs:
skill:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
- name: Sync dev environment
run: uv sync --frozen --extra dev
- name: Agent skill packaging tests
run: uv run pytest tests/test_agent_skill.py tests/test_skill_evals.py -q
+34
View File
@@ -70,6 +70,40 @@ def test_plugin_and_marketplace_names_agree() -> None:
assert plugin["license"] == "Apache-2.0"
def test_skill_version_agrees_with_plugin_and_marketplace() -> None:
text = SKILL.read_text(encoding="utf-8")
frontmatter = _frontmatter(text)
metadata_version = re.search(r"(?m)^ version:\s*(.*)$", frontmatter)
assert metadata_version is not None, "frontmatter metadata.version is missing"
skill_version = metadata_version.group(1).strip().strip("'\"")
plugin = json.loads(PLUGIN.read_text(encoding="utf-8"))
marketplace = json.loads(MARKETPLACE.read_text(encoding="utf-8"))
assert skill_version == plugin["version"] == marketplace["metadata"]["version"]
def test_skill_stays_under_the_500_line_budget() -> None:
lines = SKILL.read_text(encoding="utf-8").count("\n")
assert lines <= 500, f"SKILL.md is {lines} lines; move detail into references/"
def test_probe_min_cli_version_never_exceeds_the_package_version() -> None:
import tomllib
sys.path.insert(0, str(ROOT / "skills" / "remove-ai-watermarks" / "scripts"))
try:
import probe
pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))
released = tuple(int(part) for part in pyproject["project"]["version"].split(".")[:3])
assert released >= probe.MIN_CLI_VERSION, (
"probe.py MIN_CLI_VERSION demands a CLI newer than this package version; "
"raise the package version or lower MIN_CLI_VERSION"
)
finally:
sys.path.remove(str(ROOT / "skills" / "remove-ai-watermarks" / "scripts"))
sys.modules.pop("probe", None)
@pytest.mark.parametrize(
"relative",
[