skill: add update-sponsor for README sponsor list and triage labels - #3842
Merged
Conversation
Contributor
There was a problem hiding this comment.
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, andupdate_sponsor.py) to plan/apply README sponsor block updates and triage label backfills. - Updated
README.rstsponsor 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) |
|
Hardware-in-the-loop (HIL) Test ReportNo HIL run for this push (no affected boards, or hardware testing did not run). |
Contributor
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
hathach
force-pushed
the
update-sponsor
branch
from
August 21, 2026 10:28
3db4a96 to
6108ee7
Compare
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
force-pushed
the
update-sponsor
branch
from
August 24, 2026 02:38
6108ee7 to
8a5a83e
Compare
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.
hathach
force-pushed
the
update-sponsor
branch
from
August 25, 2026 02:49
8a5a83e to
b6dd432
Compare
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
force-pushed
the
update-sponsor
branch
from
August 25, 2026 03:38
b6dd432 to
ffafdea
Compare
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.
Adds
.claude/skills/update-sponsor/, which syncs the README sponsor sections andbackfills triage labels from live GitHub Sponsors data, and fills in the four marker
blocks in
README.rstwith the current sponsors.Every run plans first — a
README.rstdiff and a table of ticket links, authors andpending labels — and asks before touching anything. Remote label writes happen before
the local README edit, so a partial failure leaves
git statushonest. The watermark ina gitignored
state.jsonadvances only after a real apply, so a dry run or a declinedprompt can never cause the next run to skip tickets.
Relationship to
labeler.yml.github/workflows/labeler.ymlalready applies these labels when a ticket is opened, andit 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_TOKENcannot 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_extraare script-only, andthe workflow's
isOwnertest guards only its Adafruit branch, so it still gives themaintainer
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 anorg 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: abare
Prio Top 🚨is emitted by nothing else in the repo, and a barePrio 📌appears on1 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-filesclean. Applied against the live repo: README blocksregenerated, three tickets labelled, and a subsequent
--full-rescanreports 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.