9.4 KiB
Context
packages/ currently holds 41 sibling folders at depth 2 (packages/<plugin>/<pkg>/). Three plugins (geo, i18n, r3f) collide with same-named packages they contain, producing packages/geo/geo/, packages/i18n/i18n/, packages/r3f/r3f/. A 4th catch-all (platform/) groups unrelated packages, and STATUS.md admits an implicit unknown/ bucket. The user wrote PROPOSED_STRUCTURE.md with 18 stable category names; this design turns that into an executable migration.
The user's first-draft mechanic was a packages_temp/ swap. That breaks Git rename detection for files whose containing directory is recreated mid-operation. git mv per plugin folder is strictly better — same end-state, full rename detection, single atomic commit.
Constraints: no public surface change, no version bump, no code edits in the rename commit, genesis branch only.
Goals / Non-Goals
Goals:
- One canonical home for every package, enforceable by a CI gate.
- Eliminate same-name nesting (
geo/geoetc.) and theplatform/catch-all. - Preserve
git log --followhistory across the move. - Update every workspace-glob, turbo, vitest, tsup, biome, and script reference in lockstep so
bun install && bunx turbo buildworks immediately after the commit. - Refresh
MANIFEST.md,CLAUDE.md,STATUS.md,assignment.ymlto describe the 3-level layout.
Non-Goals:
- Renaming any
@suaveplan/<pkg>import path. Only filesystem paths change. - Splitting or merging packages. Code is byte-identical post-move.
- Touching archived OpenSpec changes' content (paths quoted inside archives stay frozen).
- Adopting forward-looking categories for packages that do not yet exist (e.g.
video,store,logistics,gtap,vatdashnamed inPROPOSED_STRUCTURE.md); those land when those packages land. - Bumping any package version.
Decisions
1. Three-level layout: packages/<category>/<plugin>/<pkg>/
Chosen: keep the existing <plugin>/<pkg>/ pair and add <category>/ above it.
Alternatives:
- Drop the plugin level (
packages/<category>/<pkg>/): cleaner tree but loses the suaveplan-skills plugin grouping thatassignment.ymldepends on, and forces a flat list of ~270 package folders inside large categories likereact/andgames/. - Flatten same-name pairs (hoist
geo/geo/→geo/): solves nesting but creates an inconsistent depth (some packages at 2 levels, others at 3) that breaks every workspace glob.
Three-level is the only option that keeps glob-depth uniform AND preserves the plugin grouping.
2. git mv per plugin folder, one commit
Chosen: for each of the 41 plugin folders, run git mv packages/<plugin> packages/<category>/<plugin> (creating the category directory first). Squash into a single rename commit.
Alternatives:
packages_temp/swap (user's draft): breaks rename detection on files whose directory is recreated; reviewers see add+delete, not rename. Rejected.- Script-driven from
assignment.yml: still usesgit mvinternally, but the script becomes a single point of failure. We do the moves by hand from a checklist for auditability; the script lives intasks.mdas a documented helper.
3. Path-shape CI gate
Chosen: add a small script (scripts/lint/check-package-paths.ts) that walks packages/, asserts every package.json is at depth 3 (category/plugin/pkg), and asserts the category segment is in the canonical 18-name list. Wire it into turbo lint.
Alternatives:
- Enforce only via
package.jsonworkspaces glob: Bun silently ignores unmatched folders, so a stray two-level package becomes invisible rather than an error. Rejected. - No gate: relies on convention drift; the original two-level layout is itself evidence that convention without enforcement decays.
4. Canonical 18-category list from PROPOSED_STRUCTURE.md
Chosen: adopt the 18 categories as the closed vocabulary. assignment.yml maps every current plugin to exactly one. Adding a 19th requires a new OpenSpec change that modifies the monorepo-layout spec.
Alternatives:
- Open vocabulary: invites the same drift that produced
platform/andunknown/. Rejected.
5. Plugin-to-category assignments (binding)
From PROPOSED_STRUCTURE.md, the 41 existing plugin folders map as follows:
| Category | Plugin folders moving in |
|---|---|
foundation/ |
core, data, random, timing, bitpacker, barcode, flow |
persistence/ |
database, storage, cache |
network/ |
network, drive, events |
communication/ |
messaging, emails |
api/ |
api |
auth/ |
auth |
security/ |
security, crypto |
monitor/ |
monitor |
media/ |
audio, audio-fx, audio-ir, image, fonts |
react/ |
react, forms |
r3f/ |
r3f |
i18n/ |
i18n |
geo/ |
geo |
finance/ |
finance, payment |
commerce/ |
booking, contacts |
testing/ |
testing |
tooling/ |
tooling, docs, docker |
games/ |
games |
The platform/ plugin folder is split: its contents are inspected and each child plugin moves to the category that fits (most candidates: foundation/, api/, communication/, monitor/). The exact per-package decomposition is in tasks.md step 3.
6. Workspace-glob change
Chosen: root package.json workspaces glob becomes ["packages/*/*/*"] (single line). No transitional dual-glob.
Alternatives:
- Dual glob (
packages/*/*+packages/*/*/*) during transition: encourages partial migrations and delays the path-shape gate. Rejected — we move everything in one commit, no transition window.
Risks / Trade-offs
- Risk: turbo cache misses for the whole repo on the first build after the move. Mitigation: acceptable one-time cost; cache rebuilds in one
bunx turbo buildrun. Run it locally before pushing. - Risk: a hard-coded
packages/<plugin>/<pkg>path is missed in a vitest config, tsup config, or doc snippet, breaking that package's gate. Mitigation:tasks.mdincludes arg -n 'packages/[a-z][a-z0-9-]+/[a-z][a-z0-9-]+'sweep across the repo to find every literal occurrence before commit, plus a greenbunx turbo lint typecheck testgate post-move. - Risk: archived OpenSpec changes quote old paths; rewriting them violates the archive's frozen-content rule. Mitigation: archives are explicitly out of scope; readers see the old paths in archived deltas and the new paths in current docs.
CLAUDE.mdnotes this. - Risk: IDEs and
findshortcuts that assume 2-level depth break for users. Mitigation: announce the change in the commit message; the path-shape CI gate makes the new convention discoverable. - Trade-off: small categories (
auth/,monitor/,i18n/,geo/,r3f/,testing/,games/) each hold a single plugin folder. Accepted because uniform depth beats variable depth — every glob, every script, every readme can assume 3 levels. - Trade-off:
git mv41 folders is mechanical and tedious. Acceptable for a one-shot reorg;tasks.mdlists every command verbatim.
Migration Plan
- Branch:
chore/restructure-packages-by-categoryoffgenesis. - Pre-flight: clean working tree in every worktree (
git worktree list); turbo cache cleared;MANIFEST.mdsnapshot saved for post-move diff. - Move: run the 41
git mvcommands fromtasks.mdstep 4 in order. Create eachpackages/<category>/directory just before the firstgit mvinto it. - Decompose
platform/: per-package, choose target category,git mv. Remove the emptyplatform/directory. - Update configs: root
package.jsonworkspaces glob;turbo.json; anytsconfig*.jsonpaths;scripts/folder-reorg/generate-manifest.ts;scripts/folder-reorg/assignment.yml;scripts/publish.ts;scripts/lint/check-test-runner-imports.ts; anyvitest.config.*/vite.config.*/tsup.config.*that hard-codespackages/. - Add path-shape gate: create
scripts/lint/check-package-paths.tsand wire it into the rootlintscript. - Regenerate manifest:
bun scripts/folder-reorg/generate-manifest.ts. Diff against the pre-move snapshot — only path columns and the category column should change. - Update docs:
CLAUDE.mdLayout section,STATUS.md,PROPOSED_STRUCTURE.md(mark as adopted). - Verify:
bun install && bunx turbo lint typecheck testgreen end-to-end.git log --followspot-check on three packages (one fromgeo/, one fromcore/, one from the oldplatform/). - Commit: single squash commit titled
chore(repo): restructure packages under 18 categories. - Rollback: if step 9 fails,
git reset --hard origin/genesis(with explicit user approval — see CLAUDE.md git-safety rules). No partial restructure is shipped.
Open Questions
- Q1: Does the
platform/decomposition need its own follow-up OpenSpec change? Tentative answer: no — its packages are catalogued intasks.mdstep 3 and the per-package category assignment is mechanical given the 18-category vocabulary. Promote to a follow-up only if any platform sub-package's category is genuinely contested. - Q2: Do we add a
packages/<category>/README.mdper category? Tentative answer: no for now (CLAUDE.md rule: one README at package root only). Revisit if scanning a category's plugins becomes painful. - Q3: Do we re-fold archived OpenSpec changes' quoted paths? Tentative answer: no — archives stay frozen; that is the whole point of the archive.