Uh oh!
There was an error while loading. Please reload this page.
fix(pm): derive dispatch-gates watch hints from the module body, not from comments and self-test fixtures (#8478) - #8511
Merged
Conversation
…8478) `extractWatchHints` scanned a check script's whole source for quoted path-ish spans, so a path a gate merely NAMED — in a header comment, or as a self-test fixture — was printed in the MATCHED column as a path the gate operates on. That column is what a dispatch prompt pastes, and its error is one-directional in the expensive direction: a missing lead costs one card one CI round, a fabricated one is pasted into every prompt whose file surface brushes a fixture. The scan now reads the module body: comments are masked (direction 2) and the body of every top-level self-test function is masked (direction 1), the two composed. The self-test boundary is structural — a `function selfTest() {` declaration at column 0, the spelling 61 of 61 scripts under `scripts/` use — never a comment convention, and its end is found by counting braces at code positions so a `}` inside a fixture string cannot close it early. Measured across all 66 discoverable gate scripts: 1144 hints -> 473, none gained that any repo path can reach. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018WuTtyckQa1VcXwgd52JpN
…il (#8478) Reverse-verifying the comment half — removing the mask and watching the diagnostics — showed two of the new cases staying GREEN through the ablation. The scan only ever reads QUOTED spans, and those two fixtures named their paths bare inside the comment, so they asserted nothing either way. Quoted, the same ablation reddens all three comment cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018WuTtyckQa1VcXwgd52JpN
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
hotlong
marked this pull request as ready for review
August 13, 2026 17:01
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#8478
extractWatchHintsscanned a check script's whole source for quoted path-ish spans, so a path a gate merely named — in a header comment (the scan accepts backticks), or as a self-test fixture — was printed in theLocal gates for this cardblock as a path that gate operates on. That MATCHED column is what a dispatch prompt pastes, and its error is one-directional in the expensive direction: a missing lead costs one card one CI round, while a fabricated lead reaches every dispatch prompt whose file surface brushes a fixture path, indistinguishable from a real one.What changed
Direction 1 and direction 2 from the card, composed; direction 3 was not needed because the boundary turned out to be mechanically detectable.
maskComments(source)— comments (line, block, shebang) are blanked before the scan.maskSelfTests(source)— the body of every top-level self-test function is blanked. The anchor is structural, not a comment convention: afunction selfTest() {declaration at column 0, optionallyexport/async, with any name spelling self-test. Measured over the 61 scripts underscripts/that carry one — 53 writefunction selfTest() {, 7 writeasync function selfTest() {, and 4 use compound names (fixtureSelfTest,selfTestReadSeams,prePushIsArmedSelfTest,decisionTableSelfTest) — 61 of 61 at column 0, none an arrow-function const.release-github-releases.mjscarries a URL in a module-body string (a//a naive rule would swallow the line for), and its markdown regex/^(#{1,6})\s(.*)$|^(+ backtick +{3,})/gmcontains a backtick — a scanner ignorant of regex literals opens a template literal there and never leaves. The self-test's end is found by counting braces at code positions only:check-engine-double-contract.mjshas 24 column-0}inside its self-test (TS fixtures in template literals), and a "first column-0 brace" boundary would end the mask at the first one and leak every fixture after it.../and./are stripped from a hint. Dropping the comments left one gate's only surviving literal unable to match anything:check:pm-skill-ratchetreadsnew URL('../../.claude/skills/pm-dispatch/SKILL.md', import.meta.url), and before this change it reached a SKILL.md card through the copy of that path in its own header prose. The leading segments are the script's depth, not part of what it watches.Change is confined to
extractWatchHints, its new pure helpers, and the self-test.Measured, not reasoned
Re-derived on the branch base
3208222(two PRs landed on this file today, so the card's03b5f81numbers moved by one each) and after. Coverage-capable hints:03b5f813208222scripts/pm/dispatch-gates.mjsscripts/check-adr-0087-registration.mjsscripts/check-empty-changeset.mjsscripts/pm/check-skill-id-lint.mjsAcross all 66 discoverable gate scripts: 1144 hints -> 473, and no hint gained that any repo path can reach. Scripts with zero hints — the ones that now fall honestly into the "repo-wide / undetermined" bucket instead of matching through prose — went 7 -> 15; on the card's own path the bucket reads 35 families where it read 27.
Every one of the 671 dropped hints was checked against an independent classifier for occurrences outside comment and self-test lines. The 19 flagged were all resolved: module-relative import specifiers (
'../eslint.config.mjs','./regen-artifacts.mjs') that normalise to a bare filename and were inert as hints both before and after, and'../..'(the ROOT constant), which names no file.End-to-end probes, before -> after:
packages/runtime/src/index.tscheck:changeset-gate-self-tests, both via changeset fixturesskills/demo/SKILL.mdcheck-empty-changeset+ its self-test gate, both via fixturespackages/spec/src/data/filter.zod.tspackages/specin prose; survivors matched on more specific real literals.claude/agents/os-dev.mdcheck:pm-skill-ratchet(reads only pm-dispatch's SKILL.md) and one more.claude/skills/pm-dispatch/SKILL.mdscripts/pm/dispatch-gates.mjscheck:pm-dispatch-gatesunchanged.changeset/breaking.md.changesethint keptSelf-test
61 -> 87 cases; all 61 existing cases stayed green, no flips to explain.
The new cases pin both directions — a fixture or prose path is not a hint, a module-body path still is — as offline fixtures for the boundary itself, and as live pins against the real scripts for the population the card measured: the ADR-0087 gate keeps
.changesetand losespackages/runtime;check-empty-changesetkeeps.changesetand losesskills/demo/SKILL.md; the skill ratchet keeps the SKILL.md it counts and losesreferences/, which it does not count; the skill-id lint keeps both real hints; this tool keeps the workflow directory it reads and loses the spec paths its fixtures name.Reverse verification, each with its direction predicted first. Removing the self-test mask reddens exactly 7 cases (the 4 boundary fixtures and the 3 live "no longer claims" pins), every comment case staying green. Removing the comment mask reddens 5 (the 3 comment fixtures plus 2 live pins whose false hint has a comment source as well as a fixture one), while
a self-test declaration inside a comment anchors nothingstays green — correctly, sincemaskSelfTestsconsults the comment flag independently.That second ablation earned its keep: on the first run it reddened only 1 of the 3 comment cases. The other two named their paths unquoted inside the comment, and the scan only ever reads quoted spans — so they asserted nothing and would have passed forever. Quoted, the ablation reddens all three. The fixture note now says why.
Known residue, stated plainly
check-empty-changeseton a skills-only PR) is genuinely relevant to paths it names nowhere. Its honest home is the "undetermined" bucket, which it cannot reach while it holds other real literals (.changeset). Same shape as thecheck:i18nentry thatCHANGE_KIND_GATESexists for; unchanged by this PR in either direction.scripts/check-empty-changeset.mjsstops derivingcheck:changeset-gate-self-tests. Measured, this was mostly accidental before (14 of 66 gate scripts derived any gate; now 7) and the fix belongs inderive, which resolves those file paths already and discards them. Filed as dispatch-gates never derives a gate for a card that edits the gate's OWN check script — the resolved script paths are already in hand and unused #8509 with the numbers; out of this card's stated scope.Gates
pnpm check:pm-dispatch-gatesgreen (87/87),pnpm check:nul-bytesgreen (7660 files, plus a wider control-character self-scan of the changed file),npx eslint scripts/pm/dispatch-gates.mjs --no-inline-configclean. Re-derived against the actual diff, the tool names exactlycheck:pm-dispatch-gatesfor this card — no family the dispatch prompt missed.No changeset: a
scripts/-only change releases nothing, so route 2 (skip-changeset) applies.Generated by Claude Code