# OpenSpec Change: align-packages-to-folders-spec - **Change ID**: align-packages-to-folders-spec - **Scope**: Monorepo-wide structural change - **Status**: ARCHIVED - **Date**: 2026-05-13 --- ## Errata (2026-05-14) The proposal below describes a target taxonomy that was partially executed. The actual reshuffle that shipped on `genesis` retained several intermediate categories (`barcode`, `core`, `data`, `drive`, `flow`, `monitor`, `random`, `security`, `timing`) instead of collapsing them into `foundation`, `platform`, `jobs`, `crypto`, and `storage` as originally planned. `FOLDERS.md`, `assignment.yml`, `MANIFEST.md`, `README.md`, `STATUS.md`, and in-flight OpenSpec changes have all been updated to reflect the **actual** on-disk layout. The archived proposal is preserved for historical context; the ground-truth category map is now `FOLDERS.md` at repo root. --- ## Summary Realign all existing `@suaveplan/*` packages under `packages/` to match the canonical category definitions in `FOLDERS.md`. The April 2025 domain-first reorganization (`folder-reorg-by-domain`) successfully moved packages out of runtime-tier roots (`universal/`, `browser/`, etc.) into `packages///`, but the resulting plugin folders do not match the one-word category taxonomy defined in `FOLDERS.md`. This change performs a second, purely mechanical `git mv` pass so that every on-disk package lives in exactly the category folder prescribed by `FOLDERS.md`. Concurrently: 1. Replace `scripts/folder-reorg/assignment.yml` with a version that mirrors `FOLDERS.md` exactly. 2. Update `scripts/folder-reorg/migrate-folders.ts` (or add a new `realign-to-categories.ts` companion script) to operate on the current `packages///` layout rather than legacy tier roots. 3. Regenerate `MANIFEST.md` so its plugin × package table reflects the new layout. 4. Update every `CLAUDE.md`, `openspec/AGENTS.md`, and CI path reference that hard-codes the old plugin folder names. --- ## Motivation `FOLDERS.md` is the single source of truth for the monorepo package taxonomy. It defines 30 one-word category folders, lists which packages belong in each, and is already referenced by agents and reviewers as the authoritative map. However, the on-disk reality diverges in ~70 places: - **Cross-category packages are scattered.** `drive` lives in `packages/drive/`, but `FOLDERS.md` places all drive packages under `storage`. `email` lives in `packages/email/`, but the category is `emails`. `vault` lives in `packages/vault/`, but all vault packages belong in `crypto`. - **Platform services are split across five folders.** `logger` and `opentelemetry` are in `observability/`, `cache` is in `database/`, `search` is in `search/`, `workflow` and `rules` are in `workflow/`, `audit-logging` and `data-masking` are in `security/`, and `feature-flags` is in `config/` — yet `FOLDERS.md` unifies all of these under `platform`. - **Foundation primitives are split across four folders.** `bytes`, `cron`, `datetime`, etc. are in `primitives/`, `barcode` and `qrcode` are in `codes/`, `random` and `noise` are in `random/`, `streams` and `queue` are in `streams/`, and `validation` is in `validation/` — yet `FOLDERS.md` unifies all of these under `foundation`. - **Testing packages are under `dev/`.** `FOLDERS.md` assigns every testing package to `testing`. - **Category-folder names mismatch.** `email` vs `emails`, `media/` containing audio/image packages that belong in `audio/` and `image/` respectively. - **Pending packages mis-assigned.** `delivery-tracking` (platform) belongs in `logistics`; `mcp` (platform) belongs in a new `ai` category; `dmx`, `r3f-audio`, and `r3f-dmx` belong in `audio`; `r3f-inventory` belongs in `store`. Every divergence means agents and contributors must maintain two mental models: the documented taxonomy and the on-disk reality. This change collapses them to one. --- ## Design ### New folder layout ```text packages/ / / package.json tsconfig.json vitest.config.ts src/ README.md ``` Category names are exactly the one-word keys from `FOLDERS.md`. Package directory names match the package's npm name without the `@suaveplan/` prefix. ### Package → category assignment The table below lists **every on-disk package that moves**. Packages not listed here already reside in their correct category and require no action. #### Source category folders being retired or repurposed | Source folder | Status after change | Reason | |---|---|---| | `packages/codes/` | **deleted** | `barcode`, `qrcode` → `foundation` | | `packages/config/` | **deleted** | `config`, `env` → `foundation`; `feature-flags` → `platform` | | `packages/control-flow/` | **deleted** | `fsm`, `idempotency`, `retry`, `undo` → `foundation`; `job-scheduler` → `jobs`; `rate-limiter` → `platform` | | `packages/data/` | **deleted** | `crdt`, `dedup`, `domain-normalizer`, `pagination` → `foundation`; `packer` → `bitpacker`; `ab-testing`, `etl` → `platform` | | `packages/dev/` | **deleted** | all testing packages → `testing` | | `packages/drive/` | **deleted** | all drive packages → `storage` | | `packages/email/` | **deleted** | `dmarc`, `suppression` → `emails` (note the `s`) | | `packages/media/` | **deleted** | `exif` → `image`; `metadata` → `audio`; `voice` → `audio` | | `packages/messaging/` | **deleted** | `elevenlabs` → `messaging` (from `third-party`); `webhook-client` → `platform` | | `packages/observability/` | **deleted** | `logger`, `opentelemetry` → `platform` | | `packages/primitives/` | **deleted** | all primitives → `foundation` | | `packages/random/` | **deleted** | all random packages → `foundation` | | `packages/search/` | **deleted** | `search` → `platform` | | `packages/security/` | **deleted** | `hibp`, `jws`, `password-formats`, `slsa`, `tpm-struct`, `x509` → `crypto`; `audit-logging`, `data-masking`, `security-codes` → `platform` | | `packages/streams/` | **deleted** | `queue*` → `jobs`; `streams` → `foundation` | | `packages/svg/` | **deleted** | `compress-svg` → `docs` | | `packages/third-party/` | **deleted** | `elevenlabs` → `messaging` | | `packages/validation/` | **deleted** | `schema-registry`, `validation` → `foundation` | | `packages/vault/` | **deleted** | all vault packages → `crypto` | | `packages/workflow/` | **deleted** | `rules`, `rules-hono` → `platform` | #### Individual moves | From | To | |---|---| | `packages/api/sitemap` | `packages/docs/sitemap` | | `packages/codes/barcode` | `packages/foundation/barcode` | | `packages/codes/qrcode` | `packages/foundation/qrcode` | | `packages/config/feature-flags` | `packages/platform/feature-flags` | | `packages/control-flow/job-scheduler` | `packages/jobs/job-scheduler` | | `packages/control-flow/rate-limiter` | `packages/platform/rate-limiter` | | `packages/data/ab-testing` | `packages/platform/ab-testing` | | `packages/data/crdt` | `packages/foundation/crdt` | | `packages/data/dedup` | `packages/foundation/dedup` | | `packages/data/domain-normalizer` | `packages/foundation/domain-normalizer` | | `packages/data/etl` | `packages/platform/etl` | | `packages/data/packer` | `packages/bitpacker/packer` | | `packages/data/pagination` | `packages/foundation/pagination` | | `packages/database/cache` | `packages/platform/cache` | | `packages/dev/testing` | `packages/testing/testing` | | `packages/dev/testing-dom` | `packages/testing/testing-dom` | | `packages/dev/testing-e2e` | `packages/testing/testing-e2e` | | `packages/dev/testing-integration` | `packages/testing/testing-integration` | | `packages/dev/testing-integration-garage-s3` | `packages/testing/testing-integration-garage-s3` | | `packages/dev/testing-integration-mailpit` | `packages/testing/testing-integration-mailpit` | | `packages/dev/testing-integration-postgres` | `packages/testing/testing-integration-postgres` | | `packages/dev/testing-integration-redis` | `packages/testing/testing-integration-redis` | | `packages/dev/testing-integration-step-ca` | `packages/testing/testing-integration-step-ca` | | `packages/dev/testing-integration-surrealdb` | `packages/testing/testing-integration-surrealdb` | | `packages/dev/testing-integration-typesense` | `packages/testing/testing-integration-typesense` | | `packages/drive/drive` | `packages/storage/drive` | | `packages/drive/drive-mesh` | `packages/storage/drive-mesh` | | `packages/drive/drive-mesh-protocol` | `packages/storage/drive-mesh-protocol` | | `packages/drive/drive-plugin-chunks` | `packages/storage/drive-plugin-chunks` | | `packages/drive/drive-react-sharing` | `packages/storage/drive-react-sharing` | | `packages/drive/drive-sharing` | `packages/storage/drive-sharing` | | `packages/drive/drive-template` | `packages/storage/drive-template` | | `packages/drive/drive-transport-https` | `packages/storage/drive-transport-https` | | `packages/drive/drive-transport-webrtc` | `packages/storage/drive-transport-webrtc` | | `packages/drive/drive-webrtc` | `packages/storage/drive-webrtc` | | `packages/email/dmarc` | `packages/emails/dmarc` | | `packages/email/suppression` | `packages/emails/suppression` | | `packages/media/exif` | `packages/image/exif` | | `packages/media/metadata` | `packages/audio/metadata` | | `packages/media/voice` | `packages/audio/voice` | | `packages/messaging/webhook-client` | `packages/platform/webhook-client` | | `packages/observability/logger` | `packages/platform/logger` | | `packages/observability/opentelemetry` | `packages/platform/opentelemetry` | | `packages/platform/cli` | `packages/tooling/cli` | | `packages/platform/install-agent-skills` | `packages/tooling/install-agent-skills` | | `packages/platform/installer` | `packages/tooling/installer` | | `packages/platform/readline` | `packages/foundation/readline` | | `packages/primitives/bytes` | `packages/foundation/bytes` | | `packages/primitives/cron` | `packages/foundation/cron` | | `packages/primitives/datetime` | `packages/foundation/datetime` | | `packages/primitives/diff` | `packages/foundation/diff` | | `packages/primitives/json` | `packages/foundation/json` | | `packages/primitives/mime` | `packages/foundation/mime` | | `packages/primitives/serialize` | `packages/foundation/serialize` | | `packages/primitives/templates` | `packages/foundation/templates` | | `packages/primitives/timing` | `packages/foundation/timing` | | `packages/primitives/utils` | `packages/foundation/utils` | | `packages/random/noise` | `packages/foundation/noise` | | `packages/random/pseudo-random` | `packages/foundation/pseudo-random` | | `packages/random/random` | `packages/foundation/random` | | `packages/react/react-forms` | `packages/forms/react-forms` | | `packages/react/react-i18n` | `packages/i18n/react-i18n` | | `packages/react/react-network-libp2p-demo` | `packages/network/react-network-libp2p-demo` | | `packages/search/search` | `packages/platform/search` | | `packages/security/audit-logging` | `packages/platform/audit-logging` | | `packages/security/data-masking` | `packages/platform/data-masking` | | `packages/security/hibp` | `packages/crypto/hibp` | | `packages/security/jws` | `packages/crypto/jws` | | `packages/security/password-formats` | `packages/crypto/password-formats` | | `packages/security/security-codes` | `packages/platform/security-codes` | | `packages/security/slsa` | `packages/crypto/slsa` | | `packages/security/tpm-struct` | `packages/crypto/tpm-struct` | | `packages/security/x509` | `packages/crypto/x509` | | `packages/streams/queue` | `packages/jobs/queue` | | `packages/streams/queue-distribution` | `packages/jobs/queue-distribution` | | `packages/streams/queue-distribution-http` | `packages/jobs/queue-distribution-http` | | `packages/streams/queue-distribution-worker-thread` | `packages/jobs/queue-distribution-worker-thread` | | `packages/streams/streams` | `packages/foundation/streams` | | `packages/svg/compress-svg` | `packages/docs/compress-svg` | | `packages/audio/third-party-elevenlabs` | `packages/audio/third-party-elevenlabs` | | `packages/validation/schema-registry` | `packages/foundation/schema-registry` | | `packages/validation/validation` | `packages/foundation/validation` | | `packages/vault/cbor` | `packages/foundation/cbor` | | `packages/vault/otp` | `packages/crypto/otp` | | `packages/vault/password-generator` | `packages/crypto/password-generator` | | `packages/vault/password-strength` | `packages/crypto/password-strength` | | `packages/vault/srp` | `packages/crypto/srp` | | `packages/vault/vault` | `packages/crypto/vault` | | `packages/vault/vault-sync` | `packages/crypto/vault-sync` | | `packages/vault/webauthn` | `packages/crypto/webauthn` | | `packages/workflow/rules` | `packages/platform/rules` | | `packages/workflow/rules-hono` | `packages/platform/rules-hono` | #### Pending-only categories (no packages on disk yet) | Category | Pending packages | |---|---| | `ai` | `mcp` | | `commerce` | `api-plugin-asset-register` · `api-plugin-bank` · `api-plugin-banking` · `api-plugin-payment` · `bank` · `banking` · `booking` · `currency` · `currency-providers` · `money` · `payment` · `payment-providers` · `react-bank` | | `contacts` | `api-plugin-contacts` · `contacts` · `macos-contacts` · `react-contacts` | | `games` | `animation-events` · `api-plugin-anticheat` · `api-plugin-game-persistence` · `api-plugin-game-server` · `api-plugin-matchmaking` · `behavior-tree` · `bone-mapping` · `building` · `character-physics` · `character-vehicle-picker` · `chat` · `combat` · `controller` · `crafting` · `day-night` · `destructibles` · `dialogue` · `economy` · `ecs` · `emotes` · `fauna` · `fruit-machine` · `game-loop` · `game-persistence` · `game-server` · `game-state-schemas` · `heightmap-tiles` · `hitbox` · `ik-solver` · `leaderboard` · `lobby` · `mixamo` · `npc` · `osm-buildings` · `party` · `progression` · `quest` · `react-controller` · `react-crafting` · `react-dialogue` · `react-game-hud` · `react-game-map` · `react-game-settings` · `react-social-game` · `vehicle-physics` · `weapons` · `weather` | | `gtap` | `api-plugin-gtap` | | `logistics` | `api-plugin-customs` · `api-plugin-logistics` · `api-plugin-port-tracking` · `api-plugin-procurement` · `api-plugin-warehouse-ops` · `customs` · `delivery-tracking` · `logistics` · `port-tracking` · `ports` · `procurement` · `react-port-tracking` · `warehouse-ops` | | `media` | `media` · `react-media` | | `store` | `api-plugin-inventory` · `api-plugin-store` · `api-plugin-subscription` · `api-plugin-virtual-currency` · `r3f-inventory` · `react-store` · `store` · `subscription` · `virtual-currency` | | `vatdash` | `api-plugin-vatdash-bridge` | | `video` | `react-video` · `video` · `video-thumbnails` | #### Categories that require no changes (already correct) | Category | Packages (already in place) | |---|---| | `auth` | `auth`, `jwt-tenant`, `macos-keychain`, `oauth-client`, `permissions` | | `bitpacker` | `bitpacker`, `bitpacker-compressed`, `bitpacker-crypto`, `bitpacker-delta`, `bitpacker-game`, `bitpacker-network`, `bitpacker-schema`, `bitpacker-strategies`, `bitpacker-streaming`, `bitpacker-validation` | | `crypto` | `crypto`, `crypto-bin`, `crypto-browser`, `crypto-cipher`, `crypto-cli`, `crypto-core`, `crypto-curve`, `crypto-hash`, `crypto-keys`, `crypto-rsa`, `crypto-sodium`, `crypto-utils`, `crypto-vault` | | `database` | `db`, `db-sqlite`, `migration`, `surrealdb` | | `events` | `event-sourcing`, `events`, `events-metrics`, `events-otel`, `events-schema`, `events-transport` | | `fonts` | `font-compress`, `font-convert`, `font-subset` | | `foundation` | `error`, `types`, `typescript-config` | | `geo` | `geo`, `geo-earth`, `geo-planet`, `react-map`, `solar-system`, `stars`, `tides`, `universe` | | `i18n` | `i18n`, `i18n-backend-chained`, `i18n-backend-fs-multifile`, `i18n-backend-http`, `i18n-backend-memory`, `i18n-backend-storage`, `i18n-detector`, `i18n-formatter-collator`, `i18n-formatter-currency`, `i18n-formatter-date`, `i18n-formatter-display-name`, `i18n-formatter-list`, `i18n-formatter-number`, `i18n-formatter-relative-time`, `i18n-formatter-segmenter`, `i18n-icu-parser`, `i18n-shared`, `locale-data` | | `image` | `compress-image` | | `network` | `network-bootstrap`, `network-discovery-crdt`, `network-discovery-gundb`, `network-discovery-hybrid`, `network-discovery-peerjs`, `network-discovery-trystero`, `network-discovery-webtorrent`, `network-gundb-server`, `network-libp2p-server`, `network-plugin-assignment`, `network-plugin-audit-mesh`, `network-plugin-auth`, `network-plugin-circuit-breaker`, `network-plugin-content-delivery`, `network-plugin-crypto-messages`, `network-plugin-devtools`, `network-plugin-failover`, `network-plugin-governance`, `network-plugin-intra-relay`, `network-plugin-middleware`, `network-plugin-migration`, `network-plugin-monitor`, `network-plugin-partition`, `network-plugin-protocol`, `network-plugin-qos`, `network-plugin-registry`, `network-plugin-relay`, `network-plugin-server-bridge`, `network-plugin-session`, `network-plugin-simulation`, `network-plugin-state`, `network-plugin-topology`, `network-plugin-transport-manager`, `network-testing`, `network-transport-hyperswarm`, `network-transport-libp2p`, `network-transport-standby`, `network-transport-webrtc`, `network-transport-websockets`, `network-trystero-server`, `network`, `service-discovery`, `websocket-gateway` | | `r3f` | `r3f-networking` (pending `r3f-audio` → `audio`, `r3f-dmx` → `audio`, `r3f-inventory` → `store`) | | `react` | `react-ui` | | `storage` | `storage`, `storage-browser-indexeddb`, `storage-browser-localstorage`, `storage-browser-opfs`, `storage-file`, `storage-s3` | --- ## Out of scope - **Renaming any package.** Only directories move; npm names (`@suaveplan/`) stay identical. - **Changing exports or APIs.** Pure structural change; no `package.json` `exports` map modifications except path-relative corrections if any exist. - **Pending packages.** Categories with zero existing packages (`commerce`, `contacts`, `games`, `gtap`, `logistics`, `store`, `vatdash`, `video`) do not receive empty scaffold folders. - **Migrating off Verdaccio for republish.** Republishes happen as part of normal pre-publish gates after the move. --- ## Verification approach Because this is a pure structural change, verification is structural rather than behavioral: 1. **Assignment validation** — run the updated migration script with `--check` to confirm every on-disk package is assigned to exactly one category and every assignment is unique. 2. **Path completeness** — after `git mv`, run a script that asserts: for every `packages/*/*/package.json`, the parent folder name matches the category listed in `FOLDERS.md` for that package name. 3. **Build integrity** — `bunx turbo lint typecheck test` across the full monorepo to confirm no relative-path breaks (all internal paths are either relative to package root or use workspace aliases). 4. **Manifest regeneration** — `bun scripts/folder-reorg/generate-manifest.ts` produces a `MANIFEST.md` whose plugin table matches `FOLDERS.md` exactly. 5. **Doc-reference sweep** — grep for every retired folder name (`packages/codes/`, `packages/primitives/`, etc.) in `CLAUDE.md`, `openspec/AGENTS.md`, and workflow files; verify zero hits. --- ## Migration impact - **Every package on genesis** (~140 packages) is affected: ~75 move, ~65 stay put. No code changes inside packages. - **All in-flight OpenSpec changes** that reference target paths need their `Target path:` line updated. This is mechanical; a single `sed` pass across `openspec/changes/*/` suffices. - **Root `package.json` workspaces** — no change needed; `packages/*/*` already covers the new layout. - **Turborepo** — `turbo.json` uses workspace globs and needs no changes. - **CI workflows** — any workflow that hard-codes a path like `packages/drive/` or `packages/security/` must be updated. --- ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/` for every moved package. Because this is a pure `git mv` with no API changes, no dependent source code requires modification. The verification step is confirming that `turbo lint typecheck test` passes for the full graph after the move. - All packages — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: pure directory move, no API change) --- ## References - `FOLDERS.md` — canonical category taxonomy (source of truth for this change) - `openspec/archive/2026-04-25-folder-reorg-by-domain/` — prior reorganization that created the current layout - `scripts/folder-reorg/assignment.yml` — to be rewritten - `scripts/folder-reorg/migrate-folders.ts` — to be updated or superseded - `MANIFEST.md` — to be regenerated - `CLAUDE.md` — path references to be updated