File Ownership Model
Every file in the project belongs to exactly one owner: CORE (the shared theme framework) or SITE (the per-client customization layer). Ownership defines where new work should live, which files site teams may customize directly, and which paths npm run sync:core is allowed to update.
Who this is for
- Implementors adding new components or utilities to the core theme
- Developers customizing a site instance
- AI agents deciding where to place new files or which files are safe to edit
Root Ownership Zones
The charter defines a three-zone model at the src/ root:
- Shared purpose-based roots —
src/components/,src/lib/, andsrc/pages/ - Core split-concern root —
src/core/ - Site split-concern root —
src/site/
That high-level model is implemented with a few explicit exceptions and adjunct paths:
| Zone | Ownership | Purpose |
|---|---|---|
src/components/ | SITE-OWNED | Site-specific components (not shadow overrides) |
src/core/ | CORE-OWNED | Core side of split concerns plus core-only supporting code. Current subtrees include components/, config/, content/, lib/, pages/, styles/, and assets/. |
src/site/ | SITE-OWNED | Site side of split concerns: config/, styles/, assets/, and component shadow overrides. |
src/lib/ | CORE-OWNED | Shared utilities, integrations, contracts, and helpers used across routes and components. |
src/pages/ | Mixed | Mixed by implementation. Files marked // CORE-OWNED are part of the sync surface; unmarked files or // SITE-OWNED files are site-controlled. Current CORE-OWNED examples include API routes, OG generation, sitemap/robots/RSS routes, and the home-page stub. Theme-docs routes live under src/core/pages/theme-docs/. |
src/content/ | SITE-OWNED | Site-authored content collections such as pages, articles, and page fragments. |
When a concern has both core and site aspects, it is split under src/core/ and src/site/. Site-specific components that do not shadow core components live in src/components/. Theme documentation content is a deliberate CORE-OWNED exception inside src/core/content/theme-docs/ so documentation updates propagate through core sync.
Path Ownership Summary
CORE-OWNED — src/core/ (core side of split concerns):
src/core/components/— UI components (includesdocs/for shared Docs Kit components likeDocsSidebarNav)src/core/config/— schemas, requirements, Docs Kit factory (docs-collection-factory.ts,docs.schema.ts), theme-docs collection and defaultssrc/core/content/— CORE-OWNED theme documentation contentsrc/core/lib/— core-only content and remark utilitiessrc/core/pages/— CORE-OWNED injected routes, currentlytheme-docs/*src/core/styles/— token definitions, base CSSsrc/core/assets/— icons, vendor files
CORE-OWNED — other src/ root:
src/lib/— shared utilities, integrations, contractssrc/scripts/— source-adjacent helper scripts used by the app build/runtime
SITE-OWNED — src/components/ (site-specific components):
src/components/— site-specific components that are not shadow overrides. Imported via@/components/. Does not enter the core override chain.
SITE-OWNED — src/site/ (site side of split concerns):
src/site/config/— site identity, platform settings (platform.config.ts), menus, font configuration (fonts.config.ts), site-docs collection configuration (site-docs.ts)src/site/styles/— token overridessrc/site/assets/— site images, fonts (for local provider alternative)src/site/components/— component overrides
SITE-OWNED — other:
src/content/— content collectionssrc/content.config.ts— site collection registry; imports CORE-OWNED theme-docs schema fromsrc/core/config/and SITE-OWNED site-docs schema fromsrc/site/config/public/— static assetsastro.config.mjs.env
CORE-OWNED — outside src/:
scripts/— build, validation, sync, and release scripts
Ownership Markers
Source files use a first-line comment to declare their owner:
// CORE-OWNED
// SITE-OWNED
Marker requirements depend on the file’s location:
- Core-owned paths (
src/core/,src/lib/,src/scripts/,scripts/): markers are required. The// CORE-OWNEDmarker must be present. - Site-owned directory paths (
src/site/,src/components/,src/content/): the directory determines ownership. Markers are recommended for clarity but not required. The linter does not flag missing markers in these paths. If a marker is present, it must be// SITE-OWNED— a// CORE-OWNEDmarker in a site-owned directory is an error. src/pages/: the// CORE-OWNEDmarker is the sync allowlist. Files without that marker are treated as SITE-OWNED. This fail-safe direction protects downstream routes from accidental overwrite. (D-D4-02)- Markdown content: directory ownership applies. Markdown and MDX entries in
src/content/do not need markers. Theme-docs markdown undersrc/core/content/theme-docs/is CORE-OWNED by path.
The “recommended but not required” rule for site-owned directories reflects the reality that production sites are built by both humans and AI agents — not all implementers will follow the convention of adding markers to every file, and the directory boundary already provides the necessary ownership signal.
Sync Behavior
npm run sync:core updates the downstream site’s CORE-OWNED surface from the upstream core ref. The implementation is path-allowlisted and deterministic; it does not infer ownership from git history or local edits.
Directory-based sync — these paths sync as complete CORE-OWNED trees:
src/core/src/lib/src/scripts/scripts/
Marker-based sync — src/pages/ is synced file-by-file. Only files with a // CORE-OWNED marker in the upstream source are eligible for update. Unmarked files and // SITE-OWNED files are left alone. In the current implementation this covers infrastructure and framework routes such as:
src/pages/api/*src/pages/og/*src/pages/robots.txt.tssrc/pages/rss.xml.tssrc/pages/sitemap.astrosrc/pages/sitemap.xml.tssrc/pages/index.astro
Theme-docs routes are not part of the mixed src/pages/ area anymore. They live in src/core/pages/theme-docs/ and enter the site via the theme-docs integration, so they follow the directory-based CORE sync path instead.
Site stub seeding — after core-owned files sync, the script seeds required site-owned support files when they are missing, such as src/site/config/platform.config.ts and required src/site/styles/* stubs. Existing downstream files are never overwritten; seeding only prevents synced core imports from breaking older sites that do not have a newly introduced site-owned support file yet.
Exclusion filter — after directory-based and marker-based sync complete, the script reads .downstream-exclude from the upstream core ref (via git show, never from the local disk copy) and filters the sync output. Files matching exclude patterns are restored to their pre-sync state (if they existed) or removed (if new from upstream). This prevents development artifacts (.docs/, AGENTS.md, CLAUDE.md, etc.) from appearing in the downstream working tree even though they live inside synced directories. The exclusion filter only acts on files the sync staged — it never touches existing site files outside the sync surface, even if they match an exclude pattern.
Core docs copy — after the exclusion filter, the sync script copies AGENTS.md, ARCHITECTURE.md, CLAUDE.md, and .docs/standards/GIT.md from the core git ref into a .core/ directory at the child site root. These files are core-owned and overwritten on every sync:core run. Site-owned root-level AGENTS.md, CLAUDE.md, and ARCHITECTURE.md provide site-specific guidance and reference .core/ for platform conventions — they are seeded at bootstrap by create-site.mjs and never overwritten by sync.
Not synced:
src/components/(SITE-OWNED)src/site/(SITE-OWNED)src/content/(SITE-OWNED)src/content.config.ts(SITE-OWNED — imports CORE-OWNED collection helpers but remains site-controlled)public/(SITE-OWNED)astro.config.mjs(SITE-OWNED)- Files matching
.downstream-excludepatterns (development artifacts — excluded from sync output)
After sync: sites must run npm run validate before committing the update.
Site Customization Layers
Sites customize through a layered hierarchy, from least to most invasive:
- Design tokens — Override visual properties in
src/site/styles/(colors, spacing, typography) - Configuration — Override identity and behavior in
src/site/config/(site name, menus, analytics) - Content — Add and edit site pages and articles in
src/content/. Theme documentation is CORE-OWNED insrc/core/content/theme-docs/and syncs automatically. - Static assets — Replace favicons, fonts, and images in
public/ - Component shadow/overlay — Replace a core component entirely in
src/site/components/
Most sites only need layers 1–4. Layer 5 is the escape hatch for structural component changes that tokens and props cannot address.
Component Shadow/Overlay
Components use a three-tier resolution model:
src/site/components/— shadow overrides (SITE-OWNED, checked first)src/core/components/— core components (CORE-OWNED, fallback)src/components/— site-specific components (SITE-OWNED, independent, not in the override chain)
To override a core component, create a file at the same relative path under src/site/components/:
- Core:
src/core/components/primitives/Button.astro - Site override:
src/site/components/primitives/Button.astro
The component override plugin substitutes file contents at load time. Any import that resolves to a file under src/core/components/ is compiled with the matching file from src/site/components/ when that override exists. The module id remains the core path, which keeps Astro scoped-style ids aligned between the substituted template and styles. The override must implement the same prop interface as the component it replaces. Shadow overrides are logged at build/dev startup.
Because the core module id is preserved, relative imports inside an override resolve from the matching src/core/components/ directory. Use aliases such as @site/... or @/components/... for site-only helper components or utilities.
Site-specific components in src/components/ are imported via @/components/ and bypass the override chain entirely.
Overrides are opt-in and granular — sites only shadow the specific components they need to change.
public/ Directory
The public/ directory is fully SITE-OWNED. Core provides sensible defaults at project initialization time; sync:core never touches it.
Sites replace favicons, social images, and the web manifest as part of setup. Font customization is handled via src/site/config/fonts.config.ts, which imports core defaults from src/core/config/fonts.config.ts and can replace them with a custom font set.
Breaking Changes
A change is breaking (requires major version bump) when it:
| Category | Breaking | Non-breaking |
|---|---|---|
| Config contracts | Remove/rename field; change type | Add optional field with default |
| Content schemas | Remove/rename frontmatter field; change type | Add optional field with default |
| Token contracts | Remove/rename semantic token; change layer | Add new token; change primitive value |
| Validation gates | Remove gate step; tighten threshold | Add gate step; relax threshold |
| Ownership boundaries | Move path from src/site/ to core | Move path from core to site |
| Component APIs | Remove/rename prop; change required/optional | Add optional prop with default |
Breaking changes require a BREAKING CHANGE: footer in the commit and migration guidance in the changelog.