Buckets:
| name: pr-standards | |
| on: | |
| pull_request_target: | |
| types: [opened, edited, synchronize] | |
| jobs: | |
| check-standards: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Check PR standards | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const login = pr.user.login; | |
| // Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC) | |
| const cutoff = new Date('2026-02-19T00:00:00Z'); | |
| const prCreated = new Date(pr.created_at); | |
| if (prCreated < cutoff) { | |
| console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`); | |
| return; | |
| } | |
| // Check if author is a team member or bot | |
| if (login === 'opencode-agent[bot]') return; | |
| const { data: file } = await github.rest.repos.getContent({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| path: '.github/TEAM_MEMBERS', | |
| ref: 'dev' | |
| }); | |
| const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean); | |
| if (members.includes(login)) { | |
| console.log(`Skipping: ${login} is a team member`); | |
| return; | |
| } | |
| const title = pr.title; | |
| async function addLabel(label) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels: [label] | |
| }); | |
| } | |
| async function removeLabel(label) { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| name: label | |
| }); | |
| } catch (e) { | |
| // Label wasn't present, ignore | |
| } | |
| } | |
| async function comment(marker, body) { | |
| const markerText = `<!-- pr-standards:${marker} -->`; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number | |
| }); | |
| const existing = comments.find(c => c.body.includes(markerText)); | |
| if (existing) return; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body: markerText + '\n' + body | |
| }); | |
| } | |
| // Step 1: Check title format | |
| // Matches: feat:, feat(scope):, feat (scope):, etc. | |
| const titlePattern = /^(feat|fix|docs|chore|refactor|test)\s*(\([a-zA-Z0-9-]+\))?\s*:/; | |
| const hasValidTitle = titlePattern.test(title); | |
| if (!hasValidTitle) { | |
| await addLabel('needs:title'); | |
| await comment('title', `Hey! Your PR title \`${title}\` doesn't follow conventional commit format. | |
| Please update it to start with one of: | |
| - \`feat:\` or \`feat(scope):\` new feature | |
| - \`fix:\` or \`fix(scope):\` bug fix | |
| - \`docs:\` or \`docs(scope):\` documentation changes | |
| - \`chore:\` or \`chore(scope):\` maintenance tasks | |
| - \`refactor:\` or \`refactor(scope):\` code refactoring | |
| - \`test:\` or \`test(scope):\` adding or updating tests | |
| Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`). | |
| See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`); | |
| return; | |
| } | |
| await removeLabel('needs:title'); | |
| // Step 2: Check for linked issue (skip for docs/refactor/feat PRs) | |
| const skipIssueCheck = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title); | |
| if (skipIssueCheck) { | |
| await removeLabel('needs:issue'); | |
| console.log('Skipping issue check for docs/refactor/feat PR'); | |
| return; | |
| } | |
| const query = ` | |
| query($owner: String!, $repo: String!, $number: Int!) { | |
| repository(owner: $owner, name: $repo) { | |
| pullRequest(number: $number) { | |
| closingIssuesReferences(first: 1) { | |
| totalCount | |
| } | |
| } | |
| } | |
| } | |
| `; | |
| const result = await github.graphql(query, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| number: pr.number | |
| }); | |
| const linkedIssues = result.repository.pullRequest.closingIssuesReferences.totalCount; | |
| if (linkedIssues === 0) { | |
| await addLabel('needs:issue'); | |
| await comment('issue', `Thanks for your contribution! | |
| This PR doesn't have a linked issue. All PRs must reference an existing issue. | |
| Please: | |
| 1. Open an issue describing the bug/feature (if one doesn't exist) | |
| 2. Add \`Fixes #<number>\` or \`Closes #<number>\` to this PR description | |
| See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`); | |
| return; | |
| } | |
| await removeLabel('needs:issue'); | |
| console.log('PR meets all standards'); | |
| check-compliance: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Check PR template compliance | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const login = pr.user.login; | |
| // Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC) | |
| const cutoff = new Date('2026-02-19T00:00:00Z'); | |
| const prCreated = new Date(pr.created_at); | |
| if (prCreated < cutoff) { | |
| console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`); | |
| return; | |
| } | |
| // Check if author is a team member or bot | |
| if (login === 'opencode-agent[bot]') return; | |
| const { data: file } = await github.rest.repos.getContent({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| path: '.github/TEAM_MEMBERS', | |
| ref: 'dev' | |
| }); | |
| const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean); | |
| if (members.includes(login)) { | |
| console.log(`Skipping: ${login} is a team member`); | |
| return; | |
| } | |
| const body = pr.body || ''; | |
| const title = pr.title; | |
| const isDocsRefactorOrFeat = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title); | |
| const issues = []; | |
| // Check: template sections exist | |
| const hasWhatSection = /### What does this PR do\?/.test(body); | |
| const hasTypeSection = /### Type of change/.test(body); | |
| const hasVerifySection = /### How did you verify your code works\?/.test(body); | |
| const hasChecklistSection = /### Checklist/.test(body); | |
| const hasIssueSection = /### Issue for this PR/.test(body); | |
| if (!hasWhatSection || !hasTypeSection || !hasVerifySection || !hasChecklistSection || !hasIssueSection) { | |
| issues.push('PR description is missing required template sections. Please use the [PR template](../blob/dev/.github/pull_request_template.md).'); | |
| } | |
| // Check: "What does this PR do?" has real content (not just placeholder text) | |
| if (hasWhatSection) { | |
| const whatMatch = body.match(/### What does this PR do\?\s*\n([\s\S]*?)(?=###|$)/); | |
| const whatContent = whatMatch ? whatMatch[1].trim() : ''; | |
| const placeholder = 'Please provide a description of the issue'; | |
| const onlyPlaceholder = whatContent.includes(placeholder) && whatContent.replace(placeholder, '').replace(/[*\s]/g, '').length < 20; | |
| if (!whatContent || onlyPlaceholder) { | |
| issues.push('"What does this PR do?" section is empty or only contains placeholder text. Please describe your changes.'); | |
| } | |
| } | |
| // Check: at least one "Type of change" checkbox is checked | |
| if (hasTypeSection) { | |
| const typeMatch = body.match(/### Type of change\s*\n([\s\S]*?)(?=###|$)/); | |
| const typeContent = typeMatch ? typeMatch[1] : ''; | |
| const hasCheckedBox = /- \[x\]/i.test(typeContent); | |
| if (!hasCheckedBox) { | |
| issues.push('No "Type of change" checkbox is checked. Please select at least one.'); | |
| } | |
| } | |
| // Check: issue reference (skip for docs/refactor/feat) | |
| if (!isDocsRefactorOrFeat && hasIssueSection) { | |
| const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/); | |
| const issueContent = issueMatch ? issueMatch[1].trim() : ''; | |
| const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent); | |
| if (!hasIssueRef) { | |
| issues.push('No issue referenced. Please add `Closes #<number>` linking to the relevant issue.'); | |
| } | |
| } | |
| // Check: "How did you verify" has content | |
| if (hasVerifySection) { | |
| const verifyMatch = body.match(/### How did you verify your code works\?\s*\n([\s\S]*?)(?=###|$)/); | |
| const verifyContent = verifyMatch ? verifyMatch[1].trim() : ''; | |
| if (!verifyContent) { | |
| issues.push('"How did you verify your code works?" section is empty. Please explain how you tested.'); | |
| } | |
| } | |
| // Check: checklist boxes are checked | |
| if (hasChecklistSection) { | |
| const checklistMatch = body.match(/### Checklist\s*\n([\s\S]*?)(?=###|$)/); | |
| const checklistContent = checklistMatch ? checklistMatch[1] : ''; | |
| const unchecked = (checklistContent.match(/- \[ \]/g) || []).length; | |
| const checked = (checklistContent.match(/- \[x\]/gi) || []).length; | |
| if (checked < 2) { | |
| issues.push('Not all checklist items are checked. Please confirm you have tested locally and have not included unrelated changes.'); | |
| } | |
| } | |
| // Helper functions | |
| async function addLabel(label) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels: [label] | |
| }); | |
| } | |
| async function removeLabel(label) { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| name: label | |
| }); | |
| } catch (e) {} | |
| } | |
| const hasComplianceLabel = pr.labels.some(l => l.name === 'needs:compliance'); | |
| if (issues.length > 0) { | |
| // Non-compliant | |
| if (!hasComplianceLabel) { | |
| await addLabel('needs:compliance'); | |
| } | |
| const marker = '<!-- issue-compliance -->'; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number | |
| }); | |
| const existing = comments.find(c => c.body.includes(marker)); | |
| const body_text = `${marker} | |
| This PR doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) and [PR template](../blob/dev/.github/pull_request_template.md). | |
| **What needs to be fixed:** | |
| ${issues.map(i => `- ${i}`).join('\n')} | |
| Please edit this PR description to address the above within **2 hours**, or it will be automatically closed. | |
| If you believe this was flagged incorrectly, please let a maintainer know.`; | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body: body_text | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body: body_text | |
| }); | |
| } | |
| console.log(`PR #${pr.number} is non-compliant: ${issues.join(', ')}`); | |
| } else if (hasComplianceLabel) { | |
| // Was non-compliant, now fixed | |
| await removeLabel('needs:compliance'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number | |
| }); | |
| const marker = '<!-- issue-compliance -->'; | |
| const existing = comments.find(c => c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.deleteComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id | |
| }); | |
| } | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body: 'Thanks for updating your PR! It now meets our contributing guidelines. :+1:' | |
| }); | |
| console.log(`PR #${pr.number} is now compliant, label removed`); | |
| } else { | |
| console.log(`PR #${pr.number} is compliant`); | |
| } | |
Xet Storage Details
- Size:
- 14.7 kB
- Xet hash:
- 176f9f094d302e8a2a384c6300e36f88711aac263ea44ef6a7fd6e9807732ea4
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.