The three-layer model

Every piece of configuration in the system lives in exactly one of three layers. The layer you put something in is decided by one question: what does it change with?

The layers

LayerLives inOwns
Coredotfiles-core, vendored into every OS repo’s core/zsh modules, tmux, git, starship, and the editor it vendors in
OS-nativedotfiles-{MacBook,Fedora,Arch,Debian,openSUSE,Alpine,Gentoo,NixOS}package manager, clipboard, paths
Roledotfiles-Offense, dotfiles-Defenseoffensive / defensive tooling on the OS layer
Native hostdotfiles-Windowsthe Windows host: pwsh, Terminal, the WSL bridge
Editordotfiles-nvim, vendored into Core’s nvim/ and into dotfiles-Windowsthe Neovim config

dotfiles-Windows is the model’s one named exception rather than a fourth layer: it vendors no Core at all, replicating it natively in PowerShell, so it sits beside the three rather than inside the OS-native row.

dotfiles-nvim is the other exception, and its arrow points the other way. The editor is still identical on every machine, so it ships as part of Core. But it is authored in dotfiles-nvim, whose gate can do what Core’s cannot: start a real Neovim, install the pinned plugins and run :checkhealth. Core vendors a tagged release into nvim/, pinned by nvim.lock, and a hand-edit there fails Core’s audit. Editor changes go upstream, and the pin moves with a Core release. dotfiles-Windows vendors the same release through its own nvim.lock.

The rule for where a change belongs

A change belongs in Core only if it is identical on every machine, not OS-specific, and not role-specific. Concretely:

  • Changes with the OS → the OS repo. Anything that differs by package manager, clipboard backend, or filesystem path is OS-native, not Core.
  • Changes with the operator’s role → the role repo. Offensive engagement tooling belongs in dotfiles-Offense; defensive detection tooling in dotfiles-Defense.
  • Everything else that’s truly universal → Core.

Core is authored once and vendored into each OS repo, so a defect in Core fans out to all of them at once. That leverage is the whole point — and the reason Core changes go through a single audit gate before they ship. See Vendoring Core for how the fan-out works.

Why split it this way

The alternative — one repo with per-OS conditionals — collapses under its own weight as the fleet grows: the universal parts and the host-specific parts tangle together until no one can say what is shared and what is not. The layer split keeps that boundary explicit and auditable: each OS repo is a clean, self-contained, public artifact, and Core stays plain and OS-agnostic.

It is deliberately more structure than a single-machine setup needs. If you have one or two boxes with no real OS spread, a bare $HOME git repo or GNU stow is far less ceremony — the layer model earns its keep only across a genuine multi-OS fleet.