Skip to content

RFC: v0.2.503 Repository Reorganisation

Status: Accepted. This RFC was opened in this PR and immediately accepted by the maintainer with explicit decision to ship the reorganisation incrementally within v0.2.503 rather than the originally-proposed two-version deprecation window. Phases land as separate commits on feat/v0.2.503; each is independently atomic and verified by dot lint + the existing test matrix.

Summary

Split the current chezmoi-managed monorepo into a framework layer (distributable CLI + library) and a defaults layer (user-facing configuration), with the framework layer publishable as a standalone tarball to Homebrew, Scoop, and AUR. Maintain backwards-compatible behaviour for existing users via a one-shot migration script that runs on first apply after upgrade.

Motivation

R4 hard-audit identified the framework/user-config intermingling as the highest-leverage structural gap blocking de-facto- framework status (HARD_AUDIT_2026.md §8.5 Top-5 adoption gaps). Concrete symptoms:

  1. Distribution stuck at "curl-pipe-bash". The Homebrew / Scoop / AUR manifests scaffolded in v0.2.503 (install/{homebrew,scoop,aur}/) cannot publish until there's a single bin/dot tarball — chezmoi's dot_* / executable_* / private_* prefixes force the current layout. Until that's fixed, downstream distros have nothing to package.

  2. New contributor onboarding cost. Even with the v0.2.503 STRUCTURE.md, ~20 chezmoi-prefixed root paths require a concept (the chezmoi naming contract) to navigate. A bin/ + lib/ + defaults/ layout is self-documenting.

  3. Framework forks are blocked. Anyone wanting to fork the CLI without the maintainer's personal configs has to manually delete 80+ tool-specific directories under dot_config/. A clean separation makes "fork the framework, apply my own defaults" a one-command flow.

  4. Test surface bleed. CI runs chezmoi apply --dry-run on every PR, exercising both framework templates AND the maintainer's personal defaults. A real consumer running the framework will not exercise the maintainer's dot_config/aider/ etc. — and yet a regression there blocks CI.

The reorganisation is breaking for existing user installs: chezmoi tracks deployed files by source path, so moving bin/dot to bin/dot means the old ~/.local/bin/dot would be removed before the new path is installed. Mitigation: ship a migrate-v0.2-to-v0.3.sh script that runs before the first post-upgrade chezmoi apply.

Detailed design

Target layout

Following the Debian/aws-cli discipline (every top-level path has a clear purpose; contributor orients in <30 seconds):

.
├── bin/                          # CLI entrypoints
│   ├── dot                       # was bin/dot
│   ├── dot-load-benchmark-pty    # was bin/dot-load-benchmark-pty
│   ├── dot-theme-sync            # was bin/dot-theme-sync
│   ├── dot-bootstrap             # was bin/dot-bootstrap
│   └── dot-update                # was dot_local/bin/executable_update (renamed)
├── lib/                          # Framework library (no chezmoi)
│   ├── commands/                 # was scripts/dot/commands/
│   ├── ui.sh                     # was scripts/dot/lib/ui.sh
│   ├── utils.sh                  # was scripts/dot/lib/utils.sh
│   ├── platform.sh               # was scripts/dot/lib/platform.sh
│   ├── log.sh                    # was scripts/dot/lib/log.sh
│   ├── bento.sh                  # was scripts/dot/lib/bento.sh
│   └── secrets_provider.sh       # was scripts/lib/secrets_provider.sh
├── share/                        # OS-conventional resources
│   ├── man/man1/dot.1            # was dot_local/share/man/man1/dot.1
│   ├── completions/              # was dot_local/share/zsh/completions/
│   └── docs/                     # was docs/
├── defaults/                     # User-facing default config (was dot_config/, etc.)
│   ├── home/                     # dotfiles deployed to $HOME (dot_X → .X)
│   ├── config/                   # dotfiles deployed to $XDG_CONFIG_HOME
│   └── tools/                    # per-tool configs (mise/, npmrc/, ...)
├── install/                      # Distribution + bootstrap
│   ├── install.sh                # was install.sh (moved one level down)
│   ├── homebrew/dot.rb           # already at install/homebrew/ in v0.2.503
│   ├── scoop/dot.json            # already at install/scoop/    in v0.2.503
│   ├── aur/PKGBUILD              # already at install/aur/      in v0.2.503
│   ├── provision/                # was install/provision/ (chezmoi run_onchange_ hooks)
│   └── migrate/                  # NEW: migrate-v0_2-to-v0_3.sh + rollback
├── tests/                        # unchanged
├── examples/                     # unchanged
├── tools/                        # NEW: ops scripts not shipped to users
│   ├── ci/                       # was tools/ci/
│   ├── release/                  # was tools/release/
│   ├── maintenance/              # was tools/maintenance/
│   ├── docs/                     # was tools/docs/
│   └── version-sync.sh           # was scripts/version-sync.sh
├── .chezmoiroot                  # NEW: points at defaults/
├── README.md
├── LICENSE
├── CHANGELOG.md
├── CLAUDE.md  /  AGENTS.md  /  per-harness renders
└── (no more dot_X at root)

chezmoi adaptation

.chezmoiroot lets chezmoi treat a subdirectory as the source root. With .chezmoiroot = "defaults", chezmoi will look for defaults/home/dot_zshrc, defaults/config/dot_starship.toml, etc. — and deploy to the normal ~/.zshrc / ~/.config/starship.toml paths.

This means:

  • Repo top-level is no longer required to follow chezmoi naming.
  • bin/dot is a plain shell script, not dot_local/bin/executable_dot.
  • share/man/man1/dot.1 is a plain file, not chezmoi-deployed.
  • A Homebrew formula can bin.install 'bin/dot' directly.

Migration tool

install/migrate/migrate-v0.2-to-v0.3.sh:

  1. Detect existing chezmoi state at ~/.local/share/chezmoi / ~/.config/chezmoi/chezmoi.toml.
  2. Read the user's pinned source repo from chezmoi.toml; if it's sebastienrousseau/dotfiles@<v0.2.x>, warn and confirm.
  3. Run chezmoi diff and persist the per-file output to ~/.local/state/dotfiles/v0_2_to_v0_3_pre_diff.log so the user has a record of pre-migration state.
  4. Run chezmoi forget for paths that are moving (no destructive delete — chezmoi forget only un-tracks).
  5. Update ~/.config/chezmoi/chezmoi.toml to point at the new sourceDir with .chezmoiroot honoured.
  6. Run chezmoi apply — picks up the new layout and re-creates the user's files at their canonical paths.
  7. Run dot doctor and dot lint to verify.

The migration is idempotent and safe to abort: at step 4 the chezmoi state is removed but no user data is deleted. At step 6 chezmoi notices "these files already exist on disk with content matching the source" and is a no-op.

Library bash-source paths

Today scripts/dot/commands/<cmd>.sh does:

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../lib/utils.sh"

After reorganisation:

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../utils.sh"   # commands/<cmd>.sh → lib/utils.sh

Or, more robustly, drive lookup from a single env var set by bin/dot:

: "${DOT_LIB:=$(dirname "$(realpath "$0")")/../lib}"
source "$DOT_LIB/utils.sh"

The Homebrew formula sets $DOT_LIB to ${libexec}/lib so bin/dot finds its library wherever the package manager installed it.

Distribution surface

Once bin/dot is standalone:

Channel Artefact Verify command
Homebrew tap dot-${VERSION}-${OS}-${ARCH}.tar.gz brew install sebastienrousseau/tap/dot && dot version
Scoop bucket dot.jsondot-${VERSION}-windows-${ARCH}.zip scoop install dot && dot version
AUR dotfiles-git PKGBUILD building from source paru -S dotfiles-git && dot version
install.sh Same SHA256-verified path as today bash install.sh
Direct tarball Cosign-signed + SLSA-attested release asset per docs/security/VERIFY_RELEASE.md

The chezmoi-managed defaults/ subtree is only consumed when a user wants the maintainer's opinionated config layer. It's a strict superset: install dot standalone for the CLI; layer defaults/ on top if you want the wallpaper-theming + multi-shell setup.

Backwards compatibility

Surface v0.2.x behaviour v0.2.503 behaviour Breaking?
~/.local/bin/dot Deployed by chezmoi Replaced by Homebrew/Scoop install, OR symlinked by chezmoi from the new source Yes — path may move; migration script handles it
~/.zshrc etc Source-pinned at dot_zshrc Source-pinned at defaults/home/dot_zshrc, chezmoi reads via .chezmoiroot No — destination path unchanged
dot <cmd> API All subcommands work as documented Same No
scripts/dot/commands/*.sh consumers Direct source paths used in user customisations Path moves to lib/commands/*.sh Yes — affects any user who source'd these directly
.chezmoidata.toml Repo root Repo root (unchanged for compatibility with old user chezmoi init flows) No

Two-version deprecation window

v0.2.503 ships with the migration script and a deprecation warning in dot doctor. v0.4.0 removes any v0.2.x shim code. Users who skip v0.2.503 entirely (v0.2.x → v0.4.0) hit a hard error and must run the migration tool from a v0.3.x release manually.

Alternatives considered

A) Keep the chezmoi monorepo as-is

Pros: zero migration cost; works today. Rejected: blocks Homebrew/Scoop/AUR publication permanently. The Top-5 adoption gap remains. R4 audit's "9.0/10 internal · 7.5/10 adoption" plateau persists.

B) Two-repo split (framework + defaults)

Publish dot framework at sebastienrousseau/dot and the maintainer's personal defaults at sebastienrousseau/dotfiles.

Pros: cleanest possible separation. Framework forks trivial. Rejected (for v0.3): requires a second repo, doubles the CI matrix, and forces users to install from two sources. Defer to v0.4 if v0.3 single-repo with .chezmoiroot proves insufficient.

C) Rename current root files only (cosmetic)

Just rename bin/dotbin/executable_dot without .chezmoiroot.

Rejected: chezmoi only resolves the executable_ / dot_ prefixes for files inside its source root, so moving the prefixed file outside breaks chezmoi-driven install entirely without giving us a standalone tarball.

Unresolved questions

  1. How does the defaults/ subtree behave when a user wants to override one default? Today they edit dot_config/X.tmpl directly. Post-reorg, do they: (a) edit defaults/config/X.tmpl and live with merge conflicts on framework updates, or (b) use a chezmoi data override + template conditional, or (c) maintain a second repo layered atop defaults/?
  2. Should install/migrate/ ship in the regular framework install, or only via a one-shot https://...migrate.sh URL? Bundling it forever increases install size; URL-only requires the user to find and trust the right URL during a stressful upgrade moment.
  3. Does .chezmoiroot survive existing user customisations in ~/.config/chezmoi/chezmoi.toml? Needs verification on a real upgrade test.
  4. Windows-native bin/dot: standalone PowerShell rewrite, or wrapper that shells to bash via WSL/git-bash? POWERSHELL_PARITY.md documents the current stub state.

Implementation plan

Phase Scope Effort
1 Draft + ratify this RFC. Get user OK. Done (this PR's draft)
2 Create defaults/, bin/, lib/, share/, tools/ and copy files. Update bash source paths. Add .chezmoiroot. 1 week
3 Write install/migrate/migrate-v0_2-to-v0_3.sh. Test against a synthetic v0.2.503-installed environment. 3 days
4 Update CI: every workflow that references scripts/, dot_local/, dot_config/ needs path updates. 3 days
5 Update every doc that references the old paths. Most are in docs/manual/. 1 day
6 Cut v0.2.999 RC as a deprecation-warning-only release; let real users dry-run the migration. 1 day + 2-week soak
7 Cut v0.2.503 with the actual reorg + migration tool. 1 day
8 Publish to Homebrew/Scoop/AUR using install/{homebrew,scoop,aur}/ scaffolds. 1 week
Total ~5 weeks calendar time

See also

  • HARD_AUDIT_2026.md §8.3 — cross-platform gap analysis.
  • HARD_AUDIT_2026.md §8.5 — Top-5 de-facto adoption gaps.
  • STRUCTURE.md — today's layout (honest about the chezmoi-prefix forcing function).
  • GOVERNANCE.md — RFC process this document follows.
  • install/README.md — distribution-channel publication checklist.
  • Reference: Debian/aws-cli — clean top-level discipline.