From 21cc55a1b93f9c7d5ec0db7643f6fe895312a325 Mon Sep 17 00:00:00 2001 From: Citali Date: Wed, 11 Feb 2026 12:19:39 +0100 Subject: [PATCH] ci(gitflow): enforce branch and PR governance checks --- .gitea/PULL_REQUEST_TEMPLATE.md | 17 +++++ .gitea/scripts/check-branch-name.sh | 25 +++++++ .gitea/scripts/check-pr-todo-reference.sh | 17 +++++ .gitea/scripts/configure-branch-protection.sh | 34 ++++++++++ .gitea/workflows/ci.yml | 30 +++++++++ BRANCHING.md | 7 ++ CONTRIBUTING.md | 2 + .../git-flow-governance.md | 66 +++++++++++++++++++ docs/workflow.md | 6 ++ 9 files changed, 204 insertions(+) create mode 100644 .gitea/PULL_REQUEST_TEMPLATE.md create mode 100755 .gitea/scripts/check-branch-name.sh create mode 100755 .gitea/scripts/check-pr-todo-reference.sh create mode 100755 .gitea/scripts/configure-branch-protection.sh create mode 100644 docs/product-engineering/git-flow-governance.md diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e642cb2 --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +## Summary + +- TODO item reference (exact text): `...` +- Scope (single primary TODO item): `...` + +## Checklist + +- [ ] Linked TODO item is in `TODO.md` +- [ ] Branch name follows `todo/*`, `refactor/*`, or `code/*` +- [ ] `bun run check` +- [ ] `bun run typecheck` +- [ ] `bun run test` +- [ ] E2E validation plan included (`bun run test:e2e` or reason if deferred) + +## Notes + +- Risks / migrations / rollout notes: diff --git a/.gitea/scripts/check-branch-name.sh b/.gitea/scripts/check-branch-name.sh new file mode 100755 index 0000000..7e32b43 --- /dev/null +++ b/.gitea/scripts/check-branch-name.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh +set -eu + +branch="${1:-}" + +if [ -z "$branch" ]; then + echo "Missing branch name." + exit 1 +fi + +case "$branch" in + dev|staging|main) + echo "Long-lived branch detected: $branch" + exit 0 + ;; +esac + +if printf "%s" "$branch" | grep -Eq '^(todo|refactor|code)\/[a-z0-9]+([._-][a-z0-9]+)*$'; then + echo "Branch naming valid: $branch" + exit 0 +fi + +echo "Invalid branch name: $branch" +echo "Expected: todo/ | refactor/ | code/" +exit 1 diff --git a/.gitea/scripts/check-pr-todo-reference.sh b/.gitea/scripts/check-pr-todo-reference.sh new file mode 100755 index 0000000..64b6192 --- /dev/null +++ b/.gitea/scripts/check-pr-todo-reference.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh +set -eu + +body="${1:-}" + +if [ -z "$body" ]; then + echo "PR body is empty." + exit 1 +fi + +if printf "%s" "$body" | grep -Eq 'TODO|todo|\[P[1-3]\]'; then + echo "PR body includes TODO reference." + exit 0 +fi + +echo "PR body must reference the related TODO item." +exit 1 diff --git a/.gitea/scripts/configure-branch-protection.sh b/.gitea/scripts/configure-branch-protection.sh new file mode 100755 index 0000000..c048bb2 --- /dev/null +++ b/.gitea/scripts/configure-branch-protection.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env sh +set -eu + +if [ "${#}" -ne 4 ]; then + echo "Usage: $0 " + exit 1 +fi + +base_url="$1" +owner="$2" +repo="$3" +token="$4" + +protect_branch() { + branch="$1" + + curl -sS -X POST \ + "${base_url}/api/v1/repos/${owner}/${repo}/branch_protections" \ + -H "Authorization: token ${token}" \ + -H "Content-Type: application/json" \ + -d "{ + \"branch_name\": \"${branch}\", + \"enable_push\": false, + \"enable_push_whitelist\": false, + \"enable_merge_whitelist\": false, + \"enable_status_check\": true, + \"status_check_contexts\": [\"Governance Checks\", \"Lint Typecheck Unit E2E\"] + }" >/dev/null +} + +protect_branch "main" +protect_branch "staging" + +echo "Branch protection applied for main and staging." diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index fe71858..36e8aaf 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -25,8 +25,38 @@ env: CMS_SUPPORT_LOGIN_KEY: "support-access" jobs: + governance: + name: Governance Checks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate branch naming + run: | + branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}" + sh .gitea/scripts/check-branch-name.sh "$branch" + + - name: Validate PR TODO reference + if: github.event_name == 'pull_request' + run: | + body='${{ github.event.pull_request.body }}' + sh .gitea/scripts/check-pr-todo-reference.sh "$body" + + - name: Commit schema check (latest commit) + uses: oven-sh/setup-bun@v2 + with: + bun-version: ${{ env.BUN_VERSION }} + + - name: Install dependencies for commitlint + run: bun install --frozen-lockfile + + - name: Commitlint + run: bun run commitlint + quality: name: Lint Typecheck Unit E2E + needs: governance runs-on: ubuntu-latest services: postgres: diff --git a/BRANCHING.md b/BRANCHING.md index f5d6b90..f6eae94 100644 --- a/BRANCHING.md +++ b/BRANCHING.md @@ -96,6 +96,13 @@ Apply in repository settings: Optional: - Protect `dev` from direct push if team size/process requires stricter control. +- Automate protection via `.gitea/scripts/configure-branch-protection.sh`. + +## Governance Automation + +- Branch naming check: `.gitea/scripts/check-branch-name.sh` +- PR TODO reference check: `.gitea/scripts/check-pr-todo-reference.sh` +- PR template: `.gitea/PULL_REQUEST_TEMPLATE.md` ## Commit Signing Notes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ff62b38..2712a06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,8 @@ Follow `BRANCHING.md` for long-lived and task branch rules. +Pull requests should use `.gitea/PULL_REQUEST_TEMPLATE.md` and link the exact TODO item. + ## Commit Message Schema This repository uses Conventional Commits. diff --git a/docs/product-engineering/git-flow-governance.md b/docs/product-engineering/git-flow-governance.md new file mode 100644 index 0000000..eb737e9 --- /dev/null +++ b/docs/product-engineering/git-flow-governance.md @@ -0,0 +1,66 @@ +# Git Flow Governance + +## Scope + +Governance rules for branch protections, PR gates, branch naming, and merge discipline. + +## Branch Protection + +Protected branches: + +- `main` +- `staging` + +Apply protections using: + +- Gitea UI settings +- or automation script: `.gitea/scripts/configure-branch-protection.sh` + +Minimum policy: + +- no direct pushes +- PR merge required +- required status checks +- at least one reviewer approval + +## PR Gates + +Required checks are implemented in `.gitea/workflows/ci.yml`: + +- Governance Checks +- Lint Typecheck Unit E2E + +## Branch Naming and TODO Scope + +Allowed branch prefixes: + +- `todo/` +- `refactor/` +- `code/` + +Validation script: + +- `.gitea/scripts/check-branch-name.sh` + +Rule: + +- one primary TODO item per delivery branch + +PR TODO reference enforcement: + +- template: `.gitea/PULL_REQUEST_TEMPLATE.md` +- CI check: `.gitea/scripts/check-pr-todo-reference.sh` + +## Branch Lifecycle + +1. Create short-lived branch from latest integration tip. +2. Implement one primary scope. +3. Open PR and pass required checks. +4. Merge into `dev`. +5. Promote `dev -> staging -> main`. + +## Commit and Tag Policy + +- Conventional commits required (`CONTRIBUTING.md`) +- release tags: `vX.Y.Z` +- changelog generated from commit history diff --git a/docs/workflow.md b/docs/workflow.md index 4946754..e863b80 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -28,3 +28,9 @@ Follow `BRANCHING.md`: ```bash bun run changelog:release ``` + +## Governance + +- Branch and PR governance checks run in `.gitea/workflows/ci.yml`. +- PR template: `.gitea/PULL_REQUEST_TEMPLATE.md` +- Versioning policy: `VERSIONING.md`