From 286ddbf6f8c43efbb295d33d717bb9cb213421b4 Mon Sep 17 00:00:00 2001 From: TzuHsuan <96853116+TzuH-Hsu@users.noreply.github.com> Date: Wed, 16 Sep 2026 13:35:27 +0800 Subject: [PATCH] feat: issue labeler logic moves to scripts/issue-labeler.js with a durable test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The labeler was the one workflow carrying logic — ~150 lines of JS inline in the YAML, testable only by extracting it by hand. The logic is now a plain Node module with pure functions and one run() entry point; the workflow checks out the default branch (pinned checkout, no credentials) and calls it in two lines. labels.yml is read from the checkout instead of the API. scripts/issue-labeler.test.js pins the behaviour (12 cases, node built-ins only) and runs from make check; in CI a missing node fails, locally it skips loudly. Hygiene rule 1 and AGENTS.md name the shape; ADR-0008 records it; check-label-forms.sh follows the constants to the script. Closes #49 Co-Authored-By: Claude Opus 5 --- .github/CODEOWNERS | 1 + .github/workflows/issue-labeler.yml | 229 ++---------------- AGENTS.md | 4 +- CONTRIBUTING.md | 1 + Makefile | 1 + ...DR-0008-event-workflow-logic-in-scripts.md | 30 +++ docs/adr/README.md | 1 + docs/template/architecture.md | 7 +- docs/template/design-principles.md | 2 +- docs/template/upgrading.md | 2 +- scripts/check-issue-labeler.sh | 29 +++ scripts/check-label-forms.sh | 45 +++- scripts/issue-labeler.js | 216 +++++++++++++++++ scripts/issue-labeler.test.js | 160 ++++++++++++ skills/github-actions-hygiene/SKILL.md | 5 +- 15 files changed, 508 insertions(+), 225 deletions(-) create mode 100644 docs/adr/ADR-0008-event-workflow-logic-in-scripts.md create mode 100755 scripts/check-issue-labeler.sh create mode 100644 scripts/issue-labeler.js create mode 100644 scripts/issue-labeler.test.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 618c7cc..4c67522 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,6 +23,7 @@ # Example: Require infrastructure ownership # /scripts/bootstrap.sh @infra-owner +# /scripts/issue-labeler.js @infra-owner # Example: Solo dev auto-request # * @your-username diff --git a/.github/workflows/issue-labeler.yml b/.github/workflows/issue-labeler.yml index e22d8c4..b053984 100644 --- a/.github/workflows/issue-labeler.yml +++ b/.github/workflows/issue-labeler.yml @@ -1,18 +1,21 @@ name: Issue labeler -# Syncs priority:*, area:* and the four type:* SUBTYPE labels from -# the issue form fields on open/edit. The issue body is untrusted input — it is -# only ever read inside the actions/github-script JS sandbox -# (context.payload.issue.body), never interpolated into a shell `run:` step. +# Syncs priority:*, area:* and the four type:* SUBTYPE labels from the issue +# form fields on open/edit. The logic lives in scripts/issue-labeler.js +# (ADR-0008); this file only checks out the default branch and calls it, so +# it carries no adopter values and can be replaced whole on upgrade. The +# script is tested by `make check` (scripts/check-issue-labeler.sh). # -# "the four type:* subtype labels", not "type:*": the type: namespace also -# carries labels no form produces, so this workflow owns an exact list rather -# than a prefix. See FORM_MANAGED_TYPES below. +# The issue body is untrusted input — it is only ever read inside the +# actions/github-script JS sandbox (context.payload.issue.body), never +# interpolated into a shell `run:` step, and every label the script adds is +# validated against an allowlist the body cannot influence. # -# area:* is the family adopters are told to rename, so its allowlist is not -# written here: it is read from .github/labels.yml on the default branch at -# run time (see loadAllowedAreas below). This file carries no adopter values -# and can be replaced whole on upgrade. +# Why a checkout on an issue event: the script and .github/labels.yml (the +# run-time source of the area:* allowlist) live in the repository. On `issues` +# events GITHUB_SHA is the head of the default branch, so this checks out +# exactly the code and label declarations that were merged — never a PR +# branch's copy. on: issues: @@ -34,203 +37,17 @@ jobs: timeout-minutes: 5 permissions: issues: write - contents: read # getContent on .github/labels.yml + contents: read # checkout of the default branch steps: + - name: Check out the default branch + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 1 + - name: Sync form-managed labels uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - // Parses the GitHub issue-form markdown body (###