Uh oh!
There was an error while loading. Please reload this page.
fix(pm): match a hint whose glob sits in a non-final segment - #12300
Merged
yinlianghui merged 1 commit intoAug 25, 2026
Conversation
`collapseHint` collapses globs by deletion, which is sound for a trailing glob (`packages/**` -> `packages`, the reduction `ROOT_DIR_WATCH_HINTS` is built on) but MANGLES a glob in a middle segment: `skills/*/references/_index.md` became `skills//references/_index.md`, a double separator no tree can hold. The hint named nine tracked files that `packages/spec/scripts/build-skill-references.ts` really generates and reached none of them, and `unreachableClass` then filed the family under "THE LAYOUT MOVED ... a real miss, worth triaging" with three TypeScript module specifiers as its evidence -- classification and evidence both wrong in the same row. No spelling of `collapseHint` can fix it, and the impossibility is structural: "by construction" needs `deepest(P) === P`, i.e. a tracked prefix, but any tracked prefix makes the hint live so it never enters `dead` to be classified. So `hintCovers` now matches that one shape through `triggerCovers` -- the file's own filter-pattern language, not a second one -- and everything else keeps the collapse untouched. Measured both ways over 170 families x 754 hints x 6816 files: pairs 72482 -> 86807, 4 family rows gaining and ZERO losing, 7 (check,hint) newly live and 0 newly inert, unreachable families 12 -> 11 and "layout moved" 1 -> 0. Matching ALL whole-segment globs was measured and refused (-7404 pairs each on check:test-source-alias, check:type-source-resolution, check:published-files). Also deletes `collapseHint`'s second trailing strip, which was unreachable: `/\/+$/` is greedy and anchored, so it changed the answer for 0 of 754 hints. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
yinlianghui
marked this pull request as ready for review
August 25, 2026 17:19
Uh oh!
There was an error while loading. Please reload this page.
yinlianghui
deleted the
claude/issue-12246-hint-covers-mid-segment-glob
branch
August 25, 2026 17:40
yinlianghui pushed a commit
that referenced
this pull request
Aug 25, 2026
…nputs against its own deletion criterion The convention-trigger entry this card asked for has been in the table since #10542. What the re-measurement found instead is that the entry now READS redundant against its own stated deletion criterion and is not: since PR #12300 taught hintCovers to read a glob in a non-final segment, the hint route names the gate for 2758 of the 2771 tracked test files (99.5%, against 0-3.3% for its five siblings in this kind) -- through a hint INHERITED from the declaration table the gate imports, where it is one package's declared turbo inputs glob rather than the gate's own population. Records the measurement at the criterion it qualifies, and pins every load-bearing half of it in --self-test: the residue class (no test file outside the packages tree, no .tsx inside it, nothing under apps), the live specimens, the inheritance provenance, and that the KIND still names the gate for all of them. Both routes are kept -- two routes to one gate is redundancy, not a bug. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
This was referenced Aug 25, 2026
yinlianghui pushed a commit
that referenced
this pull request
Aug 25, 2026
…mechanism The row refused `packages` on the grounds that `packages/**/src/**`, its true population, "collapseHint reduces to `packages`" -- an over-claim the function never performed. collapseHint leaves `packages//src`, and since #12300 hintCovers does not route this shape through the collapse at all: a glob in a non-final segment is MATCHED. So the narrow spelling is a live hint reaching all 1812 population files, not a claim that degenerates to the bare root. REFUSE-UNSPELLABLE survives, on the reason that was always doing the work: the file-KIND filter. The live spelling covers 4290 tracked files to reach 1812, and 2465 of the 2478 it over-names are the test files the gate skips -- 42% true where the bare root is 35%. The segment buys seven points, not precision, so both spellings are false about the same non-test filter. The check:authz-resolver comparison is kept and sharpened: there the bare root is TRUE and refused for width; here it is FALSE, and so is every narrower spelling the idiom offers. Every number in the row is re-derived from the gate's own collectFiles() walk in one reading, so the row holds one tree -- never a denominator refreshed alone. The self-test now pins the DIRECTION that reason depends on (the mid-segment spelling is live, narrows, matches the segment, and still over-names), so a future change of the #12300 shape lands as a failure beside the row it invalidates instead of rotting the prose silently. Not a prose scanner: that would need a parser over English and would check collapseHint, the wrong function -- it stays green through exactly the change that falsifies these reasons. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
This was referenced Aug 25, 2026
yinlianghui pushed a commit
that referenced
this pull request
Aug 25, 2026
…vel 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
This was referenced Aug 25, 2026
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#12246
collapseHintcollapses globs by deletion. That is sound for a glob in the lastsegment —
packages/**reduces topackages, which is the reduction theROOT_DIR_WATCH_HINTSidiom is built on and whichscripts/check-published-files.mjs:215-248justifies at 91.3%. It mangles a glob in a middle segment:
A double separator is not a path any tree can hold, so the hint matched nothing by
construction.
git ls-files 'skills/*/references/_index.md'returns 9 tracked files thatpackages/spec/scripts/build-skill-references.tsreally generates, and the derivation reachedzero of them.
It also produced the worst row this output can print.
unreachableClasscalls a family"THE LAYOUT MOVED ... a real miss, worth triaging" when a dead hint's
deepestdiffers fromits collapsed form, and the mangled form made that true for a gate whose layout did not move —
with the three "reasons" beside it drawn from other inert hints, TypeScript
paths-mappedmodule specifiers that were never repo paths. Classification and evidence, both wrong, in the
same row.
Why the fix is in
hintCoversand not incollapseHintA previous round on this card was dispatched with
collapseHintalone as its surface andshowed, as a check rather than as prose, that no spelling of
collapseHintcan fix eitherhalf. The impossibility is structural and it re-derives on this base:
unreachableClasssays"by construction" only when
deepest(P) === P, i.e. P must be a tracked prefix — but anytracked prefix P makes
hintCoversreach every file beneath it, so the hint goes live andnever enters
deadto be classified at all. The two requirements are mutually exclusive, sono string P exists. Re-checked here against every candidate P the specimen admits:
skillsskills/*skills/*/referencesskills/*/references/_index.mdskills//references/_index.mdThe change
When — and only when — a glob sits in a non-final segment,
hintCoversmatches the hintas the filter pattern it visibly is, through
triggerCovers. That is not a second matchinglanguage:
triggerCoversistriggerPatternRegexplus the literal-prefix directory reach,both of which already live in this file for this exact job, and
hintCovers' docblock refusesa second one on principle. It also lines the two directions up with the ones the collapse
gives — the regex answers "does this pattern match the file", the literal prefix answers "is
the input a directory the pattern reaches into".
Everything else keeps the collapse, untouched.
Also deletes
collapseHint's second trailing strip..replace(/\/+$/, '').replace(/\/$/, '')— the second call is unreachable, because
/\/+$/is greedy and anchored, so nothing survivesfor it to find. Measured at 0 of 754 hints and on every probe the shape admits
(
a///,a/,a//,a/**/,**/,/). Deleted rather than left as decoration: aredundant strip reads as defence against a case the first one misses, and there is no such
case — what the anchored strips genuinely cannot touch is a separator left in the middle.
Measured both directions, on 170 families x 754 distinct hints x 6816 tracked files
Read from the two real implementations — the tool at
origin/mainin a comparison worktree andthe committed tool here — never from a re-implementation of either.
check:skill-refs)Families gaining coverage: 4 rows / 3 gates. Families losing: 0.
(check, hint) newly live: 7. Newly inert: 0.
Exactly six distinct hints in the whole fleet carry a glob in a non-final segment, so the
blast radius is enumerable rather than estimated:
packages/**/*.tspackages//.tscheck:cross-package-test-inputs(+2 rows)packages/**/*.object.tspackages//.object.tsskills/*/references/_index.mdskills//references/_index.mdcheck:skill-refssrc/**/*srccheck:type-source-resolutionspec/src/*/index.tsspec/src//index.tscheck:type-source-resolutionsrc/**/*.zod.tssrc//.zod.tscheck:published-filesskills/*/references/_index.mdclaims the 9 real files and 0 others underskills/.The narrower rule is deliberate
Matching all whole-segment globs this way was measured and refused: it stops collapsing
the trailing
**theROOT_DIR_WATCH_HINTSidiom is built on, at −7404 pairs each oncheck:test-source-alias(7511 → 107),check:type-source-resolution(8682 → 1278) andcheck:published-files(7407 → 3). That is a regression, not a correction.This branch keeps the idiom bit-for-bit —
packages/*5228,examples/*241,skills/**50,packages/**5228,content/**442, all unchanged — and so is the DECIDED partial-segmenttrade:
packages/client*still refusespackages/client-react/src/index.tsand still coverspackages/client, because a partial-segment glob in the last segment is not this case. Bothdirections are pinned.
The +14325 pairs are a declaration finally meaning what it says, not a widening
All of the residual cost is two hints,
packages/**/*.tsandpackages/**/*.object.ts,declared in
scripts/cross-package-test-inputs.mjsand inherited by three family rows. Theyreach 0 today only because of this defect, so the question was not whether to widen a gate
but whether the declaration is honest — checked at the declaration site rather than assumed:
packages/**/*.tsis held bypackages/core/src/security/operation-private-keys.pin.test.ts,whose scan surface is spelled
git ls-filesover "every.ts/.tsxfile underpackages/";packages/**/*.object.tsis held by the two repo-wide*.object.tswalkers inpackages/spec.Those tests really do read every matching file, so every recovered pair is a true lead, not
a fabricated one — which is the provenance criterion
hintCovers' docblock prices, nevervolume.
The derivation cost was measured rather than feared, because "22 leads is the same as none" is
the failure this file exists to avoid. Reading each tracked file as a one-file card surface,
check:cross-package-test-inputsis named by 3323 cards today and 5561 after — 2238additional cards, 32.8% of the tree. It is not "nearly every card touching
packages/**":of the 4693
packages/**/*.tsfiles, 2455 already name the family through some other hintand 2238 do not. Mean matched families per card goes 12.05 → 13.51 for a
packages/**/*.tscard and 11.99 → 12.89 fleet-wide — about one extra lead, against the +139084-pair explosion
that took one card from 7 families to 34 and is the number this file calls unaffordable.
So the two hints stay as declared. Narrowing them at their declaration site was considered and
is refused, because the narrowing would be the false statement.
Does this change the union it is judged by?
No — and this is checked rather than assumed, following the precedent PR #12247 set on this
same file. Deriving the gate list for
scripts/pm/dispatch-gates.mjswith the BASE tool andwith the committed tool, against trees whose
git ls-filesare byte-identical (md5match),gives the same 10 families with the same provenance labels. The entire observable diff
between the two runs is the defect leaving:
Gates, at the final commit
d66f710ce8Derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(exit 0captured before any pipe; no
STALE TREEwarning), then run throughscripts/pm/os-verify-lock.sh—VERDICT command-exit 0 · held the lock 66s · waited 377s.Each exit code was captured before any pipe; each line below is the gate's own verdict.
pnpm check:pm-dispatch-gates—✓ dispatch-gates self-test: 633 cases pass.(614 on anunchanged tree, +19 new pins)
node scripts/pm/bare-root-worklist.mjs --self-test—OK self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) — none stale, none missing.Identical to thecontrol reading on the unchanged tree. No TRIAGE row was touched.
pnpm check:nul-bytes—✓ check-nul-bytes --self-test: 75 assertions over a temp git repopnpm check:cross-package-test-inputs—OK: 16 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.node scripts/check-ci-filter-parity.mjs—OK: all 96 declared cross-package glob(s) (81 unique) are covered by 'core' or 'crosspkg' …pnpm check:entry-guard—✓ check:entry-guard: 165 scripts/ file(s) — every entry guard goes through invoked-as.mjspnpm check:parse-guard—✓ check:parse-guard: 164 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.pnpm check:agent-test-spelling—✓ check-agent-test-spelling: 0 violationspnpm check:cli-command-ids—✓ check-cli-command-ids: 276 command-id literal(s) across 99 file(s) … all resolvepnpm check:pnpm-filter-targets—✓ check-pnpm-filter-targets --self-test: 40 assertionsnode scripts/check-self-test-wired.mjs—✓ check-self-test-wired: every one of the 135 script(s) CI runs that ship a '--self-test' has that self-test run by CI.No narrowing was taken: the derived union was run in full.
Deliberately not done
bare-root-worklist.mjswas edited,check:skill-refs' included. Itdoes, however, become retirable, which is reported rather than acted on because
re-deciding a verdict is a separate judgment call. The row's warrant is that "the only
spellable claim left is the bare root" at 50 files; after this change
hintCovers('skills/*/references/**', f)reaches 12 files, 9 of them_index.md—independently reproducing the row's own figure of "12 of 50 (24%), 9 of them the
_index.mdit emits". The gate can now declare its real population, so the premise the row rests on is
no longer true. It was 0 before this change.
extractWatchHintsis untouched. The 128 inert TSpaths-mapped specifiers are aseparate mechanism, measured: of 640 inert pairs only 21 carry a glob at all and only 1 is the
mid-segment specimen. Split out to
extractWatchHintsstrips a module-relative literal's own depth, sounreachableReasonprints false "never was a repo path" rows for files the gates really read — residue-side repair, shape pending the #12371 ruling #12299 so it is not closed with this card.Changeset
None — root
scripts/only, nothing published. Carries theskip-changesetlabel.Generated by Claude Code