Six-skill presentation system: ingest talks into a rhetoric vault, run interactive clarification, generate a speaker profile, create presentations that match your documented patterns, produce the deck illustrations + thumbnail visual layer, and publish talk pages to a Jekyll shownotes site. Includes a 102-entry Presentation Patterns taxonomy (91 observable, 11 unobservable go-live items) for scoring, brainstorming, and go-live preparation.
86
92%
Does it follow best practices?
Impact
86%
1.24xAverage score across 26 eval scenarios
Advisory
Suggest reviewing before use
"""Tests for skills/shownotes-publisher/scripts/content-only-gate.sh.
The gate decides whether a direct push to the protected branch would change only
the content-path allowlist (safe to direct-push, exit 0) or stray outside it
(must use branch + PR, exit 1), with exit 2 for error/empty-push states. It
covers the full push range: committed-but-unpushed changes (origin/main..HEAD)
plus pending work-tree changes (staged, unstaged, untracked).
"""
import json
import subprocess
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
GATE = REPO_ROOT / "skills" / "shownotes-publisher" / "scripts" / "content-only-gate.sh"
def _git(repo: Path, *args: str) -> None:
subprocess.run(["git", "-C", str(repo), *args], check=True,
capture_output=True, text=True)
def _commit(repo: Path, rel: str, body: str = "x\n") -> None:
path = repo / rel
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(body)
_git(repo, "add", rel)
_git(repo, "commit", "-qm", f"add {rel}")
@pytest.fixture()
def repo(tmp_path: Path) -> Path:
"""A work repo on `main` tracking a bare origin, seeded and pushed."""
origin = tmp_path / "origin.git"
subprocess.run(["git", "init", "--bare", "-b", "main", str(origin)],
check=True, capture_output=True)
work = tmp_path / "work"
subprocess.run(["git", "clone", str(origin), str(work)],
check=True, capture_output=True)
_git(work, "config", "user.email", "test@example.com")
_git(work, "config", "user.name", "Test")
_commit(work, "seed.txt", "seed\n")
_git(work, "push", "-q", "origin", "main")
return work
def _run(target: Path, *extra: str) -> subprocess.CompletedProcess:
return subprocess.run(["bash", str(GATE), str(target), *extra],
capture_output=True, text=True)
def _payload(result: subprocess.CompletedProcess) -> dict:
return json.loads(result.stdout.strip().splitlines()[-1])
def test_content_only_worktree_change_direct_pushes(repo: Path) -> None:
(repo / "_talks").mkdir()
(repo / "_talks" / "geecon-2026.md").write_text("# Talk\n")
result = _run(repo)
assert result.returncode == 0, result.stderr
payload = _payload(result)
assert payload["content_only"] is True
assert payload["changed"] == ["_talks/geecon-2026.md"]
assert payload["outside"] == []
def test_content_plus_thumbnail_direct_pushes(repo: Path) -> None:
(repo / "_talks").mkdir()
(repo / "_talks" / "geecon-2026.md").write_text("# Talk\n")
thumb = repo / "assets" / "images" / "thumbnails"
thumb.mkdir(parents=True)
(thumb / "geecon-2026-thumbnail.png").write_bytes(b"\x89PNG\r\n")
result = _run(repo)
assert result.returncode == 0, result.stderr
assert _payload(result)["content_only"] is True
def test_non_content_worktree_file_blocks(repo: Path) -> None:
(repo / "_talks").mkdir()
(repo / "_talks" / "geecon-2026.md").write_text("# Talk\n")
(repo / "_config.yml").write_text("url: https://example.com\n")
result = _run(repo)
assert result.returncode == 1, result.stderr
payload = _payload(result)
assert payload["content_only"] is False
assert payload["outside"] == ["_config.yml"]
def test_modified_tracked_non_content_file_blocks(repo: Path) -> None:
(repo / "seed.txt").write_text("changed\n")
result = _run(repo)
assert result.returncode == 1, result.stderr
assert _payload(result)["outside"] == ["seed.txt"]
def test_unpushed_non_content_commit_blocks(repo: Path) -> None:
# The carve-out scenario: an earlier non-content commit not yet pushed must
# not ride along on a direct push, even if the work tree is content-only.
_commit(repo, "_config.yml", "url: https://example.com\n")
(repo / "_talks").mkdir()
(repo / "_talks" / "geecon-2026.md").write_text("# Talk\n")
result = _run(repo)
assert result.returncode == 1, result.stderr
payload = _payload(result)
assert payload["content_only"] is False
assert "_config.yml" in payload["outside"]
def test_unpushed_content_commit_direct_pushes(repo: Path) -> None:
_commit(repo, "_talks/devoxx.md", "# Talk\n")
result = _run(repo)
assert result.returncode == 0, result.stderr
payload = _payload(result)
assert payload["content_only"] is True
assert payload["changed"] == ["_talks/devoxx.md"]
def test_no_pending_changes_is_error(repo: Path) -> None:
result = _run(repo)
assert result.returncode == 2
assert "empty" in result.stderr
def test_not_on_protected_branch_is_error(repo: Path) -> None:
_git(repo, "checkout", "-q", "-b", "feature")
(repo / "_talks").mkdir()
(repo / "_talks" / "geecon-2026.md").write_text("# Talk\n")
result = _run(repo)
assert result.returncode == 2
assert "protected branch" in result.stderr
def test_missing_upstream_is_error(repo: Path) -> None:
_git(repo, "remote", "remove", "origin")
(repo / "_talks").mkdir()
(repo / "_talks" / "geecon-2026.md").write_text("# Talk\n")
result = _run(repo)
assert result.returncode == 2
assert "origin/main" in result.stderr
def test_not_a_git_repo_is_error(tmp_path: Path) -> None:
plain = tmp_path / "plain"
plain.mkdir()
result = _run(plain)
assert result.returncode == 2
assert "not a git work tree" in result.stderr.github
eval-resources
humor-postmortem-blind-spots
qr-bitly-slug-from-outline
qr-missing-shortener-detection
shownotes-publisher-omit-placeholder
shownotes-publisher-publish-no-date
shownotes-publisher-publish-with-date
shownotes-publisher-update-add-video
video-extraction-diagnostics
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
rules
scripts
skills
illustrations
presentation-creator
references
patterns
build
deliver
prepare
scripts
shownotes-publisher
vault-clarification
vault-ingress
vault-profile
tests