From 3b8075784a202eb89220ad2048469407ea66df19 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Fri, 7 Nov 2025 15:14:14 -0600 Subject: [PATCH 01/17] chore: empty commit to sync branch From 42a4fac9558e1dc16119a9653272c783d957e9ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:24:19 +0000 Subject: [PATCH 02/17] feat: add work item linkage information to job summary and notices - Add GitHub Actions notice annotations when work items are linked - Add work item information to job summary for visibility - Update tests to mock core.notice and core.summary - Update README with Action Output section This implements the original requirement to make it easier to see what work items were linked to the PR from the job output. Co-authored-by: joshjohanning <19912012+joshjohanning@users.noreply.github.com> --- README.md | 10 ++++++++++ __tests__/index.test.js | 13 ++++++++++++- src/index.js | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac3ceca..080906f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,16 @@ This action validates that pull requests and commits contain Azure DevOps work i 2. **Validates Commits** - Ensures each commit in a pull request has an Azure DevOps work item link (e.g. `AB#123`) in the commit message 3. **Automatically Links PRs to Work Items** - When a work item is referenced in a commit message, the action adds a GitHub Pull Request link to that work item in Azure DevOps - 🎯 **This is the key differentiator**: By default, Azure DevOps only adds the Pull Request link to work items mentioned directly in the PR title or body, but this action also links work items found in commit messages! +4. **Visibility & Tracking** - Work item linkages are displayed as GitHub Actions notices and added to the job summary for easy visibility + +## Action Output + +The action provides visibility into linked work items through: + +- **GitHub Actions Notices**: Work item links are displayed as notice annotations in the workflow run, making it easy to see which work items are linked + - Example: `Work item AB#12345 (from commit abc123d) linked to pull request #42` +- **Job Summary**: A summary of all linked work items is added to the workflow run's job summary page, providing a quick reference of work items associated with the PR + - Includes clickable links to commits and work items ## Usage diff --git a/__tests__/index.test.js b/__tests__/index.test.js index c6f7f94..d637be4 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -9,12 +9,19 @@ const mockGetInput = jest.fn(); const mockSetFailed = jest.fn(); const mockInfo = jest.fn(); const mockError = jest.fn(); +const mockNotice = jest.fn(); +const mockSummary = { + addRaw: jest.fn().mockReturnThis(), + write: jest.fn().mockResolvedValue(undefined) +}; jest.unstable_mockModule('@actions/core', () => ({ getInput: mockGetInput, setFailed: mockSetFailed, info: mockInfo, - error: mockError + error: mockError, + notice: mockNotice, + summary: mockSummary })); // Mock @actions/github @@ -64,6 +71,10 @@ describe('Azure DevOps Commit Validator', () => { // Clear all mocks jest.clearAllMocks(); + // Reset summary mock + mockSummary.addRaw.mockClear().mockReturnThis(); + mockSummary.write.mockClear().mockResolvedValue(undefined); + // Setup default mock implementations mockGetInput.mockImplementation(name => { const defaults = { diff --git a/src/index.js b/src/index.js index fa41214..cf440d0 100644 --- a/src/index.js +++ b/src/index.js @@ -355,7 +355,17 @@ async function checkCommitsForWorkItems( process.env.GITHUB_SERVER_URL = process.env.GITHUB_SERVER_URL || 'https://github.com'; await linkWorkItem(); + + // Add notice annotation and job summary for visibility + const commitInfo = workItemToCommitMap.get(workItemId); + if (commitInfo) { + core.notice(`Work item AB#${workItemId} (from commit ${commitInfo.shortSha}) linked to pull request #${pullNumber}`, { + title: 'Work Item Linked' + }); + core.summary.addRaw(`- Work item AB#${workItemId} (from commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha})) linked to pull request #${pullNumber}\n`); + } } + await core.summary.write(); } // Return the workItemToCommitMap and validation results for use in PR validation @@ -481,10 +491,36 @@ async function checkPullRequestForWorkItems( return invalidWorkItems; } + // All work items valid - add notice and job summary for each + for (const workItem of uniqueWorkItems) { + const workItemNumber = workItem.substring(3); // Remove "AB#" prefix + core.notice(`Pull request linked to work item AB#${workItemNumber}`, { + title: 'Work Item Linked' + }); + core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); + } + await core.summary.write(); + // All work items valid - return empty array return []; } + // Validation disabled - add notice and job summary for each work item + for (const workItem of uniqueWorkItems) { + const workItemNumber = workItem.substring(3); // Remove "AB#" prefix + + // Add to the workItemToCommitMap if not already there + if (!workItemToCommitMap.has(workItemNumber)) { + workItemToCommitMap.set(workItemNumber, null); // null indicates it's from PR title/body + } + + core.notice(`Pull request linked to work item AB#${workItemNumber}`, { + title: 'Work Item Linked' + }); + core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); + } + await core.summary.write(); + // Validation disabled - return empty array return []; } From 0001d476bc9bb4264a84fcf64586a407e6ffcedb Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 14:49:52 -0600 Subject: [PATCH 03/17] chore: bump version to 3.0.7 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebbd9ec..2a2cd60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "azure-devops-work-item-link-enforcer-and-linker", - "version": "3.0.5", + "version": "3.0.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "azure-devops-work-item-link-enforcer-and-linker", - "version": "3.0.5", + "version": "3.0.7", "license": "MIT", "dependencies": { "@actions/core": "^2.0.1", diff --git a/package.json b/package.json index 549ac1d..07e805c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azure-devops-work-item-link-enforcer-and-linker", - "version": "3.0.5", + "version": "3.0.7", "private": true, "type": "module", "description": "GitHub Action to enforce that each commit in a pull request be linked to an Azure DevOps work item and automatically link the pull request to each work item ", From eacdf1105bfadc55d4f055ec8d31637c4b2ad4dc Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 14:56:23 -0600 Subject: [PATCH 04/17] fix: ensure job summary is written only once after execution --- src/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 56cea39..f24f510 100644 --- a/src/index.js +++ b/src/index.js @@ -156,6 +156,9 @@ export async function run() { core.info('... invalid work item comment updated to success'); } } + + // Write job summary once at the end (notices/summary were added throughout execution) + await core.summary.write(); } catch (error) { core.setFailed(`Action failed with error: ${error}`); } @@ -361,7 +364,6 @@ async function checkCommitsForWorkItems( core.summary.addRaw(`- Work item AB#${workItemId} (from commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha})) linked to pull request #${pullNumber}\n`); } } - await core.summary.write(); } // Return the workItemToCommitMap and validation results for use in PR validation @@ -495,7 +497,6 @@ async function checkPullRequestForWorkItems( }); core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); } - await core.summary.write(); // All work items valid - return empty array return []; @@ -504,7 +505,7 @@ async function checkPullRequestForWorkItems( // Validation disabled - add notice and job summary for each work item for (const workItem of uniqueWorkItems) { const workItemNumber = workItem.substring(3); // Remove "AB#" prefix - + // Add to the workItemToCommitMap if not already there if (!workItemToCommitMap.has(workItemNumber)) { workItemToCommitMap.set(workItemNumber, null); // null indicates it's from PR title/body @@ -515,7 +516,6 @@ async function checkPullRequestForWorkItems( }); core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); } - await core.summary.write(); // Validation disabled - return empty array return []; From 50a09b3331fdbaeb7097a62ebbce7eb2e5575590 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 14:58:46 -0600 Subject: [PATCH 05/17] refactor: simplify job summary annotations in commit and PR checks by removing the notices --- badges/coverage.svg | 2 +- src/index.js | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/badges/coverage.svg b/badges/coverage.svg index dbfed95..584ef7b 100644 --- a/badges/coverage.svg +++ b/badges/coverage.svg @@ -1 +1 @@ -Coverage: 80.56%Coverage80.56% \ No newline at end of file +Coverage: 80.36%Coverage80.36% \ No newline at end of file diff --git a/src/index.js b/src/index.js index 3bda802..f66e9fe 100644 --- a/src/index.js +++ b/src/index.js @@ -363,13 +363,12 @@ async function checkCommitsForWorkItems( await linkWorkItem(); - // Add notice annotation and job summary for visibility + // Add job summary for visibility const commitInfo = workItemToCommitMap.get(workItemId); if (commitInfo) { - core.notice(`Work item AB#${workItemId} (from commit ${commitInfo.shortSha}) linked to pull request #${pullNumber}`, { - title: 'Work Item Linked' - }); - core.summary.addRaw(`- Work item AB#${workItemId} (from commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha})) linked to pull request #${pullNumber}\n`); + core.summary.addRaw( + `- Work item AB#${workItemId} (from commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha})) linked to pull request #${pullNumber}\n` + ); } } } @@ -497,12 +496,9 @@ async function checkPullRequestForWorkItems( return invalidWorkItems; } - // All work items valid - add notice and job summary for each + // All work items valid - add job summary for each for (const workItem of uniqueWorkItems) { const workItemNumber = workItem.substring(3); // Remove "AB#" prefix - core.notice(`Pull request linked to work item AB#${workItemNumber}`, { - title: 'Work Item Linked' - }); core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); } @@ -510,7 +506,7 @@ async function checkPullRequestForWorkItems( return []; } - // Validation disabled - add notice and job summary for each work item + // Validation disabled - add job summary for each work item for (const workItem of uniqueWorkItems) { const workItemNumber = workItem.substring(3); // Remove "AB#" prefix @@ -519,9 +515,6 @@ async function checkPullRequestForWorkItems( workItemToCommitMap.set(workItemNumber, null); // null indicates it's from PR title/body } - core.notice(`Pull request linked to work item AB#${workItemNumber}`, { - title: 'Work Item Linked' - }); core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); } From ebf62441923c324f0213b4202be6abf79ea38aba Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:00:11 -0600 Subject: [PATCH 06/17] test: verify job summary includes work item info in commit validation --- __tests__/index.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/__tests__/index.test.js b/__tests__/index.test.js index bd857bb..a4c0211 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -334,6 +334,9 @@ describe('Azure DevOps Commit Validator', () => { expect(mockLinkWorkItem).toHaveBeenCalled(); expect(mockSetFailed).not.toHaveBeenCalled(); + // Verify job summary was written with work item info + expect(mockSummary.addRaw).toHaveBeenCalledWith(expect.stringContaining('AB#12345')); + expect(mockSummary.write).toHaveBeenCalled(); }); it('should handle duplicate work items', async () => { @@ -394,6 +397,9 @@ describe('Azure DevOps Commit Validator', () => { await run(); expect(mockSetFailed).not.toHaveBeenCalled(); + // Verify job summary was written with work item info + expect(mockSummary.addRaw).toHaveBeenCalledWith(expect.stringContaining('AB#12345')); + expect(mockSummary.write).toHaveBeenCalled(); }); it('should pass when PR has work item in body', async () => { @@ -415,6 +421,9 @@ describe('Azure DevOps Commit Validator', () => { await run(); expect(mockSetFailed).not.toHaveBeenCalled(); + // Verify job summary was written with work item info + expect(mockSummary.addRaw).toHaveBeenCalledWith(expect.stringContaining('AB#12345')); + expect(mockSummary.write).toHaveBeenCalled(); }); it('should fail when PR has no work item link', async () => { From e8264b13e2c50f7a240a71a8a7462f51c10ae157 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:00:27 -0600 Subject: [PATCH 07/17] docs: update README to clarify job summary visibility for linked work items --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 080906f..101e99e 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,15 @@ This action validates that pull requests and commits contain Azure DevOps work i 2. **Validates Commits** - Ensures each commit in a pull request has an Azure DevOps work item link (e.g. `AB#123`) in the commit message 3. **Automatically Links PRs to Work Items** - When a work item is referenced in a commit message, the action adds a GitHub Pull Request link to that work item in Azure DevOps - 🎯 **This is the key differentiator**: By default, Azure DevOps only adds the Pull Request link to work items mentioned directly in the PR title or body, but this action also links work items found in commit messages! -4. **Visibility & Tracking** - Work item linkages are displayed as GitHub Actions notices and added to the job summary for easy visibility +4. **Visibility & Tracking** - Work item linkages are added to the job summary for easy visibility ## Action Output -The action provides visibility into linked work items through: +The action provides visibility into linked work items through the **Job Summary**: -- **GitHub Actions Notices**: Work item links are displayed as notice annotations in the workflow run, making it easy to see which work items are linked - - Example: `Work item AB#12345 (from commit abc123d) linked to pull request #42` -- **Job Summary**: A summary of all linked work items is added to the workflow run's job summary page, providing a quick reference of work items associated with the PR - - Includes clickable links to commits and work items +- A summary of all linked work items is added to the workflow run's job summary page +- Includes clickable links to commits and work items +- Provides a quick reference of work items associated with the PR ## Usage From 637a598da9e970e5f680792c2b1fef58ad628968 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:11:07 -0600 Subject: [PATCH 08/17] chore: bump version to 3.0.8 in package.json --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a2cd60..607ce26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "azure-devops-work-item-link-enforcer-and-linker", - "version": "3.0.7", + "version": "3.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "azure-devops-work-item-link-enforcer-and-linker", - "version": "3.0.7", + "version": "3.0.8", "license": "MIT", "dependencies": { "@actions/core": "^2.0.1", diff --git a/package.json b/package.json index 07e805c..7489eb6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azure-devops-work-item-link-enforcer-and-linker", - "version": "3.0.7", + "version": "3.0.8", "private": true, "type": "module", "description": "GitHub Action to enforce that each commit in a pull request be linked to an Azure DevOps work item and automatically link the pull request to each work item ", From 64757da36aa190ce5adb234ebdb37608ed07cd28 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:17:03 -0600 Subject: [PATCH 09/17] fix: improve job summary handling to avoid duplicate work item entries --- src/index.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/index.js b/src/index.js index f66e9fe..d32b1ce 100644 --- a/src/index.js +++ b/src/index.js @@ -165,7 +165,7 @@ export async function run() { } } - // Write job summary once at the end (notices/summary were added throughout execution) + // Write job summary once at the end (summary content was added throughout execution) await core.summary.write(); } catch (error) { core.setFailed(`Action failed with error: ${error}`); @@ -496,26 +496,28 @@ async function checkPullRequestForWorkItems( return invalidWorkItems; } - // All work items valid - add job summary for each + // All work items valid - add job summary for each (only if not already added from commits) for (const workItem of uniqueWorkItems) { const workItemNumber = workItem.substring(3); // Remove "AB#" prefix - core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); + // Only add to summary if this work item wasn't already added from a commit + if (!workItemToCommitMap.has(workItemNumber) || workItemToCommitMap.get(workItemNumber) === null) { + core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); + } } // All work items valid - return empty array return []; } - // Validation disabled - add job summary for each work item + // Validation disabled - add job summary for each work item (only if not already added from commits) for (const workItem of uniqueWorkItems) { const workItemNumber = workItem.substring(3); // Remove "AB#" prefix - // Add to the workItemToCommitMap if not already there + // Only add to map and summary if this work item wasn't already added from a commit if (!workItemToCommitMap.has(workItemNumber)) { workItemToCommitMap.set(workItemNumber, null); // null indicates it's from PR title/body + core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); } - - core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); } // Validation disabled - return empty array From a83232df39e3ca43e15f2421a7f4f244719d896e Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:17:07 -0600 Subject: [PATCH 10/17] refactor: remove unused mockNotice from core actions mock --- __tests__/index.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/__tests__/index.test.js b/__tests__/index.test.js index a4c0211..af38298 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -9,7 +9,6 @@ const mockGetInput = jest.fn(); const mockSetFailed = jest.fn(); const mockInfo = jest.fn(); const mockError = jest.fn(); -const mockNotice = jest.fn(); const mockSummary = { addRaw: jest.fn().mockReturnThis(), write: jest.fn().mockResolvedValue(undefined) @@ -20,7 +19,6 @@ jest.unstable_mockModule('@actions/core', () => ({ setFailed: mockSetFailed, info: mockInfo, error: mockError, - notice: mockNotice, summary: mockSummary })); From cfcd71da516330b291eda01be204cc0a2e34ed5c Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:17:13 -0600 Subject: [PATCH 11/17] docs: update README to clarify job summary links and work item display --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 101e99e..60a8e88 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This action validates that pull requests and commits contain Azure DevOps work i The action provides visibility into linked work items through the **Job Summary**: - A summary of all linked work items is added to the workflow run's job summary page -- Includes clickable links to commits and work items +- Includes clickable links to commits and displays associated work items - Provides a quick reference of work items associated with the PR ## Usage From 68bdeb95a054292c7f2c90544335f83b09f643d5 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:19:01 -0600 Subject: [PATCH 12/17] feat: enhance job summary visibility for linked work items in PRs --- src/index.js | 48 +++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/index.js b/src/index.js index d32b1ce..c1f8a59 100644 --- a/src/index.js +++ b/src/index.js @@ -343,32 +343,42 @@ async function checkCommitsForWorkItems( // (Don't update success comment here - let caller handle it after checking PR too) } - // Link work items to PR if enabled (after deduplication) - if (linkCommitsToPullRequest && allWorkItems.length > 0) { + // Process work items found in commits (after deduplication) + if (allWorkItems.length > 0) { // Remove duplicates const uniqueWorkItems = [...new Set(allWorkItems)]; for (const match of uniqueWorkItems) { const workItemId = match.substring(3); // Remove "AB#" prefix - core.info(`Linking work item ${workItemId} to pull request ${pullNumber}...`); + const commitInfo = workItemToCommitMap.get(workItemId); - // Set environment variables for main.js - process.env.REPO_TOKEN = githubToken; - process.env.AZURE_DEVOPS_ORG = azureDevopsOrganization; - process.env.AZURE_DEVOPS_PAT = azureDevopsToken; - process.env.WORKITEMID = workItemId; - process.env.PULLREQUESTID = pullNumber.toString(); - process.env.REPO = `${context.repo.owner}/${context.repo.repo}`; - process.env.GITHUB_SERVER_URL = process.env.GITHUB_SERVER_URL || 'https://github.com'; + // Link work items to PR if enabled + if (linkCommitsToPullRequest) { + core.info(`Linking work item ${workItemId} to pull request ${pullNumber}...`); - await linkWorkItem(); + // Set environment variables for main.js + process.env.REPO_TOKEN = githubToken; + process.env.AZURE_DEVOPS_ORG = azureDevopsOrganization; + process.env.AZURE_DEVOPS_PAT = azureDevopsToken; + process.env.WORKITEMID = workItemId; + process.env.PULLREQUESTID = pullNumber.toString(); + process.env.REPO = `${context.repo.owner}/${context.repo.repo}`; + process.env.GITHUB_SERVER_URL = process.env.GITHUB_SERVER_URL || 'https://github.com'; - // Add job summary for visibility - const commitInfo = workItemToCommitMap.get(workItemId); + await linkWorkItem(); + } + + // Add job summary for visibility (regardless of linking setting) if (commitInfo) { - core.summary.addRaw( - `- Work item AB#${workItemId} (from commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha})) linked to pull request #${pullNumber}\n` - ); + if (linkCommitsToPullRequest) { + core.summary.addRaw( + `- ✅ **Linked:** Work item AB#${workItemId} (from commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha})) linked to PR #${pullNumber}\n` + ); + } else { + core.summary.addRaw( + `- ✔️ **Verified:** Work item AB#${workItemId} found in commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha})\n` + ); + } } } } @@ -501,7 +511,7 @@ async function checkPullRequestForWorkItems( const workItemNumber = workItem.substring(3); // Remove "AB#" prefix // Only add to summary if this work item wasn't already added from a commit if (!workItemToCommitMap.has(workItemNumber) || workItemToCommitMap.get(workItemNumber) === null) { - core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); + core.summary.addRaw(`- ✔️ **Verified:** Work item AB#${workItemNumber} found in PR title/body\n`); } } @@ -516,7 +526,7 @@ async function checkPullRequestForWorkItems( // Only add to map and summary if this work item wasn't already added from a commit if (!workItemToCommitMap.has(workItemNumber)) { workItemToCommitMap.set(workItemNumber, null); // null indicates it's from PR title/body - core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`); + core.summary.addRaw(`- ✔️ **Verified:** Work item AB#${workItemNumber} found in PR title/body\n`); } } From 12f6b0160e890e434374e80ae9bcd6d5a3aad54e Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:20:12 -0600 Subject: [PATCH 13/17] fix: update coverage badge to reflect current coverage percentage --- badges/coverage.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/badges/coverage.svg b/badges/coverage.svg index 584ef7b..eae0918 100644 --- a/badges/coverage.svg +++ b/badges/coverage.svg @@ -1 +1 @@ -Coverage: 80.36%Coverage80.36% \ No newline at end of file +Coverage: 80.29%Coverage80.29% \ No newline at end of file From 10562e759c6377fa1136ba1e6850bb60ccf54b4e Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:48:31 -0600 Subject: [PATCH 14/17] fix: update coverage badge to reflect new coverage percentage (81.08%) --- badges/coverage.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/badges/coverage.svg b/badges/coverage.svg index c60d492..2b72247 100644 --- a/badges/coverage.svg +++ b/badges/coverage.svg @@ -1 +1 @@ -Coverage: 80.29%Coverage80.29% +Coverage: 81.08%Coverage81.08% \ No newline at end of file From 168d505b4dda8e9d0403e73b6a0f50d8c9a922a3 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:50:03 -0600 Subject: [PATCH 15/17] docs: enhance job summary visibility for linked work items --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 60a8e88..8db3389 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,11 @@ This action validates that pull requests and commits contain Azure DevOps work i ## Action Output -The action provides visibility into linked work items through the **Job Summary**: +The action provides visibility into work items through the **Job Summary**: -- A summary of all linked work items is added to the workflow run's job summary page +- A summary of all work items found in commits and PR is added to the workflow run's job summary page - Includes clickable links to commits and displays associated work items +- Shows which work items were **linked** to the PR (when `link-commits-to-pull-request` is enabled) vs. **verified** (when `validate-work-item-exists` is enabled) - Provides a quick reference of work items associated with the PR ## Usage From 11876871b6070b0d07895bfcbe52d217e8889b48 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:50:06 -0600 Subject: [PATCH 16/17] test: add validation for work item presence in commit and PR --- __tests__/index.test.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/__tests__/index.test.js b/__tests__/index.test.js index 8416751..a5cb644 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -601,6 +601,41 @@ describe('Azure DevOps Commit Validator', () => { }) ); }); + + it('should pass when valid work item appears in both commit and PR', async () => { + mockGetInput.mockImplementation(name => { + if (name === 'check-commits') return 'true'; + if (name === 'check-pull-request') return 'true'; + if (name === 'github-token') return 'github-token'; + if (name === 'comment-on-failure') return 'true'; + return 'false'; + }); + + mockOctokit.rest.pulls.listCommits.mockResolvedValue({ + data: [ + { sha: 'abc123', commit: { message: 'fix: resolve issue AB#12345' } } + ] + }); + + mockOctokit.rest.pulls.get.mockResolvedValue({ + data: { + title: 'fix: resolve issue AB#12345', + body: 'This PR fixes AB#12345' + } + }); + + await run(); + + expect(mockSetFailed).not.toHaveBeenCalled(); + // Verify job summary was written and work item appears only once + expect(mockSummary.addRaw).toHaveBeenCalled(); + expect(mockSummary.write).toHaveBeenCalled(); + // Work item AB#12345 should be in the summary from commit (where it was found first) + const summaryCallArg = mockSummary.addRaw.mock.calls.find(call => + call[0].includes('AB#12345') + ); + expect(summaryCallArg).toBeDefined(); + }); }); describe('Comment management', () => { From 41e47c334a14d03451ba81c9564a8f22c81dcad0 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 6 Jan 2026 15:50:20 -0600 Subject: [PATCH 17/17] test: simplify mock data structure in commit validation tests --- __tests__/index.test.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/__tests__/index.test.js b/__tests__/index.test.js index a5cb644..de9bbf6 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -612,9 +612,7 @@ describe('Azure DevOps Commit Validator', () => { }); mockOctokit.rest.pulls.listCommits.mockResolvedValue({ - data: [ - { sha: 'abc123', commit: { message: 'fix: resolve issue AB#12345' } } - ] + data: [{ sha: 'abc123', commit: { message: 'fix: resolve issue AB#12345' } }] }); mockOctokit.rest.pulls.get.mockResolvedValue({ @@ -631,9 +629,7 @@ describe('Azure DevOps Commit Validator', () => { expect(mockSummary.addRaw).toHaveBeenCalled(); expect(mockSummary.write).toHaveBeenCalled(); // Work item AB#12345 should be in the summary from commit (where it was found first) - const summaryCallArg = mockSummary.addRaw.mock.calls.find(call => - call[0].includes('AB#12345') - ); + const summaryCallArg = mockSummary.addRaw.mock.calls.find(call => call[0].includes('AB#12345')); expect(summaryCallArg).toBeDefined(); }); });