From fec0d6c5f01d1b7e0d861eb5e710c7b330393cac Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Mon, 10 Aug 2026 16:46:43 -0400 Subject: [PATCH] fix(policy): accept AP Jira keys in PR titles --- .github/workflows/callable-pr-policy.yaml | 10 +++++----- test/pr-policy.test.mjs | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/callable-pr-policy.yaml b/.github/workflows/callable-pr-policy.yaml index 28824d8..1578840 100644 --- a/.github/workflows/callable-pr-policy.yaml +++ b/.github/workflows/callable-pr-policy.yaml @@ -84,7 +84,7 @@ jobs: function validateTitle(title, isDependabot, jiraMaybeExempt) { const errs = []; if (title.length > 120) errs.push('Title is ' + title.length + ' chars — max 120'); - const m = title.match(/^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|release)(\([^)]+\))?(!)?: (.+?)(\s+\((DEV|PLAT|SEC)-\d+\))?$/); + const m = title.match(/^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|release)(\([^)]+\))?(!)?: (.+?)(\s+\((DEV|PLAT|SEC|AP)-\d+\))?$/); if (!m) { errs.push('Title must match: type(scope): description (KEY-NNN). Allowed types: ' + CONV_TYPES.join(' ')); return errs; @@ -94,13 +94,13 @@ jobs: if (desc.endsWith('.')) errs.push('Description must not end with a period'); if (!/^[a-z]/.test(desc)) errs.push('Description must start with a lowercase letter'); if (!isDependabot && !jiraSuffix && !jiraMaybeExempt) { - errs.push('Missing Jira key — expected (DEV-NNN), (PLAT-NNN), or (SEC-NNN) at end of title'); + errs.push('Missing Jira key — expected (DEV-NNN), (PLAT-NNN), (SEC-NNN), or (AP-NNN) at end of title'); } return errs; } function getJiraKey(title) { - const m = title.match(/\((DEV|PLAT|SEC)-(\d+)\)$/); + const m = title.match(/\((DEV|PLAT|SEC|AP)-(\d+)\)$/); return m ? m[1] + '-' + m[2] : null; } @@ -113,7 +113,7 @@ jobs: errs.push('Branch "' + branch + '" must match prefix/kebab-case (no consecutive/trailing hyphens, no uppercase, one segment). Prefixes: feature fix hotfix chore docs refactor release'); return errs; } - if (/(?:DEV|PLAT|SEC|INFRA)-\d+/i.test(m[2])) errs.push('Branch segment must not contain a Jira key'); + if (/(?:DEV|PLAT|SEC|AP|INFRA)-\d+/i.test(m[2])) errs.push('Branch segment must not contain a Jira key'); return errs; } @@ -181,7 +181,7 @@ jobs: const desc = m[4]; if (!/^[a-z]/.test(desc)) errs.push('Commit description must start with a lowercase letter'); if (desc.endsWith('.')) errs.push('Commit description must not end with a period'); - if (/\s+\((DEV|PLAT|SEC)-\d+\)$/i.test(subject)) errs.push('Commit subject must not carry a Jira key suffix — only the PR title does'); + if (/\s+\((DEV|PLAT|SEC|AP)-\d+\)$/i.test(subject)) errs.push('Commit subject must not carry a Jira key suffix — only the PR title does'); return errs; } diff --git a/test/pr-policy.test.mjs b/test/pr-policy.test.mjs index 9d0b964..62942e6 100644 --- a/test/pr-policy.test.mjs +++ b/test/pr-policy.test.mjs @@ -214,6 +214,7 @@ describe('getJiraKey', () => { it('extracts DEV key', () => assert.equal(v.getJiraKey('fix: thing (DEV-42)'), 'DEV-42')); it('extracts PLAT key', () => assert.equal(v.getJiraKey('chore: thing (PLAT-1)'), 'PLAT-1')); it('extracts SEC key', () => assert.equal(v.getJiraKey('docs: thing (SEC-999)'), 'SEC-999')); + it('extracts AP key', () => assert.equal(v.getJiraKey('feat(frontend): scaffold (AP-4)'), 'AP-4')); it('returns null when no key', () => assert.equal(v.getJiraKey('chore: thing'), null)); it('returns null for mid-title key', () => assert.equal(v.getJiraKey('fix (DEV-1): something'), null)); });