GRM-21: Implement commit validation, CI/CD workflows and repo automation

- Add scripts/validate_commit_msg.py with conventional commit enforcement
- Add scripts/configure_repo.py for Gitea branch protection and labels
- Add scripts/__init__.py for Python package importability
- Create Gitea Actions workflows: ci, auto-merge, post-merge, publish
- Update .pre-commit-config.yaml with commit-msg hook
- Update pyproject.toml pythonpath and coverage for scripts
- Add comprehensive unit tests for both scripts with 100% coverage
This commit is contained in:
Emil Simeonov
2026-06-19 04:27:43 +02:00
parent 1d3d2487ac
commit 4da724ce53
10 changed files with 755 additions and 3 deletions
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""Validate commit messages for GRM.
Rules:
- On master branch: allow anything (merge commits already have task ID).
- On feature branches: must use conventional commit format, must NOT include GRM-N prefix.
"""
import re
import subprocess
import sys
CONVENTIONAL_RE = re.compile(
r"^(feat|fix|chore|docs|style|refactor|perf|test|ci|build|revert|BREAKING CHANGE)(\(.+\))?: .+"
)
TASK_ID_RE = re.compile(r"^GRM-\d+:")
def first_line(text: str) -> str:
return text.split("\n")[0]
def get_branch() -> str:
try:
result = subprocess.run(
["git", "symbolic-ref", "--short", "HEAD"],
capture_output=True, text=True, check=True,
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return ""
def main(args=None) -> None:
argv = args if args is not None else sys.argv
if len(argv) < 2:
print("Usage: validate_commit_msg.py <commit-msg-file>")
sys.exit(1)
with open(argv[1]) as f:
msg = f.read().strip()
branch = get_branch()
if branch == "master":
return
subject = first_line(msg)
if TASK_ID_RE.match(subject):
print("ERROR: Do not include task ID (GRM-N) in feature branch commits.")
print(" Task ID will be added automatically on merge via CI.")
sys.exit(1)
if not CONVENTIONAL_RE.match(subject):
print("ERROR: Commit message must follow conventional commit format.")
print(" Expected: <type>: <description>")
print(f" Got: {subject}")
print(" Allowed types: feat, fix, chore, docs, style, refactor,")
print(" perf, test, ci, build, revert, BREAKING CHANGE")
sys.exit(1)
if __name__ == "__main__": # pragma: no cover
main()