Skip to content

feat(config): multi-folder paths for components, primitives, composites, rules (#1420)#1421

Merged
ssilvius merged 3 commits intomainfrom
feat/1420-multi-folder-paths
Apr 25, 2026
Merged

feat(config): multi-folder paths for components, primitives, composites, rules (#1420)#1421
ssilvius merged 3 commits intomainfrom
feat/1420-multi-folder-paths

Conversation

@ssilvius
Copy link
Copy Markdown
Collaborator

Closes #1420.

Why

This unlocks @shingle/shared -- the workspace package where shingle composites will live (huttspawn, smugglr, gitpress, sean.silvius.me will all consume it). Each shingle site already has its own .rafters/ install for per-site token overrides; composites need to flow the other way -- written once in shared, read by every site.

What changed

RaftersConfig path fields now accept either a single string (status quo, no breakage) or an array of entries:

"compositesPath": ["src/composites", "../shared/composites"]

Entries may be plain strings or objects with { path, root: true } to override which folder is the install target.

Resolution rules

For each path field:

  1. Entry tagged { root: true } wins as the install root
  2. Else first entry whose realpath resolves inside cwd
  3. Else the framework default at cwd

rafters add writes to the install root. resolveReadSet returns the install root first, so first-write-wins loaders produce local-wins on collision automatically.

Surface

  • packages/cli/src/utils/paths.ts -- new PathEntry, PathField, resolveRoot, resolveReadSet
  • packages/cli/src/commands/init.ts -- RaftersConfig path fields are PathField, new rulesPath, installed.rules, COMPONENT_PATHS adds rules per framework
  • packages/cli/src/commands/add.ts -- transformPath and transformFileContent resolve through rootFor, trackInstalled handles rules bucket
  • packages/cli/src/mcp/tools.ts -- composite loader iterates resolveReadSet(config.compositesPath, ...) instead of a hardcoded .rafters/composites

Out of scope

Test plan

  • pnpm preflight (typecheck, lint, unit, a11y, build)
  • new unit tests cover: single-string, explicit { root: true }, first-local-wins, zero-locals fallback, install-root-first ordering, dedup
  • manual: configure a compositesPath array in a consumer project, verify MCP rafters_composite returns shared composites

ssilvius and others added 3 commits April 24, 2026 22:00
…es, rules (#1420)

Path fields in RaftersConfig now accept either a single string (status quo) or
an array of entries to support multi-folder layouts. Unlocks @shingle/shared:
each shingle site can declare its own composites folder plus the shared package
without copy-paste, while per-site token overrides stay local.

Schema:
  componentsPath / primitivesPath / compositesPath / rulesPath:
    string | (string | { path: string; root?: true })[]

Resolution:
  1. Entry tagged { root: true } wins as install root
  2. Else first entry whose realpath resolves inside cwd
  3. Else framework default at cwd

Local always wins on collision. resolveReadSet puts the install root first in
the returned array, so first-write-wins loaders produce local-wins reads.

New helpers in utils/paths.ts: resolveRoot, resolveReadSet. MCP composite loader
reads the resolved set instead of hardcoded .rafters/composites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two-branch check (one for existing path, one for non-existing) was
doing the same thing -- tryRealpath already returns the abs path on
ENOENT, so a single isInsideCwd check covers both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The init.installed shape gained a rules array in the multi-folder paths
change but the integration assertion still listed only three buckets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ssilvius ssilvius merged commit 4bf5d4c into main Apr 25, 2026
1 check passed
@ssilvius ssilvius mentioned this pull request Apr 25, 2026
2 tasks
ssilvius added a commit that referenced this pull request Apr 27, 2026
* feat(config): multi-folder paths for components, primitives, composites, rules (#1420)

Path fields in RaftersConfig now accept either a single string (status quo) or
an array of entries to support multi-folder layouts. Unlocks @shingle/shared:
each shingle site can declare its own composites folder plus the shared package
without copy-paste, while per-site token overrides stay local.

Schema:
  componentsPath / primitivesPath / compositesPath / rulesPath:
    string | (string | { path: string; root?: true })[]

Resolution:
  1. Entry tagged { root: true } wins as install root
  2. Else first entry whose realpath resolves inside cwd
  3. Else framework default at cwd

Local always wins on collision. resolveReadSet puts the install root first in
the returned array, so first-write-wins loaders produce local-wins reads.

New helpers in utils/paths.ts: resolveRoot, resolveReadSet. MCP composite loader
reads the resolved set instead of hardcoded .rafters/composites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(paths): collapse redundant existsSync branches in resolveRoot

The two-branch check (one for existing path, one for non-existing) was
doing the same thing -- tryRealpath already returns the abs path on
ENOENT, so a single isInsideCwd check covers both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(integration): add rules: [] to installed shape assertion

The init.installed shape gained a rules array in the multi-folder paths
change but the integration assertion still listed only three buckets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(release): rafters v0.0.54

Bumps the CLI from 0.0.53 -> 0.0.54 to ship the multi-folder paths
feature merged in #1421 (#1420). Adds docs/CONFIG.md walking through
the path field shape, resolution rules, and the @shingle/shared
motivation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Config: support multi-folder paths for components, primitives, composites, rules

1 participant