* refactor(skills): shipped-set slim — 15 skills to optional, github six-way merge, pdf absorbs OCR+nano-pdf, channel-gated teams pipeline
Maintainer-directed shipped-skills curation (skills index 1,900 -> ~1,400
tok/call on desktop; every session pays the index, so this is a per-call
diet on all installs):
- optional-skills moves (installable via skills hub, history preserved):
creative comfyui/ascii-art/excalidraw/pretext/sketch/touchdesigner-mcp;
ALL of mlops (huggingface-hub, llama-cpp, serving-llms-vllm,
weights-and-biases, evaluating-llms-harness — subcategory structure
kept); research-paper-writing (55 supporting files, 17.3K-tok load);
openhue; blogwatcher (first taught the cronjob monitor-field watch
pattern + web_extract instead of pre-cron manual workflows)
- DELETED session-librarian (Aug-12 'inspired by Perplexity Computer'
port, never maintainer-intended; session_search covers discovery)
- github: six skills (auth, issues, pr-workflow, issue-to-pr,
code-review, repo-management) merged into ONE software-development/
github skill — routing body + complete per-workflow references;
benbarclay authorship credited; codebase-inspection rides along;
discipline pins from test_github_issue_to_pr_skill.py preserved
against the reference body in the new test_github_skill.py
- pdf absorbs ocr-and-documents + nano-pdf as references/ + scripts
(extract_pymupdf, extract_marker converted to the argparse house
standard its contract test enforces)
- NEW session_platforms frontmatter gate (metadata.hermes): hides a
skill from the index on gateway channels it is not for; fail-open on
unknown platform; teams-meeting-pipeline gated to [teams, cron]
- blocked-page-recovery: research -> new web category; trigger-first
description ('Use when a fetch fails: 403/429, paywall, WAF, bot
wall.') so the model actually reaches for it on blocked fetches
- docs regenerated via generate-skill-docs.py (195 pages); related_skills
swept repo-wide; tests: 1672 passed (2 openclaw failures pre-existing
on clean main, Windows-local)
* chore: ignore .skills_prompt_snapshot.json (local index cache, accidentally committed)
131 lines
4.6 KiB
Python
131 lines
4.6 KiB
Python
"""Tests for the github merged skill (formerly six github-* skills).
|
|
|
|
The issue-to-pr workflow (originally the github-issue-to-pr skill,
|
|
author benbarclay) now lives complete in references/issue-to-pr.md of the
|
|
merged software-development/github skill. Frontmatter contracts apply to
|
|
the merged SKILL.md; the content pins that guarded the issue-to-pr
|
|
disciplines now check the reference body.
|
|
"""
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
SKILL_DIR = (
|
|
Path(__file__).resolve().parents[2]
|
|
/ "skills"
|
|
/ "software-development"
|
|
/ "github"
|
|
)
|
|
SKILL_PATH = SKILL_DIR / "SKILL.md"
|
|
ISSUE_TO_PR_REF = SKILL_DIR / "references" / "issue-to-pr.md"
|
|
|
|
|
|
def _frontmatter_and_body():
|
|
content = SKILL_PATH.read_text(encoding="utf-8")
|
|
assert content.startswith("---")
|
|
m = re.search(r"\n---\s*\n", content[3:])
|
|
assert m, "frontmatter must close with ---"
|
|
fm = yaml.safe_load(content[3 : m.start() + 3])
|
|
body = content[m.end() + 3 :]
|
|
return fm, body
|
|
|
|
|
|
def test_skill_file_exists():
|
|
assert SKILL_PATH.is_file()
|
|
|
|
|
|
def test_all_workflow_references_exist():
|
|
for ref in (
|
|
"auth.md",
|
|
"issues.md",
|
|
"pr-workflow.md",
|
|
"issue-to-pr.md",
|
|
"code-review.md",
|
|
"repo-management.md",
|
|
):
|
|
assert (SKILL_DIR / "references" / ref).is_file(), f"missing reference: {ref}"
|
|
|
|
|
|
def test_frontmatter_required_fields():
|
|
fm, _ = _frontmatter_and_body()
|
|
for field in ("name", "description", "version", "author", "license", "platforms"):
|
|
assert field in fm, f"missing frontmatter field: {field}"
|
|
assert fm["name"] == "github"
|
|
hermes = fm["metadata"]["hermes"]
|
|
assert hermes["tags"]
|
|
assert "related_skills" in hermes
|
|
|
|
|
|
def test_description_hardline():
|
|
fm, _ = _frontmatter_and_body()
|
|
desc = fm["description"]
|
|
assert len(desc) <= 60, f"description is {len(desc)} chars; hardline is 60"
|
|
assert desc.endswith(".")
|
|
|
|
|
|
def test_author_credits_human_first():
|
|
fm, _ = _frontmatter_and_body()
|
|
assert not fm["author"].startswith("Hermes Agent"), "human contributor must be credited first"
|
|
assert "benbarclay" in fm["author"]
|
|
|
|
|
|
def test_related_skills_resolve_in_repo():
|
|
fm, _ = _frontmatter_and_body()
|
|
repo_root = SKILL_PATH.parents[3]
|
|
for name in fm["metadata"]["hermes"]["related_skills"]:
|
|
hits = (
|
|
list(repo_root.glob(f"skills/*/{name}/SKILL.md"))
|
|
+ list(repo_root.glob(f"optional-skills/*/{name}/SKILL.md"))
|
|
+ list(repo_root.glob(f"skills/*/*/{name}/SKILL.md"))
|
|
)
|
|
assert hits, f"related_skills entry does not resolve in-repo: {name}"
|
|
|
|
|
|
def test_body_routes_every_workflow():
|
|
_, body = _frontmatter_and_body()
|
|
for ref in (
|
|
"references/auth.md",
|
|
"references/issues.md",
|
|
"references/pr-workflow.md",
|
|
"references/issue-to-pr.md",
|
|
"references/code-review.md",
|
|
"references/repo-management.md",
|
|
):
|
|
assert ref in body, f"routing table missing: {ref}"
|
|
assert len(SKILL_PATH.read_text(encoding="utf-8")) <= 100_000
|
|
|
|
|
|
def test_no_machine_local_paths():
|
|
for p in (SKILL_PATH, ISSUE_TO_PR_REF):
|
|
content = p.read_text(encoding="utf-8")
|
|
assert "/home/" not in content
|
|
assert not re.search(r"[A-Z]:\\\\Users", content)
|
|
|
|
|
|
def test_issue_to_pr_steps_have_completion_criteria():
|
|
body = ISSUE_TO_PR_REF.read_text(encoding="utf-8")
|
|
steps = re.findall(r"^### \d+\..*?(?=^### \d+\.|^## |\Z)", body, re.MULTILINE | re.DOTALL)
|
|
assert len(steps) >= 6
|
|
for step in steps:
|
|
assert "Done when" in step, f"step missing completion criterion: {step[:60]!r}"
|
|
|
|
|
|
def test_issue_to_pr_core_disciplines_present():
|
|
"""The learnings folded in from maintainer practice must survive edits."""
|
|
body = ISSUE_TO_PR_REF.read_text(encoding="utf-8")
|
|
assert "--comments" in body, "must read the full issue thread"
|
|
assert "pr list --search" in body, "must sweep for duplicate PRs before coding"
|
|
assert re.search(r"git log -p -S", body), "must check design intent via history"
|
|
assert "sabotage" in body.lower() or "FAILS" in body, "must prove the regression test bites"
|
|
assert "sibling" in body, "must fix the class, not the site"
|
|
assert "dispatches CI" in body, "must open the PR immediately after work exists"
|
|
|
|
|
|
def test_issue_to_pr_not_a_router():
|
|
"""Reference steps must carry their own procedure, not route elsewhere."""
|
|
body = ISSUE_TO_PR_REF.read_text(encoding="utf-8")
|
|
steps = re.findall(r"^### \d+\..*?(?=^### \d+\.|^## |\Z)", body, re.MULTILINE | re.DOTALL)
|
|
routing = [s for s in steps if re.match(r"^### \d+\.[^\n]*\n+Load `", s)]
|
|
assert len(routing) == 0, "steps must not open by delegating to another skill"
|