Skip to content

Commit 972d88c

Browse files
committed
chore: require PRs to link an issue or discussion
- Add PR template with required linked issue/discussion section - Add check-linked-issue CI job that validates PR body contains a reference (#123, Closes/Fixes/Relates, GitHub URL, or CF-# ticket) - Wire into required-checks-passed gate so it blocks merge - Update CONTRIBUTING.md with the policy and motivation
1 parent 67cf123 commit 972d88c

3 files changed

Lines changed: 72 additions & 1 deletion

File tree

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
## Linked issue or discussion
2+
3+
<!-- Every PR must link to an issue or discussion — this ensures the approach has been discussed with maintainers before implementation begins, so your work fits the project's direction and doesn't need to be reworked. -->
4+
<!-- Replace the line below with one of: -->
5+
<!-- Closes #<number> -->
6+
<!-- Fixes #<number> -->
7+
<!-- Relates to #<number> -->
8+
<!-- Discussion: <url> -->
9+
10+
**Required:** <!-- CI will fail if no linked issue or discussion is found. -->
11+
12+
## What changed
13+
14+
<!-- Brief description of the changes. -->
15+
16+
## Test plan
17+
18+
<!-- How was this tested? Link to passing CI, new tests, or manual verification steps. -->

.github/workflows/ci.yaml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,57 @@ concurrency:
2222
cancel-in-progress: true
2323

2424
jobs:
25+
# ---------------------------------------------------------------------------
26+
# Linked issue check — every PR must reference an issue or discussion.
27+
# Skipped on push to main and workflow_dispatch.
28+
# ---------------------------------------------------------------------------
29+
check-linked-issue:
30+
if: github.event_name == 'pull_request'
31+
runs-on: ubuntu-latest
32+
permissions:
33+
pull-requests: read
34+
steps:
35+
- name: Check PR body for linked issue or discussion
36+
env:
37+
PR_BODY: ${{ github.event.pull_request.body }}
38+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
39+
run: |
40+
# Skip for bots (dependabot, renovate, github-actions)
41+
if [[ "$PR_AUTHOR" == *"[bot]"* || "$PR_AUTHOR" == "dependabot" ]]; then
42+
echo "Bot PR — skipping linked issue check."
43+
exit 0
44+
fi
45+
46+
if [ -z "$PR_BODY" ]; then
47+
echo "::error::PR body is empty. Every PR must link an issue or discussion."
48+
echo "Use 'Closes #<number>', 'Fixes #<number>', 'Relates to #<number>', or include a discussion URL."
49+
exit 1
50+
fi
51+
52+
# Match: #123, GH-123, org/repo#123, Closes/Fixes/Relates/Resolves #123,
53+
# or a github.com URL to an issue or discussion
54+
if echo "$PR_BODY" | grep -qiP '(close[sd]?|fix(e[sd])?|relate[sd]?\s+to|resolve[sd]?)\s+#\d+'; then
55+
echo "Found linked issue keyword."
56+
exit 0
57+
fi
58+
if echo "$PR_BODY" | grep -qP '#\d+'; then
59+
echo "Found issue reference."
60+
exit 0
61+
fi
62+
if echo "$PR_BODY" | grep -qiP 'github\.com/[^\s]+/(issues|discussions)/\d+'; then
63+
echo "Found GitHub issue/discussion URL."
64+
exit 0
65+
fi
66+
if echo "$PR_BODY" | grep -qiP 'CF-#?\d+'; then
67+
echo "Found Linear ticket reference."
68+
exit 0
69+
fi
70+
71+
echo "::error::No linked issue or discussion found in PR body."
72+
echo "Every PR must reference an issue or discussion. See CONTRIBUTING.md for details."
73+
echo "Use 'Closes #<number>', 'Fixes #<number>', 'Relates to #<number>', or include a discussion URL."
74+
exit 1
75+
2576
# ---------------------------------------------------------------------------
2677
# Change detection — decides which downstream jobs actually run.
2778
# On push/workflow_dispatch every flag is true so all jobs execute.
@@ -506,6 +557,7 @@ jobs:
506557
name: required checks passed
507558
if: always()
508559
needs:
560+
- check-linked-issue
509561
- unit-tests
510562
- type-check
511563
- prek

CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,11 @@ The full ruleset is in [`.claude/rules/code-style.md`](.claude/rules/code-style.
101101

102102
## Branches, commits, and pull requests
103103

104+
- **Every PR must link an issue or discussion.** Use `Closes #<number>`, `Fixes #<number>`, or `Relates to #<number>` in the PR body. CI will fail if no linked issue or discussion is found. For trivial fixes (typos, formatting), open a lightweight issue first — it only takes a moment and keeps the history traceable. The goal is to have a conversation before the code — discussing the approach on an issue or discussion helps maintainers point you in the right direction early, so your implementation fits the project's needs and you don't spend time on work that gets reworked.
104105
- Create a feature branch off an up-to-date `main`. Never commit directly to `main`.
105106
- Use conventional-commit prefixes: `fix:`, `feat:`, `refactor:`, `docs:`, `test:`, `chore:`. Keep commit messages concise (1-2 sentence body max).
106107
- Keep commits atomic - one logical change per commit.
107-
- PR titles also use the conventional format. The PR body should be short and link any related issues.
108+
- PR titles also use the conventional format. The PR body should be short and link the related issue.
108109
- If the change corresponds to a Linear ticket, include `CF-#<number>` in the PR body.
109110
- Run `uv run prek` (or `uv run prek run --from-ref origin/main`) before pushing. CI will block merge if hooks fail.
110111

0 commit comments

Comments
 (0)