#!/usr/bin/env python3 """CI guard: validate CHANGELOG.md versioning and the in-package copy. Run on pull requests. Two checks: 1. If CHANGELOG.md changed in the PR, its top version must be a clean single-step SemVer bump above the latest ``v*`` tag. (Non-changing PRs — dependabot bumps, docs — are not version-checked, so they don't release.) 2. ``src/slack-bot/CHANGELOG.md`` (the copy that ships with the bot and feeds the App Home "What's New" tab) must match the canonical root CHANGELOG.md. The workflow passes context via env: ``PREV_TAG`` (latest tag) and ``CHANGELOG_CHANGED`` ("true"/"false"). Run locally it assumes the changelog changed so the bump is validated. """ import os import pathlib import sys sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "src" / "shared")) from shared.changelog import bump_kind, top_version # noqa: E402 ROOT = pathlib.Path(__file__).resolve().parents[1] PKG_COPY = ROOT / "src" / "slack-bot" / "CHANGELOG.md" def evaluate( top: str | None, prev_tag: str, changelog_changed: bool, copies_match: bool ) -> list[str]: """Return a list of problems (empty == pass). Pure, for unit testing.""" problems = [] if not copies_match: problems.append( "src/slack-bot/CHANGELOG.md is out of sync with CHANGELOG.md — " "run scripts/sync_changelog.py" ) if changelog_changed: if top is None: problems.append("CHANGELOG.md changed but has no version entry at the top") else: prev = (prev_tag or "v0.0.0").lstrip("v") if bump_kind(top, prev) is None: problems.append( f"top version v{top} is not a clean single-step SemVer bump " f"above the latest tag v{prev} (expected one of " f"inc-major / inc-minor / inc-patch)" ) return problems def main() -> int: root_text = (ROOT / "CHANGELOG.md").read_text() copies_match = PKG_COPY.exists() and PKG_COPY.read_text() == root_text problems = evaluate( top=top_version(root_text), prev_tag=os.environ.get("PREV_TAG", ""), changelog_changed=os.environ.get("CHANGELOG_CHANGED", "true") == "true", copies_match=copies_match, ) if problems: for problem in problems: print(f"::error::{problem}") return 1 print("CHANGELOG guard passed.") return 0 if __name__ == "__main__": raise SystemExit(main())