Refactor cycle update and delete tests #60
Workflow file for this run
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: PR Quality Check | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize] | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| contents: read | |
| jobs: | |
| quality-gate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| - name: Check PR quality signals | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const author = pr.user.login; | |
| // Don't check the maintainer | |
| if (author === 'Him-an-shi') return; | |
| const { data: files } = await github.rest.pulls.listFiles({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| }); | |
| const warnings = []; | |
| // 1. Flag: Very low line count (trivial PR) | |
| const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0); | |
| if (totalChanges < 5) { | |
| warnings.push(`Trivial change (${totalChanges} lines total). Consider if this warrants a standalone PR or could be bundled with related work.`); | |
| } | |
| // 2. Flag: Test-only PR with low assertion density | |
| const testFiles = files.filter(f => f.filename.includes('test')); | |
| if (testFiles.length > 0 && testFiles.length === files.length) { | |
| // PR only touches test files - check assertion quality | |
| for (const tf of testFiles) { | |
| try { | |
| const { data: content } = await github.rest.repos.getContent({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| path: tf.filename, | |
| ref: pr.head.sha, | |
| }); | |
| const decoded = Buffer.from(content.content, 'base64').toString(); | |
| const assertCount = (decoded.match(/assert |assertEqual|assertTrue|assertFalse|assertRaises|assertIn|pytest\.raises/g) || []).length; | |
| const testCount = (decoded.match(/def test_/g) || []).length; | |
| if (testCount > 0 && assertCount / testCount < 1.5) { | |
| warnings.push(`\`${tf.filename}\`: Low assertion density (${assertCount} asserts across ${testCount} tests). Each test should have multiple meaningful assertions.`); | |
| } | |
| } catch (e) { | |
| // File might be too large or binary - skip | |
| } | |
| } | |
| } | |
| // 3. Flag: PR opened within 10 minutes of its linked issue | |
| const body = pr.body || ''; | |
| const issueMatch = body.match(/#(\d+)/); | |
| if (issueMatch) { | |
| try { | |
| const { data: linkedIssue } = await github.rest.issues.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: parseInt(issueMatch[1]), | |
| }); | |
| const issueTime = new Date(linkedIssue.created_at).getTime(); | |
| const prTime = new Date(pr.created_at).getTime(); | |
| const diffMinutes = (prTime - issueTime) / (1000 * 60); | |
| if (diffMinutes >= 0 && diffMinutes < 10) { | |
| warnings.push(`PR opened ${Math.round(diffMinutes)} minutes after linked issue #${issueMatch[1]}. Unusually fast turnaround may indicate pre-written/automated submission.`); | |
| } | |
| } catch (e) { | |
| // Issue not found - skip | |
| } | |
| } | |
| if (warnings.length > 0) { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body: `**Quality check observations:**\n\n${warnings.map(w => '- ' + w).join('\n')}\n\n_These are informational signals, not blocking. Maintainer will review._` | |
| }); | |
| } |