From 1cc9c0e5a1e907e78cf7ea26b1f423cdd130cd7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:23:51 +0000 Subject: [PATCH 1/3] Initial plan From 6c15aceca44a5d57addd68c385578e25b40f3946 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:31:54 +0000 Subject: [PATCH 2/3] Add comprehensive logging to close_expired_issues.cjs and close_expired_discussions.cjs Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/close_expired_discussions.cjs | 65 +++++++++++++++++-- actions/setup/js/close_expired_issues.cjs | 65 +++++++++++++++++-- 2 files changed, 118 insertions(+), 12 deletions(-) diff --git a/actions/setup/js/close_expired_discussions.cjs b/actions/setup/js/close_expired_discussions.cjs index c9e4035d29..b39dfd24c8 100644 --- a/actions/setup/js/close_expired_discussions.cjs +++ b/actions/setup/js/close_expired_discussions.cjs @@ -33,10 +33,17 @@ async function searchDiscussionsWithExpiration(github, owner, repo) { const discussions = []; let hasNextPage = true; let cursor = null; + let pageCount = 0; + let totalScanned = 0; const { getErrorMessage } = require("./error_helpers.cjs"); + core.info(`Starting GraphQL search for open discussions in ${owner}/${repo}`); + while (hasNextPage) { + pageCount++; + core.info(`Fetching page ${pageCount} of open discussions (cursor: ${cursor || "initial"})`); + const query = ` query($owner: String!, $repo: String!, $cursor: String) { repository(owner: $owner, name: $repo) { @@ -65,10 +72,16 @@ async function searchDiscussionsWithExpiration(github, owner, repo) { }); if (!result || !result.repository || !result.repository.discussions) { + core.warning(`GraphQL query returned no data at page ${pageCount}`); break; } const nodes = result.repository.discussions.nodes || []; + totalScanned += nodes.length; + core.info(`Page ${pageCount}: Retrieved ${nodes.length} open discussions (total scanned: ${totalScanned})`); + + let agenticCount = 0; + let withExpirationCount = 0; // Filter for discussions with agentic workflow markers and expiration comments for (const discussion of nodes) { @@ -76,6 +89,10 @@ async function searchDiscussionsWithExpiration(github, owner, repo) { const agenticPattern = /^> AI generated by/m; const isAgenticWorkflow = discussion.body && agenticPattern.test(discussion.body); + if (isAgenticWorkflow) { + agenticCount++; + } + if (!isAgenticWorkflow) { continue; } @@ -85,14 +102,19 @@ async function searchDiscussionsWithExpiration(github, owner, repo) { const match = discussion.body ? discussion.body.match(expirationPattern) : null; if (match) { + withExpirationCount++; + core.info(` Found discussion #${discussion.number} with expiration marker: "${match[1]}" - ${discussion.title}`); discussions.push(discussion); } } + core.info(`Page ${pageCount} summary: ${agenticCount} agentic discussions, ${withExpirationCount} with expiration markers`); + hasNextPage = result.repository.discussions.pageInfo.hasNextPage; cursor = result.repository.discussions.pageInfo.endCursor; } + core.info(`Search complete: Scanned ${totalScanned} discussions across ${pageCount} pages, found ${discussions.length} with expiration markers`); return discussions; } @@ -195,31 +217,53 @@ async function main() { // Check which discussions are expired const now = new Date(); + core.info(`Current date/time: ${now.toISOString()}`); const expiredDiscussions = []; + const notExpiredDiscussions = []; for (const discussion of discussionsWithExpiration) { + core.info(`Processing discussion #${discussion.number}: ${discussion.title}`); + // Validate creation date if (!validateCreationDate(discussion.createdAt)) { - core.warning(`Discussion #${discussion.number} has invalid creation date, skipping`); + core.warning(` Discussion #${discussion.number} has invalid creation date: ${discussion.createdAt}, skipping`); continue; } + core.info(` Creation date: ${discussion.createdAt}`); // Extract and validate expiration date const expirationDate = extractExpirationDate(discussion.body); if (!expirationDate) { - core.warning(`Discussion #${discussion.number} has invalid expiration date, skipping`); + core.warning(` Discussion #${discussion.number} has invalid expiration date format, skipping`); continue; } + core.info(` Expiration date: ${expirationDate.toISOString()}`); // Check if expired - if (now >= expirationDate) { + const isExpired = now >= expirationDate; + const timeDiff = expirationDate.getTime() - now.getTime(); + const daysUntilExpiration = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + const hoursUntilExpiration = Math.floor(timeDiff / (1000 * 60 * 60)); + + if (isExpired) { + const daysSinceExpiration = Math.abs(daysUntilExpiration); + const hoursSinceExpiration = Math.abs(hoursUntilExpiration); + core.info(` ✓ Discussion #${discussion.number} is EXPIRED (expired ${daysSinceExpiration} days, ${hoursSinceExpiration % 24} hours ago)`); expiredDiscussions.push({ ...discussion, expirationDate: expirationDate, }); + } else { + core.info(` ✗ Discussion #${discussion.number} is NOT expired (expires in ${daysUntilExpiration} days, ${hoursUntilExpiration % 24} hours)`); + notExpiredDiscussions.push({ + ...discussion, + expirationDate: expirationDate, + }); } } + core.info(`Expiration check complete: ${expiredDiscussions.length} expired, ${notExpiredDiscussions.length} not yet expired`); + if (expiredDiscussions.length === 0) { core.info("No expired discussions found"); return; @@ -232,24 +276,31 @@ async function main() { if (expiredDiscussions.length > MAX_UPDATES_PER_RUN) { core.warning(`Found ${expiredDiscussions.length} expired discussions, but only closing the first ${MAX_UPDATES_PER_RUN}`); + core.info(`Remaining ${expiredDiscussions.length - MAX_UPDATES_PER_RUN} expired discussions will be closed in the next run`); } + core.info(`Preparing to close ${discussionsToClose.length} discussion(s)`); + let closedCount = 0; const closedDiscussions = []; for (let i = 0; i < discussionsToClose.length; i++) { const discussion = discussionsToClose[i]; + core.info(`[${i + 1}/${discussionsToClose.length}] Processing discussion #${discussion.number}: ${discussion.url}`); + try { const closingMessage = `This discussion was automatically closed because it expired on ${discussion.expirationDate.toISOString()}.`; // Add comment first - core.info(`Adding closing comment to discussion #${discussion.number}`); + core.info(` Adding closing comment to discussion #${discussion.number}`); await addDiscussionComment(github, discussion.id, closingMessage); + core.info(` ✓ Comment added successfully`); // Then close the discussion as outdated - core.info(`Closing discussion #${discussion.number} as outdated`); + core.info(` Closing discussion #${discussion.number} as outdated`); await closeDiscussionAsOutdated(github, discussion.id); + core.info(` ✓ Discussion closed successfully`); closedDiscussions.push({ number: discussion.number, @@ -258,14 +309,16 @@ async function main() { }); closedCount++; - core.info(`✓ Closed discussion #${discussion.number}: ${discussion.url}`); + core.info(`✓ Successfully processed discussion #${discussion.number}: ${discussion.url}`); } catch (error) { core.error(`✗ Failed to close discussion #${discussion.number}: ${getErrorMessage(error)}`); + core.error(` Error details: ${JSON.stringify(error, null, 2)}`); // Continue with other discussions even if one fails } // Add delay between GraphQL operations to avoid rate limiting (except for the last item) if (i < discussionsToClose.length - 1) { + core.info(` Waiting ${GRAPHQL_DELAY_MS}ms before next operation...`); await delay(GRAPHQL_DELAY_MS); } } diff --git a/actions/setup/js/close_expired_issues.cjs b/actions/setup/js/close_expired_issues.cjs index d36c85ea47..f410de4574 100644 --- a/actions/setup/js/close_expired_issues.cjs +++ b/actions/setup/js/close_expired_issues.cjs @@ -33,10 +33,17 @@ async function searchIssuesWithExpiration(github, owner, repo) { const issues = []; let hasNextPage = true; let cursor = null; + let pageCount = 0; + let totalScanned = 0; const { getErrorMessage } = require("./error_helpers.cjs"); + core.info(`Starting GraphQL search for open issues in ${owner}/${repo}`); + while (hasNextPage) { + pageCount++; + core.info(`Fetching page ${pageCount} of open issues (cursor: ${cursor || "initial"})`); + const query = ` query($owner: String!, $repo: String!, $cursor: String) { repository(owner: $owner, name: $repo) { @@ -65,10 +72,16 @@ async function searchIssuesWithExpiration(github, owner, repo) { }); if (!result || !result.repository || !result.repository.issues) { + core.warning(`GraphQL query returned no data at page ${pageCount}`); break; } const nodes = result.repository.issues.nodes || []; + totalScanned += nodes.length; + core.info(`Page ${pageCount}: Retrieved ${nodes.length} open issues (total scanned: ${totalScanned})`); + + let agenticCount = 0; + let withExpirationCount = 0; // Filter for issues with agentic workflow markers and expiration comments for (const issue of nodes) { @@ -76,6 +89,10 @@ async function searchIssuesWithExpiration(github, owner, repo) { const agenticPattern = /^> AI generated by/m; const isAgenticWorkflow = issue.body && agenticPattern.test(issue.body); + if (isAgenticWorkflow) { + agenticCount++; + } + if (!isAgenticWorkflow) { continue; } @@ -85,14 +102,19 @@ async function searchIssuesWithExpiration(github, owner, repo) { const match = issue.body ? issue.body.match(expirationPattern) : null; if (match) { + withExpirationCount++; + core.info(` Found issue #${issue.number} with expiration marker: "${match[1]}" - ${issue.title}`); issues.push(issue); } } + core.info(`Page ${pageCount} summary: ${agenticCount} agentic issues, ${withExpirationCount} with expiration markers`); + hasNextPage = result.repository.issues.pageInfo.hasNextPage; cursor = result.repository.issues.pageInfo.endCursor; } + core.info(`Search complete: Scanned ${totalScanned} issues across ${pageCount} pages, found ${issues.length} with expiration markers`); return issues; } @@ -188,31 +210,53 @@ async function main() { // Check which issues are expired const now = new Date(); + core.info(`Current date/time: ${now.toISOString()}`); const expiredIssues = []; + const notExpiredIssues = []; for (const issue of issuesWithExpiration) { + core.info(`Processing issue #${issue.number}: ${issue.title}`); + // Validate creation date if (!validateCreationDate(issue.createdAt)) { - core.warning(`Issue #${issue.number} has invalid creation date, skipping`); + core.warning(` Issue #${issue.number} has invalid creation date: ${issue.createdAt}, skipping`); continue; } + core.info(` Creation date: ${issue.createdAt}`); // Extract and validate expiration date const expirationDate = extractExpirationDate(issue.body); if (!expirationDate) { - core.warning(`Issue #${issue.number} has invalid expiration date, skipping`); + core.warning(` Issue #${issue.number} has invalid expiration date format, skipping`); continue; } + core.info(` Expiration date: ${expirationDate.toISOString()}`); // Check if expired - if (now >= expirationDate) { + const isExpired = now >= expirationDate; + const timeDiff = expirationDate.getTime() - now.getTime(); + const daysUntilExpiration = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + const hoursUntilExpiration = Math.floor(timeDiff / (1000 * 60 * 60)); + + if (isExpired) { + const daysSinceExpiration = Math.abs(daysUntilExpiration); + const hoursSinceExpiration = Math.abs(hoursUntilExpiration); + core.info(` ✓ Issue #${issue.number} is EXPIRED (expired ${daysSinceExpiration} days, ${hoursSinceExpiration % 24} hours ago)`); expiredIssues.push({ ...issue, expirationDate: expirationDate, }); + } else { + core.info(` ✗ Issue #${issue.number} is NOT expired (expires in ${daysUntilExpiration} days, ${hoursUntilExpiration % 24} hours)`); + notExpiredIssues.push({ + ...issue, + expirationDate: expirationDate, + }); } } + core.info(`Expiration check complete: ${expiredIssues.length} expired, ${notExpiredIssues.length} not yet expired`); + if (expiredIssues.length === 0) { core.info("No expired issues found"); return; @@ -225,24 +269,31 @@ async function main() { if (expiredIssues.length > MAX_UPDATES_PER_RUN) { core.warning(`Found ${expiredIssues.length} expired issues, but only closing the first ${MAX_UPDATES_PER_RUN}`); + core.info(`Remaining ${expiredIssues.length - MAX_UPDATES_PER_RUN} expired issues will be closed in the next run`); } + core.info(`Preparing to close ${issuesToClose.length} issue(s)`); + let closedCount = 0; const closedIssues = []; for (let i = 0; i < issuesToClose.length; i++) { const issue = issuesToClose[i]; + core.info(`[${i + 1}/${issuesToClose.length}] Processing issue #${issue.number}: ${issue.url}`); + try { const closingMessage = `This issue was automatically closed because it expired on ${issue.expirationDate.toISOString()}.`; // Add comment first - core.info(`Adding closing comment to issue #${issue.number}`); + core.info(` Adding closing comment to issue #${issue.number}`); await addIssueComment(github, owner, repo, issue.number, closingMessage); + core.info(` ✓ Comment added successfully`); // Then close the issue as not planned - core.info(`Closing issue #${issue.number} as not planned`); + core.info(` Closing issue #${issue.number} as not planned`); await closeIssue(github, owner, repo, issue.number); + core.info(` ✓ Issue closed successfully`); closedIssues.push({ number: issue.number, @@ -251,14 +302,16 @@ async function main() { }); closedCount++; - core.info(`✓ Closed issue #${issue.number}: ${issue.url}`); + core.info(`✓ Successfully processed issue #${issue.number}: ${issue.url}`); } catch (error) { core.error(`✗ Failed to close issue #${issue.number}: ${getErrorMessage(error)}`); + core.error(` Error details: ${JSON.stringify(error, null, 2)}`); // Continue with other issues even if one fails } // Add delay between GraphQL operations to avoid rate limiting (except for the last item) if (i < issuesToClose.length - 1) { + core.info(` Waiting ${GRAPHQL_DELAY_MS}ms before next operation...`); await delay(GRAPHQL_DELAY_MS); } } From bc41bb6d480edbd61ed85daa1a4d41e33eaae84c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:52:21 +0000 Subject: [PATCH 3/3] Add comprehensive step summaries for expired issues/discussions cleanup Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/close_expired_discussions.cjs | 87 +++++++++++++++++-- actions/setup/js/close_expired_issues.cjs | 87 +++++++++++++++++-- 2 files changed, 162 insertions(+), 12 deletions(-) diff --git a/actions/setup/js/close_expired_discussions.cjs b/actions/setup/js/close_expired_discussions.cjs index b39dfd24c8..5beabf8fbe 100644 --- a/actions/setup/js/close_expired_discussions.cjs +++ b/actions/setup/js/close_expired_discussions.cjs @@ -115,7 +115,13 @@ async function searchDiscussionsWithExpiration(github, owner, repo) { } core.info(`Search complete: Scanned ${totalScanned} discussions across ${pageCount} pages, found ${discussions.length} with expiration markers`); - return discussions; + return { + discussions, + stats: { + pageCount, + totalScanned, + }, + }; } /** @@ -206,10 +212,17 @@ async function main() { core.info(`Searching for expired discussions in ${owner}/${repo}`); // Search for discussions with expiration markers - const discussionsWithExpiration = await searchDiscussionsWithExpiration(github, owner, repo); + const { discussions: discussionsWithExpiration, stats: searchStats } = await searchDiscussionsWithExpiration(github, owner, repo); if (discussionsWithExpiration.length === 0) { core.info("No discussions with expiration markers found"); + + // Write summary even when no discussions found + let summaryContent = `## Expired Discussions Cleanup\n\n`; + summaryContent += `**Scanned**: ${searchStats.totalScanned} discussions across ${searchStats.pageCount} page(s)\n\n`; + summaryContent += `**Result**: No discussions with expiration markers found\n`; + await core.summary.addRaw(summaryContent).write(); + return; } @@ -266,6 +279,15 @@ async function main() { if (expiredDiscussions.length === 0) { core.info("No expired discussions found"); + + // Write summary when no expired discussions + let summaryContent = `## Expired Discussions Cleanup\n\n`; + summaryContent += `**Scanned**: ${searchStats.totalScanned} discussions across ${searchStats.pageCount} page(s)\n\n`; + summaryContent += `**With expiration markers**: ${discussionsWithExpiration.length} discussion(s)\n\n`; + summaryContent += `**Expired**: 0 discussions\n\n`; + summaryContent += `**Not yet expired**: ${notExpiredDiscussions.length} discussion(s)\n`; + await core.summary.addRaw(summaryContent).write(); + return; } @@ -283,6 +305,7 @@ async function main() { let closedCount = 0; const closedDiscussions = []; + const failedDiscussions = []; for (let i = 0; i < discussionsToClose.length; i++) { const discussion = discussionsToClose[i]; @@ -313,6 +336,12 @@ async function main() { } catch (error) { core.error(`✗ Failed to close discussion #${discussion.number}: ${getErrorMessage(error)}`); core.error(` Error details: ${JSON.stringify(error, null, 2)}`); + failedDiscussions.push({ + number: discussion.number, + url: discussion.url, + title: discussion.title, + error: getErrorMessage(error), + }); // Continue with other discussions even if one fails } @@ -323,16 +352,62 @@ async function main() { } } - // Write summary + // Write comprehensive summary + let summaryContent = `## Expired Discussions Cleanup\n\n`; + summaryContent += `**Scan Summary**\n`; + summaryContent += `- Scanned: ${searchStats.totalScanned} discussions across ${searchStats.pageCount} page(s)\n`; + summaryContent += `- With expiration markers: ${discussionsWithExpiration.length} discussion(s)\n`; + summaryContent += `- Expired: ${expiredDiscussions.length} discussion(s)\n`; + summaryContent += `- Not yet expired: ${notExpiredDiscussions.length} discussion(s)\n\n`; + + summaryContent += `**Closing Summary**\n`; + summaryContent += `- Successfully closed: ${closedCount} discussion(s)\n`; + if (failedDiscussions.length > 0) { + summaryContent += `- Failed to close: ${failedDiscussions.length} discussion(s)\n`; + } + if (expiredDiscussions.length > MAX_UPDATES_PER_RUN) { + summaryContent += `- Remaining for next run: ${expiredDiscussions.length - MAX_UPDATES_PER_RUN} discussion(s)\n`; + } + summaryContent += `\n`; + if (closedCount > 0) { - let summaryContent = `## Closed Expired Discussions\n\n`; - summaryContent += `Closed **${closedCount}** expired discussion(s):\n\n`; + summaryContent += `### Successfully Closed Discussions\n\n`; for (const closed of closedDiscussions) { summaryContent += `- Discussion #${closed.number}: [${closed.title}](${closed.url})\n`; } - await core.summary.addRaw(summaryContent).write(); + summaryContent += `\n`; + } + + if (failedDiscussions.length > 0) { + summaryContent += `### Failed to Close\n\n`; + for (const failed of failedDiscussions) { + summaryContent += `- Discussion #${failed.number}: [${failed.title}](${failed.url}) - Error: ${failed.error}\n`; + } + summaryContent += `\n`; } + if (notExpiredDiscussions.length > 0 && notExpiredDiscussions.length <= 10) { + summaryContent += `### Not Yet Expired\n\n`; + for (const notExpired of notExpiredDiscussions) { + const timeDiff = notExpired.expirationDate.getTime() - now.getTime(); + const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + const hours = Math.floor(timeDiff / (1000 * 60 * 60)) % 24; + summaryContent += `- Discussion #${notExpired.number}: [${notExpired.title}](${notExpired.url}) - Expires in ${days}d ${hours}h\n`; + } + } else if (notExpiredDiscussions.length > 10) { + summaryContent += `### Not Yet Expired\n\n`; + summaryContent += `${notExpiredDiscussions.length} discussion(s) not yet expired (showing first 10):\n\n`; + for (let i = 0; i < 10; i++) { + const notExpired = notExpiredDiscussions[i]; + const timeDiff = notExpired.expirationDate.getTime() - now.getTime(); + const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + const hours = Math.floor(timeDiff / (1000 * 60 * 60)) % 24; + summaryContent += `- Discussion #${notExpired.number}: [${notExpired.title}](${notExpired.url}) - Expires in ${days}d ${hours}h\n`; + } + } + + await core.summary.addRaw(summaryContent).write(); + core.info(`Successfully closed ${closedCount} expired discussion(s)`); } diff --git a/actions/setup/js/close_expired_issues.cjs b/actions/setup/js/close_expired_issues.cjs index f410de4574..95a5e6621c 100644 --- a/actions/setup/js/close_expired_issues.cjs +++ b/actions/setup/js/close_expired_issues.cjs @@ -115,7 +115,13 @@ async function searchIssuesWithExpiration(github, owner, repo) { } core.info(`Search complete: Scanned ${totalScanned} issues across ${pageCount} pages, found ${issues.length} with expiration markers`); - return issues; + return { + issues, + stats: { + pageCount, + totalScanned, + }, + }; } /** @@ -199,10 +205,17 @@ async function main() { core.info(`Searching for expired issues in ${owner}/${repo}`); // Search for issues with expiration markers - const issuesWithExpiration = await searchIssuesWithExpiration(github, owner, repo); + const { issues: issuesWithExpiration, stats: searchStats } = await searchIssuesWithExpiration(github, owner, repo); if (issuesWithExpiration.length === 0) { core.info("No issues with expiration markers found"); + + // Write summary even when no issues found + let summaryContent = `## Expired Issues Cleanup\n\n`; + summaryContent += `**Scanned**: ${searchStats.totalScanned} issues across ${searchStats.pageCount} page(s)\n\n`; + summaryContent += `**Result**: No issues with expiration markers found\n`; + await core.summary.addRaw(summaryContent).write(); + return; } @@ -259,6 +272,15 @@ async function main() { if (expiredIssues.length === 0) { core.info("No expired issues found"); + + // Write summary when no expired issues + let summaryContent = `## Expired Issues Cleanup\n\n`; + summaryContent += `**Scanned**: ${searchStats.totalScanned} issues across ${searchStats.pageCount} page(s)\n\n`; + summaryContent += `**With expiration markers**: ${issuesWithExpiration.length} issue(s)\n\n`; + summaryContent += `**Expired**: 0 issues\n\n`; + summaryContent += `**Not yet expired**: ${notExpiredIssues.length} issue(s)\n`; + await core.summary.addRaw(summaryContent).write(); + return; } @@ -276,6 +298,7 @@ async function main() { let closedCount = 0; const closedIssues = []; + const failedIssues = []; for (let i = 0; i < issuesToClose.length; i++) { const issue = issuesToClose[i]; @@ -306,6 +329,12 @@ async function main() { } catch (error) { core.error(`✗ Failed to close issue #${issue.number}: ${getErrorMessage(error)}`); core.error(` Error details: ${JSON.stringify(error, null, 2)}`); + failedIssues.push({ + number: issue.number, + url: issue.url, + title: issue.title, + error: getErrorMessage(error), + }); // Continue with other issues even if one fails } @@ -316,16 +345,62 @@ async function main() { } } - // Write summary + // Write comprehensive summary + let summaryContent = `## Expired Issues Cleanup\n\n`; + summaryContent += `**Scan Summary**\n`; + summaryContent += `- Scanned: ${searchStats.totalScanned} issues across ${searchStats.pageCount} page(s)\n`; + summaryContent += `- With expiration markers: ${issuesWithExpiration.length} issue(s)\n`; + summaryContent += `- Expired: ${expiredIssues.length} issue(s)\n`; + summaryContent += `- Not yet expired: ${notExpiredIssues.length} issue(s)\n\n`; + + summaryContent += `**Closing Summary**\n`; + summaryContent += `- Successfully closed: ${closedCount} issue(s)\n`; + if (failedIssues.length > 0) { + summaryContent += `- Failed to close: ${failedIssues.length} issue(s)\n`; + } + if (expiredIssues.length > MAX_UPDATES_PER_RUN) { + summaryContent += `- Remaining for next run: ${expiredIssues.length - MAX_UPDATES_PER_RUN} issue(s)\n`; + } + summaryContent += `\n`; + if (closedCount > 0) { - let summaryContent = `## Closed Expired Issues\n\n`; - summaryContent += `Closed **${closedCount}** expired issue(s):\n\n`; + summaryContent += `### Successfully Closed Issues\n\n`; for (const closed of closedIssues) { summaryContent += `- Issue #${closed.number}: [${closed.title}](${closed.url})\n`; } - await core.summary.addRaw(summaryContent).write(); + summaryContent += `\n`; + } + + if (failedIssues.length > 0) { + summaryContent += `### Failed to Close\n\n`; + for (const failed of failedIssues) { + summaryContent += `- Issue #${failed.number}: [${failed.title}](${failed.url}) - Error: ${failed.error}\n`; + } + summaryContent += `\n`; } + if (notExpiredIssues.length > 0 && notExpiredIssues.length <= 10) { + summaryContent += `### Not Yet Expired\n\n`; + for (const notExpired of notExpiredIssues) { + const timeDiff = notExpired.expirationDate.getTime() - now.getTime(); + const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + const hours = Math.floor(timeDiff / (1000 * 60 * 60)) % 24; + summaryContent += `- Issue #${notExpired.number}: [${notExpired.title}](${notExpired.url}) - Expires in ${days}d ${hours}h\n`; + } + } else if (notExpiredIssues.length > 10) { + summaryContent += `### Not Yet Expired\n\n`; + summaryContent += `${notExpiredIssues.length} issue(s) not yet expired (showing first 10):\n\n`; + for (let i = 0; i < 10; i++) { + const notExpired = notExpiredIssues[i]; + const timeDiff = notExpired.expirationDate.getTime() - now.getTime(); + const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + const hours = Math.floor(timeDiff / (1000 * 60 * 60)) % 24; + summaryContent += `- Issue #${notExpired.number}: [${notExpired.title}](${notExpired.url}) - Expires in ${days}d ${hours}h\n`; + } + } + + await core.summary.addRaw(summaryContent).write(); + core.info(`Successfully closed ${closedCount} expired issue(s)`); }