fix(check-watch-hint-literal): red on a declared watch-hint literal the extractor drops at admission - #16799
Merged
Conversation
…inst the unfixed implementation Records acceptance item 1's first half: with the case present and the finding class absent, the self-test reports the case as failing -- the drop is not reported by the unfixed checker. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY
…ractor drops A watch-hint declaration can be a perfect literal array and still contribute nothing: extractWatchHints admits a literal only when it starts with a word character, a dot or an @, so one opening with a glob never becomes a hint and places its gate on no card. Four instruments passed that drop because each asked its own question and none asked whether the literals were admissible. The sweep now asks the second question, of the extractor rather than of a copy of its rule, one literal at a time -- a whole-module diff falsely accuses a literal spelled inside a self-test the extractor blanks, and one written module-relative whose extracted hint is the resolved path. The remedy names the dropped literal, prescribes the enumerable root-prefixed spelling, and carries the +139084-pair measurement behind the refusal so widening admission does not read as the obvious repair. The admission rule itself is untouched. The manifest gate's docblock stops calling this drop silent, because it no longer is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY
This was referenced Sep 8, 2026
hotlong
marked this pull request as ready for review
September 8, 2026 06:20
hotlong
enabled auto-merge
September 8, 2026 06:20
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.
Fixes #16447
check:watch-hint-literalnow reds on a*_WATCH_HINTSliteral that the hint extractor'sadmission silently drops, names the remedy, and carries the measurement behind the refusal.
The admission rule itself is untouched — this is triage's option 2, and nothing that is
refused today becomes accepted.
What was silent
A watch-hint declaration can be a perfect literal array and still contribute nothing:
extractWatchHintsadmits a literal only when it STARTS with a word character, a dot or an@, so one opening with a glob never reaches the resolve step, never becomes a hint, andplaces its gate on NO card. Four instruments passed that drop, because each asked its own
question and none asked whether the literals were admissible.
extractWatchHintsconst ROOT_DIR_WATCH_HINTS = ['**/package.json'];[]The sweep now asks the second question, of the extractor rather than of a copy of its
rule — a second copy of an admission regex is a thing that drifts, and it would drift in the
same silent direction.
Why one literal at a time, rather than a whole-module diff
The obvious spelling is to extract the whole module and diff the declared literals against
the result. Measured on this tree, that spelling is wrong in both directions:
scripts/pm/dispatch-gates.mjs, which spells a rostered declarationinside its own self-test as a fixture string — the extractor blanks self-tests, so a
perfectly admissible literal is absent from that module's extraction. Measured: 1 of 151
live declared literals falsely flagged by the whole-module diff, 0 by the per-literal probe;
hint is the RESOLVED path and not the literal as spelled.
Probing one literal at a time asks exactly the question the finding is about — does this
literal produce a hint at all — and neither residue reaches it.
验收备注
Answering triage's five acceptance items one by one (ruling comment 5579014475).
1. finding 必须先红. The known positive was added to the self-test FIRST and run against
the UNFIXED implementation, which does not report it:
With the finding class in place (commit 59a0adb) the same case passes, and the whole battery
is 14 cases. An ablation from the committed state confirms it is the finding class doing the
work, not the fixture: deleting it on disk (blob
b129dfe8→6d9673fc; anchor occurrences1 → 0, injected marker 0 → 1) turns 7 cases red; restoring with
git checkout HEAD --puts theblob back at
b129dfe8with an emptygit diff HEADand 0 leftover markers, and the self-testreturns to 72 passing cases.
2. finding 的文案要说清补救. The remedy text names the dropped literal, states the
admission rule that dropped it, prescribes the enumerable root-prefixed spelling plus the pin
against the gate's own walk, cites the measurement —
+139084fabricated pairs forbare-top-level-word admission, and the re-measured refusal of the resolved-form widening — and
says relaxing it is a change to every gate in the farm rather than a repair to one declaration.
It closes with the ruling's own sentence in the checker's register: "This finding does not
solve the trouble of enumerating; it solves not knowing that you need to enumerate." Five
self-test cases assert those five properties of the text, so it cannot decay into "not legal".
3. 阴性对照必测.
repository.directoryagainst the manifest's own directory #16446's four admissible literals are accepted, together and individually — two self-testcases, plus
pnpm check:manifest-repository-directorygreen.names no subtree at all) and a computed onekeeps its own (
is COMPUTED, not a literal array); the value-side question is asked only afterthe spelling-side one passes, so no declaration is named under two remedies. Two self-test cases.
extractWatchHintsover the derivation's whole corpus: 225 of 226 gate files byte-identical,280 of 281 family hint-sets byte-identical. The one delta is this gate's own entry, and it is
the import of the extractor it now consults — see Declared deviation below, with the measurement
that placement is unchanged.
4. ⛔ 不动
dispatch-gates.mjs的 admission 正则. Untouched — the diff is two files andneither is it. Nothing that fails admission today is admitted by this PR; the only thing that
changed is whether the failure is reported.
5.
check-manifest-repository-directory.mjs's docblock. No longer accurate, and edited —only that sentence. It called the drop silent; after this PR it is not. The paragraph now says
so and points at the remedy the gate prints.
Declared deviation — one hint, and it does not place anything
Consulting the extractor means importing it, and the import specifier is itself a path-shaped
literal in this gate's module body, so this gate's own extraction gains
scripts/pm/dispatch-gates.mjs(and, through the import follow,
.github/workflows). Measured before and after:Acceptance item 3's third bullet asks for byte-identical extraction over every gate; this is the
one place it is not, and it is structural — a gate that calls the extractor cannot be invisible to
it. What the bullet's second half asks for does hold, and it is measured rather than argued:
check:watch-hint-literaldeclaresdispatch-gates: whole-tree-population, so it is placed bydeclaration and never by its hints — it is in the
alwaysRunsPopulationcolumn for every card andin
matchedfor none. Across--jsonruns before and after, on probe paths chosen to expose thedelta (
scripts/pm/dispatch-gates.mjs,scripts/check-watch-hint-literal.mjs,packages/spec/src/index.ts, and separately.github/workflows/lint.yml):⇒ no card's brief moves. The alternative — routing the import through a shim module so the
specifier carries no slash — was rejected: a file whose only purpose is to be invisible to a
detector is the workaround Prime Directive #5 refuses, and the next author would copy it.
Verification
Re-derived in the worktree at
59a0adb3cwithnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths) —31 families, the 30 the dispatch named plus
pnpm check:manifest-repository-directory, whicharrived because this diff now touches that file. All 31 run; reconciled with
--ran:pnpm check:pm-dispatch-gatesexit 0 (✓ dispatch-gates self-test: 1552 cases pass.).pnpm check:watch-hint-literalexit 0 — 72 self-test cases, then 66 declarations across 4rostered names with all 151 of their literals admitted.
Lint, as a proven narrowing — three readings, taken at
59a0adb3c:ESLint#isPathIgnoredsays 6347 of 6347 are not ignored;--format json: 2, 0 errors, 0 warnings;eslint.config.mjsnever enables type-aware linting for ANY file —calculateConfigForFileon the changed file returnsparserOptions {"ecmaVersion":"latest","sourceType":"module"}, noparserOptions.project, notyped rules — so this diff cannot move the verdict on any untouched file.
Merge-tree against
origin/mainb38821d1c, probed from a throwaway bare clone with no custommerge driver registered: exit 0, no conflicted paths.
node scripts/pm/check-governed-merges.mjs --teston the final file list:NOT governed.skip-changeset: the repo-root package is private (@objectstack/spec-monorepo), the two changedfiles sit outside every workspace package, and the new symbol has zero occurrences anywhere under
packages/while the positive control (defineStackinpackages/spec/src/index.ts, 6 hits) showsthe grep channel works — nothing published moves.
Deliberately out of scope, per the ruling: widening admission to accept a leading glob (triage's
option 1) is a separate card and a decision-box one, because it changes the derivation's admission
semantics for every gate in the farm and owes an answer to the
+139084measurement. No card isopened for it here.
Generated by Claude Code