Uh oh!
There was an error while loading. Please reload this page.
fix(devx): widen check:watch-hint-literal to every watch-hint declaration name - #13443
Merged
os-project-manager merged 1 commit intoAug 30, 2026
Merged
Conversation
…tion name The gate closes a real silent-drop mechanism: a watch-hint population declaration spelled as a literal array contributes its hints to the dispatch extractor, while the same declaration computed from a population constant contributes nothing -- identical runtime value, every local assertion green, and the gate drops out of every dispatch brief. Its scan was keyed to one constant name, so the sibling names carrying the identical mechanism were unguarded. extractWatchHints keys on no constant name at all -- it scans the module body for quoted path-shaped literals -- so the mechanism is a property of the spelling, not of one constant's name. The roster is therefore a set of four measured names, each with its own non-empty floor: a name whose constant was renamed away finds zero declarations and would otherwise print this gate's healthiest green. Discovery closes the other direction: a declaration spelling the idiom under a name the roster has never heard of is refused, which is how the fourth name arrived unguarded in the first place. Population on this head: 33 declarations across 4 names (22/8/2/1), every one a literal. Self-test 29 cases -> 57. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
os-project-manager
marked this pull request as ready for review
August 30, 2026 12:33
os-project-manager
enabled auto-merge
August 30, 2026 12:33
Uh oh!
There was an error while loading. Please reload this page.
os-project-manager
deleted the
claude/issue-13301-watch-hint-literal-name-scope
branch
August 30, 2026 12:53
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#13301
check:watch-hint-literalcloses a real silent-drop mechanism: a watch-hint population declaration spelled as a literal array contributes its hints to the dispatch extractor, while the same declaration computed from a population constant contributes nothing — identical runtime value, every local assertion green, and the gate drops out of every dispatch brief. Its scan was keyed to one constant name, spelled once, so the sibling names carrying the identical mechanism were unguarded.All measurements below are re-derived on this branch's head
f952a602, not carried from the card.The measurement that decides the route
The card offered three routes and did not pick one. Reading
extractWatchHintssettles it:extractWatchHintskeys on no constant name at all. It masks comments and self-tests, then scans the whole module body for quoted path-shaped literals and collects them. The silent drop is therefore a property of the SPELLING, not of one constant's name — every declaration name carrying the idiom loses its hints the same way the moment it is computed.Two consequences:
scripts/pm/dispatch-gates.mjsis claimed by [finding]dispatch-gates --residueprints THREE fabricated leads for check:query-options-erasure — literals naming files that have never existed, unannotated because one live sibling keeps the family reachable #13312); the fence was never reached, because the route has nothing to import. Nothing in that file was edited, and none of its logic was copied here.Re-derived counts (card figures were 5 days stale)
74049254f952a602ROOT_DIR_WATCH_HINTSROOT_FILE_WATCH_HINTSROOT_WATCH_HINTSDECLARED_WATCH_HINTSTwo corrections to the card, both found by reading rather than trusting the list:
DECLARED_WATCH_HINTShas landed.[finding]dispatch-gates.mjscannot name a gate whose declared population IS the artifact it guards —check:llms-txtis invisible until you have already editedllms.txt#13207's population constant is live inpackages/spec/scripts/check-llms-txt.ts, so the population is four names, not three. It arrived outside the guard's reach with nothing red — which is the card's own asymmetry, recurring while the card sat in the queue.ROOT_FILE_WATCH_HINTSgates is wrong in both directions. It named four gates "at least"; there are eight. And one it named,scripts/check-agent-test-spelling.mjs, does not use that constant at all — it declaresROOT_DIR_WATCH_HINTSand was already guarded. The eight real ones:check-doc-anchors.mjs,check-required-contexts.mjs,check-turbo-task-graph.mjs,docs-audit/check-audit-scope.mjs,pm/check-governed-merges.mjs,pm/check-governed-prose.mjs,pm/check-skill-id-lint.mjs,pm/check-skill-line-ratchet.mjs. The line ratchet the triage singled out is confirmed present.Every one of the 33 declarations is already a literal, so this gate is green on landing. The loss it closes is prospective — as the card said.
What changed
DECL_NAMEbecomesDECL_NAMES, a rostered set of four. Rows are now keyed per file AND per name, so a file carrying two spellings is judged on both.The per-name floor. The gate refuses an empty population deliberately, and once the scope is a set that refusal has to be per name. A single global floor does not catch a renamed constant: three healthy names carry the total well clear of zero while the fourth is silently gone, and "every declaration is a literal" is vacuously true over its zero declarations.
missingNamesruns before any verdict and names the offender, with the one-line remedy in the failure text.Discovery, the other direction. A floor catches a name that disappears; it cannot catch one that appears. That is not hypothetical — it is exactly how the fourth name arrived. So a declaration spelling the idiom under an unrostered name is refused by name. A hand-maintained roster that rots silently would be the same species of defect as the single name it replaced, one level up. Zero strays on this tree today.
The gate's own declaration is unchanged (
ROOT_DIR_WATCH_HINTS = ['scripts/**']); only the stale ratio in its docblock moved, from "13 of the 14" to the measured 29 of the 33, now pinned by a self-test case. All four declarers outsidescripts/sit under apackages/scripts directory, so namingpackages/**to reach them is still the costlier error.Ablation — three mutations, each predicted before running, each proved on disk
Each leg verified the injected text present AND the removed text absent before reading any result; restore was
git checkout HEAD -- ABSOLUTE_PATHunder anEXIT INT TERMtrap, proved bygit diff HEADempty with no untracked residue. The implementation was committed first, so the restore leg's reference contains it. No prediction was wrong.A — a
ROOT_FILE_WATCH_HINTSdeclaration made computed. Predicted: widened gate red, pre-change gate green. Both observed, on the same mutated tree:That is the defect measured directly rather than argued: the pre-change gate scores its healthiest green on a tree carrying exactly the drop it exists to refuse. (
grep -c ROOT_FILE_WATCH_HINTSon the pre-change script is0.)B — the only
DECLARED_WATCH_HINTSdeclaration renamed away. Predicted: red via the floor, discovery silent (the replacement name does not end in the idiom suffix), and a global floor would have passed on 32 healthy declarations. All observed:C — a declaration moved to an unrostered
ROOT_MD_WATCH_HINTS. Predicted: red via discovery, floor silent (ROOT_FILE_WATCH_HINTSkeeps 7 other declarers). Both observed:Verification, all on
f952a602Self-test 29 cases to 57. Gate family derived on this head via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(repo assertion held), not from the dispatch list. Exit codes captured before any pipe.Green:
check:watch-hint-literal·check:pm-dispatch-gates(919 self-test cases, under the shared verify lock, 2m11s) ·bare-root-worklist --self-test(43 recorded verdicts, none stale, missing or contradicted) ·check-self-test-wired·check:agent-test-spelling·check:bash32-floor·check:cli-command-ids·check:cross-package-test-inputs·check:entry-guard·check:parse-guard·check:pnpm-filter-targets·check-ci-filter-parity·check-shard-attestation.check-ci-filter-parityandcheck-shard-attestationfirst exited 1 withPREREQUISITE NOT MET(theyamldep, absent in a fresh worktree); both are green above afterpnpm install.NOT MEASURED, not green:
check-test-completeness— it grades a savedturbo run testlog that only CI produces, and its own text instructs recording it this way when run from the derived family.No changeset: this PR edits one CI-internal gate script and releases nothing, which lint.yml calls the textbook
skip-changesetcase. The label is applied on this PR.Not done here, deliberately
The card's third route — deciding the other names should not exist — is a change to ten other gates' declarations, outside this PR's file surface. It is also not obviously right: the names encode what KIND of root the population has (
ROOT_FILE_WATCH_HINTS = ['AGENTS.md/**']againstROOT_DIR_WATCH_HINTS = ['scripts/**']), which is a claim a reader can make from the constant name alone. Recorded rather than acted on.Generated by Claude Code