Files
playbook/test/test_thirdparty_skills_pipeline.py
T

447 lines
18 KiB
Python

import json
import os
import shutil
import subprocess
import tempfile
import textwrap
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MANIFEST = ROOT / ".gitea" / "ci" / "thirdparty_skills.json"
WORKFLOW = ROOT / ".gitea" / "workflows" / "update-thirdparty-skills.yml"
TSL_SYNC_WORKFLOW = ROOT / ".gitea" / "workflows" / "sync-tsl-playbook.yml"
LEGACY_WORKFLOW = ROOT / ".gitea" / "workflows" / "update-thirdparty-superpowers.yml"
UPDATE_SCRIPT = ROOT / ".gitea" / "ci" / "update_thirdparty_skills.sh"
SYNC_SCRIPT = ROOT / ".gitea" / "ci" / "sync_thirdparty_skills.sh"
SKILLS_MD = ROOT / "SKILLS.md"
def load_manifest() -> dict:
return json.loads(MANIFEST.read_text(encoding="utf-8"))
def bash_path(path: Path) -> str:
resolved = path.resolve()
if os.name != "nt":
return resolved.as_posix()
drive = resolved.drive.rstrip(":").lower()
rest = resolved.as_posix()[2:]
return f"/mnt/{drive}{rest}"
def run_command(*args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
return subprocess.run(
list(args),
cwd=cwd,
capture_output=True,
text=True,
)
def extract_workflow_region(name: str) -> str:
text = WORKFLOW.read_text(encoding="utf-8")
begin = f"# BEGIN {name}"
end = f"# END {name}"
if begin not in text or end not in text:
raise AssertionError(f"workflow region markers not found: {name}")
body = text.split(begin, 1)[1].split(end, 1)[0]
return textwrap.dedent(body).strip() + "\n"
class ThirdpartySkillsPipelineTests(unittest.TestCase):
def test_manifest_declares_all_thirdparty_sources(self):
data = load_manifest()
self.assertEqual(
[entry["id"] for entry in data["sources"]],
[
"matt-pocock-skills",
"ui-ux-pro-max",
"brooks-lint",
"codebase-recon",
"cangjie-skill",
"darwin-skill",
],
)
def test_matt_pocock_manifest_syncs_stable_skill_groups(self):
data = load_manifest()
matt = next(
item for item in data["sources"] if item["id"] == "matt-pocock-skills"
)
self.assertEqual(
matt["upstream_repo"], "https://github.com/mattpocock/skills.git"
)
self.assertEqual(matt["snapshot_dir"], "matt-pocock-skills")
self.assertEqual(matt["sync_mode"], "copy_skill_dirs")
self.assertEqual(
matt["skills_subdirs"],
["skills/engineering", "skills/productivity", "skills/misc"],
)
self.assertIn("grill-with-docs", matt["include_skill_dirs"])
self.assertIn("grilling", matt["include_skill_dirs"])
self.assertIn("to-tickets", matt["include_skill_dirs"])
def test_matt_pocock_manifest_includes_required_workflow_skills(self):
manifest_skills = set(
next(
item
for item in load_manifest()["sources"]
if item["id"] == "matt-pocock-skills"
)["include_skill_dirs"]
)
required = {
"setup-matt-pocock-skills",
"grill-with-docs",
"grilling",
"domain-modeling",
"to-spec",
"to-tickets",
"tdd",
"codebase-design",
"code-review",
"handoff",
}
self.assertTrue(required <= manifest_skills)
legacy_main_chain = {
"using-superpowers",
"brainstorming",
"writing-plans",
"executing-plans",
}
self.assertTrue(legacy_main_chain.isdisjoint(manifest_skills))
def test_copy_skill_root_sources_declare_curated_paths(self):
data = load_manifest()
sources = {item["id"]: item for item in data["sources"]}
expected = {
"cangjie-skill": {
"snapshot_dir": "cangjie-skill",
"output_name": "cangjie-skill",
"required_paths": {"SKILL.md", "methodology", "extractors", "templates"},
},
"darwin-skill": {
"snapshot_dir": "darwin-skill",
"output_name": "darwin-skill",
"required_paths": {"SKILL.md", "references", "scripts", "templates"},
},
}
for source_id, contract in expected.items():
source = sources[source_id]
self.assertEqual(source["sync_mode"], "copy_skill_root")
self.assertEqual(source["snapshot_dir"], contract["snapshot_dir"])
self.assertEqual(source["output_name"], contract["output_name"])
self.assertTrue(contract["required_paths"] <= set(source["include_paths"]))
def test_ui_ux_pro_max_uses_render_skill_sync_mode(self):
data = load_manifest()
ui_skill = next(item for item in data["sources"] if item["id"] == "ui-ux-pro-max")
self.assertEqual(ui_skill["sync_mode"], "render_skill")
self.assertEqual(ui_skill["snapshot_dir"], "ui-ux-pro-max")
def test_architecture_skill_sources_use_include_filters(self):
data = load_manifest()
brooks = next(item for item in data["sources"] if item["id"] == "brooks-lint")
self.assertEqual(brooks["sync_mode"], "copy_skill_dirs")
self.assertEqual(brooks["snapshot_dir"], "brooks-lint")
self.assertEqual(brooks["skills_subdir"], "skills")
self.assertEqual(
brooks["source_list"], "skills/thirdparty/.sources/brooks-lint.list"
)
self.assertIn("brooks-audit", brooks["include_skill_dirs"])
self.assertIn("brooks-review", brooks["include_skill_dirs"])
self.assertIn("_shared", brooks["include_skill_dirs"])
recon = next(item for item in data["sources"] if item["id"] == "codebase-recon")
self.assertEqual(recon["sync_mode"], "copy_skill_dirs")
self.assertEqual(recon["snapshot_dir"], "outfitter-agents")
self.assertEqual(recon["skills_subdir"], "plugins/outfitter/skills")
self.assertEqual(
recon["source_list"], "skills/thirdparty/.sources/codebase-recon.list"
)
self.assertEqual(recon["include_skill_dirs"], ["codebase-recon"])
self.assertEqual(
recon["overlay_patch"],
".gitea/ci/thirdparty-skill-overlays/codebase-recon.patch",
)
def test_workflow_inlines_update_and_sync_in_single_serial_job(self):
text = WORKFLOW.read_text(encoding="utf-8")
self.assertFalse(LEGACY_WORKFLOW.exists())
self.assertFalse(UPDATE_SCRIPT.exists())
self.assertFalse(SYNC_SCRIPT.exists())
self.assertIn("update_and_sync:", text)
self.assertNotIn("\n update:\n", text)
self.assertNotIn("\n sync:\n", text)
self.assertIn("# BEGIN update_thirdparty_snapshots", text)
self.assertIn("# BEGIN sync_thirdparty_skills", text)
self.assertNotIn("update_thirdparty_skills.sh", text)
self.assertNotIn("sync_thirdparty_skills.sh", text)
self.assertNotIn("git merge", text)
self.assertNotIn("git pull", text)
def test_workflow_has_serial_concurrency_and_literal_generic_paths(self):
text = WORKFLOW.read_text(encoding="utf-8")
self.assertIn("concurrency:", text)
self.assertIn("update-thirdparty-${{ github.repository }}", text)
self.assertIn('MANIFEST_PATH: ".gitea/ci/thirdparty_skills.json"', text)
self.assertIn("update_thirdparty_snapshots", text)
self.assertIn("sync_thirdparty_skills", text)
def test_inline_workflow_exposes_manifest_and_publish_contract(self):
text = WORKFLOW.read_text(encoding="utf-8")
self.assertIn('MANIFEST_PATH="${MANIFEST_PATH:-.gitea/ci/thirdparty_skills.json}"', text)
self.assertIn('TARGET_BRANCH="${TARGET_BRANCH:-thirdparty/skill}"', text)
self.assertIn('TARGET_BRANCH="${TARGET_BRANCH:-main}"', text)
self.assertIn(':package: deps(thirdparty): update snapshots', text)
self.assertIn(':package: deps(skills): sync thirdparty skills', text)
self.assertIn('git push origin "$TARGET_BRANCH"', text)
def test_ci_committers_share_explicit_git_identity(self):
workflow_text = TSL_SYNC_WORKFLOW.read_text(encoding="utf-8")
thirdparty_text = WORKFLOW.read_text(encoding="utf-8")
for text in (workflow_text, thirdparty_text):
self.assertIn('GIT_USER_NAME: "ci[bot]"', text)
self.assertIn('GIT_USER_EMAIL: "ci[bot]@tinysoft.com.cn"', text)
self.assertIn('git config user.name "$GIT_USER_NAME"', text)
self.assertIn('git config user.email "$GIT_USER_EMAIL"', text)
self.assertNotIn("COMMIT_AUTHOR_NAME", text)
self.assertNotIn("COMMIT_AUTHOR_EMAIL", text)
self.assertNotIn("@local", text)
def test_skills_doc_points_to_generic_thirdparty_sources(self):
text = SKILLS_MD.read_text(encoding="utf-8")
# Check that third-party skills section exists (without enforcing exact heading format)
self.assertIn("thirdparty", text.lower())
self.assertIn("skills/thirdparty/", text)
self.assertNotIn("Third-party Skills (superpowers)", text)
def test_manifest_declares_unique_thirdparty_source_lists(self):
source_lists = [entry["source_list"] for entry in load_manifest()["sources"]]
self.assertEqual(len(source_lists), len(set(source_lists)))
self.assertTrue(
all(path.startswith("skills/thirdparty/.sources/") for path in source_lists)
)
def test_superpowers_source_and_vendored_skills_are_absent(self):
data = load_manifest()
self.assertNotIn("superpowers", {item["id"] for item in data["sources"]})
source_list = (
ROOT / "skills" / "thirdparty" / ".sources" / "superpowers.list"
)
self.assertFalse(source_list.exists())
legacy_skill_dirs = {
"brainstorming",
"dispatching-parallel-agents",
"executing-plans",
"finishing-a-development-branch",
"receiving-code-review",
"requesting-code-review",
"subagent-driven-development",
"systematic-debugging",
"test-driven-development",
"using-git-worktrees",
"using-superpowers",
"verification-before-completion",
"writing-plans",
"writing-skills",
}
thirdparty_root = ROOT / "skills" / "thirdparty"
self.assertEqual(
{name for name in legacy_skill_dirs if (thirdparty_root / name).exists()},
set(),
)
def test_inline_update_materializes_manifest_before_target_checkout(self):
text = WORKFLOW.read_text(encoding="utf-8")
self.assertIn('manifest_copy="$tmp_dir/thirdparty_skills.json"', text)
self.assertIn('cp "$MANIFEST_PATH" "$manifest_copy"', text)
self.assertIn('MANIFEST_PATH="$manifest_copy"', text)
self.assertIn("remove_paths", text)
self.assertIn('remove_snapshot_paths "$snapshot_dir" "$remove_paths"', text)
self.assertIn("- Remove-Paths:", text)
self.assertIn('if ! emit_sources_tsv > "$sources_file"; then', text)
self.assertNotIn("done < <(emit_sources_tsv)", text)
self.assertLess(
text.index('cp "$MANIFEST_PATH" "$manifest_copy"'),
text.index('git checkout -B "$TARGET_BRANCH" "origin/$TARGET_BRANCH"'),
)
def test_inline_sync_assumes_thirdparty_snapshot_is_already_clean(self):
text = WORKFLOW.read_text(encoding="utf-8")
self.assertIn('"\\x1f".join(', text)
self.assertIn("while IFS=$'\\x1f' read -r", text)
self.assertIn("include_skill_dirs", text)
self.assertIn('skill_dir_included "$name" "$include_skill_dirs"', text)
self.assertNotIn("while IFS=$'\\t' read -r", text)
self.assertNotIn("exclude_skill_dirs", text)
self.assertNotIn("is_excluded_skill_dir", text)
def test_inline_sync_applies_codebase_recon_overlay_in_temp_repo(self):
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_root = Path(tmp_dir)
mirror = tmp_root / "origin.git"
work = tmp_root / "work"
clone_mirror = run_command(
"git",
"-c",
f"safe.directory={(ROOT / '.git').as_posix()}",
"clone",
"--mirror",
str(ROOT),
str(mirror),
)
self.assertEqual(clone_mirror.returncode, 0, msg=clone_mirror.stderr)
main_ref = run_command(
"git",
f"--git-dir={mirror}",
"rev-parse",
"refs/remotes/origin/main",
)
self.assertEqual(main_ref.returncode, 0, msg=main_ref.stderr)
expose_main_branch = run_command(
"git",
f"--git-dir={mirror}",
"update-ref",
"refs/heads/main",
main_ref.stdout.strip(),
)
self.assertEqual(
expose_main_branch.returncode,
0,
msg=expose_main_branch.stderr,
)
thirdparty_ref = run_command(
"git",
f"--git-dir={mirror}",
"rev-parse",
"refs/remotes/origin/thirdparty/skill",
)
self.assertEqual(thirdparty_ref.returncode, 0, msg=thirdparty_ref.stderr)
expose_thirdparty_branch = run_command(
"git",
f"--git-dir={mirror}",
"update-ref",
"refs/heads/thirdparty/skill",
thirdparty_ref.stdout.strip(),
)
self.assertEqual(
expose_thirdparty_branch.returncode,
0,
msg=expose_thirdparty_branch.stderr,
)
clone_work = run_command("git", "clone", str(mirror), str(work))
self.assertEqual(clone_work.returncode, 0, msg=clone_work.stderr)
set_remote = run_command(
"git", "-C", str(work), "remote", "set-url", "origin", bash_path(mirror)
)
self.assertEqual(set_remote.returncode, 0, msg=set_remote.stderr)
manifest_data = load_manifest()
manifest_data["sources"] = [
entry
for entry in manifest_data["sources"]
if entry["id"] == "codebase-recon"
]
(work / ".gitea" / "ci" / "thirdparty_skills.json").write_text(
json.dumps(manifest_data, indent=2) + "\n",
encoding="utf-8",
)
overlay_path = Path(manifest_data["sources"][0]["overlay_patch"])
shutil.copy2(ROOT / overlay_path, work / overlay_path)
fixture_commit = run_command(
"git",
"-C",
str(work),
"add",
".gitea/ci/thirdparty_skills.json",
overlay_path.as_posix(),
)
self.assertEqual(fixture_commit.returncode, 0, msg=fixture_commit.stderr)
fixture_commit = run_command(
"git",
"-C",
str(work),
"-c",
"user.name=test",
"-c",
"user.email=test@example.invalid",
"commit",
"-m",
"test: configure thirdparty sync fixture",
)
self.assertEqual(fixture_commit.returncode, 0, msg=fixture_commit.stderr)
fixture_push = run_command(
"git", "-C", str(work), "push", "origin", "HEAD:main"
)
self.assertEqual(fixture_push.returncode, 0, msg=fixture_push.stderr)
sync_script = extract_workflow_region("sync_thirdparty_skills")
script_path = work / ".sync-thirdparty-test.sh"
script_path.write_text(
'export GIT_USER_NAME="test"\n'
'export GIT_USER_EMAIL="test@example.invalid"\n'
+ sync_script,
encoding="utf-8",
newline="\n",
)
env = os.environ.copy()
env.update(
{
"REPO_DIR": str(work),
"THIRDPARTY_BRANCH": "thirdparty/skill",
"MANIFEST_PATH": ".gitea/ci/thirdparty_skills.json",
}
)
sync_result = subprocess.run(
["bash", script_path.name],
cwd=work,
env=env,
capture_output=True,
text=True,
)
self.assertEqual(
sync_result.returncode,
0,
msg=sync_result.stdout + sync_result.stderr,
)
generated_list = (
work / "skills" / "thirdparty" / ".sources" / "codebase-recon.list"
)
generated_skill = (
work / "skills" / "thirdparty" / "codebase-recon" / "SKILL.md"
)
generated_reference = (
work
/ "skills"
/ "thirdparty"
/ "codebase-recon"
/ "references"
/ "confidence-calibration.md"
)
self.assertTrue(generated_list.is_file())
self.assertTrue(generated_skill.is_file())
self.assertTrue(generated_reference.is_file())
self.assertIn(
"codebase-recon", generated_list.read_text(encoding="utf-8")
)
self.assertIn(
"[confidence-calibration.md](references/confidence-calibration.md)",
generated_skill.read_text(encoding="utf-8"),
)
if __name__ == "__main__":
unittest.main()