diff --git a/.github/scripts/__tests__/agents-pr-meta-update-body.test.js b/.github/scripts/__tests__/agents-pr-meta-update-body.test.js index be674acb6..1fca90926 100644 --- a/.github/scripts/__tests__/agents-pr-meta-update-body.test.js +++ b/.github/scripts/__tests__/agents-pr-meta-update-body.test.js @@ -446,6 +446,20 @@ test('buildSourceContextResolvedCommentBody explains sync-campaign source contex assert.ok(result.includes('No linked GitHub issue is required')); }); +test('buildSourceContextResolvedCommentBody explains issue-backed source context', () => { + const result = buildSourceContextResolvedCommentBody(123, { + sourceType: 'github_issue', + issueNumber: 456, + sourceRef: '#456', + requiresIssue: true, + }); + + assert.ok(result.includes('origin=github_issue')); + assert.ok(result.includes('ref=#456')); + assert.ok(result.includes('A linked GitHub issue is present')); + assert.ok(!result.includes('No linked GitHub issue is required')); +}); + test('resolveExplicitNonIssueWorkflowSourceContext preserves explicit automation source despite stale issue preamble', () => { const context = resolveExplicitNonIssueWorkflowSourceContext({ body: [ @@ -487,11 +501,11 @@ test('resolveExplicitNonIssueWorkflowSourceContext ignores source issue markers' assert.equal(context, null); }); -test('resolveNonIssueWorkflowSourceContextForBodySync preserves issue-sourced sync precedence', () => { +test('resolveNonIssueWorkflowSourceContextForBodySync preserves explicit issue-sourced sync precedence', () => { const context = resolveNonIssueWorkflowSourceContextForBodySync( { body: [ - '', + 'Closes #123', '', '', ].join('\n'), @@ -502,6 +516,22 @@ test('resolveNonIssueWorkflowSourceContextForBodySync preserves issue-sourced sy assert.equal(context, null); }); +test('resolveNonIssueWorkflowSourceContextForBodySync lets explicit non-issue markers replace generated issue metadata', () => { + const context = resolveNonIssueWorkflowSourceContextForBodySync( + { + body: [ + '', + '', + '', + ].join('\n'), + }, + 123, + ); + + assert.equal(context.sourceType, 'local_request'); + assert.equal(context.sourceRef, 'codex-thread-2026-04-26'); +}); + test('hasExplicitIssueSyncReference ignores heuristic issue-number sources', () => { assert.equal( hasExplicitIssueSyncReference({ @@ -512,7 +542,13 @@ test('hasExplicitIssueSyncReference ignores heuristic issue-number sources', () false, ); assert.equal(hasExplicitIssueSyncReference({ body: 'Closes #123' }), true); - assert.equal(hasExplicitIssueSyncReference({ body: '' }), true); + assert.equal(hasExplicitIssueSyncReference({ body: 'Related to #123' }), true); + assert.equal(hasExplicitIssueSyncReference({ body: 'Related to issue #123' }), true); + assert.equal(hasExplicitIssueSyncReference({ body: 'References issue #123' }), true); + assert.equal(hasExplicitIssueSyncReference({ body: 'source issue #123' }), true); + assert.equal(hasExplicitIssueSyncReference({ body: 'This issue only mentions review follow-up PR #123' }), false); + assert.equal(hasExplicitIssueSyncReference({ body: 'refs in this paragraph mention PR #123' }), false); + assert.equal(hasExplicitIssueSyncReference({ body: '' }), false); }); test('resolveNonIssueWorkflowSourceContextForBodySync honors explicit non-issue markers over heuristics', () => { diff --git a/.github/scripts/agents_pr_meta_update_body.js b/.github/scripts/agents_pr_meta_update_body.js index 9c0969b1c..a65589cb0 100644 --- a/.github/scripts/agents_pr_meta_update_body.js +++ b/.github/scripts/agents_pr_meta_update_body.js @@ -980,13 +980,16 @@ async function createIssueCommentWithRetry({ github, owner, repo, issueNumber, b } function buildSourceContextResolvedCommentBody(prNumber, sourceContext) { + const isIssueBacked = sourceContext?.requiresIssue || sourceContext?.issueNumber || sourceContext?.sourceType === SOURCE_TYPES.GITHUB_ISSUE; return [ '', '### Workflow source detected', '', `PR #${prNumber} now has valid workflow source context (${formatSourceContextForLog(sourceContext)}).`, '', - 'No linked GitHub issue is required for this PR.', + isIssueBacked + ? 'A linked GitHub issue is present for this PR.' + : 'No linked GitHub issue is required for this PR.', ].join('\n'); } @@ -1024,13 +1027,10 @@ function resolveExplicitNonIssueWorkflowSourceContext(pr = {}) { } function hasExplicitIssueSyncReference(pr = {}) { - const body = String(pr.body || ''); - if (//i.test(body)) { - return true; - } - - const text = `${pr.title || ''}\n${body}`; - return /\b(?:close[sd]?|closing|fix(?:e[sd])?|fixing|resolve[sd]?|resolving|address(?:e[sd])?|addressing|relate[sd]?\s+to|refs?|references?|issue|source\s+issue|github\s+issue)\s*[:#-]?\s*#[0-9]+\b/i.test(text); + const text = `${pr.title || ''}\n${pr.body || ''}`; + const explicitClosingReference = /\b(?:close[sd]?|closing|fix(?:e[sd])?|fixing|resolve[sd]?|resolving|address(?:e[sd])?|addressing)\s*[:#-]?\s*#[0-9]+\b/i; + const explicitIssueReference = /\b(?:(?:relate[sd]?\s+to|references?)\s+(?:issue\s+)?|(?:source|github|linked)\s+issue\s*)[:#-]?\s*#[0-9]+\b/i; + return explicitClosingReference.test(text) || explicitIssueReference.test(text); } function resolveNonIssueWorkflowSourceContextForBodySync(pr = {}, issueNumber = null) { diff --git a/templates/consumer-repo/.github/scripts/agents_pr_meta_update_body.js b/templates/consumer-repo/.github/scripts/agents_pr_meta_update_body.js index 9c0969b1c..a65589cb0 100644 --- a/templates/consumer-repo/.github/scripts/agents_pr_meta_update_body.js +++ b/templates/consumer-repo/.github/scripts/agents_pr_meta_update_body.js @@ -980,13 +980,16 @@ async function createIssueCommentWithRetry({ github, owner, repo, issueNumber, b } function buildSourceContextResolvedCommentBody(prNumber, sourceContext) { + const isIssueBacked = sourceContext?.requiresIssue || sourceContext?.issueNumber || sourceContext?.sourceType === SOURCE_TYPES.GITHUB_ISSUE; return [ '', '### Workflow source detected', '', `PR #${prNumber} now has valid workflow source context (${formatSourceContextForLog(sourceContext)}).`, '', - 'No linked GitHub issue is required for this PR.', + isIssueBacked + ? 'A linked GitHub issue is present for this PR.' + : 'No linked GitHub issue is required for this PR.', ].join('\n'); } @@ -1024,13 +1027,10 @@ function resolveExplicitNonIssueWorkflowSourceContext(pr = {}) { } function hasExplicitIssueSyncReference(pr = {}) { - const body = String(pr.body || ''); - if (//i.test(body)) { - return true; - } - - const text = `${pr.title || ''}\n${body}`; - return /\b(?:close[sd]?|closing|fix(?:e[sd])?|fixing|resolve[sd]?|resolving|address(?:e[sd])?|addressing|relate[sd]?\s+to|refs?|references?|issue|source\s+issue|github\s+issue)\s*[:#-]?\s*#[0-9]+\b/i.test(text); + const text = `${pr.title || ''}\n${pr.body || ''}`; + const explicitClosingReference = /\b(?:close[sd]?|closing|fix(?:e[sd])?|fixing|resolve[sd]?|resolving|address(?:e[sd])?|addressing)\s*[:#-]?\s*#[0-9]+\b/i; + const explicitIssueReference = /\b(?:(?:relate[sd]?\s+to|references?)\s+(?:issue\s+)?|(?:source|github|linked)\s+issue\s*)[:#-]?\s*#[0-9]+\b/i; + return explicitClosingReference.test(text) || explicitIssueReference.test(text); } function resolveNonIssueWorkflowSourceContextForBodySync(pr = {}, issueNumber = null) {