Skip to content

fix(devx): widen check:watch-hint-literal to every watch-hint declaration name - #13443

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13301-watch-hint-literal-name-scope
Aug 30, 2026
Merged

fix(devx): widen check:watch-hint-literal to every watch-hint declaration name#13443
os-project-manager merged 1 commit into
mainfrom
claude/issue-13301-watch-hint-literal-name-scope

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#13301

check:watch-hint-literal 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, 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 extractWatchHints settles it:

extractWatchHints keys 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:

Re-derived counts (card figures were 5 days stale)

declaration namecard, at 74049254this head f952a602
ROOT_DIR_WATCH_HINTS136 mentions143 mentions, 22 declarations
ROOT_FILE_WATCH_HINTS41 mentions41 mentions, 8 declarations
ROOT_WATCH_HINTS8 mentions8 mentions, 2 declarations
DECLARED_WATCH_HINTSnot counted4 mentions, 1 declaration

Two corrections to the card, both found by reading rather than trusting the list:

  • DECLARED_WATCH_HINTS has landed.[finding] dispatch-gates.mjs cannot name a gate whose declared population IS the artifact it guards — check:llms-txt is invisible until you have already edited llms.txt #13207's population constant is live in packages/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.
  • The card's list of ROOT_FILE_WATCH_HINTS gates 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 declares ROOT_DIR_WATCH_HINTS and 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_NAME becomes DECL_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. missingNames runs 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 outside scripts/ sit under a packages/ scripts directory, so naming packages/** 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_PATH under an EXIT INT TERM trap, proved by git diff HEAD empty 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_HINTS declaration made computed. Predicted: widened gate red, pre-change gate green. Both observed, on the same mutated tree:

WIDENED EXIT=1 the ROOT_FILE_WATCH_HINTS declaration is COMPUTED, not a literal array
1 of 34 declaration(s) are not readable as literals
BASELINE EXIT=0 23 ROOT_DIR_WATCH_HINTS declaration(s), every one an array of quoted literals

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_HINTS on the pre-change script is 0.)

B — the only DECLARED_WATCH_HINTS declaration 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:

EXIT=1 DECLARED_WATCH_HINTS -- NO declaration found anywhere in this tree. Refused rather than
passed: "every declaration is a literal" is vacuously true over an empty population
1 of 4 rostered name(s) have an EMPTY population.

C — a declaration moved to an unrostered ROOT_MD_WATCH_HINTS. Predicted: red via discovery, floor silent (ROOT_FILE_WATCH_HINTS keeps 7 other declarers). Both observed:

EXIT=1 scripts/check-doc-anchors.mjs -- declares ROOT_MD_WATCH_HINTS, which this gate has never heard of

Verification, all on f952a602

Self-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-parity and check-shard-attestation first exited 1 with PREREQUISITE NOT MET (the yaml dep, absent in a fresh worktree); both are green above after pnpm install.

NOT MEASURED, not green:check-test-completeness — it grades a saved turbo run test log 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-changeset case. 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/**'] against ROOT_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

…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
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 30, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 30, 2026 12:33
@os-project-manager
os-project-manager added this pull request to the merge queueAug 30, 2026
Merged via the queue into main with commit 0b6e479Aug 30, 2026
34 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-13301-watch-hint-literal-name-scope branch August 30, 2026 12:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:watch-hint-literal guards only ROOT_DIR_WATCH_HINTS, so the 41 ROOT_FILE_WATCH_HINTS mentions keep the silent-drop mechanism it exists to close

1 participant

@os-project-manager