Files
hermes-agent/tests/skills/test_github_skill.py
Teknium c49fa88b80 refactor(skills): shipped-set slim — 15 to optional, github 6-way merge, pdf absorbs OCR, channel-gated teams pipeline (index −26%) (#98539)
* 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)
2026-08-30 04:53:39 -07:00

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"