1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# The squash gate. protect-trunk sets squash_merge_commit_title to
# PR_TITLE and squash_merge_commit_message to PR_BODY, so the request's
# title becomes the trunk commit's subject and its description the body,
# and the bot derives the version and the changelog from that message;
# this check holds the title to a scoped Conventional Commit and the body
# to the content guards before the merge is offered. The trunk ruleset requires it by the job name pr-title, beside
# the project's own check.
#
# pull_request_target runs the trunk's own copy of this file, so a request
# cannot edit the check it must pass; nothing here checks out or executes
# the request's code, and the title reaches the shell through the
# environment, never through template interpolation.
name: pr-title
on:
pull_request_target:
types:
permissions:
jobs:
pr-title:
name: pr-title
runs-on: ubuntu-latest
steps:
- name: the title is a scoped Conventional Commit
env:
TITLE: ${{ github.event.pull_request.title }}
# The second alternative is the bot's own release request, whose
# title the release automation authors, not an operator.
run: |
printf '%s' "$TITLE" | grep -Eq \
'^((build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)\((RK_SCOPES_PIPE)\)!?: .+|chore(\((release|master|main)\))?: (release|v).+)$' \
&& exit 0
echo "the title '$TITLE' must read type(scope) then a colon and a description, with the scope one of RK_SCOPES_CSV"
exit 1
- name: the body carries no attribution and no internal path
env:
TITLE: ${{ github.event.pull_request.title }}
BODY: ${{ github.event.pull_request.body }}
# The bot's release request is exempt whole: release-plz authors
# its body, generated-with line and bot co-author included, and
# the exemption is exactly the title check's bot alternative. No
# checkout happens here, so the gate cannot consult check-ignore;
# it greps the fixed guard list, duplicated from
# blocks/message-guards and held equal by a test in the rk
# repository, and the landed rk-message hook stays the stronger,
# ignore-aware check.
run: |
printf '%s' "$TITLE" | grep -Eq \
'^chore(\((release|master|main)\))?: (release|v).+$' \
&& exit 0
fail=0
while IFS='|' read -r class pattern; do
if printf '%s' "$BODY" | grep -Eq "$pattern"; then
echo "the body carries a $class finding: a line matches $pattern"
fail=1
fi
done <<'GUARDS'
attribution|[Gg]enerated with \[?Claude
attribution|🤖 Generated with
attribution|[Cc]o-[Aa]uthored-[Bb]y:.*([Cc]laude|[Cc]opilot|Codex|ChatGPT)
attribution|noreply@anthropic\.com
internal-path|(^|[^A-Za-z0-9])\.draft/
GUARDS
exit "$fail"