feat: label PRs by zone and issues by area, and enforce both lists in CI - #202
Merged
Conversation
Ports the label automation from UTMIST/UTMIST and settles the zone/area split that docs/CODE-OWNERSHIP.md left open. Zones label PRs, areas label issues. The two are different axes and, more importantly, different cardinality: a file has exactly one zone (that is what pr-zone-check nags about), while 26 of the 56 labelled issues in this repo already carry two or three areas. Collapsing them would make zones meaningless or areas unusable, so an issue is never given a zone and a PR is never given an area. New automation: zone-label.yml zone: * on a PR, from the changed paths area-label-issues.yml area/* on an issue, from the form's Area dropdown pr-size-label.yml size/xs .. size/xl, ported unchanged label-consistency.yml + scripts/check-labels.mjs check-labels.mjs closes the gap CODE-OWNERSHIP.md named: the zone list is copied across five files and the area list across four, and nothing checked they agreed. Both drift incidents so far traced to that. It also probes one path per zone through labeler.yml's globs, so a catch-all bucket missing a `!` negation (which double-labels) or a mistyped glob (which labels nothing) fails the build rather than looking correct. The three issue templates become issue forms so the Area dropdown can be read mechanically. blocked-ready-automation.yml gains a parser for the form-rendered "### Blocked by" section; the pre-forms inline shape still works, and produces identical results on all 17 existing issue bodies. Not enforcing yet: label-consistency is not a required status check. Deliberately un-path-filtered so it can become one — GitHub creates no check run for a workflow skipped by a paths filter, which would leave required checks waiting forever. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
PRs get a
zone: *label from the paths they touch; issues getarea/*labels from a new Area dropdown in the issue forms. A CI check fails the build when either list drifts between the files that carry it.Ports the label automation from
UTMIST/UTMISTand settles the zone/area questiondocs/CODE-OWNERSHIP.mdleft open.Why
docs/CODE-OWNERSHIP.mdnamed the gap directly:Both drift incidents so far trace to that. In August 2026 five service directories had no zone entry, collapsed into the
services/*catch-all, and a PR spanningllmandmeetingcounted as a single zone and never warned.On zones vs areas: they are different axes, but the deciding factor is cardinality. A file has exactly one zone — that is the entire basis of
pr-zone-checknagging about multi-zone PRs. Areas are multi-valued: 26 of the 56 labelled issues in this repo already carry two or three. Collapsing them would make zones meaningless or areas unusable. So an issue is never given a zone, and a PR is never given an area.Zone
.github·docs·scripts·rootThis PR spans four zones and
pr-zone-checkwill warn. It can't be split usefully: the enforcement (scripts/), the thing enforced (.github/), and the documentation of the rule (docs/,README.md,AGENTS.md,Makefile) have to land together orlabel-consistencyfails on its own first run.What's new
zone-label.yml+labeler.ymlzone: *on a PR from changed pathsarea-label-issues.ymlarea/*on an issue from the form's Area dropdownpr-size-label.ymlsize/xs…size/xl, ported unchangedlabel-consistency.yml+scripts/check-labels.mjsISSUE_TEMPLATE/*.mdbecome issue forms so the Area dropdown is machine-readable.blocked-ready-automation.ymlgains a parser for the form-rendered### Blocked bysection.How to verify
make labels # same check CI runs node scripts/check-labels.mjsDrift detection was tested by injecting eight failures into a copy of the tree — new service directory with no zone, missing CODEOWNERS line, dropped zone from the PR template, mistyped labeler glob, missing
!negation, missing area from the array, missing area from a form, removed escape option. All eight fail with an actionable message; the clean tree passes.The
labeler.ymlglobs were cross-checked againstzone_for()on all 497 tracked files plus synthetic paths forservices/other,packages/otherandroot— every path resolves to exactly one zone, identical under both mechanisms.Compatibility
The blocker parser handles both shapes:
### Blocked by→#40, #42Blocked by: #40, #42Verified to produce identical results to the previous parser on all 17 existing issue bodies, so no issue changes blocked/ready state as a result of this PR. 20 parser unit tests cover CRLF,
_No response_, and the section-is-last case.Checklist
stagingand targetingstaging.make labelsclean; all workflow/form YAML parses.CODE-OWNERSHIP.md,README.md,AGENTS.md,Makefile.Deployment notes
zone: *(BFD4F2), 5size/*(UTMIST's colours), plusblocked/ready, whichblocked-ready-automationreferenced but which did not exist. Already applied to the repo.label-consistencyis not a required status check. It is deliberately un-path-filtered so it can become one: GitHub creates no check run for a workflow skipped by apaths:filter, so a required check would wait forever.Anything you're unsure about
None of this has executed on GitHub yet, and the UTMIST originals it derives from have never run either (added 2026-08-14, 0 runs).
pr-size-label.ymlis the exception — 6 successful upstream runs, copied unchanged.Because of how GitHub resolves workflow files, only
label-consistencycan prove itself on this PR.zone-labelandpr-size-labelusepull_request_target(workflow read from the base branch),area-label-issuesusesissues(read from the default branch), and the issue forms themselves are read from the default branch — so those four are only exercised after this merges. Worth a throwaway issue and one small PR immediately afterwards.The
labeler.ymlglobs are the piece I could verify least directly: the cross-check above models minimatch rather than running it.🤖 Generated with Claude Code