diff --git a/.github/workflows/pr-standards.yml b/.github/workflows/pr-standards.yml index 1edbd5d061d..e844530c552 100644 --- a/.github/workflows/pr-standards.yml +++ b/.github/workflows/pr-standards.yml @@ -11,8 +11,17 @@ jobs: contents: read pull-requests: write steps: - - name: Check PR standards + - name: Validate PR Title + id: title-check + continue-on-error: true + uses: andreiships/shared-ai-standards/.github/actions/pr-validation@4a7766058a3981a4cd54b440c594aae1d8121c77 # main + with: + title-types: feat,fix,docs,chore,refactor,test + + - name: Manage PR labels and check linked issue uses: actions/github-script@v7 + env: + TITLE_OUTCOME: ${{ steps.title-check.outcome }} with: script: | const pr = context.payload.pull_request; @@ -41,6 +50,10 @@ jobs: } const title = pr.title; + // 'success' = title valid; 'failure' = title invalid OR infra error (network/action fetch); + // 'skipped'/'cancelled' = step didn't run — treat as unknown (pass to avoid false positives) + const titleOutcome = process.env.TITLE_OUTCOME; + const titlePassed = titleOutcome === 'success' || titleOutcome === 'skipped' || titleOutcome === 'cancelled'; async function addLabel(label) { await github.rest.issues.addLabels({ @@ -71,10 +84,10 @@ jobs: repo: context.repo.repo, issue_number: pr.number }); - - const existing = comments.find(c => c.body.includes(markerText)); + + 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, @@ -83,12 +96,8 @@ jobs: }); } - // 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) { + // Title validation (shared action handles regex, we manage labels/comments) + if (!titlePassed) { await addLabel('needs:title'); await comment('title', `Hey! Your PR title \`${title}\` doesn't follow conventional commit format. @@ -103,16 +112,17 @@ jobs: Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`). See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`); + core.setFailed('PR title does not follow conventional commit format'); 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); + // Check for linked issue (skip for docs/refactor PRs) + const skipIssueCheck = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title); if (skipIssueCheck) { await removeLabel('needs:issue'); - console.log('Skipping issue check for docs/refactor/feat PR'); + console.log('Skipping issue check for docs/refactor PR'); return; } const query = ` @@ -146,6 +156,7 @@ jobs: 2. Add \`Fixes #\` or \`Closes #\` to this PR description See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`); + core.setFailed('PR must have a linked issue'); return; }