Skip to content

skill: add update-sponsor for README sponsor list and triage labels - #3842

Merged
hathach merged 1 commit into
masterfrom
update-sponsor
Aug 25, 2026
Merged

skill: add update-sponsor for README sponsor list and triage labels#3842
hathach merged 1 commit into
masterfrom
update-sponsor

Conversation

@hathach

@hathach hathach commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Adds .claude/skills/update-sponsor/, which syncs the README sponsor sections and
backfills triage labels from live GitHub Sponsors data, and fills in the four marker
blocks in README.rst with the current sponsors.

python3 .claude/skills/update-sponsor/update_sponsor.py --rules     # the policy
python3 .claude/skills/update-sponsor/update_sponsor.py --dry-run   # preview
python3 .claude/skills/update-sponsor/update_sponsor.py             # preview, then confirm

Every run plans first — a README.rst diff and a table of ticket links, authors and
pending labels — and asks before touching anything. Remote label writes happen before
the local README edit, so a partial failure leaves git status honest. The watermark in
a gitignored state.json advances only after a real apply, so a dry run or a declined
prompt can never cause the next run to skip tickets.

Relationship to labeler.yml

.github/workflows/labeler.yml already applies these labels when a ticket is opened, and
it stays the source of truth — the label rules here mirror it, including first-match-wins
rather than a union. This script exists to backfill the two things the workflow cannot
reach: tickets older than it, and private sponsors, which its GITHUB_TOKEN cannot see.

The two have since drifted in three places worth a follow-up that puts the policy in one
file both read: org-member inheritance and adafruit_members_extra are script-only, and
the workflow's isOwner test guards only its Adafruit branch, so it still gives the
maintainer Prio 📌 as a collaborator.

Privacy

Two decisions here were driven by measurement rather than intent, and both are worth
knowing:

Adafruit membership comes from orgs/adafruit/public_members, never /members. As an
org admin the maintainer's token sees 35 members where the public list has 13. Labelling
one of the 22 concealed people "Reported by an Adafruit member" publishes exactly what
they chose to hide.

A private sponsor receives no label at all. Delivering the tier's triage perk while
withholding the public Sponsor 💖 leaves a label set that is itself the disclosure: a
bare Prio Top 🚨 is emitted by nothing else in the repo, and a bare Prio 📌 appears on
1 of 204 open issues and 0 of 873 open discussions. Any label applied only to private
sponsors identifies them. Their README entry is masked to a 3-character prefix with no
avatar and no profile link; the priority perk has to be honoured off-ticket, which is
worth reflecting in the Word tier description.

Verification

pre-commit run --all-files clean. Applied against the live repo: README blocks
regenerated, three tickets labelled, and a subsequent --full-rescan reports no change,
confirming the pass is idempotent. Rendering paths not exercised by the current sponsor
set (avatars, company logos, RST escaping of free-form display names, duplicate-login
dedup) are covered by direct calls into render() and checked against docutils.

Copilot AI lite review requested due to automatic review settings August 21, 2026 09:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new .claude skill (update-sponsor) that pulls live GitHub Sponsors data to (1) regenerate the sponsor blocks in README.rst and (2) backfill sponsor/priority/Adafruit triage labels on open issues/PRs/discussions, with a plan/preview-first workflow and a persisted scan watermark.

Changes:

  • Added .claude/skills/update-sponsor/ (skill docs, config, and update_sponsor.py) to plan/apply README sponsor block updates and triage label backfills.
  • Updated README.rst sponsor marker blocks with the currently-detected sponsors (including a masked private sponsor entry).
  • Ignored the skill’s persisted state file (.claude/skills/update-sponsor/state.json) via .gitignore.

Reviewed changes

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

Show a summary per file
File Description
README.rst Populates WORD/BYTE sponsor marker blocks with current sponsor output.
.gitignore Ignores the update-sponsor skill’s local state.json watermark file.
.claude/skills/update-sponsor/update_sponsor.py New script implementing sponsor fetch, README block regeneration, and triage-label backfill planning/apply.
.claude/skills/update-sponsor/SKILL.md New skill documentation describing intended usage and safety/preview workflow.
.claude/skills/update-sponsor/config.json New hand-edited config for exclusions and org-sponsor member inheritance.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +186 to +187
added = [l for l in difflib.unified_diff(original.splitlines(),
new_text.splitlines(), n=0) if l.startswith("+")]
Comment on lines +313 to +317
'issues(first:1,orderBy:{field:CREATED_AT,direction:DESC}){nodes{number}}'
'pullRequests(first:1,orderBy:{field:CREATED_AT,direction:DESC}){nodes{number}}'
'discussions(first:1,orderBy:{field:CREATED_AT,direction:DESC}){nodes{number}}}}') % (OWNER, NAME)
r = graphql(q)["repository"]
return max((v["nodes"][0]["number"] for v in r.values() if v["nodes"]), default=0)
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected across 2512 targets. View Project Dashboard →

@github-actions

Copy link
Copy Markdown

Hardware-in-the-loop (HIL) Test Report

No HIL run for this push (no affected boards, or hardware testing did not run).

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

pull Bot pushed a commit to jnnycn007/tinyusb that referenced this pull request Aug 23, 2026
Rule 17 (unclassified -> full on both axes) is the fail-open net for paths nobody
anticipated, and it must stay that way: a wrong `full` costs runner minutes and is
visible in the run, a wrong `empty` costs a merged regression and is invisible.
But nothing in the tree should REACH it, and 254 tracked files did.

The cost was real. PR hathach#3842 changed a skill, a README and .gitignore; .gitignore
matched no rule, so both axes went full and 74 cmake legs span up runners to do
checkout + toolchain + get_deps before skipping the build, plus the whole 30-board
rig. Three changes, none of which touch rule 17 itself:

1. _META_RE - repo metadata and tooling no Build step reads: .gitignore,
   .gitattributes, .clang-format, .codespellrc, .pre-commit-config.yaml,
   .readthedocs.yaml, .PVS-Studio/, .idea/, sonar-project.properties, the
   packaging manifests, CMakePresets, udev rules, test/{fuzz,unit-test} (their own
   jobs build those), the non-build .github/ files, and the tools/*.py scripts no
   build invokes. Deliberately NOT included, and still full: .circleci/**,
   .github/workflows/build*.yml, .github/actions/**, .github/scripts/**. The line
   is "does a Build step read this", not "is it source".

2. Rules 15 and 16 now match what they already claimed. Row 15 names
   examples/<role>/CMakeLists.txt and the regex never had it; row 16 says
   tools/build*.py but anchored tools/build\.py$. Both got the right answer only
   because rule 17 caught them on the way past. Also names their siblings -
   family_support.mk, family_rules.mk, src/CMakeLists.txt, src/tinyusb.mk - and
   .circleci/**, which generates the whole CircleCI matrix and was in no row at all.

3. src/typec/** gets row 12b. It is listed unconditionally by both build systems
   but its body is `#if CFG_TUC_ENABLED`, which only examples/typec/power_delivery
   sets - the same shape as the class rule, so the same answer: the examples that
   enable it (stm32g4 and stm32u5 after the buildability prune), and nothing on the
   rig, which runs no typec test. It was force-fulling 82 families and all 30 boards.

TestNoTrackedFileIsUnclassified walks every tracked file and asserts none reaches
rule 17, on both axes - 254 -> 0. Verified it fails when a new unclassified path
appears. That turns 17 into what it should be: unreachable for anything in the
tree, so it fires only for genuinely new shapes, and the author is told to write
the row rather than letting the fall-through pick an answer for them.

test_full_paths used sonar-project.properties as its stand-in for "unclassified";
that is now metadata, so the case moved to the new
test_repo_metadata_is_not_a_build_input, with test_the_build_machinery_is_still_full
pinning the other side of the line.
hathach added a commit that referenced this pull request Aug 25, 2026
.build.families is read twice in the build-extras block: as a `|`-joined regex for
the metrics artifact pattern, and implicitly as "is anything selected". An EMPTY
list and one REJECTED by the charset guard both leave the regex empty and mean
opposite things, but the branch tested only `-z "$FAMILY_REGEX"` - so every
nothing-selected PR took the fall-open path and DISCARDED the correct all-empty
matrix ci_set_matrix had just produced.

Both merged PRs show it: #3842 (a skill, README.rst and .gitignore) and #3840
(test/hil/** plus docs) each rebuilt all 74 cmake legs after the selector had
correctly chosen no families. The HIL axis was right in both - rig jobs skipped on
#3842, full rig on #3840 per rule 2 - because it never passes through this block,
and no UNSCOPED marker appeared because ci_set_matrix behaved perfectly; the
override happens in the shell afterwards. That combination made it look like the
classifier was at fault when it was not.

Now branches on which case it is: a rejected list falls open exactly as before (all
three values drop together), while an empty list keeps the all-empty matrix so
every leg skips, sets build_filtered=false because nothing was built and there is
nothing to compare a baseline against, and leaves EXAMPLE_MAP at '{}'.

Renames FAM_* to FAMILY_* - FAM meant hw/bsp board family, and the abbreviation
read like it might be something else in a file that also says "family" for the
per-family example map and the get_deps family tokens.

The block had no test at all, which is how this shipped twice. test_ci_metrics now
extracts it from build.yml and runs it for real against all three shapes; the
empty case fails with `129 != 0` against master, and the scoped case with
`'' != 'stm32f4|rp2040'`. The probe quotes its shell assignments with shlex.quote,
so a TMPDIR containing a space cannot fail it for an unrelated reason.
hathach added a commit that referenced this pull request Aug 25, 2026
…3845)

ci: an empty selection must build nothing, plus selector follow-ups

A PR whose build axis legitimately selected nothing rebuilt everything.
build.yml reads .build.families twice - as a |-joined regex, and implicitly
as "is anything selected" - but tested only -z "$FAMILY_REGEX", which an
empty list and a charset-rejected one both satisfy while meaning opposite
things. ci_set_matrix had already returned the correct all-empty matrix;
the fall-open branch discarded it. #3842 and #3840 each spent 74 cmake legs
on it. Branch on the two cases instead, rename FAM_* to FAMILY_*, and cover
the block with a test that extracts it from build.yml and executes it - it
had no test at all, which is how this shipped through two merges.

Follow-ups to the same machinery: glob.escape the repo root at five sites,
so a checkout path containing [ or * stops failing closed; drop the ci-full
label, read after the matrix was already computed and so never functional;
delete 13 mcu:MKL25ZXX / mcu:SAME5X skip tokens matching no board; carry the
rule table in the module docstring, guarded against drift; and pin six
selection behaviours a mutation pass proved untested.

Cut the selector's cost 1.8x (26.0s -> 14.6s) with 0 divergences over 260
paths, and stop scoping the membrowse upload by the PR example filter.
Sync README.rst's four sponsor marker blocks and the sponsor/priority
labels from live GitHub Sponsors data.

Sponsors are listed chronologically, oldest first. A private sponsor is
masked to the first 3 characters of their login plus a fixed four stars,
with no avatar and no profile link, and never receives the public
Sponsor label -- only the priority one, so the tier perk is delivered
without disclosing a sponsorship they chose to keep private.

Labels start at the Word tier, matching the Sponsor label's own "Word+
tier" description. Adafruit members are tagged from the org's public
member list; hathach is excluded there, or every self-authored ticket
would be tagged as an incoming Adafruit report.

Every run previews a README diff and a table of ticket links, authors
and pending labels, then asks before applying. The watermark in the
gitignored state.json advances only after a real apply, so a dry run or
a declined prompt cannot cause the next run to skip tickets.
@hathach
hathach merged commit 60194f8 into master Aug 25, 2026
23 of 24 checks passed
@hathach
hathach deleted the update-sponsor branch August 25, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants