Repository navigation
fix(scripts): name-fold loader gate rule 2 resolves the fixtures directory, not one spelling of it - #24236
Merged
objectstack-fleet[bot] merged 1 commit intoOct 18, 2026
Conversation
…rectory, not one spelling of it Rule 2 of check-position-name-fold-loaders counted a file only when its text held the repo-relative literal packages/metadata/src/__fixtures__, so a relative climb from the file that holds it, a case variant or a backslash-separated spelling, with neither hotcrm nor .artifact on the line, was never a reference. Each path spelling on a line carrying __fixtures__ is now folded (case, backslash, ., .. and // segments) and read two ways: as written, holding the directory's four segments in a row, and joined to the directory of the file that holds it. Segments are compared, never substrings, so packages/services/service-metadata/src/__fixtures__ stays another package's directory. The literal substring is still asked first. referencesArtifact now takes the holding file's repo-relative path and throws without it, since a dropped path would lose every climb silently. Rule 3's in-the-guarded-directory test uses the same folded comparison. The self-test gains nine matcher cases (floor 20 -> 29), the header's soundness sentence now says what the scan checks and what it cannot follow, and the tree scan reads the same eight references as before. Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
席位核对ACCEPT ·
Generated by Claude Code |
objectstack-fleet
Bot
deleted the
claude/issue-24050-fold-loaders-resolved-path
branch
October 18, 2026 01:10
This was referenced Oct 18, 2026
[PM seat] domain:skills — 🟢 sunhaolin · session_01QBcLjVCmcWbZEk6qhYMib2 · serial (maintainer)
#7623
Open
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 #24050
Clause-②: no. A repo gate's internal matcher;
scripts/ships in no package, so nothing publishes (skip-changeset).One file:
scripts/check-position-name-fold-loaders.mjs(+164 / -33). Branched frommainat6e43b7bc3f; heade1b31d6ba.What changed
Rule 2 resolves the guarded directory instead of matching one spelling of it. It used to count a file only when its text held the repo-relative literal
packages/metadata/src/__fixtures__. Now every path spelling on a line carrying__fixtures__is folded (case; a backslash read as/;.,..and//segments resolved) and read two ways:ROOT + '/packages/…', a URL, a vendored prefix, and a climb that comes back in;Segments are compared, never substrings. So
packages/services/service-metadata/src/__fixtures__stays another package's directory, even though it contains the stringmetadata/src/__fixtures__. The literal substring check still runs first, unchanged, so rule 2 counts every file it counted at BASE.referencesArtifact(text, relPath)now takes the holding file's repo-relative path, and throws aTypeErrorwithout it. A caller that dropped it would otherwise lose every relative climb with no error.scanTreenow computesrelbefore the call.Rule 3's "lies in the guarded directory" test (
isOtherPackageFixturePath) now uses the same folded comparison (liesInFixturesDir), so the file has one definition of it. This changes no verdict. A token that rule 3 sets aside holds only word characters,.,-and/, and it sits between rule-3 delimiters, which are all rule-2 delimiters too. So it is exactly one rule-2 spelling, and if its folded form lies in the guarded directory, rule 2 has already answeredtrue.classifyReference'sselftest is unchanged. It judges the real path the walk returns, not a spelling.The header's soundness sentence (
:40–:51, formerly:41–:43) now says what the scan checks:__fixtures__withhotcrmor.artifacton one line.It also says what the scan does not follow:
.replace, a variable, or a base other than the holding file's directory or the repo root;The rule-2 and rule-3 docblocks are updated to match.
Self-test: 30 cases now 39; the matcher battery's floor goes from 20 to 29
There are nine new matcher cases. The existing cases now pass a holding file (
PROBE_FILE = 'packages/objectql/src/loader.ts'), and their verdicts are unchanged. The roster and its floor (4) are unchanged.readdirSync(resolve(HERE, '../../../metadata/src/__fixtures__')), held bypackages/objectql/src/loaders/catalog.tspackages/services/service-metadata/src/loaders/catalog.ts(it lands inpackages/services/metadata/…)readdirSync('packages/Metadata/src/__fixtures__')readdirSync('packages/services/service-metadata/src/__fixtures__')`packages/objectql/src/__fixtures__/hotcrm-${kind}.artifact.json`glob('packages/objectql/src/__fixtures__/hotcrm-?.artifact.json')glob('packages/objectql/src/__fixtures__/hotcrm-[ab].artifact.json')glob('packages/objectql/src/__fixtures__/x-{a,hotcrm}.artifact.json')The "at BASE" column comes from feeding each new case's text and holding file to BASE's own
referencesArtifact(the6e43b7bc3fblob, which ignores the file argument).Deviation from Acceptance 2, measured. "Each must fail against today's matcher" holds for the relative climb and the case variant (and the backslash case). It is false for the four token-boundary cases: today's matcher already answers
truefor all four. Record6225074650's probe measured the same ("the head handles all four").Those four cases pin rule 3's path token running past
$,?,[and{. Their red is shown by legs L6 to L9 below: each leg cuts the token at one of those characters, and only its own case goes red.{a,hotcrm}sits in the middle of a name (x-{a,hotcrm}) on purpose. Directly after__fixtures__/, a cut at{leaves a token ending in/, and the literal-file test keeps such a token anyway, so the case would pin nothing. The record found the old${name}case pins nothing for the same reason.Ablations: each leg mutates the committed head, runs
--self-test, then restoresEach leg goes through
scripts/ablation-replace.mjsin WRAP mode. Per leg, it checks on disk that the anchor count went from 1 to 0 and that the blob changed. It proves the restore by blob == HEAD (5e94de5be75b) and an emptygit diff HEAD. An outertrapalso restores the absolute path from HEAD. This is a root script, so there is no build ordist/leg.metadata/src/__fixtures__falsecase, and the same climb from the other holder$${kind}case?hotcrm-?[hotcrm-[ab]{x-{a,hotcrm}After the last leg: worktree blob
5e94de5be75bequals the HEAD blob,git diff HEADis 0 bytes, andgit status --porcelainis empty.The gate over the real tree, against BASE
A scratch probe imported both blobs and ran them over the same walk: 10246 files, worktree at
e1b31d6ba.Rule 2 at BASE counts 1 file:
scripts/check-position-name-fold-loaders.mjs.Rule 2 now counts 4 files: that one, plus three tests in
packages/metadata/src/. Each names the directory byjoin(HERE, '__fixtures__/…'), resolved from its own directory:plugin-artifact-forward-conversion.test.tsplugin-artifact-forward-conversion-retired-after.test.tsplugin-unbound-form-predicate-roots.test.tsAll three were already references through rule 1 or rule 3.
References: 8 at BASE and 8 now, the same set (6 test, 2 declared instrument, 0 loader). The gate reports no new reference on
main.The gate prints:
has no non-test loader. 10246 file(s) scanned under packages, examples, apps, scripts, tools, .github; 8 reference(s): 6 test, 2 declared instrument, 0 loader.Gate wall time on this box is about 1.5 s, against about 1.3 s at BASE.
Verification, on head
e1b31d6banode scripts/check-position-name-fold-loaders.mjs --self-testexits 0:✓ check-position-name-fold-loaders self-test: 39 cases pass.At BASE it exits 0 with 30 cases.node scripts/check-position-name-fold-loaders.mjsexits 0, with the line quoted above.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackwith no paths: 32 commands, the same 32 the seat derived. All 32 ran locally and exited 0,pnpm check:pm-dispatch-gatesincluded (✓ dispatch-gates self-test (fast tier): 1884 cases pass).--ranreconciliation:32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN(a derived zero; all 32 recorded an exit code).pnpm exec eslint --no-inline-configon the one file exits 0. The repo-widepnpm lintis CI's.Acceptance notes
..guard is still undiscriminated. No case discriminates rule 3's..guard, the first test inisOtherPackageFixturePath. The card's Measured section names it, but its Acceptance does not ask for a case. Now that rule 2 resolves climbs, a..token that lands in the guarded directory is rule 2's. The guard only keeps a token that normalises to another package's literal file, which is conservative and not load-bearing. Observation; carrier: none (承接者:无).glob('packages/*/src/__fixtures__/*.json'). The header now says so. Reach: no file under the scan roots spells such a glob. Not filed; carrier: none (承接者:无).Generated by Claude Code