From 89c7e927340eae126e643880599b1f67bf86726b Mon Sep 17 00:00:00 2001 From: Tim Hsiung Date: Thu, 7 May 2026 16:51:54 +0800 Subject: [PATCH] ci: add workflow to validate PR description sections Checks that PR descriptions include the required sections from the template: Description, Checklist, Expected Behavior, and Steps to Test. - Triggers on opened, edited, reopened, and ready_for_review - Skips draft PRs and bot accounts (dependabot, renovate) - Uses line-anchored regex to match section headers - Additional Context section is intentionally optional Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/validate_pr.yml | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/validate_pr.yml diff --git a/.github/workflows/validate_pr.yml b/.github/workflows/validate_pr.yml new file mode 100644 index 000000000..93f1aff32 --- /dev/null +++ b/.github/workflows/validate_pr.yml @@ -0,0 +1,41 @@ +name: "Validate PR Description" + +on: + pull_request: + types: [opened, edited, reopened, ready_for_review] + +jobs: + validate-pr: + if: >- + !github.event.pull_request.draft && + github.actor != 'dependabot[bot]' && + github.actor != 'renovate[bot]' + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - name: Validate PR description sections + uses: actions/github-script@v9 + with: + script: | + const body = context.payload.pull_request.body || ""; + + const required = [ + { header: /^##\s+Description\s*$/m, name: "Description" }, + { header: /^##\s+Checklist\s*$/m, name: "Checklist" }, + { header: /^##\s+Expected Behavior\s*$/m, name: "Expected Behavior" }, + { header: /^##\s+Steps to Test This Pull Request\s*$/m, name: "Steps to Test This Pull Request" }, + ]; + + const missing = required + .filter(s => !s.header.test(body)) + .map(s => s.name); + + if (missing.length > 0) { + core.setFailed( + `PR description is missing required sections: ${missing.join(", ")}.\n` + + `Please use the PR template from .github/pull_request_template.md.` + ); + } else { + core.info("All required PR description sections are present."); + }