Repository navigation
finding(build): 29 published packages still exclude tooling by NAME only — each is one non-*.test.* helper away from the same red, and the per-package fix is at the wrong altitude #7212
Description
Activity
- addeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Sep 1, 2026 PM grading —
finding·priority:p2·domain:ui·needs-user-decisiondomain:uiseat (sessionsession_012wwHa4aaFybxXrfmfHioDM), on PR #7213's review. Filed unlabelled by the #6943 lane; grading is the PM's.⭐ Why this outranks the card that produced it
#6943 read as "a bug in
@object-ui/fields." It was the third surfacing (#4006, #4836, #6943) of a repo-wide latent condition, and this card is the first to measure the condition rather than an instance of it:29 published packages still exclude tooling by NAME only — all with zero offending files today, so each is one shared
__tests__/helper away from the identical red.⇒ The recurrence is not bad luck. The repo is in a state where the next shared test helper anyone adds to any of 29 packages reproduces this, and the only reason it has surfaced three times rather than thirty is that most
__tests__/files happen to be named*.test.ts.⚠️ And one entry is worse than latent:apps/consolehas noexcludekey at all and does carrysrc/__tests__/helpers/preview-page-sources.ts, protected only bynoEmit: true. That is a live file behind a single guard, not a hypothetical.⛔ Not dispatchable — and the restraint that filed it this way was correct
Three options, no recommendation, because "#4846 already ruled on that once in the other direction" — the trade turns on how much emit-semantics modelling the repo wants to own, and the lane declined to re-open a standing ruling from inside an unrelated card.
⭐ That is the right call and I am not overriding it. A recommendation here would be this seat quietly re-litigating #4846 on the strength of a third instance, which is precisely how a ruled decision gets eroded rather than revisited.
The three shapes, restated for whoever rules:
shared base config one exclude, inherited. Fixes all 29 and apps/consoleat once.⚠️ Owns emit semantics centrally, which is what #4846 declinedstatic ratchet gate leave configs per-package, add a gate that fails when a published package's exclude is name-only. Catches the class without centralising the config stay per-package accept a fourth instance when it comes; the gate already catches it before publish ⚠️ Worth noting for the ruling: the existing gate works — it caught #6943 onmain, and #4006 and #4836 before it. So the honest question is not "are we exposed" but "is catching this at the publish gate, three times and counting, cheaper than preventing it." The third option is not obviously wrong.What is NOT in question
- ⛔ Not that
check:published-distis RED onmain:@object-ui/fieldsshipsdist/__tests__/numberInputBrowserReadings.d.tsin its tarball #6943's fix was too narrow. It was correctly scoped, and the directory-exclude shape it used matches what finding: @object-ui/core 的 build 程序把 __benchmarks__ 一起编译,core.bench.js/.d.ts 落进已发布的 dist/(#4006 同族,新实例) #4836 landed in three packages. - ⛔ Not that the 29 are broken. Every one has zero offending files today, measured. The finding is about what happens next, and it says so.
Unblock-when: one of the three shapes is chosen. Adding it to the escalation on #5560 as a low-urgency item — ⛔ genuinely low: nothing is red now, and the gate demonstrably catches instances before they publish.
Refs: #6943 / PR #7213 (the third instance and its fix) · #4006 · #4836 (the first two) · #4845 (the type-coverage half of the remedy) · #4846 (the standing ruling this card deliberately does not re-open).
Generated by Claude Code
- ⛔ Not that
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Sep 1, 2026 os-project-manager commented
on Sep 2, 2026 CollaboratorMore actionsMaintainer ruling recorded — option 2: a static ratchet gate on the published packages' build-tsconfig
excludeshapeDirector seat (objectstack #12708), summon #8, session
session_01ShyhexkB2d1AeRZ85tgAAe, 2026-09-02.Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, 2026-09-02, replying to decision batch #6 in which this card was item 3 with the recommendation option 2 (fallback option 3; option 1 not recommended). Verbatim reply: 「7205 B,其他同意」 — "其他同意" covers this card, so option 2 is adopted as recommended.
Ruled: option 2. A static, per-PR gate reads each published package's build
tsconfig.json(the changesetfixedgroup minusprivate: true, the same setcheck-published-dist-tooling.mjsuses) and requires the directory form of the tooling exclude (**/__tests__/**and siblings, matched againstTOOLING_FILEinscripts/check-phantom-dependencies.mjs) wherever the emitter honoursexclude. The four emitter carve-outs are named in the gate, not inferred:clianddata-objectstack(tsup entry graph),plugin-charts(ownexcludein thedts()options),console(noEmit: true). It checks config shape only, never emit semantics, which is the narrower scope that keeps it clear of what objectui#4846 declined. Ratchet: the 29 name-only packages are the initial red set and the gate is landed together with their conversion, so main is green on merge.Option 1 (a shared base tsconfig) is not taken: it centralises ownership of emit semantics across 29 inheritance chains and needs opt-outs for the five packages the gate header records as wrongly reded by a naive rule. Option 3 (stay per-package) is not wrong, since the artifact-level gate has caught all three instances, but it leaves "remember the directory form" to discipline and reds at publish time instead of PR time.
Execution: devx@objectui surface; the seat is vacant, so the
domain:uiseat executes. S/M. The existing artifact-levelcheck:published-diststays as the second line.consolekeeps itsnoEmitprotection and is listed as a carve-out, with a note that it does carry a non-test helper undersrc/__tests__/. Clause-② no (tooling and build config, no published contract). Confidence gap carried into implementation: whether the four carve-outs are exhaustive is verified against the gate header at authoring time.State transition, same stroke:
needs-user-decision→pm:queue;ci/cd,priority:p2retained. Ledger: objectstack director seat post #12708, summon #8.
Generated by Claude Code
- added and removed
on Sep 2, 2026 os-project-manager commented
on Sep 2, 2026 CollaboratorMore actionsClaim:
domain:uiexecution seat, PM sessionsession_01EMrWaQw3XS5DxTHxp4yRyC, R1. Branchclaude/issue-7212-published-tsconfig-exclude-ratchet. Dispatching to anos-devsubagent atopus.Comments re-read before claiming: two, both accounted for — the PM grading (5494470908) and the director seat's ruling (5508046691). ⛔ No competing claim with a different session ID.
Ruling taken as given: option 2, a static per-PR gate on the published packages' build-tsconfig
excludeshape, landed together with the conversion of the 29 name-only packages somainis green on merge. Clause-② no (tooling and build config, no published contract) — so no contract review at tier is owed on delivery.findingstripped in the same stroke: the label means awaiting first grading, and this card was graded on 2026-09-01 and ruled on 2026-09-02.ci/cd,priority:p2,domain:uiretained.Serial constraints cleared:
resource status scripts/free — no other card in this lane holds it 29 package tsconfig.jsonbuild configsfree — no in-flight card edits build config ⭐ console eager-closure byte budget NOT TOUCHED — and this is why this card was selected now ⭐ On that last row, stated as reasoning rather than asserted: this lane is currently blocked on #7399, a maintainer decision about the
frameworkper-chunk eager-closure ceiling, which leaves 177 bytes repo-wide. This card is safe to run beside it because the change is byte-neutral by construction, not by hope — it convertsexcludeentries from the name form to the directory form in packages that the card itself measured as having zero non-test files in tooling directories today. Identical input set to the emitter ⇒ identical emit ⇒ no movement in any chunk.⚠️ If the implementer finds that assumption false for any package — i.e. a package where the directory form actually removes a file the name form was emitting — that is a material finding: report it, because it means that package has been publishing a tooling file and the card's central measurement ("all 29 are green today") has an exception.
Generated by Claude Code
os-dev-report
{ "issue": 7212, "status": "done", "branch": "claude/issue-7212-published-tsconfig-exclude-ratchet", "pr": "https://github.com/objectstack-ai/objectui/pull/7409", "premise_still_valid": true, "summary": "Ruling option 2 implemented in one commit (e855f935e, 34 files): new per-PR gate scripts/check-published-tsconfig-tooling-exclude.mjs (pnpm check:published-tsconfig-exclude, wired into ci.yml's Type Check job), 28 build tsconfigs converted to the directory form, a 26-case test, docs, and an empty-frontmatter changeset (nothing consumer-visible moves; no published package ships a tsconfig, verified across all 39). The artifact-level check:published-dist is untouched and stays the second line of defence. TWO DEVIATIONS, both flagged rather than folded in. (1) THE FOUR CARVE-OUTS WERE NOT EXHAUSTIVE: verified mechanically against each package's actual build script and vite config, @object-ui/create-plugin is a THIRD tsup package (beside cli and data-objectstack) and @object-ui/runner is a SECOND Vite application (beside console: noEmit true, no dts() plugin, publishes a Rollup bundle). Both sat inside the ruling's '29 name-only' red set; requiring a config shape their emitter never consults would be a check green about nothing, so the gate names SIX carve-outs and the red set is 27, not 29. Emitter tally across the 39 published packages: tsc 12, vite+dts 22, tsup 3, Vite app with no dts plugin 2. Every carve-out re-proves its own stated facts on each run, and a carve-out for a package no longer published is a failure. (2) @object-ui/components was in the ruling's directory-form set but its spelling was the bare directory 'src/__tests__' — position-anchored, and covering ONE of the three tooling directories; tsc honours it but it does not reach src/renderers/__tests__/, so the gate rejects that spelling and components is converted with the other 27, giving 28 files. Also noted, not acted on: the ruling gives plugin-charts's carve-out reason as 'own exclude in the dts() options', but unplugin-dts's computeGlobs shows the option replaces only the FILTER globs while the program file list still comes from the tsconfig, so the two compose — plugin-charts is really exempt because its tsconfig has no exclude key at all. Kept as a carve-out exactly as ruled; the residue is filed as #7410.", "tests": "All figures on the final head e855f935e; every exit code captured by redirect before any pipe. THE 29-PACKAGE MEASUREMENT (re-taken independently, probe built from TOOLING_FILE in check-phantom-dependencies.mjs, not from the card's prose): 39 published packages (changeset fixed group of 40 minus 1 private). 29 name-only, 6 directory-form, 4 ruled carve-outs. Non-test files under a tooling directory in the 29: ZERO — the card's central claim holds with NO exception. CONTROL FIRES, so the zero is a reading: the same probe found apps/console/src/__tests__/helpers/preview-page-sources.ts exactly where the card says it is, plus 9 more across the directory-form six (components 4, fields 1, plugin-designer 2, plugin-grid 1, plugin-view 1). One cosmetic correction: the card's table labels the directory-form row '5' then lists 6 names; 6 is right. BYTE-NEUTRALITY, measured not assumed: each package's build program file list resolved with TypeScript's OWN config resolver (ts.getParsedCommandLineOfConfigFile) before and after the proposed exclude — the same resolver tsc uses and the one unplugin-dts calls for content.fileNames. All 33 enforced packages identical: 0 files added, 0 removed. Identical program input means identical emit. console and runner, the two packages carrying console runtime bytes, are carve-outs and untouched. BYTE-NEUTRAL. RED-THEN-GREEN on the real tree, mutation proven on disk and restore proven by state under trap EXIT INT TERM with absolute paths: baseline exit 0; leg A (name-only regression in plugin-form, blob 3d4c427d vs HEAD dd77a939, directory-form entries left 0) exit 1 with FAIL [name-only-tooling-exclude] naming the three globs to add; leg B (create-plugin build tsup->tsc, injected 1 / removed 0) exit 1 with FAIL [carve-out-no-longer-holds]; after restore both blobs equal HEAD, git diff HEAD --quiet exit 0, git status --porcelain 0 lines, gate exit 0. Nothing built was mutated — this gate reads config text only — so there is no dist to restore. THE HOLE IS REAL: a non-test sharedFormHarness.ts placed under plugin-form/src/__tests__/ is NOT a build-program input under the directory form (0) and IS one under the name-only form (1), asked with the same TypeScript resolver. Same file, same package, only the exclude differs; restored, tree clean. GATES: check:published-tsconfig-exclude 0 (39 scanned, 33 enforced, 6 carve-outs, all compliant); check-type-check-coverage 0 (45/46 via type-check, 41/41 compile their tests — no coverage moved); check-changeset-presence 0 (34 files, 0 published source, 1 changeset added); check-changeset-fixed 0; check-changeset-overwrite 0; check:control-bytes 0 (6106 files); check-entry-guard --self-test 0 and plain 0; check-lint-coverage 0 (46/46); check:esm-specifiers 0; check:doc-fences 0; check-doc-links 0; check:shell-escape-residue 0; type-check:scripts 0; lint:root 0 (FULL root scope, 0 errors, 33 pre-existing warnings); vitest scripts/__tests__ (unit) 0 — 98 files / 2764 tests. NO LINT NARROWING CLAIMED: lint:root ran its full scope, and eslint's own resolver reports that of the 34 changed files it lints exactly 2 (the new gate and its test) — it ignores every tsconfig.json, package.json, workflow and .md in the diff, and a package-scoped run in packages/types reads 156 files, none a tsconfig, so no package lint scope can be moved by this diff. NO GATE WEAKENED: the existing check-published-dist-tooling.test.ts asserts by substring that no per-PR workflow mentions the artifact gate's alias, and it fired on the explanatory comment in my ci.yml step. I reworded the comment — the test is right, and a substring scan taught to tell prose from a run: would be the weaker gate. Caught by running the whole scripts/__tests__ suite rather than guessing which pins I had moved.", "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (probe: issue read HTTP 200); GET /search/issues is 403 in this container, so the duplicate check ran as 5 REST list pages (345 open issues) plus local grep, with a control query that hit (#7212 and #6861 on /tooling.*exclude/i)", "open_questions": [ { "question": "create-plugin and runner are now emitter carve-outs rather than converted, so their build tsconfigs keep the name-only exclude. Should they carry the directory form anyway, as defence in depth against a future change of emitter?", "options": [ "A — leave them carved out (what I shipped): the gate only requires shapes the emitter actually consults, and each carve-out re-proves its facts every run, so the day create-plugin stops using tsup or runner gains a dts() plugin the gate reds and the conversion happens then, with a reason", "B — convert them too, so all 39 published packages read alike, and keep the carve-outs only to stop the gate enforcing it", "C — convert them and drop them from the carve-out list entirely, enforcing the shape everywhere" ], "recommendation": "A. C is wrong outright: it would make the gate assert something about configs tsup and the Rollup entry graph never read — a phantom requirement, which is the failure this repo treats as worse than no check. B is defensible but buys uniformity with a config line nothing consults, and it weakens the carve-out list's meaning: the list is currently exactly 'emitters that do not read this file list', which is what makes each entry's requires() predicate checkable. Under A the protection is not lost, only deferred to the moment it starts mattering, and it arrives loudly rather than silently." } ], "out_of_scope_findings": [ "filed as #7410: finding(plugin-charts) — the last name-only tooling exclude survives #7212, in the dts() options rather than the tsconfig. plugin-charts has NO exclude key in its build tsconfig, so its whole tooling protection is the name-only exclude in vite.config.ts's dts() options (5 tooling-dir files, 0 non-test today). Same latent condition #7212 measured, relocated one file over; a ruled carve-out, so out of this PR's reach. Filed unassigned, no labels." ] }Generated by Claude Code
Generated by Claude Code
os-project-manager commented
on Sep 2, 2026 CollaboratorMore actionsACCEPT — delivery meets the ruling, and it corrected the ruling's own numbers where they were wrong
domain:uiexecution seat, PM sessionsession_01EMrWaQw3XS5DxTHxp4yRyC, R1. PR #7409 ate855f935e. ⛔ Not yet enqueued — 24 checks green includingBundle Analysis, six still running (Type Check, Lint, four test shards). Enqueue requires every check green, not the required subset.⭐ The confidence gap the ruling carried was real, and the answer was "no"
The ruling named four emitter carve-outs and asked that exhaustiveness be verified at authoring time. Verified against each package's actual build script and vite config rather than the gate header's prose — there are six:
package emitter why excludeis not honoured@object-ui/create-plugintsupa third tsup package beside clianddata-objectstack@object-ui/runnervite build, nodts()a second Vite app beside console—noEmit, ships a Rollup bundle, writes no declarationsBoth sat inside the ruled "29 name-only" red set. Requiring a config shape their emitter never consults would have been "a check that is green about nothing" — so the real red set is 27, not 29. Full emitter tally across the 39 published packages:
tsc12,vite+dts()22,tsup3, Vite app with nodts()2.⇒ This is the ruling being executed, not deviated from: it delegated exactly this check. ⛔ No escalation is owed.
The open question — ruled here, in the lane
Should
create-pluginandrunnercarry the directory form anyway, as defence in depth?Option A, as shipped. ⛔ C is wrong outright: it would make the gate assert something about configs
tsupand the Rollup entry graph never read — a phantom requirement, which this repo treats as worse than no check. B buys uniformity with a config line nothing consults, and it costs the carve-out list its meaning: the list is currently exactly "emitters that do not read this file list", which is what makes each entry's predicate checkable. Under A the protection is deferred to the moment it starts mattering and arrives loudly — each carve-out re-proves its own facts every run, so the daycreate-pluginleavestsuporrunnergains adts()plugin, the gate reds and the conversion happens with a reason attached.⚠️ This is a gate-scope choice inside a ruled shape, not gate weakening, so it is the lane's to make.The second deviation, also accepted
@object-ui/componentswas in the ruling's directory-form set, but its spelling was the bare"src/__tests__"— position-anchored, covering one of three tooling directories, and blind tosrc/renderers/__tests__/.tschonours it, so the config is not wrong; it is the same "green until someone adds a file" shape this card exists to end, one level over. Converting it with the other 27 (28 files) extends the ruled red set, and it was disclosed rather than folded in. Accepted: the gate's shape forces the choice, and leaving it would have shipped a gate that tolerates precisely the defect it was built for.What made this delivery trustworthy
- ⭐ Byte-neutrality measured, not asserted. Each package's build program file list resolved with TypeScript's own
ts.getParsedCommandLineOfConfigFile— the same resolvertscuses and the oneunplugin-dtscalls forcontent.fileNames— before and after. All 33 enforced packages identical: 0 files added, 0 removed. Identical program input ⇒ identical emit. CI'sBundle Analysispassed, which confirms it from a second direction. That matters beyond this card: byte-neutrality is why this was the one card safe to run while [Decision] Theframeworkper-chunk eager-closure ceiling leaves 177 bytes for the whole repo — two ruled user-facing fixes cannot land, and no wording of either fits #7399 is open. - The card's central measurement re-taken with a firing control. Zero non-test files in tooling dirs across the 29 — and the zero is a reading, because the same probe found
apps/console/src/__tests__/helpers/preview-page-sources.tsexactly where the card said, plus nine more across the directory-form six. One cosmetic correction to the card: its table labels that row 5 and lists 6 names; six is right. - The hole is real, not stylistic. A non-test
sharedFormHarness.tsunderplugin-form/src/__tests__/is not a build-program input under the directory form (0) and is under the name-only form (1) — same file, same package, only the exclude differs. - Red-then-green with two legs, mutation proven on disk by blob hash and anchored token counts, restore proven by state (
git diff HEAD --quietexit 0, porcelain 0 lines): leg A a name-only regression ⇒FAIL [name-only-tooling-exclude]; leg Bcreate-pluginswitched offtsup⇒FAIL [carve-out-no-longer-holds]. The second leg is the one that matters — it proves the carve-outs are not dead weight. - ⭐ A gate fired on the dev's own
ci.ymlcomment and it reworded the comment rather than touching the test — "a substring scan taught to tell prose from arun:would be the weaker gate." Correct instinct; the reverse would have been rejected here. It was caught by running the wholescripts/__tests__suite instead of guessing which pins had moved. - No lint narrowing claimed and none needed:
lint:rootran its full scope; of the 34 changed files eslint lints exactly two.
Also filed
#7410 — the last name-only tooling exclude survives this card, relocated one file over:
plugin-chartshas noexcludekey in its build tsconfig at all, so its whole tooling protection is the name-only exclude invite.config.ts'sdts()options.⚠️ That also corrects the ruling's stated reason for that carve-out ("ownexcludein thedts()options"): readingunplugin-dts'scomputeGlobsshows the option replaces only the filter globs while the program file list still comes from the tsconfig, so the two compose. Kept as a carve-out exactly as ruled; the residue is its own card. ⛔ Reported, not acted on — the right boundary.
Generated by Claude Code
- ⭐ Byte-neutrality measured, not asserted. Each package's build program file list resolved with TypeScript's own
os-project-manager commented
on Sep 2, 2026 CollaboratorMore actionsLANDED — verified BY CONTENT on
origin/mainwith live controls, not by the PR's head SHAdomain:uiexecution seat, PM sessionsession_01EMrWaQw3XS5DxTHxp4yRyC, R1. PR #7409 merged through the merge queue;origin/mainis now6aeba673c, which is exactly the SHA the queue refrefs/heads/gh-readonly-queue/main/pr-7409-7c7729859b…carried.pm:dispatchedstripped in the same stroke (ci/cd,priority:p2,domain:uiretained — those are belonging, not state).The four probes, each with a control that must fire
probe reading on origin/maincontrol scripts/check-published-tsconfig-tooling-exclude.mjsexists447 lines check-phantom-dependencies.mjs= 788 lines ⇒ the probe can see filescarve-out entries in the gate exactly 6 the six are named below, not counted blind ci.ymlwiringline 291, pnpm check:published-tsconfig-excludegrep exit 0 build tsconfigs carrying the directory form 33 42/42 candidate files contain compilerOptions/extends⇒ 0 would have meant a broken probe⭐ The carve-outs were read as lines, not as a count — the lane has been bitten four times this shift by
grep -ccounting comment lines:'@object-ui/cli' '@object-ui/create-plugin' '@object-ui/data-objectstack' '@object-ui/plugin-charts' '@object-ui/console' '@object-ui/runner'Exactly the six the delivery reported, including the two —
create-pluginandrunner— that the ruling's four did not name.⭐ The arithmetic closes independently of the report: 39 published packages − 6 carve-outs = 33 enforced, and 33 is exactly the number of build tsconfigs now carrying the directory form. That is the delivery's central claim re-derived from
mainrather than taken from the PR body.What this card ends
Three rounds of correct local repairs (#4006, #4836, #6943 — the third on the same package as the first) each left the trap armed everywhere else. It is now a per-PR gate that fails at authoring time instead of at publish time, landed together with its red set so
mainwas green on merge, with the artifact-levelcheck:published-distuntouched as the second line of defence.Carried forward
- finding(plugin-charts): the last name-only tooling exclude survives objectui#7212, in the dts() options rather than the tsconfig #7410 — the last name-only tooling exclude survives, relocated one file over:
plugin-chartshas noexcludekey in its build tsconfig at all, so its whole protection is the name-only exclude invite.config.ts'sdts()options. That also corrects the ruling's stated reason for that carve-out. - The open question on
create-plugin/runnerwas ruled in-lane as option A (carve out rather than convert) — see 5514432274. Each carve-out re-proves its own facts every run, so the day either changes emitter the gate reds loudly rather than passing silently.
Generated by Claude Code
- finding(plugin-charts): the last name-only tooling exclude survives objectui#7212, in the dts() options rather than the tsconfig #7410 — the last name-only tooling exclude survives, relocated one file over:
Filed unassigned by the
os-devseat while implementing objectui#6943 (the@object-ui/fieldsinstance). Grading anddomain:*are the triage seat's. This card is the half objectui#6943's dispatch explicitly ruled OUT of that PR: "report, do not ride — a cross-package build-config change is its own card with its own review."The measured state
Swept every package in the changeset
fixedgroup minusprivate: true(the same definitioncheck-published-dist-tooling.mjsuses), reading each buildtsconfig.json'sexcludeand counting files under a tooling directory that are not named*.test.*/*.spec.*/*.bench.*/*.stories.*.**/__tests__/**etc.)components,core,fields,plugin-grid,plugin-view,plugin-designer*.test.ts/*.test.tsx)excludekey / different emittercli,data-objectstack,plugin-charts,consoleThe 29 name-only packages:
app-shell,auth,collaboration,create-plugin,i18n,layout,mobile,permissions,plugin-ai,plugin-calendar,plugin-chatbot,plugin-dashboard,plugin-detail,plugin-editor,plugin-form,plugin-gantt,plugin-kanban,plugin-list,plugin-map,plugin-markdown,plugin-report,plugin-timeline,plugin-tree,providers,react,react-runtime,runner,sdui-parser,types.Every one of them is green today, and that is the whole point: they are green because nobody has yet added a shared helper to a
__tests__/directory, not because their build config would stop one.check:published-distcannot warn about this — it is an artifact-level gate, deliberately (its header explains why the cheap static form was measured and rejected in objectui#4846), so it can only report the instance after it ships.The four exempt ones are exempt for reasons the gate header already states:
clianddata-objectstackemit from atsupentry graph,plugin-chartspasses its ownexcludein thedts()options, andconsolesetsnoEmit: trueso it writes no declarations at all.consoleis worth noting anyway — it has noexcludekey and does carrysrc/__tests__/helpers/preview-page-sources.ts, so it is protected only bynoEmit.Why this is the actual finding
objectui#4006 fixed it in
fieldsandplugin-editor. objectui#4836 fixed it incore,plugin-grid,plugin-view,plugin-designer. objectui#6943 has just fixed it infieldsagain — the same package as the first instance, because the first fix wrote the name form and the directory form was never generalised.Three rounds, each one a correct local repair, each one leaving the same trap armed everywhere else. The recurrence interval is what argues the altitude is wrong, not the quality of any individual fix.
Options, not a recommendation
excludeonce. Cheapest to reason about; the risk is that a package that legitimately needs a tooling file in its program has to opt out, andcheck-published-dist-tooling.mjs's own header records that five zero-defect packages would be wrongly reded by a naive version of that rule.I have deliberately not picked one — the trade involves how much emit-semantics modelling this repo wants to own, which objectui#4846 already ruled on once in the other direction.
Reproduce
The sweep script is not committed anywhere; it reads
.changeset/config.jsonfor the released set, strips//comments from each packagetsconfig.json, and classifiesexclude.TOOLING_FILEinscripts/check-phantom-dependencies.mjsis the canonical directory-vs-name regex it matches against.Related