test(scripts): split check-readme-exports' repo-state cases per package, not per tree - #7794
Merged
Merged
Conversation
…ge, not per tree
Three cases in `scripts/__tests__/check-readme-exports.test.ts` split the tree
with the whole-tree boolean `census.packagesUnbuilt === 0`: one leg for "built",
one for "unbuilt". A partly built tree is a third state neither leg describes,
and it is what another gate's own printed command produces —
`turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter)`
covers only the 26 packages that gate's documents import, leaving `plugin-ai`
and `plugin-tree` unbuilt. In that state the unbuilt leg judged a mostly-built
tree and two cases failed with `expected false to be true` and
`expected +0 to be 3`, naming neither the counter nor the state.
The split is now per PACKAGE, which is the split the gate itself already uses
("THE SHRINK-ONLY RULE IS SUSPENDED WHERE NOTHING WAS COMPARED"): a declaration
is judgeable iff its own package's export surface is on disk. Built, unbuilt and
every mix fall out of one rule instead of a leg each. Measured while writing it,
the old leg had a second break from the same cause: `plugin-tree` is unbuilt
with zero README self-imports, so on a tree with only that one unbuilt the
whole-tree "the gate must FAIL" line reddened a correct tree. That line is now
conditioned on there being unjudgeable work to report.
Every state-dependent assertion also fails with a message naming
`census.packagesUnbuilt`, the packages behind it, and the fact that the build
state is not the cause — the half of the fix that buys back the round the
filer spent suspecting their own diff.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
os-sam
marked this pull request as ready for review
September 5, 2026 20:54
This was referenced Sep 5, 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 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 #7460
Which repair path, and the measurement that chose it
Both — the three-way split and the mandatory diagnostic — but the split is not a third
if. It is the split the gate already uses.scripts/check-readme-exports.mjs's own header rule (THE SHRINK-ONLY RULE IS SUSPENDED WHERE NOTHING WAS COMPARED) is stated per declaration: a declaration is judgeable iff its own package's export surface is on disk. The test invented a whole-tree boolean (census.packagesUnbuilt === 0) on top of that, which is why a mix had nowhere to go. Replacing the boolean with the per-package partition means built / unbuilt / any mix fall out of one rule, with no state enumerated and therefore no fourth state to be surprised by. Per the triage note on the card, this introduces no second build-state semantics — it removes the one that was invented.The measurement that chose it, on the real half-built tree (
packagesUnbuilt = 2):So the correct answer for a partly built tree is not a loosening: all three ledgered packages are built here, so all three must still come back
stale-omissionwith the ledger off, andexcerptsNotJudgedmust be 0, not 3. The old line hard-codedObject.keys(PARTIAL_EXCERPTS).length, which is only the CI (never-built) answer. Fully built, the new form reduces to exactly the old built leg; completely unbuilt, to exactly the old unbuilt leg.A second break in the same leg, measured while writing this
The old unbuilt leg also asserted
findings.length + vacuous.length > 0— "on an unbuilt tree the gate must FAIL". That is not sound of every mix. Measured on a real tree with onlyplugin-treeunbuilt (build the doc-gate closure, thenplugin-ai):plugin-treeis unbuilt carrying zero README self-imports and no documented type, so there is genuinely nothing to report and the gate exits 0 correctly. That line is now conditioned on there being unjudgeable work, and the "nothing is silently skipped" guarantee it stood for is stated per package instead (every unbuilt package's README self-imports must all be reportedunjudgeable), which is strictly stronger.The diagnostic (mandatory half)
Every state-dependent assertion now ends with a line naming
census.packagesUnbuilt, the packages behind it, and the fact that the state is not the cause:A third case,
judges the packages the card named, was also silently early-returning on any non-fully-built tree — on the half-built tree it stopped asserting anything about the 34 packages that were built. It now excuses unbuilt packages by name and checks the excuse against the census.Three build states, real readings
Command for every row:
pnpm exec vitest run scripts/__tests__/check-readme-exports.test.ts."before" =
origin/main's version of the file on the same tree; "after" = this branch.pnpm install, no buildpackagesUnbuilt=36of 40,packagesRead=1, findings 427, vacuous 4pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2(the card's named real path; 34 of 40 get adist/)packagesUnbuilt=2—plugin-ai,plugin-treepnpm exec turbo run build --concurrency=2packagesUnbuilt=0,packagesRead=37plugin-treeunbuiltturbo run build --filter=@object-ui/plugin-ai...packagesUnbuilt=1The half-built reds reproduced on a real, unmutated tree, verbatim:
Neither names
packagesUnbuilt; both case titles promise the opposite.Known unrelated red on the fully-built row
scripts/__tests__/check-sdui-registration-pins.test.tsis RED on the fully-built tree — measured here as1 failed | 10 passed (11), onderives the keys from the arrays, and the ruled controls are among them. That is #6893, in flight onclaude/issue-6893-sdui-pins-build-state. Not touched here; this branch changes exactly one file.Lit control
Two legs, both on the half-built tree, both restored under
trap RESTORE_FN EXIT INT TERMwithgit checkout HEAD -- ABSOLUTE_PATH.Leg 1 — disable the new per-package split (put the old hard-coded
Object.keys(PARTIAL_EXCERPTS).lengthback). Mutation confirmed on disk before reading: injected marker count 1, deleted anchor count 0, blobb55bc69d…→f121b056…. Result: RED, and it names itself:That is the card's
expected +0 to be 3, with the state now spelled out ahead of it.Leg 2 — same broken split, but the diagnostic blanked (
note: ''), to show which text is load-bearing. Mutation confirmed on disk: both markers count 1, anchor count 0, blobdf35fc58…. Result: RED, and the stringpackagesUnbuiltoccurs 0 times in the entire failure log — back to today's "nothing to search for".Restoration, proven for both legs (not by the trap firing, by comparing bytes):
The two
beforerows in the table above were produced the same way (git checkout origin/main -- ABSOLUTE_PATH, swap confirmed by blob hash3501467424f9…≠ HEAD, restored and re-proved identically).Gates
node scripts/check-changeset-presence.mjs— EXIT 0, verdict line verbatim:(preceded by
Compared the working tree with abdcd189c (merge-base with origin/main): 1 file(s) changed, 0 of them published source of a package the release covers, 0 of them a manifest whose published contract moved, 0 under a package changesets ignores, 0 changeset(s) added.) No changeset added.pnpm type-check:scripts(tsc -p tsconfig.scripts.json) — EXIT 0. This is the project that actually compilesscripts/__tests__/*.ts;turbo run type-checkstructurally cannot reach it.pnpm check:control-bytes—✅ check-control-bytes: OK (scanned 6352 tracked text file(s); skipped 85 binary).pnpm check:shell-escape-residue—✅ check-shell-escape-residue: OK (…)pnpm check:vi-mock-specifiers,pnpm check:vi-mock-inherit,pnpm check:doc-example-readers,pnpm type-check:coverage— all EXIT 0.ESLint, narrowed to the one changed file (
eslint scripts/__tests__/check-readme-exports.test.ts --no-inline-config --format json): 1 file linted, 0 errors, 0 warnings. The repo-widepnpm lintsweep was NOT run locally and is left to CI.All readings above are on
7bc4f79f7, the branch head.🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Generated by Claude Code