xmatix init scaffolds two things in your repository root: the .xmatix/ config directory, and the canonical xmatix/ folder tree where round-tripped artifacts live. Every pull writes into that tree and every push reads from it — the layout is the contract between the CLI and your repository, which is why it is fixed rather than configurable.
.xmatix/
config.json # tenant + API + sign-in settings (no secrets)
.gitignore # keeps cache/ and tokens/ out of the repo
xmatix/
metadata/
tenant-owned.json # seed-pack from `metadata pull` (per-scope names)
automation/
scripts/<Entity>/<Name>.csx # one C# script per file…
scripts/<Entity>/<Name>.meta.json # …with its id/version/binding companion
rules/<Entity>/<Rule>.json
workflows/<Name>.json # full workflow definition incl. activity tree
analytics/
dashboards/<Name>.json # full row incl. the designer XML
reports/<Name>.json # full row incl. banded-report layout XML
.snapshot/
migrate-plan.json # output of `migrate plan`
server/ # live export kept by `migrate plan --keep-server-snapshot`
Sign-in tokens are deliberately not in this tree — they are cached per user and per tenant under ~/.xmatix/tokens/<tenant> in your home directory, encrypted by the OS credential store (Authentication has the details).
Discovery
The CLI finds its workspace by walking up from the current directory until it meets a directory containing .xmatix/ — so any subdirectory of the repository hosts a working invocation, and there is no flag or environment variable for "which workspace": your location decides. xmatix doctor prints the resolved root and validates the config and every canonical folder (folders missing before the first pull are noted, not failed).
Two practical consequences. Nesting workspaces is asking for surprises — the nearest .xmatix/ up the tree wins. And running the CLI outside any repository fails immediately with "no workspace found", which is the correct outcome rather than an accidental default tenant.
File-name policy
- Files are named for the artifact's logical name, never its id — so diffs and directory listings read like the product.
- Names are sanitized: characters invalid in filenames are replaced with
_. - Scripts are grouped into a folder per target entity (from the script's first binding); scripts with no binding land under
_unbound/. - Workflows with an empty name fall back to their definition id as the filename.
Renames follow from this: renaming an artifact in the product means the next pull writes a new file, and the old file lingers untracked-by-the-server. Delete the stale file in the same commit you pull the rename, or a later snapshot import will faithfully push the old artifact back.
What belongs in source control
Commit .xmatix/config.json and everything under xmatix/ except .snapshot/ (plan output and server exports are build artifacts — most teams gitignore xmatix/.snapshot/). Never commit tokens or caches; init's .gitignore guards the .xmatix/ side of that automatically.
Common questions
Can I move artifacts to different folders?
No — the folder tree is the contract, and push, watch, snapshot and migrate all resolve artifact kinds from their canonical locations. What you can choose is where the workspace root lives (any directory, typically the repo root) and which kinds you use at all: unused folders simply stay empty.
Why did pull create a file that duplicates an existing one?
Almost always a rename or a sanitized character: the artifact's server name changed (new file, old file stale) or two artifacts sanitize to similar names. Check git status after every pull — added-plus-unmodified pairs with similar names are the fingerprint of a rename, and the stale file should be deleted in the same commit.
Does one repository support multiple tenants?
One workspace supports exactly one tenant, but a repository can host several workspaces in separate directories — each with its own .xmatix/ and xmatix/ tree. Commands act on whichever workspace encloses your current directory, and each keeps its own token cache entry, so switching tenants is cd, not reconfiguration.
