feat(config): multi-folder paths for components, primitives, composites, rules (#1420)#1421
Merged
feat(config): multi-folder paths for components, primitives, composites, rules (#1420)#1421
Conversation
…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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
RaftersConfigpath fields now accept either a single string (status quo, no breakage) or an array of entries: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:
{ root: true }wins as the install rootrafters addwrites to the install root.resolveReadSetreturns the install root first, so first-write-wins loaders produce local-wins on collision automatically.Surface
packages/cli/src/utils/paths.ts-- newPathEntry,PathField,resolveRoot,resolveReadSetpackages/cli/src/commands/init.ts--RaftersConfigpath fields arePathField, newrulesPath,installed.rules,COMPONENT_PATHSadds rules per frameworkpackages/cli/src/commands/add.ts--transformPathandtransformFileContentresolve throughrootFor,trackInstalledhandles rules bucketpackages/cli/src/mcp/tools.ts-- composite loader iteratesresolveReadSet(config.compositesPath, ...)instead of a hardcoded.rafters/compositesOut of scope
rulesPathfield is honored end-to-end so the rules system can land cleanly later.Test plan
pnpm preflight(typecheck, lint, unit, a11y, build){ root: true }, first-local-wins, zero-locals fallback, install-root-first ordering, dedupcompositesPatharray in a consumer project, verify MCPrafters_compositereturns shared composites