Files
2026-05-27 23:29:53 +01:00

4.2 KiB

OpenSpec Change: barrel-explicit-exports

  • Change ID: barrel-explicit-exports
  • Scope: cross-cutting — every package under packages/*/
  • Status: PROPOSED
  • Date: 2026-05-03
  • Reference: openspec/AGENTS.md §6a (Barrel exports — explicit named, not export *)

Summary

Replace export * from "./<feature>.js" with explicit named re-exports in every per-feature <feature>/index.ts barrel across the monorepo. The package-root src/index.ts files are out of scope for this change — those keep export * for now and may be migrated to export * as <name> from namespace exports in a future change (also permitted under §6a). Generated selector indexes (*.select/index.ts) are also out of scope, since they are the one place §6a explicitly allows transparent forwarding.

The motivation is direct from §6a: the public API of a feature module must be the contract the barrel declares, not whatever the source file happens to export today. export * silently leaks every new symbol added to the source, which (a) defeats reviewability, (b) makes accidental private-helper exposure trivial, and (c) blurs the documented surface area against the actual one. Tree-shaking is unaffected — "sideEffects": false rollup/esbuild treat both forms identically.

Scope

In: 536 distinct <feature>/index.ts files across 70 packages, totalling ~875 export * lines (the remaining ~37 of the 912 total live in package roots / generated selectors / impl files).

Out:

  • 21 package-root src/index.ts files (deferred to a follow-up change).
  • 14 *.select/index.ts generated selector forwarders (allowed by §6a).
  • 11 non-index impl files using export * (e.g. aes-256-gcm.ts forwarding to its .select/ directory) — these are forwarder shims for the selector mechanism, not feature barrels.
  • 1 .d.ts third-party type stub.

Why one cross-cutting change instead of one per package

This is a single mechanical rule application, not a design decision per package. The rule is already canonicalised in openspec/AGENTS.md §6a; there is nothing to negotiate per package. Splitting into 70 micro-proposals would inflate process overhead without changing what gets shipped or how it is reviewed. The per-package verification gate (§13: lint typecheck test + 100% coverage, plus dependent sweep on foundation packages) is preserved — the change is implemented one package at a time, gates run per package, and only after all 70 packages are green is the change archived.

Behavior change

None. Every symbol that was reachable through export * remains reachable, with the same name and same type. Consumers continue to import via import { X } from "@suaveplan/<pkg>/<feature>/<feature>" exactly as before. The diff is barrel-internal: the barrel now lists the symbols by name instead of forwarding the whole module.

The one observable difference is what does NOT happen any more — adding a new export to <feature>.ts will not auto-leak through the barrel. This is the intended invariant.

Verification per package

For every package touched:

  1. bunx turbo lint typecheck test --filter=@suaveplan/<pkg> — green.
  2. bunx turbo test:coverage --filter=@suaveplan/<pkg> — 100/100/100/100.
  3. Foundation packages (error, events, logger, validation, plus crypto-utils since several primitive packages depend on it): also bunx turbo test --filter=...@suaveplan/<pkg> to catch dependent regressions.
  4. Banned-pattern scan and missing-.md scan re-run to confirm no incidental regressions.

Impact

Affected dependents

Cross-cutting; every package in packages/*/ is affected. Foundation packages enumerate their dependent sets via bunx turbo ls --filter=...@suaveplan/<pkg> per the §13 step 10 / §14a contract — those sweeps run per foundation package as it ships. For non-foundation packages, the per-package gate in step 1 above is sufficient (no dependents on a leaf package exist by definition).

Migration

None for consumers. Internal-only refactor.

Spec delta

openspec/specs/monorepo-structure/spec.md gains a Barrel export form requirement that codifies §6a as an enforceable spec scenario (so future packages cannot regress).