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

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/geo etc.) and the platform/ catch-all.
  • Preserve git log --follow history across the move.
  • Update every workspace-glob, turbo, vitest, tsup, biome, and script reference in lockstep so bun install && bunx turbo build works immediately after the commit.
  • Refresh MANIFEST.md, CLAUDE.md, STATUS.md, assignment.yml to 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, vatdash named in PROPOSED_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 that assignment.yml depends on, and forces a flat list of ~270 package folders inside large categories like react/ and games/.
  • 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 uses git mv internally, but the script becomes a single point of failure. We do the moves by hand from a checklist for auditability; the script lives in tasks.md as 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.json workspaces 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/ and unknown/. 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 build run. 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.md includes a rg -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 green bunx turbo lint typecheck test gate 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.md notes this.
  • Risk: IDEs and find shortcuts 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 mv 41 folders is mechanical and tedious. Acceptable for a one-shot reorg; tasks.md lists every command verbatim.

Migration Plan

  1. Branch: chore/restructure-packages-by-category off genesis.
  2. Pre-flight: clean working tree in every worktree (git worktree list); turbo cache cleared; MANIFEST.md snapshot saved for post-move diff.
  3. Move: run the 41 git mv commands from tasks.md step 4 in order. Create each packages/<category>/ directory just before the first git mv into it.
  4. Decompose platform/: per-package, choose target category, git mv. Remove the empty platform/ directory.
  5. Update configs: root package.json workspaces glob; turbo.json; any tsconfig*.json paths; scripts/folder-reorg/generate-manifest.ts; scripts/folder-reorg/assignment.yml; scripts/publish.ts; scripts/lint/check-test-runner-imports.ts; any vitest.config.* / vite.config.* / tsup.config.* that hard-codes packages/.
  6. Add path-shape gate: create scripts/lint/check-package-paths.ts and wire it into the root lint script.
  7. Regenerate manifest: bun scripts/folder-reorg/generate-manifest.ts. Diff against the pre-move snapshot — only path columns and the category column should change.
  8. Update docs: CLAUDE.md Layout section, STATUS.md, PROPOSED_STRUCTURE.md (mark as adopted).
  9. Verify: bun install && bunx turbo lint typecheck test green end-to-end. git log --follow spot-check on three packages (one from geo/, one from core/, one from the old platform/).
  10. Commit: single squash commit titled chore(repo): restructure packages under 18 categories.
  11. 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 in tasks.md step 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.md per 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.