Uh oh!
There was an error while loading. Please reload this page.
feat(pm): derive test-convention gates by change kind in dispatch-gates - #8159
Merged
Conversation
…: labels `scripts/pm/dispatch-gates.mjs` matches gate families to a card's file surface by the path literals discoverable in each gate's own source. Two gates redden almost every PR that adds test code and can never be reached that way, because they compute their population instead of naming it: - check:query-options-erasure — its test-surface ceiling counts sites in *.test/*.spec files; - check:type-check-coverage — TEST_DEBT ratchets a package's test-layer type errors. Both sit permanently in the "repo-wide / undetermined" bucket, so no per-card gate list could name them and both failures were only ever found by CI. Derive them by change KIND instead — the convention the script's own closing note already prescribes — and print them under a heading distinct from the path-matched list, so they read as convention-triggered leads rather than path matches. The predicate is aligned with what the two gates themselves count (filename infix, never directory), every name is resolved against the families discovered at runtime so a renamed gate reports itself STALE, and the closing note now derives its convention list from the table rather than restating it. Rider: seed the `target:<major>` release-board family in ensure-pm-labels.sh, which seeded no `target:` vocabulary while all three backlogs use it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01139NJ9Wg5pFeZi1Zh8WLg6
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
This was referenced Aug 12, 2026
hotlong
marked this pull request as ready for review
August 12, 2026 18:37
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Fixes#8117
Scoped to direction 2 of the finding, exactly as the finding-grading comment (2026-08-12 17:59Z) fixed it: derive by change kind, not a hand-maintained repo-wide shortlist (direction 1) and not nothing (direction 3).
The problem
scripts/pm/dispatch-gates.mjsmatches gate families to a card's file surface by the path literals discoverable in each gate's own source. Two gates redden nearly every PR that adds test code, and the path derivation can never reach either — both compute their population instead of naming it, so their sources carry no literal to match:check:query-options-erasure— its test-surface ceiling counts sites in*.test/*.specfiles;check:type-check-coverage— TEST_DEBT ratchets a package's test-layer type errors.Both sit permanently in the "repo-wide / undetermined" bucket, so no per-card gate list could name them, and both failures were only ever found after CI ran.
What changed
dispatch-gates.mjsnow derives these by change kind and prints them under a heading distinct from the path-matched list, so they read as convention-triggered leads rather than path matches. Three things keep it from becoming the rot-prone list direction 1 was rejected for:*.test.*/*.spec.*), never by directory —QUERY_OPTIONS_TEST_GLOBSineslint.config.mjsandTEST_FILE = /\.(test|spec)\.tsx?$/incheck-type-check-coverage.mjs. So a helper at__tests__/fixtures.tsdeliberately does not qualify: neither gate counts it, and matching directories would name two gates that cannot move. The extension set is the union of the two gates' own (one also counts.mts/.cts); these are leads to run locally, not verdicts.The closing note is touched up so it no longer describes as PM judgment a convention the script now derives itself — and it builds its convention list from the table rather than restating it, so the prose cannot drift from the code.
Considered and rejected: deriving the pair from sources
The tempting generalisation is to scan every discovered check script for
*.test.ts-shaped literals and call those the test-sensitive gates. Measured against this tree, that names 22 families, because scripts mention test paths in their fixtures, self-tests and comments. "Mentions a test file" is not "counts test files", and 22 leads is the same as none. Hence a named table of two, with the three properties above as its price.Rider (declared in the grading comment)
scripts/pm/ensure-pm-labels.shseeded notarget:vocabulary while all three backlogs use it. Seeded now for the current release window, three repos, following the file's existing loop shape.target:v17verified present in all three repos today (colourededed), so the seeding is a no-op there — creation is create-if-missing and never edits an existing label's colour or description. Only the current window is seeded: a major seeded ahead of its window puts an empty board into every autocomplete with no producer behind it, the same harm as the retired-lane caveat above it. Thetarget:family is not a retired lane, so that caveat is unaffected.target:meets the file's own invariant that every label it seeds is consumed by a named query: SKILL.md's label table carriestarget:(major), andreferences/seat-post-protocol.mdnames the consumer as three per-repo queries,label:target:(major) is:open.Verification
Self-test grew from 17 to 28 cases (
node scripts/pm/dispatch-gates.mjs --self-test, all pass), pinning the predicate in both directions, the emission, and the STALE branch.Before, on a real test path:
After, same path — the path-matched list is unchanged, the new section is additive:
After, on a non-test path (this PR's own two files) — no convention section, and the closing note still reads correctly with nothing above it:
Reverse verification of the rot detector, direction predicted before running: rename one gate in the table to a name no workflow runs, and the live run must print STALE rather than crashing or going silent. Observed exactly that, with the sibling still resolving:
Restored from the commit afterwards;
git statusclean, so the restore is byte-identical.Gates run locally:
npx eslint scripts/pm/dispatch-gates.mjs(exit 0),pnpm check:nul-bytes(7422 files, OK), plus the two pm-adjacent gatescheck:pm-skill-ratchetandcheck:pm-skill-id-lint(both pass, unaffected). Re-derived on the actual changed paths after the change: no path-matched family, so the named list stands.No changeset: internal PM tooling, nothing user-visible ships from either file.
Generated by Claude Code