Skip to content

✨ feat(ci): tier-based change classification workflow (labels only)#8251

Merged
clubanderson merged 1 commit intomainfrom
feat/tier-classifier
Apr 16, 2026
Merged

✨ feat(ci): tier-based change classification workflow (labels only)#8251
clubanderson merged 1 commit intomainfrom
feat/tier-classifier

Conversation

@clubanderson
Copy link
Copy Markdown
Collaborator

Summary

Second of four PRs from the fullsend-ai/fullsend automation evaluation. Adds a tier-classifier workflow that labels every PR with exactly one tier/* label based on the file paths touched, so reviewers can prioritize and (in a follow-up PR) tier/0 PRs can auto-merge.

Adapted from fullsend-ai/fullsend's tier-based change classification pattern — graduated autonomy where safe changes (dep bumps, docs, generated files) can fast-track and risky changes (workflows, auth, RBAC) require multi-maintainer sign-off.

What lands here

  • .github/workflows/tier-classifier.ymlpull_request_target trigger, reads the rules file, calls pulls.listFiles, classifies, applies the label. Uses actions/github-script@v8 — no new dependencies. Hand-parses the rules YAML (tiny subset — three top-level keys with lists) to avoid pulling in a yaml dep.
  • .github/tier-classifier-rules.yml — path globs per tier as a data file, so rules can be tweaked without editing the workflow.
  • CONTRIBUTING.md — new ## Change Tiers section explaining what each label means and where the rules live.

The four tiers

Label Covers
tier/0-automatic package-lock.json, go.sum, go.mod, docs/**, README.md, **/*.md, web/src/locales/**/*.json, snapshots
tier/1-lightweight **/__tests__/**, **/*.test.ts[x], **/*.spec.ts[x], **/*_test.go, web/e2e/**, editor config
tier/2-standard Default — everything not matched by another tier
tier/3-restricted CODEOWNERS, .github/workflows/**, .github/actions/**, pkg/auth/**, pkg/api/middleware/**, pkg/api/handlers/auth*, Helm RBAC/ClusterRole/values, docs/security/**, .goreleaser*

Classification logic

  • Tier 3 wins if ANY file matches a tier-3 pattern. A single workflow edit pulls the whole PR up to restricted, even if the other 50 files are harmless.
  • Tier 0 and Tier 1 require EVERY file to match. Conservative — any unclassified file bumps the PR to tier 2.
  • Tier 2 is the implicit default.

Label auto-creation

The labels are created on first use by the workflow itself (green for tier/0, light blue for tier/1, yellow for tier/2, red for tier/3) so maintainers don't need to pre-seed them.

Scope: labels only, no enforcement yet

This PR only labels. A follow-up PR will wire auto-merge for tier/0-automatic once the rules have proven accurate on real PRs for a week. Rolling it out blind is risky — wait for empirical validation of the allowlist.

Test plan

  • npm run build — passes
  • python3 -c "import yaml; yaml.safe_load(...)" — both YAML files parse
  • Open a docs-only PR → tier/0-automatic label applied
  • Open a test-only PR → tier/1-lightweight label applied
  • Open a PR touching web/src/components/cards/...tier/2-standard label applied
  • Open a PR touching .github/workflows/*.ymltier/3-restricted label applied
  • Open a PR touching docs + a workflow → tier/3-restricted wins

🤖 Generated with Claude Code

Second of four PRs from the fullsend-ai/fullsend automation
evaluation. Labels every PR with exactly one tier/* label based on
the paths touched, so reviewers can prioritize and (in a follow-up
PR) tier/0 PRs can auto-merge.

Adds:
- .github/workflows/tier-classifier.yml — pull_request_target trigger,
  reads the rules file, calls pulls.listFiles, classifies, applies
  the label. Uses actions/github-script@v8 — no new dependencies.
  Hand-parses the rules YAML (tiny subset) to avoid pulling in a
  yaml dep.
- .github/tier-classifier-rules.yml — path globs per tier. Data file
  so the rules can be tweaked without editing the workflow itself.
  Covers:
    tier/0-automatic   lockfiles, docs, *.md, i18n, snapshots
    tier/1-lightweight test-only, editor config
    tier/2-standard    (implicit default — no patterns)
    tier/3-restricted  CODEOWNERS, .github/workflows/**, pkg/auth,
                       pkg/api/middleware, Helm RBAC, docs/security,
                       GoReleaser config

Classification logic: tier/3 wins if ANY file matches (a single
dangerous file pulls the whole PR up). tier/0 and tier/1 require
EVERY file to match (conservative — any unclassified file bumps
the PR to tier 2).

Also:
- CONTRIBUTING.md — new "Change Tiers" section explaining the four
  labels and where the rules live.

This PR is labels-only. A follow-up will wire auto-merge for
tier/0-automatic once the rules have proven accurate on real PRs.

The label itself is auto-created on first use with a sensible color
(green for tier/0, light blue for tier/1, yellow for tier/2, red
for tier/3) so maintainers don't need to pre-seed them.

Signed-off-by: Andrew Anderson <[email protected]>
Copilot AI review requested due to automatic review settings April 15, 2026 23:57
@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Apr 15, 2026
@kubestellar-prow
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign clubanderson for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 15, 2026

Deploy Preview for kubestellarconsole canceled.

Name Link
🔨 Latest commit cd27883
🔍 Latest deploy log https://app.netlify.com/projects/kubestellarconsole/deploys/69e02602ab5520000866206f

@github-actions
Copy link
Copy Markdown
Contributor

👋 Hey @clubanderson — thanks for opening this PR!

🤖 This project is developed exclusively using AI coding assistants.

Please do not attempt to code anything for this project manually.
All contributions should be authored using an AI coding tool such as:

This ensures consistency in code style, architecture patterns, test coverage,
and commit quality across the entire codebase.


This is an automated message.

@kubestellar-prow kubestellar-prow Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Apr 15, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an automated “tier-based” PR labeling system so every pull request is classified into exactly one tier/* label based on touched paths, helping reviewers triage risk and review priority.

Changes:

  • Introduces a new pull_request_target GitHub Actions workflow that computes and applies a single tier/* label per PR.
  • Adds a rules data file (.github/tier-classifier-rules.yml) defining tier glob patterns without requiring extra workflow dependencies.
  • Documents the tier system and classification semantics in CONTRIBUTING.md.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
CONTRIBUTING.md Documents tier meanings and how classification works.
.github/workflows/tier-classifier.yml Implements the classifier + label application/auto-creation logic.
.github/tier-classifier-rules.yml Defines tier glob patterns consumed by the workflow.

github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const path = require('path');
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const path = require('path'); is unused in this script. Removing it will keep the workflow script minimal and avoid suggesting there’s path manipulation happening.

Suggested change
const path = require('path');

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +131
// A PR is classified to the HIGHEST tier any of its files matches.
// Default (no match anywhere) = tier/2-standard.
// -----------------------------------------------------------------
const tierOrder = [
'tier/3-restricted',
'tier/2-standard',
'tier/1-lightweight',
'tier/0-automatic',
];
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The classification header comment says the PR is classified to the “highest tier any file matches”, but the actual logic uses special “ALL files must match” rules for tier/0 and tier/1. Also tierOrder is declared but never used. Update the comment to reflect the real logic and remove tierOrder (or use it) to avoid future drift.

Suggested change
// A PR is classified to the HIGHEST tier any of its files matches.
// Default (no match anywhere) = tier/2-standard.
// -----------------------------------------------------------------
const tierOrder = [
'tier/3-restricted',
'tier/2-standard',
'tier/1-lightweight',
'tier/0-automatic',
];
// tier/3-restricted applies when ANY changed file matches a tier 3
// pattern. Otherwise, tier/0-automatic applies only when EVERY
// changed file matches a tier 0 pattern, and tier/1-lightweight
// applies only when EVERY changed file matches either a tier 0 or
// tier 1 pattern. If none of those conditions are met, fall back to
// tier/2-standard.
// -----------------------------------------------------------------

Copilot uses AI. Check for mistakes.
Comment on lines +191 to +197
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: classified,
color: colors[classified] || 'ededed',
description: descriptions[classified] || '',
}).catch(() => {}); // Ignore if it was created by another run in parallel.
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createLabel(...).catch(() => {}) swallows all errors, not just the expected “already exists” race. This can hide real failures (e.g., insufficient permissions, invalid color/description) and makes troubleshooting hard. Consider only ignoring the specific 422/"already exists" case (or checking e.status) and logging/rethrowing unexpected errors.

Suggested change
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: classified,
color: colors[classified] || 'ededed',
description: descriptions[classified] || '',
}).catch(() => {}); // Ignore if it was created by another run in parallel.
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: classified,
color: colors[classified] || 'ededed',
description: descriptions[classified] || '',
});
} catch (createLabelError) {
if (createLabelError.status === 422) {
core.info(`Label ${classified} already exists; continuing.`);
} else {
core.warning(`createLabel failed: ${createLabelError.message}`);
throw createLabelError;
}
}

Copilot uses AI. Check for mistakes.

permissions:
contents: read
pull-requests: write
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workflow uses github.rest.issues.addLabels/removeLabel/createLabel, which requires issues: write permission. With only pull-requests: write, these API calls can fail with 403, preventing tier labels from being applied. Add issues: write (and consider reducing pull-requests to read since this job only reads PR files and writes labels).

Suggested change
pull-requests: write
pull-requests: read
issues: write

Copilot uses AI. Check for mistakes.
jobs:
classify:
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name != ''
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if: github.event.pull_request.head.repo.full_name != '' condition is always true for real PR events, so it adds confusion without changing behavior. Consider removing it, or replace it with a concrete guard (e.g., skip drafts/bots) if that was the intent.

Suggested change
if: github.event.pull_request.head.repo.full_name != ''

Copilot uses AI. Check for mistakes.
@clubanderson clubanderson merged commit cfdf3e6 into main Apr 16, 2026
48 of 49 checks passed
@kubestellar-prow kubestellar-prow Bot deleted the feat/tier-classifier branch April 16, 2026 00:06
@github-actions
Copy link
Copy Markdown
Contributor

Thank you for your contribution! Your PR has been merged.

Check out what's new:

Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey

@github-actions
Copy link
Copy Markdown
Contributor

Post-merge build verification passed

Both Go and frontend builds compiled successfully against merge commit cfdf3e6921f39c2d3d230281f8036340f07df3d4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants