Project Structure
How to organize .xcaf files — from flat single-file setups to domain-driven folder structures.
xcaffold discovers resources by scanning your project directory recursively for *.xcaf files. Every file is routed by its kind: field — there is no filename convention required and no manifest that must be kept in sync. The filesystem is the source of truth.
Filesystem Boundaries
Before choosing a layout, understand the three hard boundaries xcaffold enforces:
- Source files (
xcaf/) — User-authored.xcafresource definitions. Scanned recursively. Version-controlled. - State files (
.xcaffold/) — Tool-managed, committed to git. Contains*.xcaf.statefiles written byxcaffold apply. Never edit these manually. Provides the team-wide drift detection baseline. - Compiled output (
.claude/,.cursor/,.gemini/,.agents/,.github/) — Generated byxcaffold apply. Do not edit these directories directly; edits are overwritten on the next apply and will be flagged as drift byxcaffold status.
Important:
kind: projectand all resource files live inxcaf/(or at the project root). The.xcaffold/directory is tool-managed — it holds state files, not source files. State files are committed to git so the team shares a common drift baseline. A snapshot of the compiled manifest at.xcaffold/project.xcaf.stateis tool-generated, not the authoritative source.
Quickstart Layout
Best for: Getting started quickly, personal experiments, or single-agent projects. This is the simplest valid xcaffold structure — a project.xcaf and one or more resource files at the root:
my-project/
├── project.xcaf ← kind: project (metadata + targets)
├── agent.xcaf ← kind: agent (inline)
└── .claude/ ← generated outputproject.xcaf (pure YAML, no frontmatter delimiters):
kind: project
version: "1.0"
name: my-project
targets:
- claudeagent.xcaf (frontmatter + body):
---
kind: agent
version: "1.0"
name: developer
description: "General-purpose developer agent."
model: sonnet-4
---
You are a generalist developer. Help with code reviews and debugging.Once your project grows beyond a handful of resources, graduate to the structured layout below. The flat approach works but offers no organization — every file sits at the same level.
Simple Layout
Best for: Small to medium projects that want some organization without the full directory-per-resource structure. Each resource gets its own file inside xcaf/, grouped by kind directory:
my-project/
├── project.xcaf
└── xcaf/
├── agents/
│ └── developer/ ← agents need subdirectory (for memory)
│ └── agent.xcaf
├── skills/
│ └── git-workflow.xcaf ← flat file, instructions in body
├── rules/
│ └── testing.xcaf ← flat file, rule content in body
└── mcp/
└── filesystem.xcaf ← flat file, config in YAMLIn this layout, each .xcaf file has explicit kind: and name: fields. The markdown body carries the instructions directly — no supporting directories needed:
xcaf/agents/developer/agent.xcaf:
---
kind: agent
version: "1.0"
name: developer
description: "Backend developer agent."
---
I am a developer.xcaf/skills/git-workflow.xcaf:
---
kind: skill
version: "1.0"
name: git-workflow
description: "Standard git workflow for feature branches."
allowed-tools: [Bash, Read]
---
Follow this workflow for all feature branches:
1. Create branch from main
2. Make changes in small commits
3. Open PR when ready for reviewxcaf/rules/testing.xcaf:
---
kind: rule
version: "1.0"
name: testing
description: "Testing standards."
activation: always
---
Write tests for all new functions. Use table-driven tests in Go.When to graduate: Move to the standard directory layout when a skill needs supporting files (references, scripts, assets) or an agent needs memory entries. The flat layout works for resources where the body contains everything.
Standard Layout
Best for: Most projects. This is the recommended default for any project with more than one or two agents.
Separate each resource kind into its own directory under xcaf/:
my-project/
├── project.xcaf
└── xcaf/
├── agents/
│ └── developer/
│ └── agent.xcaf
├── skills/
│ └── git-workflow/
│ └── skill.xcaf
└── rules/
└── testing/
└── rule.xcafNote: Agents must live in a subdirectory:
xcaf/agents/<name>/agent.xcaf. A flat file atxcaf/agents/developer.xcaftriggers a validation warning because agents require directory structure for memory discovery. All other kinds (skills, rules, workflows, MCP, etc.) work as either flat files (xcaf/skills/my-skill.xcaf) or directory-based (xcaf/skills/my-skill/skill.xcaf). The directory layout is recommended for skills that use references, scripts, or assets.
Large-Scale Layout
Best for: Large, cross-functional projects with multiple teams and CODEOWNERS rules.
Group resources by the domain they govern. The scanner discovers all *.xcaf files recursively regardless of nesting depth, so domain-based grouping is simply a naming convention — xcaffold imposes no restrictions on how you nest directories under xcaf/:
my-project/
├── project.xcaf
└── xcaf/
├── frontend/
│ ├── agents/
│ │ └── ui-developer/
│ │ └── agent.xcaf
│ └── rules/
│ └── accessibility/
│ └── rule.xcaf
├── backend/
│ ├── agents/
│ │ └── api-developer/
│ │ └── agent.xcaf
│ └── rules/
│ └── api-conventions/
│ └── rule.xcaf
├── policies/
│ └── require-descriptions.xcaf
└── blueprints/
├── frontend.xcaf
└── backend.xcafWith this layout, CODEOWNERS rules naturally map to xcaffold resource ownership:
xcaf/frontend/ @frontend-team
xcaf/backend/ @backend-team
xcaf/policies/ @platform-teamWhen to Introduce Blueprints
Once you have a domain-split layout with multiple teams, kind: blueprint files let each team compile only their subset of agents and rules. See Blueprint Design for guidance on when and how to introduce them.
Decision Guide
| Project Size | Recommended Layout |
|---|---|
| Single agent, personal experiments | Quickstart — flat files at project root |
| A handful of resources, small team | Simple — flat files in xcaf/ |
| Multiple resource types, growing team | Standard — organized by resource type in xcaf/ |
| Multiple domains, large team | Large-scale — organized by domain in xcaf/ |
| Multiple developers with different needs | Add blueprints to any layout above |
Key Rules at a Glance
| Rule | Why |
|---|---|
Agents use xcaf/agents/<name>/agent.xcaf | Canonical filename; parser triggers a validation warning for flat agent files under xcaf/agents/ because agents need directory structure for memory discovery |
| Skills can be flat or directory-based | xcaf/skills/my-skill.xcaf (flat) or xcaf/skills/my-skill/skill.xcaf (directory); use directory layout when you need references, scripts, or assets |
| Rules, workflows, MCP, and other kinds work flat | All kinds except agents accept flat files with explicit kind: and name: fields |
| Duplicate resource IDs across files are rejected | mergeAllStrict enforces uniqueness — reorganize, don't rename |
.xcaffold/ is tool-managed and committed | State files provide shared audit trail — never edit manually |
| Never edit compiled output directories | xcaffold status flags manual edits as drift |
Version Control
The .xcaf manifests are the single source of truth. Compiled output is a deterministic build artifact — regenerated by xcaffold apply from source at any time. The recommended practice is to version-control only the source and gitignore everything that can be derived from it.
What to Track
| Path | Track in Git | Reason |
|---|---|---|
xcaf/, project.xcaf | Yes | The one and only source of truth |
Compiled output (.claude/, .cursor/, etc.) | No | Build artifact — regenerated by xcaffold apply |
.xcaffold/ | Yes (recommended for teams) | State files provide a shared audit trail — git history shows who compiled what and when |
xcaf/project.vars.local | No | Local developer overrides (may contain secrets) |
Why Compiled Output Is Not Tracked
Tracking both the source and its deterministic derivative creates redundancy — two representations of the same configuration in the same repository. This weakens the source-of-truth guarantee:
- PRs show double diffs. Every
.xcafchange produces a source diff and N provider output diffs. The output diffs are noise — if you understand the source change, the compiled output follows deterministically. - Merge conflicts double. Conflicts can occur in both source and output. Only the source conflict matters.
- Manual edits become impossible. If compiled output is gitignored, a developer cannot accidentally commit a hand-edit to a provider directory. The error class is eliminated, not just detected.
Gitignoring compiled output follows the same principle as gitignoring node_modules/, dist/, or .terraform/ — if an artifact can be regenerated from committed source, it should not be committed itself.
Setup After Clone
After cloning a project, regenerate compiled output before using any provider tool:
git clone <repo>
xcaffold apply # generate provider configurationsThis is the same pattern as npm install or go mod download — a standard build step that every developer already expects.
Drift Detection
The state file (.xcaffold/project.xcaf.state) records the SHA-256 hash of every source file and every compiled artifact.
When a developer edits a compiled file directly:
xcaffold statusreads the state file- For each tracked artifact, it recomputes the SHA-256 hash from the file on disk
- If the computed hash differs from the recorded hash, the file is reported as artifact drift (
modified)
Drift detection does not rely on git. It compares the state file (what was last compiled) against the filesystem (what exists now). See State Files and Drift Detection for the full mechanism.
When .xcaffold/ is committed to git, the state file becomes a shared audit trail. Every xcaffold apply updates the state hashes and the git diff shows exactly what changed and who compiled it. In CI, xcaffold status compares the committed state against the compiled output, catching drift that any team member introduced.
Without committing .xcaffold/, drift detection is machine-local only — each developer has their own state baseline, and CI has no baseline at all.
CI Integration
In CI pipelines, run xcaffold apply as a build step. Since output is generated fresh from source on every run, there is no stale-output risk and no drift to check — the output is always correct by construction.
Recommended .gitignore
.claude/
.cursor/
.gemini/
.agents/
.github/copilot-instructions.md
.github/agents/
.github/instructions/
xcaf/project.vars.local
Note: Committing
.xcaffold/state files is the recommended practice for teams — it enables shared drift detection and provides a git-traceable audit trail. If your team prefers machine-local state only, add.xcaffold/to your.gitignore. Drift detection andxcaffold statusfunction identically in both modes — the difference is whether the baseline is shared across the team or local to each developer.
Related
- Blueprint Design — when and how to introduce blueprints as a project grows
- Skill Organization — directory layout conventions for skill resources
- Multi-Target Compilation — how project structure interacts with multi-target output