mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
chore: require issue link on all PRs
- PR template: move "Closes #" to top as required field with explicit warning that PRs without a linked issue are closed without review - CONTRIBUTING.md: add mandatory issue-first policy with clear rationale - Add require-issue-link.yml workflow: checks PR body for a closing keyword (Closes/Fixes/Resolves #NNN) on open/edit/reopen/sync events; posts a comment and fails CI if no reference is found PR body is bound to an env var before shell use (injection-safe). The github-script step uses the API SDK, not shell interpolation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
@@ -1,3 +1,10 @@
|
||||
## Linked Issue
|
||||
|
||||
> **Required.** PRs without a linked issue are closed without review.
|
||||
> Open an issue first if one doesn't exist: https://github.com/gsd-build/get-shit-done/issues/new/choose
|
||||
|
||||
Closes #
|
||||
|
||||
## What
|
||||
|
||||
<!-- One sentence: what does this PR do? -->
|
||||
@@ -6,8 +13,6 @@
|
||||
|
||||
<!-- One sentence: why is this change needed? -->
|
||||
|
||||
Closes #<!-- issue number -->
|
||||
|
||||
## How
|
||||
|
||||
<!-- Brief description of the approach taken. Skip for trivial changes. -->
|
||||
@@ -35,6 +40,7 @@ Closes #<!-- issue number -->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Issue linked above (`Closes #NNN`) — **PR will be auto-closed if missing**
|
||||
- [ ] Follows GSD style (no enterprise patterns, no filler)
|
||||
- [ ] Updates CHANGELOG.md for user-facing changes
|
||||
- [ ] No unnecessary dependencies added
|
||||
|
||||
52
.github/workflows/require-issue-link.yml
vendored
Normal file
52
.github/workflows/require-issue-link.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Require Issue Link
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
check-issue-link:
|
||||
name: Issue link required
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR body for issue reference
|
||||
id: check
|
||||
env:
|
||||
# Bound to env var — never interpolated into shell directly
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
run: |
|
||||
if echo "$PR_BODY" | grep -qiE '(closes|fixes|resolves)\s+#[0-9]+'; then
|
||||
echo "found=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Comment and fail if no issue link
|
||||
if: steps.check.outputs.found == 'false'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
# Uses GitHub API SDK — no shell string interpolation of untrusted input
|
||||
script: |
|
||||
const repoUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}`;
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
body: [
|
||||
'## Missing issue link',
|
||||
'',
|
||||
'This PR does not reference an issue. **All PRs must link to an open issue** using a closing keyword in the PR body:',
|
||||
'',
|
||||
'```',
|
||||
'Closes #123',
|
||||
'```',
|
||||
'',
|
||||
`If no issue exists for this change, [open one first](${repoUrl}/issues/new/choose), then update this PR body with the reference.`,
|
||||
'',
|
||||
'This PR will remain blocked until a valid `Closes #NNN`, `Fixes #NNN`, or `Resolves #NNN` line is present in the description.',
|
||||
].join('\n')
|
||||
});
|
||||
core.setFailed('PR body must contain a closing issue reference (e.g. "Closes #123")');
|
||||
Reference in New Issue
Block a user