name: PR Title Check # Cancel previous runs for the same PR concurrency: group: pr-title-check-${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true on: pull_request: types: [opened, edited, synchronize, reopened, ready_for_review, labeled, unlabeled] # Default to least privilege. Override per-job where needed. permissions: contents: read jobs: check-pr-evidence: runs-on: ubuntu-latest permissions: contents: read steps: - name: Check out the repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: # Full history so the merge-base of the PR base and head resolves — the # evidence diff must reflect only THIS PR's changes, not whatever landed # on the base branch after the PR's branch point (#16125). fetch-depth: 0 - name: Validate PR evidence rows run: | jq -r '.pull_request.body // ""' "$GITHUB_EVENT_PATH" > "$RUNNER_TEMP/pr-body.md" PR_LABELS=$(jq -r '.pull_request.labels | map(.name) | join(",")' "$GITHUB_EVENT_PATH") BASE_SHA=$(jq -r '.pull_request.base.sha' "$GITHUB_EVENT_PATH") HEAD_SHA=$(jq -r '.pull_request.head.sha' "$GITHUB_EVENT_PATH") git fetch --no-tags origin "$BASE_SHA" "$HEAD_SHA" # Three-dot: diff the merge-base..head, i.e. only what THIS PR changed, # not files the base branch advanced past the branch point (#16125). git diff --name-only --diff-filter=ACMRT "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/pr-changed-files.txt" git diff --name-only --diff-filter=A "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/pr-added-files.txt" node scripts/check-pr-evidence.mjs \ --body-file "$RUNNER_TEMP/pr-body.md" \ --labels "$PR_LABELS" \ --changed-files-file "$RUNNER_TEMP/pr-changed-files.txt" \ --added-files-file "$RUNNER_TEMP/pr-added-files.txt" check-pr-title: runs-on: ubuntu-latest # The failure-path `gh pr comment` (GraphQL addComment) needs PR write; # the workflow-level default grants only contents: read, which made the # reporting step crash with "Resource not accessible by integration". permissions: contents: read pull-requests: write steps: - name: Check out the repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - name: Validate PR title id: validate run: | PR_TITLE=$(jq -r .pull_request.title "$GITHUB_EVENT_PATH") echo "PR Title: $PR_TITLE" if [[ ! "$PR_TITLE" =~ ^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|release)(\([a-zA-Z0-9-]+\))?:\ .+ ]]; then echo "PR title does not match the required pattern." exit 1 fi - name: Set status if: failure() env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh pr comment ${{ github.event.pull_request.number }} --body "❌ PR title does not match the required pattern. Please use one of these formats: - 'type: description' (e.g., 'feat: add new feature') - 'type(scope): description' (e.g., 'chore(core): update dependencies') Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, release"