Architecture
Repository Layout
A map of the top-level folders and files in this repo, grouped by purpose. Use this when you're not sure where something belongs or why a particular directory exists.
For naming conventions (file prefixes, run-script numbering tiers), see ../NAMING_CONVENTIONS.md. For the broader architectural rationale (philosophy, startup model, flake strategy), see ARCHITECTURE.md. For the configuration-management strategy at a higher level, see ../CONFIG_STRATEGY.md.
1. Chezmoi source tree (defaults/): the actual dotfiles
.chezmoiroot contains defaults, so chezmoi reads defaults/ and deploys it to $HOME. Nothing outside defaults/ is deployed. Every path in this section is relative to defaults/. Filename prefixes carry semantics:
| Prefix | Effect | Example |
|---|---|---|
dot_ | Deployed with a leading . | dot_gitconfig.tmpl → ~/.gitconfig |
private_ | Sets 0600 perms on deploy | private_dot_ssh/ → ~/.ssh/ |
executable_ | Sets +x on deploy | dot_local/bin/executable_dot.tmpl → ~/.local/bin/dot |
run_onchange_ | Runs once when its content hash changes | run_onchange_20-ghostty-config.sh.tmpl |
.tmpl | Rendered as a Go template at apply time | dot_gitconfig.tmpl |
Gotcha:
executable_dot_foodeploys as.foo(notdot_foo). Chezmoi consumes thedot_prefix even when stacked withexecutable_.
Chezmoi data and templates
| Path | Role |
|---|---|
.chezmoi.toml.tmpl | Init-time prompts (name, email, signing key). |
.chezmoidata.toml | Source of truth for version, profile, default theme, shell, feature flags. |
.chezmoidata/ | Split data files: apple.toml, hardware.toml, keybinds.toml, secrets-patterns.toml. |
.theme-manifest/themes.toml | Theme palette manifest read by the theme templates. |
.chezmoiignore, .chezmoiignore.tmpl | Feature-flag-gated "don't deploy these to $HOME" list. |
.chezmoitemplates/ | Reusable template partials (aliases/, functions/, desktop/, go-build-command, theme-name). Not deployed. |
Shell rc files
| Path | Target | Purpose |
|---|---|---|
dot_bashrc, dot_profile | ~/.bashrc, ~/.profile | Bash startup |
dot_zshenv, dot_zprofile, dot_zshrc | ~/.zshenv etc. | Zsh startup chain (see ARCHITECTURE.md for order) |
dot_vimrc, dot_inputrc, dot_psqlrc, dot_sqliterc, dot_Xresources | ~/.* | Classic per-tool dotfiles |
dot_gitconfig.tmpl, modify_private_dot_npmrc | ~/.gitconfig, ~/.npmrc | Templated, identity/tokens injected at apply |
dot_cargo/config.toml.tmpl | ~/.cargo/config.toml | Cargo build settings |
dot_fdignore, dot_noderc, dot_rustfmt.toml | ~/.* | Per-tool config |
private_dot_netrc.tmpl, private_dot_ssh/ | ~/.netrc, ~/.ssh/ | 0600 files |
Large chezmoi directories
| Path | Target | Notes |
|---|---|---|
dot_config/ | ~/.config/ | Largest: fish, zsh, nvim, ghostty, tmux, niri, mise, and about 80 other app configs. |
dot_local/bin/ | ~/.local/bin/ | Helper scripts, plus the ~/.local/bin/dot shim that execs the repo's bin/dot. |
dot_local/share/ | ~/.local/share/ | Shared data: completions, man pages, icons, and the Go sources of dot-ui, dot-ai-tui and dot-mcp. |
dot_claude/, dot_codex/, dot_warp/ | ~/.claude/ etc. | Agent instructions and skills, and editor/terminal themes. |
private_Library/LaunchAgents/ | ~/Library/LaunchAgents/ | macOS launch agents. |
Code/ | ~/Code/ | Cross-repo standards (AGENTS.md, REPO-STANDARD.md, templates). |
run_* hooks
Scripts triggered when their content changes (or, for run_before_, before every apply). Numbering follows the convention in ../NAMING_CONVENTIONS.md. Examples:
run_onchange_20-ghostty-config.sh.tmpl: re-renders Ghostty configrun_onchange_24-build-dot-ui.sh.tmplto26-build-dot-mcp.sh.tmpl: build the Go toolsrun_onchange_after_fonts.sh.tmpl: post-deploy font cache refresh
2. The dot CLI and its framework
| Path | Purpose |
|---|---|
bin/ | The host bin/dot (bash), dot.ps1, and helpers (dot-bootstrap, dot-theme-sync, dot-load-benchmark-pty) |
plugins/<domain>/ | One plugin per domain: dot-plugin.toml (the manifest), commands.txt (its route fragment), dot-<domain> (its entry) and libexec/ |
lib/dot/ | Shared bash library for the host and plugins, and lib/dot/data/ (the command table, route and group records) |
lib/wasm-tools/ | dot-sys Rust crate (lib + bin), built for wasm32-wasip1: the health-probe record and the sandboxed verifier behind dot attest verify; gated by rust.yml |
core/ | Go core: dot-core, dot-sandbox, the host process layer and the transaction engine |
scripts/ | Runtime scripts the dot CLI and chezmoi hooks invoke (see ../../scripts/README.md) |
schemas/ | JSON schemas, including schemas/cli/ (manifest, registry, verbs, flags, domains) |
share/ | Generated completions, man pages and the PowerShell module |
pkg/ | Packaging: brew/, scoop/, aur/, nix/, docker/ |
security/ | Registry public key and pinned remote-installer checksums |
supply-chain/ | Dependency provenance policy and state |
3. AI-assistant files: distinct roles
This is the most common source of confusion in the repo. Each file exists on purpose:
| File | Deployed to | Audience | Scope |
|---|---|---|---|
defaults/dot_claude/CLAUDE.md | ~/.claude/CLAUDE.md | Claude Code, in any cwd on this machine | Personal, cross-project |
CLAUDE.md (repo root) | Not deployed | Claude Code, when cwd is this repo | Repo-scoped guidance |
AGENTS.md (repo root) | Not deployed | Codex, Copilot, Cursor and other AGENTS.md readers | Repo-scoped guidance (synced from CLAUDE.md by dot agent context apply) |
docs/OPENCODE.md | Not deployed | OpenCode CLI, when cwd is this repo | Repo-scoped guidance (mirror) |
.claude/settings.local.json | Not deployed, not tracked | Claude Code | Per-machine permission allowlist |
Each tracked file carries a header comment explaining its role to prevent drift. Keep that header in place when editing.
4. Agent-protocol surface
Standards-compliant discovery endpoints that let external agents and tools find this workstation's agent capabilities:
| Path | Purpose |
|---|---|
.well-known/agent-card.json | A2A v0.3 agent card: skills, capabilities, URL |
.well-known/agent.json | Legacy pointer card, kept for back-compat |
.well-known/mcp/server-card.json | MCP server discovery manifest |
5. CI/CD and repo metadata
| Path | Purpose |
|---|---|
.github/workflows/ | Workflows: ci.yml, ci-enforced.yml, reusable lints, release pipelines |
.github/workflows/reusable-*.yml | Shared workflow fragments |
.github/actions/ | Composite actions (setup-chezmoi) |
.github/ISSUE_TEMPLATE/, PULL_REQUEST_TEMPLATE.md, CODEOWNERS, FUNDING.yml | Standard GitHub metadata |
CODE_OF_CONDUCT.md, CONTRIBUTING.md, SECURITY.md (root, with copies under .github/) | Community and security policy |
.github/security-policies/ | Org-level security policy files |
.github/branch-protection-config.json, .github/BRANCH_PROTECTION.md, .github/rulesets/ | Codified branch-protection settings |
.github/dependabot.yml | Dependency bump schedule |
.devcontainer/ | GitHub Codespaces + VS Code dev container |
tests/Dockerfile.test, tests/Dockerfile.sandbox | Ubuntu sandboxes for integration / e2e tests |
fuzz/, .clusterfuzzlite/ | Go and shell fuzz harnesses, OSS-Fuzz and ClusterFuzzLite build files |
6. Build, bootstrap, and dev tooling
| Path | Purpose |
|---|---|
install.sh | Top-level one-liner installer |
install/provision/, install/lib/, install/migrate/ | Platform-specific install helpers invoked by install.sh |
GNUmakefile, Makefile | make install contract for the dot CLI; make test and other runners |
defaults/dot_config/just/justfile, config/treefmt.toml | Convenience runners and formatter config |
mise.toml, mise.lock, mise-versions.lock.json | Mise toolchain versions (locked) |
package.json, requirements-contracts.in, requirements-contracts.txt | Node and Python dev dependencies |
flake.nix, flake.lock (root) | Root Nix flake for direnv + repo dev shell |
nix/flake.nix, nix/home.nix | Separate flake for Home Manager activation (used by dot system update and ci-enforced.yml → lint-nix) |
.envrc | direnv hook into the root flake |
7. Repo automation (tools/)
Nothing under tools/ is deployed or called by the dot CLI. It's tooling for running the repo itself (see ../../tools/README.md).
| Subdir | Purpose |
|---|---|
tools/ci/ | CI helpers and gates (check-cli-contract.py, cli_command_table.py, complexity.py, mutation-test.py, check-copyright-headers.sh, ...) |
tools/docs/ | Manual and site builds, and the generators for man pages, completions and cmdlets |
tools/release/, tools/maintenance/ | Release staging and maintenance tasks |
8. Documentation (docs/)
| Path | Purpose |
|---|---|
docs/manual/ | GNU-Stow-style reference manual (the v0.2.501 feature) |
docs/architecture/ | This file + INTEROP, fleet, walkthrough, theme contracts and AI cost docs (ARCHITECTURE.md here points to docs/ARCHITECTURE.md) |
docs/operations/ | Runbooks, traceability matrix, migration notes |
docs/reference/ | Command/config reference material |
docs/guides/ | Long-form how-tos |
docs/security/ | Threat model, disclosure policy, audit records |
docs/adr/ | Architecture Decision Records |
docs/themes/ | Theme catalogue / screenshots |
docs/interop/ | Agent/MCP interop notes |
docs/archive/ | Superseded docs kept for history |
docs/NAMING_CONVENTIONS.md | Naming bible (file prefixes, run-script numbering tiers) |
docs/index.md, docs/README.md, docs/AI.md, docs/COPYRIGHT | Entry points |
9. Tests (tests/)
| Path | Purpose |
|---|---|
tests/framework/ | test_runner.sh, assertions.sh, mocks.sh: the custom shell-test framework |
tests/unit/ | Unit tests organised by domain (aliases/, dot-cli/, functions/, shell/, theme/ and more; ls tests/unit for the list) |
tests/integration/ | End-to-end install and apply flows |
tests/regression/ | Guardrail tests for previously-broken behaviours |
benches/ | benchmark_runner.sh and friends |
tests/benchmark.sh, tests/test-aliases.sh, tests/test-docker.sh | Top-level entrypoints |
tests/Dockerfile.sandbox, tests/Dockerfile.test | Fresh-Ubuntu sandbox images for integration tests |
Tests execute shell source files directly: do not use Go template syntax in non-.tmpl files, or the test framework will choke on the curly braces.
10. Other top-level
| Path | Purpose |
|---|---|
config/ | Repo-local tool configs: cliff.toml (changelog), gitleaks.toml, pre-commit-config.yaml, stylua.toml, treefmt.toml, typos.toml, and others |
templates/chezmoi-data/, templates/projects/ | Starter scaffolding for new machines / new projects |
examples/ | Standalone demo scripts used by the manual and tutorials |
CHANGELOG.md, README.md, LICENSE-APACHE, LICENSE-MIT, LICENSES/, REUSE.toml | Standard, and the dual licence |
AGENTS.md, CLAUDE.md, DEVELOPMENT.md, GOVERNANCE.md, ROADMAP.md, SUPPORT.md | Project guides |
CITATION.cff, DCO, KEYS.asc, llms.txt | Citation metadata, contribution sign-off, signing keys, LLM site index |
docs/CONFIG_STRATEGY.md | High-level "how configuration management works here" overview |
.gitattributes, .gitignore, .gitleaksignore, .gitleaks.toml, .editorconfig, .secrets.baseline, .sops.yaml, .luacheckrc, .markdownlint*.jsonc, .taplo.toml, .typos.toml, .yamllint.yaml | Repo-level tool configs |
.pre-commit-config.yaml → config/pre-commit-config.yaml | Symlink so pre-commit finds the canonical config |
11. Local-only artefacts (gitignored: if you see them, don't commit them)
The following paths can appear during local work but are excluded from version control. If one of them ever shows up in git status, check the .gitignore entry rather than adding the file:
| Path | Source |
|---|---|
_build/, build/ | Output of tools/docs/build-manual.sh and of make |
.pnpm-store/ | pnpm's content-addressable store (should never appear here) |
.claude/ | Claude Code per-machine state (settings.local.json permission allowlists) |
node_modules/ | Node dependency trees from repo-local scripts |
.version-sync-backup/ | Timestamped backups from version-sync.sh |
lib/wasm-tools/target/ | Rust build artefacts for the dot-sys crate, including the wasm32-wasip1/release/dot-sys.wasm module dot attest verify runs (and fuzz/target/) |
dot_etc/machines/ | Host-specific installer overrides |
How to decide where a new file belongs
A quick decision tree for common cases:
- New app config that lives under
~/.config/<app>/→defaults/dot_config/<app>/(seedocs/NAMING_CONVENTIONS.md→ "Adding New Modules"). - New user-facing CLI script →
defaults/dot_local/bin/executable_<name>. - New
dotsubcommand → declare it inplugins/<domain>/dot-plugin.tomland implement it inplugins/<domain>/dot-<domain>or itslibexec/(see../adr/ADR-020-command-naming-system.mdand../../scripts/README.md). - New runtime script the CLI calls →
scripts/<domain>/<name>.sh. - New repo-only automation (CI helper, maintenance task, release step, doc generator) →
tools/<ci|release|maintenance|docs>/<name>. - New test →
tests/unit/<domain>/test_<feature>.sh(followtest_{domain}_{feature}.shnaming). - New doc → pick the narrowest of
docs/architecture/,docs/reference/,docs/guides/,docs/operations/,docs/security/. - New alias file →
defaults/.chezmoitemplates/aliases/<category>/<tool>.aliases.sh(see conventions doc). - New run-once hook →
defaults/run_onchange_<NN>-<verb>-<noun>.sh.tmpl, using the numbering tier appropriate to its category.
If none of the above fits, stop and ask in an issue or PR before introducing a new top-level directory. The current layout is deliberate and one-off additions cost everyone later.