mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #331] [MERGED] feat(area-label): deterministic path sub-labels alongside the classified area #332
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#332
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/331
Author: @mattmillerai
Created: 9/19/2026
Status: ✅ Merged
Merged: 9/19/2026
Merged by: @mattmillerai
Base:
main← Head:matt/area-label-path-sub-labels📝 Commits (1)
233dd8afeat(area-label): deterministic path sub-labels alongside the classified area📊 Changes
10 files changed (+562 additions, -27 deletions)
View changed files
📝
.github/workflows/pr-area-label.yml(+11 -2)📝
.github/workflows/test-area-label.yml(+11 -4)📝
README.md(+1 -1)📝
docs/callers/pr-area-label.md(+42 -1)📝
scripts/area-label/README.md(+38 -4)📝
scripts/area-label/classify-and-apply.sh(+56 -14)📝
scripts/area-label/lib.sh(+102 -0)📝
scripts/area-label/sync-labels.sh(+23 -1)➕
scripts/area-label/tests/test_apply.sh(+200 -0)📝
scripts/area-label/tests/test_lib.sh(+78 -0)📄 Description
ELI-5
The area classifier picks exactly one
area:*label and deletes every other one. That is the right shape for "which area owns this PR?" — but it leaves a subsystem that lives inside another area's service with no way to stay filterable. Both options are bad:The fix is that it was never a one-or-the-other question. "Which area owns this?" is a judgement call, which is why a model makes it. "Does this PR touch X?" is a path question, and a path question has a deterministic answer that can ride alongside the classification instead of competing with it.
So: an optional
sub_labels:block in the taxonomy. Each entry is anarea:*name carryingpaths:globs, matched against the PR's changed files — no model, no extra API call, no extra cost. A PR touching those paths ends up with the classified area and the sub-label.What changed
scripts/area-label/lib.sh— four new pure functions, all hermetically tested:taxonomy_sub_labels— parses the optional block;[]when absent.validate_sub_labels— the gate. Names must be canonicalarea:*slugs, unique, disjoint fromlabels[], each with at least one non-blank glob.glob_to_regex— segment-aware glob → anchored regex.*/?stay within a segment,**crosses them, a leading**/matches zero or more directories. Deliberately the same semantics a workflowpaths:filter uses, so a glob can be copied between the two. Everything else that means something to a regex is escaped.matched_sub_labels— the names whose globs hit at least one changed file, in taxonomy order.scripts/area-label/classify-and-apply.sh— the end state is nowKEEP= the classified area plus every match. That one value drives all three decisions: the no-op short-circuit, the additive POST, and the cleanup's exemption list. The matched names are exempt from thearea:*cleanup; a sub-label that stops matching is not inKEEP, so the same loop retires it automatically when a PR drops those files.The sub-label POST is deliberately separate from the primary one, so the existing "add the classified area FIRST, so no failure can leave a PR unlabeled" guarantee is untouched — a hiccup adding a sub-label can never cost the PR its area.
scripts/area-label/sync-labels.sh— sub-labels are ordinary repository labels; only how they're applied differs. They're created and healed from the taxonomy exactly like the classified ones.Why the names are constrained
^area:[a-z0-9-]+$and disjoint fromlabels[]is load-bearing twice:area:*namespace. These names are handed togh label create --forceand to the label delete path, so an unconstrained name could overwrite or remove an unrelated repository label.A malformed block skips the run with a warning rather than labeling under half a taxonomy, matching the existing
validate_names/validate_vocabgates.Opt-in and backwards compatible: a taxonomy without the key parses as an empty set and behaves exactly as before. No caller change is needed to adopt it.
Tests
scripts/area-label/tests/test_apply.shis new.classify-and-apply.shhad zero coverage — the lib suite only ever exercised the pure core — and this change lands squarely in its label-writing path, which is the part that can do damage across every consuming repo.ghandcurlare stubbed, and the fakeghholds the PR's label set in a file that POST/DELETE actually mutate. That's what makes the assertions end states rather than a transcript diff:area:labels survive the cleanup untouched;sub_labels, HTTP 500) all write nothing.tests/test_lib.shgains 23 assertions over the new functions, including the glob semantics table and the disjointness gate.CI now loops
tests/*.shinstead of naming one file —bash a.sh b.shruns onlya.sh, so a listed second suite would have been silently skipped. This is the convention AGENTS.md already documents for shell suites.Non-vacuity
Mutating
matched_sub_labelsto never match kills 9 assertions intest_apply.shand 7 intest_lib.sh. Restored, both suites are green.Also green:
check_workflow_pins.py,check-org-repo-literals.sh,check_agents_md.py.A
glob_to_regexbug worth noting, since shellcheck caught it and a test now pins it: a lone backslash written as'\\'in acasepattern is two characters and can never match a one-character input, so a literal\in a glob would have gone unescaped into the regex.Docs
scripts/area-label/README.md,docs/callers/pr-area-label.md(including the glob semantics and the honest limits — matching is paths-only, and the changed-file list comes fromgh pr view --json files, which caps at 100), the README catalog row, and the reusable's header contract.🤖 Generated with Claude Code
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.