feat(ci): gate pull requests on an accepted issue (#6894)

This commit is contained in:
Kartik
2026-08-14 17:05:27 +05:30
committed by GitHub
parent ef6f51d977
commit 290de24bb8
35 changed files with 2029 additions and 967 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
}
base_version=$(git show "$BASE_SHA:pyproject.toml" 2>/dev/null | extract_version || echo "")
head_version=$(extract_version < pyproject.toml)
head_version=$(git show "$HEAD_SHA:pyproject.toml" | extract_version)
echo "Base version: ${base_version:-<unknown>}"
echo "Head version: $head_version"
+103
View File
@@ -0,0 +1,103 @@
name: PR Gate
on:
pull_request_target:
types: [opened, reopened, edited, ready_for_review]
concurrency:
group: pr-gate-${{ github.event.pull_request.number }}
cancel-in-progress: true
env:
GATE_EFFECTIVE_FROM: '2026-08-12T00:00:00Z'
permissions:
contents: read
pull-requests: write
issues: read
jobs:
gate:
if: >-
github.event.pull_request.draft == false &&
github.event.pull_request.user.type != 'Bot' &&
!contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const { owner, repo } = context.repo;
const effectiveFrom = process.env.GATE_EFFECTIVE_FROM;
if (effectiveFrom && Date.parse(pr.created_at) < Date.parse(effectiveFrom)) {
core.info(`Opened ${pr.created_at}, before the gate took effect ${effectiveFrom}. Skipped.`);
return;
}
const { data: current } = await github.rest.pulls.get({
owner, repo, pull_number: pr.number,
});
if (current.state !== 'open') {
core.info(`#${pr.number} is already ${current.state}. Skipped.`);
return;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number: pr.number, per_page: 100,
});
if (files.length > 0 && files.every((file) => file.filename.startsWith('docs/'))) {
core.info('Docs-only PR, gate skipped');
return;
}
const { repository } = await github.graphql(
`query ($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
closingIssuesReferences(first: 20) {
nodes { number labels(first: 50) { nodes { name } } }
}
}
}
}`,
{ owner, repo, number: pr.number },
);
const accepted = repository.pullRequest.closingIssuesReferences.nodes
.filter((issue) => issue.labels.nodes.some((label) => label.name === 'accepted'))
.map((issue) => issue.number);
if (accepted.length > 0) {
core.info(`Accepted issue linked: #${accepted.join(', #')}`);
return;
}
const body = [
'Thanks for taking the time to open this.',
'',
'We only review pull requests that fix an issue we have already agreed to take on, so this one is closed for now.',
'**Closed does not mean rejected.** It means it is not in the queue yet, and reopening takes about a minute.',
'',
'To get it reviewed:',
'',
'1. Make sure an issue describes the problem, with the version you are on, a runnable reproduction, and the real output or traceback you saw.',
'2. Link it from this pull request description with `Closes #<number>`.',
'3. Ask a maintainer to label that issue `accepted`.',
'4. Reopen this pull request. The check runs again and it stays open.',
'',
'Already linked an accepted issue? Edit the description to include `Closes #<number>` and reopen. The check reruns automatically.',
'',
'Documentation-only changes skip this gate entirely.',
'',
'See [CONTRIBUTING.md](https://github.com/mem0ai/mem0/blob/main/CONTRIBUTING.md) for the full policy.',
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number, body,
});
await github.rest.pulls.update({
owner, repo, pull_number: pr.number, state: 'closed',
});
core.info(`Closed #${pr.number}: no accepted issue linked`);
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
set -euo pipefail
base_version=$(git show "$BASE_SHA:mem0-ts/package.json" 2>/dev/null | jq -r .version || echo "")
head_version=$(jq -r .version mem0-ts/package.json)
head_version=$(git show "$HEAD_SHA:mem0-ts/package.json" | jq -r .version)
echo "Base version: ${base_version:-<unknown>}"
echo "Head version: $head_version"
+27
View File
@@ -0,0 +1,27 @@
name: Vouch - Check PR
on:
pull_request_target:
types: [opened, reopened]
concurrency:
group: vouch-check-pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
jobs:
check:
if: >-
github.event.pull_request.user.type != 'Bot' &&
!contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
runs-on: ubuntu-latest
steps:
- uses: mitchellh/vouch/action/check-pr@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
with:
pr-number: ${{ github.event.pull_request.number }}
auto-close: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,42 @@
name: Vouch - Manage by Issue
on:
issue_comment:
types: [created]
concurrency:
group: vouch-manage
cancel-in-progress: false
permissions:
contents: write
issues: write
pull-requests: write
jobs:
manage:
if: contains(github.event.comment.body, '!vouch') || contains(github.event.comment.body, '!denounce') || contains(github.event.comment.body, '!unvouch')
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ secrets.VOUCH_APP_ID }}
private-key: ${{ secrets.VOUCH_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- uses: mitchellh/vouch/action/manage-by-issue@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
with:
repo: ${{ github.repository }}
issue-id: ${{ github.event.issue.number }}
comment-id: ${{ github.event.comment.id }}
vouch-keyword: "!vouch"
denounce-keyword: "!denounce"
unvouch-keyword: "!unvouch"
pull-request: "true"
merge-immediately: "true"
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}