diff --git a/.github/workflows/stale_pr_close.yml b/.github/workflows/stale_pr_close.yml index a8ebb057..c1b5cd15 100644 --- a/.github/workflows/stale_pr_close.yml +++ b/.github/workflows/stale_pr_close.yml @@ -22,7 +22,7 @@ jobs: const now = new Date(); const owner = context.repo.owner; const repo = context.repo.repo; - + // --- When stale label is added, comment immediately --- if (context.eventName === "pull_request_target" && context.payload.action === "labeled") { const label = context.payload.label?.name; @@ -37,19 +37,74 @@ jobs: } return; } - - // --- Scheduled run: check all stale PRs --- + + // --- Scheduled run: fetch all open PRs --- const { data: prs } = await github.rest.pulls.list({ owner, repo, state: "open", per_page: 100 }); - + for (const pr of prs) { - const hasStale = pr.labels.some(l => l.name === "stale"); - if (!hasStale) continue; - + const labels = pr.labels.map(l => l.name); + const hasStale = labels.includes("stale"); + const hasKeepOpen = labels.includes("keep-open"); + + // ------------------------------------------------------- + // NEW: Auto-label PRs with no activity in the last 14 days + // ------------------------------------------------------- + if (!hasStale && !hasKeepOpen) { + // Find the most recent commit date + const { data: commits } = await github.rest.pulls.listCommits({ + owner, + repo, + pull_number: pr.number + }); + const lastCommitDate = commits.length > 0 + ? new Date(commits[commits.length - 1].commit.author.date) + : new Date(pr.created_at); + + // Find the most recent non-bot comment date + const { data: comments } = await github.rest.issues.listComments({ + owner, + repo, + issue_number: pr.number, + per_page: 100 + }); + const humanComments = comments.filter(c => c.user?.type !== "Bot"); + const lastCommentDate = humanComments.length > 0 + ? new Date(humanComments[humanComments.length - 1].created_at) + : null; + + // Most recent activity across commits and comments + const lastActivityDate = lastCommentDate && lastCommentDate > lastCommitDate + ? lastCommentDate + : lastCommitDate; + + const daysSinceActivity = (now - lastActivityDate) / (1000 * 60 * 60 * 24); + + if (daysSinceActivity > 14) { + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: pr.number, + labels: ["stale"] + }); + // The pull_request_target labeled event will fire the comment automatically. + // Skip further processing for this PR in this run. + continue; + } + + // Not stale, nothing else to do for this PR. + continue; + } + + // ------------------------------------------------------- + // EXISTING: Manage already-stale PRs + // ------------------------------------------------------- + if (!hasStale) continue; // has keep-open but not stale — skip + // Get timeline events to find when stale label was added const { data: events } = await github.rest.issues.listEvents({ owner, @@ -57,27 +112,27 @@ jobs: issue_number: pr.number, per_page: 100 }); - + // Find the most recent time the stale label was added const staleLabelEvents = events .filter(e => e.event === "labeled" && e.label?.name === "stale") .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - + if (staleLabelEvents.length === 0) continue; - + const staleLabelDate = new Date(staleLabelEvents[0].created_at); const daysSinceStale = (now - staleLabelDate) / (1000 * 60 * 60 * 24); - + // Check for new commits since stale label was added const { data: commits } = await github.rest.pulls.listCommits({ owner, repo, pull_number: pr.number }); - + const lastCommitDate = new Date(commits[commits.length - 1].commit.author.date); const author = pr.user.login; - + // If there are new commits after the stale label, remove it if (lastCommitDate > staleLabelDate) { await github.rest.issues.removeLabel({