Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
Aleksei Sviridkin
1d938a8598
fix(ci): exempt priority/important-longterm from issue staling
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 <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-27 18:40:34 +03:00
Aleksei Sviridkin
16a5c721aa
chore: retrigger CI
Build job hit an OCI registry race on the ubuntu-container-disk:latest
manifest push ("Conflicted with another upload of the same manifest").
Empty commit to re-trigger.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-27 18:08:18 +03:00
Aleksei Sviridkin
bc6d147b85
chore(ci): replace Dosu stale and size labeling with native workflows
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 <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-27 18:00:01 +03:00
3 changed files with 163 additions and 8 deletions

22
.github/labels.yml vendored
View file

@ -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

93
.github/workflows/pr-size.yaml vendored Normal file
View file

@ -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}`);

56
.github/workflows/stale.yaml vendored Normal file
View file

@ -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,priority/important-longterm,
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.