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

272 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<plugin>/<pkg>/`, 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/<plugin>/<pkg>/` 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/
<category>/
<package-name>/
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/<pkg>`) 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/<pkg>` 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