diff --git a/loopx/capabilities/project_skill_delivery/README.md b/loopx/capabilities/project_skill_delivery/README.md index 023cea326b..025bd6bf37 100644 --- a/loopx/capabilities/project_skill_delivery/README.md +++ b/loopx/capabilities/project_skill_delivery/README.md @@ -72,6 +72,12 @@ earlier targets if a later replacement fails. The implementation copies the whole skill directory instead of linking `SKILL.md`, so supporting scripts, references, and host metadata remain complete. +Python distributions bundle the canonical `loopx-material` and +`loopx-change-quality` sources, scope markers and host display metadata +alongside the global workflows. Bundling a project skill does not add it to the +global host catalog or activate its capability. Project install, readback and +uninstall use the same release-owned sources in a wheel or source checkout. + ## Authority Boundary Project skill delivery controls discoverability, not domain authority. A diff --git a/pyproject.toml b/pyproject.toml index cd7f3cab5a..2162921c4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,20 @@ include = ["loopx*"] "loopx.kunluncode_goal_mode" = ["README.md"] [tool.setuptools.data-files] +"share/loopx/skills/loopx-material" = [ + "skills/loopx-material/SKILL.md", + "skills/loopx-material/.loopx-skill-scope", +] +"share/loopx/skills/loopx-material/agents" = [ + "skills/loopx-material/agents/openai.yaml", +] +"share/loopx/skills/loopx-change-quality" = [ + "skills/loopx-change-quality/SKILL.md", + "skills/loopx-change-quality/.loopx-skill-scope", +] +"share/loopx/skills/loopx-change-quality/agents" = [ + "skills/loopx-change-quality/agents/openai.yaml", +] "share/loopx/skills/loopx-performance-diagnosis" = [ "skills/loopx-performance-diagnosis/SKILL.md", "skills/loopx-performance-diagnosis/.loopx-skill-scope", diff --git a/tests/test_packaged_skill_metadata.py b/tests/test_packaged_skill_metadata.py index b09cb8f8b3..bba24e2222 100644 --- a/tests/test_packaged_skill_metadata.py +++ b/tests/test_packaged_skill_metadata.py @@ -1,10 +1,18 @@ import re from pathlib import Path +import pytest + +from loopx.capabilities.project_skill_delivery import classify_host_skill_sources import yaml REPO_ROOT = Path(__file__).resolve().parents[1] +_SCOPED_SOURCES = classify_host_skill_sources(REPO_ROOT / "skills") +RELEASE_SKILL_IDS = ( + *_SCOPED_SOURCES["deliverable_skill_ids"], + *_SCOPED_SOURCES["project_skill_ids"], +) def test_packaged_workflow_skill_frontmatter_is_discoverable() -> None: @@ -31,24 +39,22 @@ def test_packaged_loopx_skills_use_canonical_brand_display_names() -> None: assert not display_name.startswith("Loopx"), display_name -def test_packaged_scope_markers_ship_with_workflow_sources(): +@pytest.mark.parametrize("skill_id", RELEASE_SKILL_IDS) +def test_release_scoped_skills_ship_source_and_scope_markers(skill_id): import tomllib - from loopx.skill_install_readback import PACKAGED_HOST_SKILL_IDS package = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text()) data_files = package["tool"]["setuptools"]["data-files"] - for skill_id in PACKAGED_HOST_SKILL_IDS: - assert f"skills/{skill_id}/.loopx-skill-scope" in data_files[ - f"share/loopx/skills/{skill_id}" - ] + sources = data_files[f"share/loopx/skills/{skill_id}"] + assert f"skills/{skill_id}/SKILL.md" in sources + assert f"skills/{skill_id}/.loopx-skill-scope" in sources -def test_packaged_skill_display_metadata_is_in_distribution(): +@pytest.mark.parametrize("skill_id", RELEASE_SKILL_IDS) +def test_release_scoped_skill_display_metadata_is_in_distribution(skill_id): import tomllib - from loopx.skill_install_readback import PACKAGED_HOST_SKILL_IDS package = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")) data_files = package["tool"]["setuptools"]["data-files"] - for skill_id in PACKAGED_HOST_SKILL_IDS: - assert f"skills/{skill_id}/agents/openai.yaml" in data_files[ - f"share/loopx/skills/{skill_id}/agents"] + assert f"skills/{skill_id}/agents/openai.yaml" in data_files[ + f"share/loopx/skills/{skill_id}/agents"]