From 66e027f9285543023d9e3ac33d87b2f0cf4cdf7b Mon Sep 17 00:00:00 2001 From: shaeck Date: Thu, 9 Jul 2026 14:47:09 +0200 Subject: [PATCH] Copilot autoapprover toevoegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neemt de opzet uit AFASSoftware/focus over: een workflow keurt een PR automatisch goed wanneer de Copilot-review "review nodig: nee" aangeeft. - .github/workflows/copilot-autoapprove.yml โ€” auto-approve via AFASACTION app, met anti prompt-injection guard (auteur moet schrijfrechten hebben). Geen limiet op aantal gewijzigde bestanden. - .github/instructions/codereview.instructions.md โ€” review-richtlijnen met de parseerbare markers (review nodig, severity- en PR-labels). - .github/CODEOWNERS โ€” alle wijzigingen naar @AFASSoftware/development-experience. Co-Authored-By: Claude Opus 4.8 --- .github/CODEOWNERS | 2 + .../instructions/codereview.instructions.md | 66 +++++++++ .github/workflows/copilot-autoapprove.yml | 135 ++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/instructions/codereview.instructions.md create mode 100644 .github/workflows/copilot-autoapprove.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..f8f7a54f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Alle wijzigingen worden gereviewd door development-experience +* @AFASSoftware/development-experience diff --git a/.github/instructions/codereview.instructions.md b/.github/instructions/codereview.instructions.md new file mode 100644 index 00000000..cafd2c70 --- /dev/null +++ b/.github/instructions/codereview.instructions.md @@ -0,0 +1,66 @@ +--- +description: "Instructions for copilot on github.com" +applyTo: "**" +excludeAgent: "coding-agents" +--- + +## Reviewing a PR + +- Write the summary in **Dutch** +- Be constructive and specific โ€” state **what** is the problem and **why** it is a problem. +- Use your existing labeling system, so high, medium, low and nit. +- Give for `medium` and higher always a suggestion for improvement or fix. + +### Review comment labels + +Give a review comment one of the following labels: + +- ๐Ÿ”ด high (`severity: high`) +- ๐ŸŸ  medium (`severity: medium`) +- ๐ŸŸก low (`severity: low`) +- ๐ŸŸข nit (`severity: nitpick`) + +### Summary and review + +Make sure that the review summary **always** contains exactly one of the following lines as line, just above 'Changes:' or just below the title, for example 'PR Overview', of the review. It is very important that you do it this way, since everything above the title is ignored and everything below Changes: as well. The line should be precisely this, including double stars and spaces: + +``` +**review nodig: ja** +``` + +or + +``` +**review nodig: nee** +``` + +#### When `review nodig: ja` + +- There is at least one **high** or **medium** comment +- There are more than 4 **low** comments +- The changes are too complex or sizeable to be able to give a trustworthy automatic review +- You doubt that the review you did covered everything + +#### When `review nodig: nee` + +- All comments are **low** or **nitpicks** (maximum 4 lows) +- The changes are trivial and safe +- You are confident that your review covered everything + +**Important:** when in doubt, always choose `review nodig: ja`. + +### PR labels + +Add one or more of the following labels to the PR (in the summary, as a line below `review nodig`): + +``` +**labels: label1, label2** +``` + +Available labels: + +- `๐Ÿ“š leerzaam` โ€” PR demonstrates a pattern, technique, or solution other contributors can learn from. When applying this label, briefly explain in the summary **why** the PR is educational (e.g., which pattern or technique is noteworthy). +- `๐Ÿงน refactor` โ€” Primarily restructuring without content changes +- `๐Ÿ’ฅ breaking` โ€” Contains breaking changes to OpenAPI specs or menu structures that affect published documentation +- `๐Ÿ—๏ธ infra` โ€” Build, CI/CD, or tooling changes (scripts, workflows) +- `๐Ÿ“ large` โ€” Very large PR; significantly more files or lines changed than typical diff --git a/.github/workflows/copilot-autoapprove.yml b/.github/workflows/copilot-autoapprove.yml new file mode 100644 index 00000000..a4b8776e --- /dev/null +++ b/.github/workflows/copilot-autoapprove.yml @@ -0,0 +1,135 @@ +name: Auto-approve PR na Copilot review + +# Keurt de PR automatisch goed wanneer Copilot een inline comment plaatst +# met "โœ… PR automatisch goedgekeurd" (dit gebeurt alleen bij "review nodig: nee"). +# +# Vereisten: +# 1. Copilot als automatische reviewer inschakelen in repo-instellingen +# (Settings > Copilot > Code review > Automatic) +# 2. .github/instructions/codereview.instructions.md bevat de review-richtlijnen +# 3. De GitHub App (AFASACTION) heeft pull-request write-rechten + +on: + pull_request_review: + types: [submitted, edited] + +jobs: + auto-approve: + name: Auto-approve if Copilot review is clean + runs-on: ubuntu-latest + # Alleen reageren op een review van een bot-account waarvan de login met + # "copilot" begint. `type == 'Bot'` sluit menselijke accounts volledig uit + # (een mens kan nooit type Bot zijn), dus dit is een veel strengere gate dan + # een substring-match op de login. + if: > + github.event.review.user.type == 'Bot' && + startsWith(github.event.review.user.login, 'copilot') + permissions: + contents: read + pull-requests: write + issues: write + + steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v3.1.1 + with: + app-id: ${{ vars.AFASACTION_APPID }} + private-key: ${{ secrets.AFASACTION_PKEY }} + + - name: Auto-approve PR + uses: actions/github-script@v9 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const pull_number = context.payload.pull_request.number; + const reviewBody = context.payload.review.body || ''; + + // === Parse labels uit de review body (altijd) === + const labels = []; + const labelsMatch = reviewBody.match(/\*\*labels:\s*(.+?)\*\*/i); + if (labelsMatch) { + const parsed = labelsMatch[1].split(',').map(l => l.trim()).filter(Boolean); + labels.push(...parsed.map(l => `review - ${l}`)); + } + + // === Auto-approve logica (alleen bij "review nodig: nee") === + const isAutoApproved = /\*\*review nodig:\s*nee\*\*/i.test(reviewBody); + + if (isAutoApproved) { + const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number }); + const prCreator = pr.user.login.toLowerCase(); + if (prCreator.includes('copilot')) { + core.info(`PR is aangemaakt door ${prCreator} โ€” overslaan`); + return; + } + + // === Auteur moet schrijfrechten hebben (anti prompt-injection) === + // De auto-approve wordt gestuurd door vrije tekst in de review-body. + // Een externe bijdrager zou de Copilot-reviewer via de diff kunnen + // prompt-injecten om "review nodig: nee" te laten schrijven. Daarom + // keuren we alleen PR's goed van auteurs die zelf al schrijfrechten + // op de repo hebben. + const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner, repo, username: pr.user.login, + }); + if (!['write', 'maintain', 'admin'].includes(perm.permission)) { + core.info(`PR-auteur ${pr.user.login} heeft onvoldoende rechten (${perm.permission}) โ€” overslaan`); + return; + } + + // Check of al goedgekeurd + const { data: reviews } = await github.rest.pulls.listReviews({ owner, repo, pull_number }); + const alreadyApproved = reviews.some(r => + r.state === 'APPROVED' && r.body?.includes('Automatisch goedgekeurd') + ); + if (alreadyApproved) { + core.info('PR is al automatisch goedgekeurd โ€” overslaan'); + return; + } + + // === Approve! + await github.rest.pulls.createReview({ + owner, repo, pull_number, + event: 'APPROVE', + body: [ + '### โœ… Automatisch goedgekeurd', + '', + 'Deze PR is automatisch goedgekeurd omdat Copilot heeft aangegeven:', + '`review nodig: nee`', + ].join('\n'), + }); + + core.info('PR automatisch goedgekeurd'); + labels.push('review - AI-Approved'); + } + + // === Labels toevoegen === + if (labels.length === 0) { + core.info('Geen labels om toe te voegen'); + return; + } + + // Zorg dat labels bestaan + for (const label of labels) { + try { + await github.rest.issues.getLabel({ owner, repo, name: label }); + } catch (e) { + if (e.status === 404) { + await github.rest.issues.createLabel({ owner, repo, name: label, color: '6f42c1' }); + core.info(`Label "${label}" aangemaakt`); + } + } + } + + try { + await github.rest.issues.addLabels({ + owner, repo, issue_number: pull_number, + labels, + }); + core.info(`Labels toegevoegd: ${labels.join(', ')}`); + } catch (labelError) { + core.warning(`Kon labels niet toevoegen: ${labelError.message}`); + }