diff --git a/.github/scripts/helpers/api.cjs b/.github/scripts/helpers/api.cjs index 2f926db..9ea9f85 100644 --- a/.github/scripts/helpers/api.cjs +++ b/.github/scripts/helpers/api.cjs @@ -134,6 +134,27 @@ async function postComment(botContext, body) { } } +async function updatePullRequest(botContext, title) { + try { + requireNonEmptyString(title, 'title'); + + await botContext.github.rest.pulls.update({ + owner: botContext.owner, + repo: botContext.repo, + pull_number: botContext.number, + title, + }); + + getLogger().log(`Updated PR #${botContext.number} title`); + return { success: true }; + } catch (error) { + getLogger().error( + `Could not update PR #${botContext.number} title: ${error.message}`, + ); + return { success: false, error: error.message }; + } +} + function hasLabel(issueOrPr, labelName) { if (!issueOrPr?.labels?.length) return false; return issueOrPr.labels.some((label) => { @@ -467,7 +488,7 @@ async function hasNeedsReviewPR(github, owner, repo, username, issueNumber) { } module.exports = { - buildBotContext, addLabels, removeLabel, addAssignees, removeAssignees, postComment, hasLabel, getLabelsByPrefix, + buildBotContext, addLabels, removeLabel, addAssignees, removeAssignees, postComment, updatePullRequest, hasLabel, getLabelsByPrefix, swapLabels, getBotComment, postOrUpdateComment, fetchPRCommits, fetchOpenPRs, fetchIssue, fetchClosingIssueNumbers, fetchLatestMilestone, setMilestone, swapStatusLabel, runAllChecksAndComment, resolveLinkedIssue, acknowledgeComment, fetchComments, fetchIssueEvents, closeItem, getHighestIssueSkillLevel, countIssuesByAssignee, listAssignedIssues, hasNeedsReviewPR, diff --git a/.github/scripts/pr-labeler.cjs b/.github/scripts/pr-labeler.cjs index 3a5978f..c5bcb33 100644 --- a/.github/scripts/pr-labeler.cjs +++ b/.github/scripts/pr-labeler.cjs @@ -1,13 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 -const { buildBotContext, addLabels } = require('./helpers/api.cjs'); +const { buildBotContext, addLabels, updatePullRequest } = require('./helpers/api.cjs'); const { loadAutomationConfig } = require('./helpers/config-loader.cjs'); function detectType(title) { if (!title || typeof title !== 'string') return null; const upper = title.toUpperCase(); - const kdm = upper.match(/\[KDM-\d+-(FIX|FEAT|REFACTOR)/); + const kdm = upper.match(/^\[KDM-[A-Z0-9-]+-(FIX|FEAT|REFACTOR)-\d+\]/); if (kdm) { const map = { FIX: 'bugFix', FEAT: 'feature', REFACTOR: 'refactor' }; return map[kdm[1]] || null; @@ -81,6 +81,27 @@ function determineComplexity(score, complexityConfig) { return 'complex'; } +function buildPRIdentityTitle(title, module, typeKey, prNumber) { + if (!module || !typeKey || !prNumber) return null; + + const typeMap = { + bugFix: 'FIX', + feature: 'FEAT', + refactor: 'REFACTOR', + }; + + const type = typeMap[typeKey]; + if (!type) return null; + + const prefix = `[KDM-${module.toUpperCase()}-${type}-${prNumber}]`; + + const cleanedTitle = title + .replace(/^\[KDM-(?:[A-Z0-9-]+-(?:FIX|FEAT|REFACTOR)-\d+|\d+-(?:FIX|FEAT|REFACTOR))\]\s*/i, '') + .trim(); + +return `${prefix} ${cleanedTitle}`.trim(); +} + async function labelPR({ github, context }) { let botContext; try { @@ -162,6 +183,19 @@ async function labelPR({ github, context }) { } } + const prModule = matchedModules[0]; + const newTitle = buildPRIdentityTitle(title, prModule, typeKey, number); + + if (newTitle && newTitle !== title) { + console.log(`[pr-labeler] Updating PR title: "${newTitle}"`); + + const titleResult = await updatePullRequest(botContext, newTitle); + + if (!titleResult.success) { + console.log(`[pr-labeler] Failed to update PR title: ${titleResult.error}`); + } + } + if (labelsToAdd.length) { console.log(`[pr-labeler] Adding labels: ${labelsToAdd.join(', ')}`); const result = await addLabels(botContext, labelsToAdd);