You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A ** in a watch hint covers ZERO segments too, so a top-level population stops being a dead-by-construction spelling (#12386)
* fix(pm): a `**` in a watch hint covers ZERO segments too, so a top-level population stops being a trap
`hintCovers` judges any hint with a glob in a non-final segment through
`triggerCovers`, i.e. through GitHub's filter-pattern language, where `**` is a
CHARACTER wildcard and the `/` written after it is a literal that must still
appear. `scripts/**/*.d.mts` therefore compiles to
`^scripts/.*/[^/]*\.d\.mts$` and needs at least one intervening segment: the
three tracked `scripts/*.d.mts` mirrors are unreachable BY CONSTRUCTION by the
natural spelling for them — the dead-hint species #12246 was filed for,
arriving through the branch that fixed it.
Repaired on the HINT side only. `triggerPatternRegex` is the CI mirror and
`triggerListCovers`/`coveringTrigger` evaluate real workflow `paths:` lists
with it (`validate-deps.yml` declares `'**/package.json'`), so teaching `**` to
swallow its own separator there would make this file lie about CI. A hint is a
glob a gate author wrote to describe what the gate reads, and in that language
`a/**/b` covers `a/b`.
A hint's forms are itself plus every spelling reachable by deleting a subset of
its whole-`**` non-final segments — the power set, since each `**` is "zero or
more" independently. Only a segment that is exactly `**` is droppable, only in
a non-final position, and a single `*` never is.
Measured on 173 families x 763 distinct hints x 6859 tracked files, driven
through the real `hintCovers`:
watch-hint (gate, file) pairs 70172 -> 70172 (ZERO change)
families gaining or losing coverage 0
hints reaching zero tracked files 388 -> 388
packages/**/*.ts 4718, packages/**/*.object.ts 79, skills/*/references/
_index.md 9, src/**/* 0, src/**/*.zod.ts 0, spec/src/*/index.ts 0 — all held
Zero is the expected reading: `packages/` holds no file at its top level and
the two `src/**` hints are package-relative module specifiers. What the rule
buys is that `scripts/**/*.d.mts` goes 0 -> 3 the moment a gate declares it.
The ROOT_DIR_WATCH_HINTS idiom #12300 priced at -7404 pairs is untouched —
`packages/*` 5253, `examples/*` 241, `skills/**` 50, `content/**` 442,
`scripts/**` 271, and check:test-source-alias 5534, check:type-source-
resolution 5534, check:published-files 5535, all unchanged — because no
trailing glob reaches this rule at all.
21 self-test cases pin both directions, including the refusals that keep it
narrow and the trigger-side assertion that the CI mirror still says what CI
says.
Part of #12329
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
* docs(pm): re-take the zero-segment measurement at the merged head
The docblock's readings were taken at 7986d97. Merging origin/main moved the
corpus (174 families, 764 distinct hints, 6861 tracked files), so the table is
re-derived at the head that lands: pairs 70188 -> 70188, still ZERO change, and
`scripts/**` 272.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
---------
Co-authored-by: Claude <noreply@anthropic.com>
t('the tree really does hold top-level `.d.mts` files under a root',topLevelMirrors.length>=3);
5146
+
t('a `**` root reaches the top-level files under it',topLevelMirrors.every((f)=>hintCovers('scripts/**/*.d.mts',f)));
5147
+
t('and claims nothing else in the whole tree',trackedFiles().filter((f)=>hintCovers('scripts/**/*.d.mts',f)).length===topLevelMirrors.length);
5148
+
t('the ONE-OR-MORE reading it used to have is still there',hintCovers('scripts/**/*.d.mts','scripts/pm/x.d.mts'));
5149
+
t('at any depth',hintCovers('scripts/**/*.d.mts','scripts/a/b/x.d.mts'));
5150
+
t('the extension the glob names is still honoured at the top level',!hintCovers('scripts/**/*.d.mts','scripts/invoked-as.mjs'));
5151
+
t('and a directory surface above it still derives the gate',hintCovers('scripts/**/*.d.mts','scripts'));
5152
+
// The forms are itself first, then the reductions — the original spelling is
5153
+
// never lost, which is what keeps the one-or-more cases above passing.
5154
+
t('the forms of a `**` hint are the hint and its zero-segment reduction',zeroSegmentForms('scripts/**/*.d.mts').join(' ')==='scripts/**/*.d.mts scripts/*.d.mts');
5155
+
t('each `**` drops independently, so two of them give the power set',zeroSegmentForms('a/**/b/**/c').join(' ')==='a/**/b/**/c a/b/**/c a/**/b/c a/b/c');
5156
+
t('a hint with no `**` segment has exactly one form',zeroSegmentForms('skills/*/references/_index.md').join(' ')==='skills/*/references/_index.md');
5157
+
t('and so does a hint with no glob at all',zeroSegmentForms('packages/spec/src/index.ts').join(' ')==='packages/spec/src/index.ts');
5158
+
t('a hint above the cap keeps its written and fully-reduced forms only',zeroSegmentForms('a/**/**/**/**/**/**/**/**/**/z').length===2);
5159
+
// ⛔ Deliberately NOT droppable — three refusals that keep this narrow.
5160
+
t('a single `*` segment is not a zero-segment wildcard',!hintCovers('skills/*/references/_index.md','skills/references/_index.md'));
5161
+
t('nor is a `**` that is only PART of a segment',zeroSegmentForms('packages/a**/b.ts').join(' ')==='packages/a**/b.ts');
5162
+
t('and a trailing `**` never reaches this rule at all',hintCovers('packages/**','packages/spec/src/index.ts')&&!globInNonFinalSegment('packages/**'));
5163
+
// The CI mirror is untouched, which is the whole reason the repair lives in
5164
+
// `hintCovers` and not in `triggerPatternRegex`: a hint is a glob a gate
5165
+
// author wrote, a trigger is a filter GitHub will evaluate, and this file
5166
+
// must keep saying what GitHub does. `validate-deps.yml` declares
5167
+
// `'**/package.json'` and is the live specimen.
5168
+
t('the trigger language still reads `**` as the character wildcard GitHub documents',!triggerCovers('**/package.json','package.json'));
5169
+
t('while the same spelling as a HINT covers the root file',hintCovers('**/package.json','package.json'));
5170
+
// ⛔ The sibling spelling is dead by the OLDER route and is NOT repaired
5171
+
// here: a glob in the LAST segment still goes through `collapseHint`, which
5172
+
// yields `scripts/.d.mts`. A different species (deletion-collapse mangling a
5173
+
// final segment whose glob carries a literal SUFFIX), next door to the
5174
+
// DECIDED partial-segment trade. Pinned so the asymmetry reads as recorded
5175
+
// rather than overlooked — see zeroSegmentForms' docblock.
5176
+
t('the final-segment spelling of the same population is still dead',collapseHint('scripts/*.d.mts')==='scripts/.d.mts');
5177
+
t('and still reaches none of the files it names',!topLevelMirrors.some((f)=>hintCovers('scripts/*.d.mts',f)));
5178
+
5009
5179
// The trailing-separator strip is ONE call, not two: `/\/+$/` is greedy and
5010
5180
// anchored, so nothing survives for a second `/\/$/` to remove. Measured at
5011
5181
// zero of 754 hints; pinned on the probes that could tell them apart, so a
0 commit comments