feat: add RunContract quality dimensions #38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: kapi-agent formal approval gate | |
| on: | |
| pull_request: | |
| types: [opened, reopened, synchronize, ready_for_review, converted_to_draft] | |
| pull_request_review: | |
| types: [submitted, edited, dismissed] | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| issues: read | |
| checks: read | |
| statuses: read | |
| jobs: | |
| require-formal-kapi-agent-approval: | |
| name: require formal kapi-agent approval | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Require formal current-head kapi-agent approval | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const reviewer = 'kapi-agent'; | |
| const requiredCheck = 'kapi-agent/review'; | |
| const pr = context.payload.pull_request; | |
| if (!pr) core.setFailed('This workflow must run on pull_request or pull_request_review events.'); | |
| const { owner, repo } = context.repo; | |
| const pull = (await github.rest.pulls.get({ owner, repo, pull_number: pr.number })).data; | |
| const query = ` | |
| query($owner: String!, $repo: String!, $number: Int!) { | |
| repository(owner: $owner, name: $repo) { | |
| pullRequest(number: $number) { | |
| comments(last: 100) { nodes { body } } | |
| reviews(last: 100) { | |
| nodes { | |
| author { login } | |
| state | |
| submittedAt | |
| commit { oid } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `; | |
| const data = await github.graphql(query, { owner, repo, number: pr.number }); | |
| const prNode = data.repository.pullRequest; | |
| const reviews = prNode.reviews.nodes || []; | |
| const comments = prNode.comments.nodes || []; | |
| const checkRuns = (await github.rest.checks.listForRef({ owner, repo, ref: pull.head.sha, per_page: 100 })).data.check_runs; | |
| const statuses = (await github.rest.repos.getCombinedStatusForRef({ owner, repo, ref: pull.head.sha })).data.statuses; | |
| const latestReview = reviews | |
| .filter((review) => review.author?.login === reviewer) | |
| .sort((a, b) => String(a.submittedAt || '').localeCompare(String(b.submittedAt || ''))) | |
| .at(-1); | |
| const checkRun = checkRuns | |
| .filter((check) => check.name === requiredCheck) | |
| .sort((a, b) => String(a.completed_at || a.started_at || '').localeCompare(String(b.completed_at || b.started_at || '')) || Number(a.id) - Number(b.id)) | |
| .at(-1); | |
| const status = statuses | |
| .filter((item) => item.context === requiredCheck) | |
| .sort((a, b) => String(a.updated_at || a.created_at || '').localeCompare(String(b.updated_at || b.created_at || '')) || Number(a.id) - Number(b.id)) | |
| .at(-1); | |
| const checkState = checkRun?.conclusion || checkRun?.status || status?.state; | |
| const approvalCommentCount = comments.filter((comment) => /^## kapi-agent review\s*\n\s*\n\*\*Verdict:\*\*\s*APPROVE(?:\s*\n|\s*$)/i.test(String(comment.body || '').trimStart())).length; | |
| const diagnostics = []; | |
| if (pull.draft) diagnostics.push('PR is draft'); | |
| if (!latestReview) diagnostics.push(`missing formal Pull Request Review by ${reviewer}`); | |
| if (latestReview && latestReview.state !== 'APPROVED') diagnostics.push(`latest formal ${reviewer} review is ${latestReview.state}`); | |
| if (latestReview && latestReview.commit?.oid !== pull.head.sha) diagnostics.push(`latest formal ${reviewer} review is not for current head ${pull.head.sha}`); | |
| if (!checkState) diagnostics.push(`missing ${requiredCheck} check/status on current head`); | |
| if (checkState && checkState !== 'success') diagnostics.push(`${requiredCheck} is ${checkState}`); | |
| const summaryItems = diagnostics.length ? [...diagnostics] : ['pass']; | |
| if (approvalCommentCount) { | |
| summaryItems.push(`ignored ${approvalCommentCount} approval-shaped comment(s); comments are not PR reviews`); | |
| } | |
| await core.summary | |
| .addHeading('kapi-agent formal approval gate') | |
| .addRaw(`PR: #${pr.number}\n`) | |
| .addRaw(`Head: ${pull.head.sha}\n`) | |
| .addRaw(`Latest formal review: ${latestReview ? `${latestReview.author?.login} / ${latestReview.state} / ${latestReview.commit?.oid || 'no-commit'}` : 'missing'}\n`) | |
| .addRaw(`Required check: ${checkState || 'missing'}\n`) | |
| .addList(summaryItems) | |
| .write(); | |
| if (diagnostics.length) core.setFailed(diagnostics.join('; ')); |