Split out of objectui#7625 rather than fixed there — triage on that card flagged it explicitly as its own work, not that card's:
⚠️ 顺带:"声明了但没人 import"这个方向值得一个门禁,尤其对 Atoms 包那条 zero-heavy-deps 约束 —— ⛔ 但那是另一张卡,别在本卡里做。
The gap
scripts/check-phantom-dependencies.mjs (pnpm check:phantom-deps) judges imports that are not declared. Its own summary line names the direction:
Scanned 40 released package(s), 3970 source file(s), 20091 module specifier(s) ...
OK Every in-scope import is declared by the package that publishes it.
The reverse direction — a package declaring a dependency no file in it imports — is judged by nothing in CI. Nothing goes red when a declaration outlives its last consumer, and, equally, nothing goes red if a declaration that IS needed gets removed. Both directions of that mistake are currently invisible to the farm.
The live instance this was found on
objectui#7625: recharts stayed declared in packages/components/package.json for the whole window after objectui#7397 deleted packages/components/src/ui/chart.tsx, its only consumer in that package. Measured on 0c8dbc492, with the positive control the zero needs:
recharts under packages/components/src 0 files
lucide-react, same command shape 70 files (control fires)
The declaration was removed by hand on objectui#7625. Nothing would have reported it, and nothing reports the next one.
Why it matters more for this repo than for a generic monorepo
AGENTS.md section 3 constrains @object-ui/components — the Atoms package — to "Shadcn primitives, zero heavy 3rd-party deps", and routes heavy widget dependencies to @object-ui/plugin-*. That is a written layering constraint whose only enforcement today is that somebody notices. objectui#7625 is one instance of it being violated for a reason that had already disappeared; a gate is what makes the constraint mechanical rather than remembered.
The cost of an unused declaration is install-graph weight for every consumer of the package, not shipped bytes — @object-ui/components' Vite external predicate is path-based and never reads dependencies, so the built artifact is unaffected either way. That is what makes it easy to miss: no bundle budget moves, no test fails.
Design notes for whoever picks this up (not a spec)
- The naive shape — "every key in
dependencies appears in some import specifier" — has known false positives that need a decision before implementation, not during: CSS-only packages (tailwindcss-animate), packages consumed through a bundler alias or a config file rather than an import, type-only packages, and packages loaded by side effect. An allowlist with a stated reason per entry is the usual answer; check-phantom-dependencies.mjs already carries the scanning machinery and the released-package set to build on.
- Worth deciding at the same time: whether the Atoms zero-heavy-deps constraint gets its own explicit assertion, rather than falling out of a generic unused-dependency scan.
Dedup: searched open and closed; the near neighbours are objectui#4394 (phantom dependency, the opposite direction), objectui#3943 (sideEffects consistency) and objectui#3663 (files entries exist on disk) — all closed, none covering this direction.
Split out of objectui#7625 rather than fixed there — triage on that card flagged it explicitly as its own work, not that card's:
The gap
scripts/check-phantom-dependencies.mjs(pnpm check:phantom-deps) judges imports that are not declared. Its own summary line names the direction:The reverse direction — a package declaring a dependency no file in it imports — is judged by nothing in CI. Nothing goes red when a declaration outlives its last consumer, and, equally, nothing goes red if a declaration that IS needed gets removed. Both directions of that mistake are currently invisible to the farm.
The live instance this was found on
objectui#7625:
rechartsstayed declared inpackages/components/package.jsonfor the whole window after objectui#7397 deletedpackages/components/src/ui/chart.tsx, its only consumer in that package. Measured on0c8dbc492, with the positive control the zero needs:The declaration was removed by hand on objectui#7625. Nothing would have reported it, and nothing reports the next one.
Why it matters more for this repo than for a generic monorepo
AGENTS.md section 3 constrains
@object-ui/components— the Atoms package — to "Shadcn primitives, zero heavy 3rd-party deps", and routes heavy widget dependencies to@object-ui/plugin-*. That is a written layering constraint whose only enforcement today is that somebody notices. objectui#7625 is one instance of it being violated for a reason that had already disappeared; a gate is what makes the constraint mechanical rather than remembered.The cost of an unused declaration is install-graph weight for every consumer of the package, not shipped bytes —
@object-ui/components' Viteexternalpredicate is path-based and never readsdependencies, so the built artifact is unaffected either way. That is what makes it easy to miss: no bundle budget moves, no test fails.Design notes for whoever picks this up (not a spec)
dependenciesappears in some import specifier" — has known false positives that need a decision before implementation, not during: CSS-only packages (tailwindcss-animate), packages consumed through a bundler alias or a config file rather than an import, type-only packages, and packages loaded by side effect. An allowlist with a stated reason per entry is the usual answer;check-phantom-dependencies.mjsalready carries the scanning machinery and the released-package set to build on.Dedup: searched open and closed; the near neighbours are objectui#4394 (phantom dependency, the opposite direction), objectui#3943 (
sideEffectsconsistency) and objectui#3663 (filesentries exist on disk) — all closed, none covering this direction.