From bc6d147b85bd20a88072d272ee9a21b779980a12 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 27 Apr 2026 17:20:26 +0300 Subject: [PATCH 1/3] chore(ci): replace Dosu stale and size labeling with native workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two GitHub-native workflows ahead of disabling the Dosu app and migrate size labels to canonical k8s namespacing. - .github/workflows/stale.yaml uses actions/stale@v10 to manage the lifecycle/stale and lifecycle/rotten lifecycle. Daily cron, 60d before stale, 14d before close. Issue/PR exempt lists differ on purpose: an accepted issue (triage/accepted) is a known long-tail task, not stale; a held PR (do-not-merge/hold) is paused intentionally. Priority/security/regression labels protect issues someone explicitly decided to keep visible. - .github/workflows/pr-size.yaml uses actions/github-script@v7 to apply size/* labels based on diff line count. Thresholds match the descriptions in .github/labels.yml. Inline ignore patterns skip vendored deps, kubebuilder/openapi inline generated files (zz_generated.{deepcopy,conversion,defaults,openapi}.go), whole code-generated trees (any path under generated/ or *.pb.go), vendored Helm charts, and lockfiles. Concurrency group keyed on PR number with cancel-in-progress: true. removeLabel is wrapped in try/catch tolerating 404 for races between event payload and execution. - Migrate the six size labels from legacy 'size:X' (colon) to canonical 'size/X' (slash) via aliases in .github/labels.yml. The colon form was an artefact of the previous sizing bot's output; with the bot replaced and namespacing already used everywhere else (kind/, area/, priority/, triage/, lifecycle/, do-not-merge/, security/), the slash form aligns with the rest of the scheme. The alias migration preserves IDs and keeps every existing labelled PR pointing at the renamed entry. pr-size.yaml emits the new 'size/' form from day 1 and accepts either prefix when looking up the previous size label, to handle the brief window between PR merge and the labels-sync run. Verification of zero hard dependencies on Dosu in workflows: only backport.yaml (consumes backport*, applied by humans only) and pull-requests*.yaml + tags.yaml (consume release, applied by tags workflow itself) — none of those labels come from Dosu. The lgtm label (33/33 in last 100 PRs from Dosu, none from humans) is intentionally not replaced in this PR — observe reviewer behaviour for 7 days after Dosu is disabled and decide whether a pull_request_review-on-approve workflow is needed. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .github/labels.yml | 22 +++++--- .github/workflows/pr-size.yaml | 93 ++++++++++++++++++++++++++++++++++ .github/workflows/stale.yaml | 56 ++++++++++++++++++++ 3 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/pr-size.yaml create mode 100644 .github/workflows/stale.yaml diff --git a/.github/labels.yml b/.github/labels.yml index 2ce04130..a5a43fba 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -21,7 +21,7 @@ # area/ subsystem; extensible — add when 3+ open issues exist # do-not-merge/ PR merge blockers # security/ security-finding severity and status (Cozystack-specific) -# size: PR size (auto-applied) +# size/ PR size (auto-applied) # # `aliases:` lets EndBug/label-sync rename existing labels without losing # references on already-tagged issues and PRs. @@ -295,32 +295,38 @@ description: Indicates a non-member PR is safe to run CI on # ────────────────────────────────────────────── -# size: — PR size (auto-applied by sizing bot) +# size/ — PR size (auto-applied by .github/workflows/pr-size.yaml) # ────────────────────────────────────────────── -- name: 'size:XS' +- name: size/XS color: '00ff00' description: This PR changes 0-9 lines, ignoring generated files + aliases: ['size:XS'] -- name: 'size:S' +- name: size/S color: '77b800' description: This PR changes 10-29 lines, ignoring generated files + aliases: ['size:S'] -- name: 'size:M' +- name: size/M color: 'ebb800' description: This PR changes 30-99 lines, ignoring generated files + aliases: ['size:M'] -- name: 'size:L' +- name: size/L color: 'eb9500' description: This PR changes 100-499 lines, ignoring generated files + aliases: ['size:L'] -- name: 'size:XL' +- name: size/XL color: 'ff823f' description: This PR changes 500-999 lines, ignoring generated files + aliases: ['size:XL'] -- name: 'size:XXL' +- name: size/XXL color: 'ffb8b8' description: This PR changes 1000+ lines, ignoring generated files + aliases: ['size:XXL'] # ────────────────────────────────────────────── # security/ — security-finding severity and status diff --git a/.github/workflows/pr-size.yaml b/.github/workflows/pr-size.yaml new file mode 100644 index 00000000..99b27e03 --- /dev/null +++ b/.github/workflows/pr-size.yaml @@ -0,0 +1,93 @@ +name: PR size label + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + +concurrency: + group: pr-size-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + size: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + + // Skip files that should not count as "real diff" — they are produced by tools, + // not authored by humans, and including them lies to reviewers about review effort: + // - vendored Go deps, + // - kubebuilder/openapi inline generated files (zz_generated.{deepcopy,conversion,defaults,openapi}.go), + // - whole code-generated trees from client-gen / lister-gen / applyconfiguration-gen + // and protobuf (any path under a `generated/` directory or ending in `.pb.go`), + // - vendored Helm charts, + // - lockfiles. + const isIgnored = (path) => + path.startsWith('vendor/') || + /\bzz_generated\.[^/]*\.go$/.test(path) || + /(^|\/)generated\//.test(path) || + path.endsWith('.pb.go') || + /^packages\/system\/[^/]+\/charts\//.test(path) || + path === 'go.sum' || path.endsWith('/go.sum') || + path.endsWith('.lock') || path.endsWith('.lockb'); + + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + }); + const lines = files + .filter((f) => !isIgnored(f.filename)) + .reduce((acc, f) => acc + (f.additions || 0) + (f.deletions || 0), 0); + + // Thresholds match the descriptions of size/* labels in .github/labels.yml. + const bucket = + lines <= 9 ? 'XS' : + lines <= 29 ? 'S' : + lines <= 99 ? 'M' : + lines <= 499 ? 'L' : + lines <= 999 ? 'XL' : 'XXL'; + const target = `size/${bucket}`; + + // Match both legacy "size:" and canonical "size/" during the migration window. + const existing = (pr.labels || []).map((l) => l.name); + const oldSizes = existing.filter((n) => n.startsWith('size/') || n.startsWith('size:')); + const alreadyTarget = oldSizes.includes(target); + + // Remove every size/* label that is not the target. Tolerate 404 — concurrent + // runs or manual edits between event payload and execution can race here. + for (const name of oldSizes) { + if (name === target) continue; + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + name, + }); + } catch (e) { + if (e.status !== 404) throw e; + core.info(`label ${name} already gone (404)`); + } + } + + if (alreadyTarget && oldSizes.length === 1) { + core.info(`PR #${pr.number}: ${lines} lines, label already ${target}`); + return; + } + if (!alreadyTarget) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + labels: [target], + }); + } + core.info(`PR #${pr.number}: ${lines} lines -> ${target}`); diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 00000000..77de97f4 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,56 @@ +name: Stale issues + +on: + schedule: + - cron: '37 4 * * *' # daily at 04:37 UTC + workflow_dispatch: + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: stale + cancel-in-progress: false + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v10 + with: + stale-issue-label: lifecycle/stale + stale-pr-label: lifecycle/stale + close-issue-label: lifecycle/rotten + close-pr-label: lifecycle/rotten + # Exempt lists differ on purpose: issues sit longer than PRs by nature. + # An accepted issue (`triage/accepted`) is a known long-tail task, not stale; + # a held PR (`do-not-merge/hold`) is paused intentionally and shouldn't auto-close. + # Priority/security/regression labels protect issues that someone explicitly + # decided to keep visible. + exempt-issue-labels: >- + lifecycle/frozen,epic, + priority/critical-urgent,priority/important-soon, + security/critical,security/high,security/confirmed, + security/triage-needed,security/in-progress, + triage/accepted,kind/regression + exempt-pr-labels: >- + lifecycle/frozen,do-not-merge/hold, + backport,backport-previous + days-before-stale: 60 + days-before-close: 14 + operations-per-run: 100 + remove-stale-when-updated: true + stale-issue-message: | + This issue has had no activity for 60 days and was marked `lifecycle/stale`. + It will be closed in 14 days unless commented or labelled `lifecycle/frozen`. + stale-pr-message: | + This PR has had no activity for 60 days and was marked `lifecycle/stale`. + It will be closed in 14 days unless commented or labelled `lifecycle/frozen`. + close-issue-message: | + Closed because no activity followed the `lifecycle/stale` warning. + Reopen if the issue is still relevant. + close-pr-message: | + Closed because no activity followed the `lifecycle/stale` warning. + Reopen if the change is still wanted. From 1d938a85985ad52cd36e541573f80f543fd5c5a7 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 27 Apr 2026 18:40:34 +0300 Subject: [PATCH 3/3] fix(ci): exempt priority/important-longterm from issue staling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback from coderabbitai on .github/workflows/stale.yaml:37: add priority/important-longterm to exempt-issue-labels. The label is documented in labels.yml as "Important over the long term, but may not be staffed and/or may need multiple releases to complete" — such issues sit idle for 60+ days by design and must not be auto-staled. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 77de97f4..17df206b 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -31,7 +31,7 @@ jobs: # decided to keep visible. exempt-issue-labels: >- lifecycle/frozen,epic, - priority/critical-urgent,priority/important-soon, + priority/critical-urgent,priority/important-soon,priority/important-longterm, security/critical,security/high,security/confirmed, security/triage-needed,security/in-progress, triage/accepted,kind/regression