@sebastienrousseau/dotfiles 0.2.519 → 0.2.520

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/README.md +1163 -163
  3. package/docs/ARCHITECTURE.md +117 -0
  4. package/docs/COPYRIGHT +1 -1
  5. package/docs/ECOSYSTEM.md +220 -0
  6. package/docs/GOLD-STANDARD-AUDIT.md +352 -0
  7. package/docs/GOVERNANCE.md +1 -1
  8. package/docs/MAINTAINERS.md +1 -1
  9. package/docs/MINIMUM-TOOLCHAIN.md +100 -0
  10. package/docs/README.md +1 -1
  11. package/docs/STRUCTURE.md +1 -1
  12. package/docs/architecture/ARCHITECTURE.md +11 -108
  13. package/docs/architecture/REPO_LAYOUT.md +3 -3
  14. package/docs/guides/MACOS_ICLOUD_SYMLINKS.md +121 -0
  15. package/docs/index.md +3 -3
  16. package/docs/manual/00-introduction.md +1 -1
  17. package/docs/manual/03-reference/01-dot-cli.md +28 -3
  18. package/docs/manual/command-index.md +24 -4
  19. package/docs/manual/concept-index.md +2 -0
  20. package/docs/migration/README.md +81 -0
  21. package/docs/migration/from-bare-git-repo.md +156 -0
  22. package/docs/migration/from-gnu-stow.md +165 -0
  23. package/docs/migration/from-plain-chezmoi.md +148 -0
  24. package/docs/migration/from-yadm.md +187 -0
  25. package/docs/operations/PERFORMANCE_BUDGETS.md +196 -0
  26. package/docs/operations/REGISTRY.md +1 -1
  27. package/docs/operations/RELEASE_PIPELINE.md +4 -4
  28. package/docs/operations/TESTING.md +3 -3
  29. package/docs/operations/TRACEABILITY.md +1 -0
  30. package/docs/packaging.md +222 -0
  31. package/docs/reference/FEATURE-MATRIX.md +646 -0
  32. package/docs/reference/TOOLS.md +1 -1
  33. package/docs/reference/UTILS.md +1 -0
  34. package/docs/security/COMPLIANCE.md +1 -1
  35. package/docs/security/DISCLOSURE.md +4 -4
  36. package/docs/security/FUZZING.md +113 -18
  37. package/docs/security/KEY_ROTATION.md +1 -1
  38. package/docs/security/SCORECARD.md +3 -3
  39. package/docs/security/VERIFY_RELEASE.md +2 -2
  40. package/install.sh +6 -6
  41. package/package.json +2 -2
  42. package/scripts/ci/check-copyright-headers.sh +1 -1
  43. package/scripts/ci/check-shell-preamble.sh +1 -1
  44. package/scripts/ci/guard-gitleaks-checkout.sh +1 -1
  45. package/scripts/demo/record.sh +1 -1
  46. package/scripts/diagnostics/a2a-conformance.sh +1 -1
  47. package/scripts/diagnostics/alias-governance.sh +30 -3
  48. package/scripts/diagnostics/aliases-cheatsheet.sh +1 -1
  49. package/scripts/diagnostics/aliases-manifest.sh +1 -1
  50. package/scripts/diagnostics/attest-verify.sh +147 -0
  51. package/scripts/diagnostics/benchmark.sh +1 -1
  52. package/scripts/diagnostics/conflicts.sh +1 -1
  53. package/scripts/diagnostics/doctor-unified.sh +6 -2
  54. package/scripts/diagnostics/doctor.sh +56 -10
  55. package/scripts/diagnostics/drift-dashboard.sh +3 -2
  56. package/scripts/diagnostics/health.sh +43 -10
  57. package/scripts/diagnostics/history-analysis.sh +1 -1
  58. package/scripts/diagnostics/mcp-doctor.sh +2 -2
  59. package/scripts/diagnostics/perf.sh +1 -1
  60. package/scripts/diagnostics/scorecard.sh +3 -2
  61. package/scripts/diagnostics/secret-governance.sh +1 -1
  62. package/scripts/diagnostics/security-score.sh +1 -1
  63. package/scripts/diagnostics/smoke-test.sh +1 -1
  64. package/scripts/diagnostics/snapshot.sh +1 -1
  65. package/scripts/diagnostics/verify.sh +1 -1
  66. package/scripts/diagnostics/verify_state.sh +1 -1
  67. package/scripts/diagnostics/version-locks.sh +1 -1
  68. package/scripts/diagnostics/workstation-attestation.sh +26 -1
  69. package/scripts/dot/commands/agent.sh +64 -14
  70. package/scripts/dot/commands/agents.sh +24 -8
  71. package/scripts/dot/commands/ai.sh +20 -7
  72. package/scripts/dot/commands/aliases.sh +1 -1
  73. package/scripts/dot/commands/appearance.sh +1 -1
  74. package/scripts/dot/commands/completion.sh +42 -5
  75. package/scripts/dot/commands/core.sh +1 -1
  76. package/scripts/dot/commands/diagnostics.sh +1 -1
  77. package/scripts/dot/commands/env-emit.sh +1 -1
  78. package/scripts/dot/commands/fleet.sh +41 -18
  79. package/scripts/dot/commands/init.sh +1 -1
  80. package/scripts/dot/commands/lint.sh +1 -1
  81. package/scripts/dot/commands/manual.sh +1 -1
  82. package/scripts/dot/commands/meta.sh +115 -10
  83. package/scripts/dot/commands/patterns.sh +1 -1
  84. package/scripts/dot/commands/registry.sh +43 -7
  85. package/scripts/dot/commands/restore.sh +1 -1
  86. package/scripts/dot/commands/secrets.sh +1 -1
  87. package/scripts/dot/commands/security.sh +1 -1
  88. package/scripts/dot/commands/tools.sh +21 -7
  89. package/scripts/fonts/install-nerd-fonts.sh +1 -1
  90. package/scripts/fonts/patch-fonts.sh +1 -1
  91. package/scripts/git-hooks/install.sh +1 -1
  92. package/scripts/git-hooks/pre-commit-audit.sh +2 -2
  93. package/scripts/lib/secrets_provider.sh +19 -4
  94. package/scripts/nvim/headless-upgrade.lua +81 -0
  95. package/scripts/ops/ai-setup.sh +1 -1
  96. package/scripts/ops/bundle.sh +1 -1
  97. package/scripts/ops/chaos.sh +1 -1
  98. package/scripts/ops/chezmoi-apply.sh +1 -1
  99. package/scripts/ops/chezmoi-diff.sh +1 -1
  100. package/scripts/ops/chezmoi-remove.sh +3 -3
  101. package/scripts/ops/chezmoi-update.sh +6 -2
  102. package/scripts/ops/heal-chezmoi.sh +1 -1
  103. package/scripts/ops/heal-system.sh +1 -1
  104. package/scripts/ops/heal-tools.sh +1 -1
  105. package/scripts/ops/heal.sh +1 -1
  106. package/scripts/ops/post-apply-repair.sh +1 -1
  107. package/scripts/ops/prewarm.sh +1 -1
  108. package/scripts/ops/release.sh +1 -1
  109. package/scripts/ops/rollback.sh +11 -2
  110. package/scripts/ops/setup.sh +1 -1
  111. package/scripts/ops/teleport.sh +1 -1
  112. package/scripts/qa/check-feature-matrix.sh +296 -0
  113. package/scripts/qa/check-version-consistency.sh +7 -119
  114. package/scripts/qa/coverage-baseline.sh +1 -1
  115. package/scripts/qa/docs-coverage.sh +9 -3
  116. package/scripts/qa/examples-coverage.sh +1 -1
  117. package/scripts/qa/reliability-audit.sh +1 -1
  118. package/scripts/qa/scorecard-snapshot.sh +1 -1
  119. package/scripts/qa/traceability-coverage.sh +9 -2
  120. package/scripts/qa/validate-examples.sh +66 -3
  121. package/scripts/qa/wsl-contract.sh +1 -1
  122. package/scripts/secrets/age-init.sh +1 -1
  123. package/scripts/secrets/create-secrets-file.sh +1 -1
  124. package/scripts/secrets/encrypt-ssh-key.sh +1 -1
  125. package/scripts/security/backup.sh +1 -1
  126. package/scripts/security/check-disclosure-key-expiry.sh +1 -1
  127. package/scripts/security/dns-doh.sh +1 -1
  128. package/scripts/security/encryption-check.sh +1 -1
  129. package/scripts/security/enforce-policies.sh +315 -98
  130. package/scripts/security/firewall.sh +1 -1
  131. package/scripts/security/lock-configs.sh +1 -1
  132. package/scripts/security/lock-screen.sh +1 -1
  133. package/scripts/security/manage-secrets.sh +1 -1
  134. package/scripts/security/ssh-cert.sh +1 -1
  135. package/scripts/security/telemetry-kill.sh +1 -1
  136. package/scripts/security/usb-safety.sh +1 -1
  137. package/scripts/theme/apply-gnome-theme.sh +1 -1
  138. package/scripts/theme/extract-heic-frames.sh +1 -1
  139. package/scripts/theme/extract-theme.py +284 -6
  140. package/scripts/theme/install-boot-logo.sh +1 -1
  141. package/scripts/theme/install-catppuccin-themes.sh +1 -1
  142. package/scripts/theme/install-cursors.sh +1 -1
  143. package/scripts/theme/install-file-icons.sh +1 -1
  144. package/scripts/theme/install-grub-theme.sh +1 -1
  145. package/scripts/theme/install-lock-icon.sh +1 -1
  146. package/scripts/theme/merge-wallpaper.sh +1 -1
  147. package/scripts/theme/rebuild-themes.sh +64 -5
  148. package/scripts/theme/switch.sh +46 -19
  149. package/scripts/theme/wallpaper-rotate.sh +1 -1
  150. package/scripts/theme/wallpaper-sync.sh +1 -1
  151. package/scripts/tools/cmatrix.sh +1 -1
  152. package/scripts/tools/detect-collisions.py +1 -1
  153. package/scripts/tools/emoji-picker.sh +1 -1
  154. package/scripts/tools/figlet-banner.sh +1 -1
  155. package/scripts/tools/log-rotate.sh +1 -1
  156. package/scripts/tools/lolcat-wrap.sh +1 -1
  157. package/scripts/tools/pipes.sh +1 -1
  158. package/scripts/tuning/linux.sh +1 -1
  159. package/scripts/tuning/macos.sh +1 -1
  160. package/scripts/uninstall.sh +1 -1
  161. package/scripts/verify-release-versions +156 -0
  162. package/scripts/version-sync.sh +63 -3
  163. package/LICENSE +0 -21
@@ -0,0 +1,165 @@
1
+ ---
2
+ render_with_liquid: false
3
+ ---
4
+
5
+ <!-- SPDX-License-Identifier: Apache-2.0 OR MIT -->
6
+ <!-- Copyright (c) 2015-2026 Sebastien Rousseau -->
7
+
8
+ # Migrating from GNU Stow
9
+
10
+ Stow builds a symlink farm: `~/.bashrc` is a symlink into
11
+ `~/dotfiles/bash/.bashrc`. This framework renders real files into
12
+ `$HOME` from a source tree. That difference is the whole migration —
13
+ everything else follows from it.
14
+
15
+ Be honest about whether you want this. If your setup is "symlink a
16
+ dozen files on one machine", Stow is doing that job well and the
17
+ migration will not repay itself. The reason to move is templating,
18
+ multiple machines, or the tooling around the files.
19
+
20
+ ## Concept mapping
21
+
22
+ | Stow | Here |
23
+ |---|---|
24
+ | `~/dotfiles/<package>/` | `defaults/` (one tree, not per-package) |
25
+ | `stow bash` | `dot sync` (applies everything) |
26
+ | `stow -D bash` | `chezmoi forget <file>`, then `dot sync` |
27
+ | `stow -n` (simulate) | `dot diff` or `dot sync --check` |
28
+ | `.stow-local-ignore` | `.chezmoiignore` |
29
+ | Symlinks in `$HOME` | Real files in `$HOME` |
30
+ | Per-host: separate packages or branches | `.tmpl` files with `{{ if }}` |
31
+
32
+ ## 1. Record what Stow currently owns
33
+
34
+ Symlinks are self-describing, which makes this the easiest inventory
35
+ of any migration:
36
+
37
+ ```sh
38
+ find "$HOME" -maxdepth 3 -type l -lname "*dotfiles*" \
39
+ -printf '%p -> %l\n' 2>/dev/null | tee ~/stow-inventory.txt
40
+ ```
41
+
42
+ On macOS (BSD `find` has no `-printf`):
43
+
44
+ ```sh
45
+ find "$HOME" -maxdepth 3 -type l | while read -r l; do
46
+ case "$(readlink "$l")" in *dotfiles*) echo "$l -> $(readlink "$l")";; esac
47
+ done | tee ~/stow-inventory.txt
48
+ ```
49
+
50
+ Back up the real content, following the links:
51
+
52
+ ```sh
53
+ tar -czhf ~/stow-backup-$(date +%F).tar.gz -C "$HOME" \
54
+ $(sed 's| ->.*||; s|^'"$HOME"'/||' ~/stow-inventory.txt)
55
+ ```
56
+
57
+ `-h` is the important flag: it dereferences symlinks and archives the
58
+ actual files.
59
+
60
+ ## 2. Unstow before installing
61
+
62
+ This is the step people skip, and it is the one that matters. If you
63
+ install over a live symlink farm, `chezmoi apply` will try to replace
64
+ symlinks with regular files and the result is confusing.
65
+
66
+ ```sh
67
+ cd ~/dotfiles
68
+ stow -D */ # remove every package's links
69
+ find "$HOME" -maxdepth 3 -type l -lname "*dotfiles*" 2>/dev/null # expect empty
70
+ ```
71
+
72
+ Your `~/dotfiles` directory still holds the real files. Nothing is lost.
73
+
74
+ ## 3. Install
75
+
76
+ ```sh
77
+ bash -c "$(curl -fsSL https://raw.githubusercontent.com/sebastienrousseau/dotfiles/main/install.sh)"
78
+ ```
79
+
80
+ ## 4. Import your files
81
+
82
+ Stow packages flatten into a single tree. Copy each package's contents
83
+ into `$HOME` and add them:
84
+
85
+ ```sh
86
+ cd ~/dotfiles
87
+ for pkg in */; do
88
+ pkg="${pkg%/}"
89
+ echo "== $pkg"
90
+ ( cd "$pkg" && find . -type f -print0 ) | while IFS= read -r -d '' rel; do
91
+ rel="${rel#./}"
92
+ mkdir -p "$(dirname "$HOME/$rel")"
93
+ cp "$pkg/$rel" "$HOME/$rel"
94
+ dot add "$HOME/$rel"
95
+ done
96
+ done
97
+ ```
98
+
99
+ Review before committing anything — this is a good moment to drop the
100
+ files you have not opened in three years:
101
+
102
+ ```sh
103
+ dot status
104
+ cd "$(dot cd)" && git status
105
+ ```
106
+
107
+ ## 5. Replace per-host packages with templates
108
+
109
+ If you had `bash-work/` and `bash-home/` packages, collapse them:
110
+
111
+ ```text
112
+ {{ if eq .chezmoi.hostname "work-laptop" }}
113
+ export HTTP_PROXY=http://proxy.corp:3128
114
+ {{ end }}
115
+ ```
116
+
117
+ Available variables: `.chezmoi.os`, `.chezmoi.arch`, `.chezmoi.hostname`,
118
+ `.chezmoi.username`, `.chezmoi.osRelease.id`, plus this project's
119
+ `.profile` and `.features.*` from `defaults/.chezmoidata.toml`.
120
+
121
+ Test a template without applying:
122
+
123
+ ```sh
124
+ chezmoi execute-template < defaults/dot_bashrc.tmpl
125
+ ```
126
+
127
+ ## 6. Apply and verify
128
+
129
+ ```sh
130
+ dot diff
131
+ dot sync
132
+ dot doctor
133
+
134
+ # Nothing should be a symlink into ~/dotfiles any more:
135
+ find "$HOME" -maxdepth 3 -type l -lname "*dotfiles*" 2>/dev/null
136
+ ```
137
+
138
+ ## 7. Retire the old tree
139
+
140
+ After a few days:
141
+
142
+ ```sh
143
+ mv ~/dotfiles ~/dotfiles.stow.bak # rename, do not delete
144
+ # delete ~/dotfiles.stow.bak once you are sure
145
+ ```
146
+
147
+ ## Rolling back
148
+
149
+ ```sh
150
+ bash ~/.dotfiles/scripts/uninstall.sh --force
151
+ mv ~/dotfiles.stow.bak ~/dotfiles # if you renamed it
152
+ cd ~/dotfiles && stow */
153
+ ```
154
+
155
+ ## What you gain, what you lose
156
+
157
+ **Gain:** templating (the thing Stow structurally cannot do); one tree
158
+ instead of a package-per-tool layout; the `dot` CLI; multi-shell
159
+ parity; secrets management; signed releases.
160
+
161
+ **Lose:** the elegance of the symlink model — editing `~/.bashrc` no
162
+ longer edits your repo directly, so you edit the source and run
163
+ `dot sync` (or `dot add` after the fact). Stow is ~5k lines of Perl
164
+ with essentially no runtime dependencies; this pulls in chezmoi. And
165
+ Stow's mental model fits in a paragraph, which has real value.
@@ -0,0 +1,148 @@
1
+ ---
2
+ render_with_liquid: false
3
+ ---
4
+
5
+ <!-- SPDX-License-Identifier: Apache-2.0 OR MIT -->
6
+ <!-- Copyright (c) 2015-2026 Sebastien Rousseau -->
7
+
8
+ # Migrating from plain chezmoi
9
+
10
+ The shortest migration here, because there is no engine change: this
11
+ framework *is* chezmoi, plus a source-tree layout, a CLI, and the
12
+ tooling around them. Your templates, your `.chezmoidata`, and your
13
+ muscle memory all carry over.
14
+
15
+ You can also stop after step 2 and keep using `chezmoi` commands
16
+ directly forever. Nothing here replaces chezmoi; `dot` shells out to it.
17
+
18
+ ## What actually changes
19
+
20
+ | Plain chezmoi | Here |
21
+ |---|---|
22
+ | Source at `~/.local/share/chezmoi` | Source at `~/.dotfiles`, with `.chezmoiroot` pointing chezmoi at `defaults/` |
23
+ | Everything at the source root | `defaults/` = your files; `bin/`, `lib/`, `scripts/`, `docs/`, `tests/` = the framework |
24
+ | `chezmoi apply` | `dot sync` (which runs `chezmoi apply` with sensible exclusions) |
25
+ | `chezmoi diff` / `status` | `dot diff` / `dot status` — same thing, prettier |
26
+ | — | `dot doctor`, `dot health`, `dot heal`, `dot rollback`, `dot attest` |
27
+ | `chezmoi secret` | `dot secrets` (age + sops) |
28
+ | `run_onchange_` scripts | same, plus `install/provision/` |
29
+
30
+ **`.chezmoiroot` is the key idea.** It contains `defaults`, so chezmoi
31
+ treats `~/.dotfiles/defaults/` as the source root. Framework code sits
32
+ outside that directory and is therefore never deployed to `$HOME`.
33
+
34
+ ## 1. Back up and record
35
+
36
+ ```sh
37
+ chezmoi source-path
38
+ chezmoi managed > ~/chezmoi-managed.txt
39
+ wc -l ~/chezmoi-managed.txt
40
+
41
+ cp -a "$(chezmoi source-path)" ~/chezmoi-source-backup-$(date +%F)
42
+ tar -czf ~/chezmoi-backup-$(date +%F).tar.gz -C "$HOME" -T ~/chezmoi-managed.txt
43
+ ```
44
+
45
+ Commit and push your existing source repo if it has a remote. That is
46
+ the real backup.
47
+
48
+ ## 2. Two paths
49
+
50
+ **A. Keep your own repo** (you have a dotfiles repo you want to keep
51
+ as the source of truth): install the framework, then move your files
52
+ into `defaults/`.
53
+
54
+ **B. Adopt this repo's defaults** and layer your changes on top:
55
+
56
+ ```sh
57
+ bash -c "$(curl -fsSL https://raw.githubusercontent.com/sebastienrousseau/dotfiles/main/install.sh)"
58
+ ```
59
+
60
+ Path A continues below.
61
+
62
+ ## 3. Install and relocate your source tree
63
+
64
+ ```sh
65
+ old_source="$(chezmoi source-path)"
66
+
67
+ bash -c "$(curl -fsSL https://raw.githubusercontent.com/sebastienrousseau/dotfiles/main/install.sh)"
68
+
69
+ # Copy your source files under defaults/, preserving chezmoi's naming.
70
+ rsync -av --exclude '.git' "$old_source/" ~/.dotfiles/defaults/
71
+ ```
72
+
73
+ Then reconcile the chezmoi-metadata files, which now exist in both
74
+ places — merge yours into the framework's rather than overwriting:
75
+
76
+ | File | What to do |
77
+ |---|---|
78
+ | `.chezmoidata.toml` | **Merge.** The framework's carries `dotfiles_version`, `profile`, and `features`; keep those and add your keys. |
79
+ | `.chezmoiignore` | Merge; the framework's entries are feature-flag-gated. |
80
+ | `.chezmoi.toml.tmpl` | Merge your prompts into the framework's. |
81
+ | `.chezmoitemplates/` | Merge; name collisions are the only risk. |
82
+ | `.chezmoiscripts/`, `run_*` | Copy across as-is. |
83
+
84
+ Sanity-check the merge before applying anything:
85
+
86
+ ```sh
87
+ cd ~/.dotfiles
88
+ chezmoi --source "$PWD" data | head -40
89
+ chezmoi --source "$PWD" managed | head
90
+ ```
91
+
92
+ ## 4. Preview, then apply
93
+
94
+ ```sh
95
+ dot diff # or: chezmoi diff — identical underneath
96
+ dot sync --check # dry run
97
+ dot sync
98
+ dot doctor
99
+ ```
100
+
101
+ Confirm nothing was dropped:
102
+
103
+ ```sh
104
+ while read -r f; do
105
+ [ -e "$f" ] || echo "MISSING: $f"
106
+ done < ~/chezmoi-managed.txt
107
+ ```
108
+
109
+ ## 5. Optional: adopt the extras
110
+
111
+ None of these are required; adopt them when you want them.
112
+
113
+ ```sh
114
+ dot secrets-init # age-based secrets
115
+ dot theme list # wallpaper-driven theming
116
+ dot profile show # profiles and feature flags
117
+ dot completion fish > ~/.config/fish/completions/dot.fish
118
+ ```
119
+
120
+ ## Rolling back
121
+
122
+ The gentlest rollback of any of these guides, because chezmoi is still
123
+ the engine:
124
+
125
+ ```sh
126
+ chezmoi --source ~/chezmoi-source-backup-<date> apply
127
+ bash ~/.dotfiles/scripts/uninstall.sh --force
128
+ ```
129
+
130
+ Or simply point chezmoi back at your old source and carry on:
131
+
132
+ ```sh
133
+ chezmoi init --source ~/chezmoi-source-backup-<date>
134
+ ```
135
+
136
+ ## What you gain, what you lose
137
+
138
+ **Gain:** the `dot` CLI (diagnostics, repair, rollback, attestation,
139
+ fleet); a curated multi-shell configuration with parity across bash,
140
+ zsh, fish, nushell and PowerShell; secrets, theming and provisioning
141
+ already wired; a test suite and CI you can run against your own
142
+ changes; signed, attested releases.
143
+
144
+ **Lose:** simplicity. Plain chezmoi is one binary and your files;
145
+ this adds a source-tree convention, a CLI layer, and opinions about
146
+ shell configuration you may not share. If you already have a chezmoi
147
+ setup you are happy with, "it works and I understand every line" is a
148
+ perfectly good reason to stay.
@@ -0,0 +1,187 @@
1
+ ---
2
+ render_with_liquid: false
3
+ ---
4
+
5
+ <!-- SPDX-License-Identifier: Apache-2.0 OR MIT -->
6
+ <!-- Copyright (c) 2015-2026 Sebastien Rousseau -->
7
+
8
+ # Migrating from yadm
9
+
10
+ yadm is the closest neighbour: both track real files in `$HOME` with
11
+ git, both template per host, both encrypt secrets. The migration is
12
+ mostly a mechanical re-home of files plus a translation of yadm's
13
+ alternate-file suffixes into chezmoi templates.
14
+
15
+ ## Concept mapping
16
+
17
+ | yadm | Here | Note |
18
+ |---|---|---|
19
+ | `yadm add` / `yadm commit` | `dot add` then a normal `git commit` in the source dir | Source tree is a plain git repo |
20
+ | `$HOME` **is** the work tree | Files live in `defaults/` and are *rendered* into `$HOME` | The big conceptual change |
21
+ | `##os.Linux`, `##hostname.foo` suffixes | `{{ if eq .chezmoi.os "linux" }}` in a `.tmpl` | One file with branches, not N files |
22
+ | `yadm alt` | `chezmoi apply` | Automatic; no separate step |
23
+ | `yadm encrypt` (GPG) | `dot secrets` (age + sops) | Different crypto; see below |
24
+ | `yadm bootstrap` | `install/provision/run_onchange_*` | Runs on apply, idempotent |
25
+ | `yadm status` / `diff` | `dot status` / `dot diff` | Same intent |
26
+
27
+ ## 1. Inventory and back up
28
+
29
+ ```sh
30
+ yadm list -a > ~/yadm-inventory.txt
31
+ wc -l ~/yadm-inventory.txt
32
+ tar -czf ~/yadm-backup-$(date +%F).tar.gz -C "$HOME" -T ~/yadm-inventory.txt
33
+ ```
34
+
35
+ Keep `yadm-inventory.txt`: it is your checklist, and step 5 diffs
36
+ against it.
37
+
38
+ ## 2. Install alongside yadm
39
+
40
+ The installer does not touch yadm's repo (`~/.local/share/yadm/repo.git`)
41
+ or its config, so both can coexist while you migrate.
42
+
43
+ ```sh
44
+ bash -c "$(curl -fsSL https://raw.githubusercontent.com/sebastienrousseau/dotfiles/main/install.sh)"
45
+ dot version
46
+ ```
47
+
48
+ If you want to bring your *own* dotfiles repo rather than the
49
+ maintainer's defaults, use `dot init` instead:
50
+
51
+ ```sh
52
+ dot init yourusername --dry-run # prints the resolved URL and target dir
53
+ dot init yourusername
54
+ ```
55
+
56
+ ## 3. Move files across
57
+
58
+ Plain (non-alternate) files first:
59
+
60
+ ```sh
61
+ while read -r f; do
62
+ case "$f" in
63
+ *##*) continue ;; # alternates handled in step 4
64
+ esac
65
+ dot add "$f"
66
+ done < ~/yadm-inventory.txt
67
+ ```
68
+
69
+ `dot add` wraps `chezmoi add`, so a file in `~/.config/foo/bar` lands
70
+ at `defaults/dot_config/foo/bar`, and `~/.gitconfig` at
71
+ `defaults/dot_gitconfig`.
72
+
73
+ ## 4. Translate alternates into templates
74
+
75
+ For each `##`-suffixed file, replace the family with one `.tmpl`.
76
+
77
+ yadm:
78
+
79
+ ```text
80
+ ~/.gitconfig##os.Darwin
81
+ ~/.gitconfig##os.Linux
82
+ ```
83
+
84
+ Here — `defaults/dot_gitconfig.tmpl`:
85
+
86
+ ```text
87
+ [user]
88
+ name = {{ .name }}
89
+ email = {{ .email }}
90
+ {{ if eq .chezmoi.os "darwin" }}
91
+ [credential]
92
+ helper = osxkeychain
93
+ {{ else if eq .chezmoi.os "linux" }}
94
+ [credential]
95
+ helper = cache --timeout=3600
96
+ {{ end }}
97
+ ```
98
+
99
+ The common yadm conditions map as:
100
+
101
+ | yadm suffix | chezmoi expression |
102
+ |---|---|
103
+ | `##os.Darwin` | `eq .chezmoi.os "darwin"` |
104
+ | `##os.Linux` | `eq .chezmoi.os "linux"` |
105
+ | `##hostname.foo` | `eq .chezmoi.hostname "foo"` |
106
+ | `##user.alice` | `eq .chezmoi.username "alice"` |
107
+ | `##distro.Ubuntu` | `eq .chezmoi.osRelease.id "ubuntu"` |
108
+ | `##default` | the `{{ else }}` branch |
109
+
110
+ Verify before applying — templates fail loudly, but only when rendered:
111
+
112
+ ```sh
113
+ chezmoi execute-template < defaults/dot_gitconfig.tmpl
114
+ dot diff
115
+ ```
116
+
117
+ ## 5. Migrate encrypted files
118
+
119
+ yadm uses GPG; this framework uses age via sops. There is no automatic
120
+ converter, and that is deliberate — re-encrypting secrets is a step
121
+ you should perform consciously.
122
+
123
+ ```sh
124
+ # Decrypt with yadm, in a directory that is not the repo.
125
+ mkdir -p /tmp/secrets-migration && cd /tmp/secrets-migration
126
+ yadm decrypt
127
+
128
+ # Set up age and re-encrypt.
129
+ dot secrets-init # generates the age key
130
+ dot secrets-create # creates the encrypted store
131
+ dot secrets set GITHUB_TOKEN # one per secret, prompts for the value
132
+ dot secrets list
133
+
134
+ cd - && rm -rf /tmp/secrets-migration
135
+ ```
136
+
137
+ Details and the provider model: [`../security/SECRETS.md`](../security/SECRETS.md).
138
+
139
+ ## 6. Apply and verify
140
+
141
+ ```sh
142
+ dot diff # review every pending change
143
+ dot sync # apply
144
+ dot doctor # health check
145
+ ```
146
+
147
+ Then diff against the inventory to catch anything missed:
148
+
149
+ ```sh
150
+ while read -r f; do
151
+ [ -e "$HOME/$f" ] || echo "MISSING: $f"
152
+ done < ~/yadm-inventory.txt
153
+ ```
154
+
155
+ ## 7. Retire yadm
156
+
157
+ Only after a full working day on the new setup:
158
+
159
+ ```sh
160
+ yadm list -a > ~/yadm-final-check.txt # last snapshot, keep it
161
+ rm -rf ~/.local/share/yadm
162
+ rm -rf ~/.config/yadm
163
+ # and remove the yadm package via your package manager
164
+ ```
165
+
166
+ ## Rolling back
167
+
168
+ At any point before step 7, yadm is untouched:
169
+
170
+ ```sh
171
+ bash ~/.dotfiles/scripts/uninstall.sh --force
172
+ tar -xzf ~/yadm-backup-<date>.tar.gz -C "$HOME"
173
+ yadm status
174
+ ```
175
+
176
+ ## What you gain, what you lose
177
+
178
+ **Gain:** one templated file instead of alternate families; the `dot`
179
+ CLI (`doctor`, `health`, `heal`, `rollback`, `attest`); multi-shell
180
+ parity including fish, nushell and PowerShell; signed and attested
181
+ releases; fleet commands for more than one machine.
182
+
183
+ **Lose:** `$HOME` is no longer a git work tree, so `git status` in your
184
+ home directory stops being meaningful — you use `dot status` instead.
185
+ GPG-encrypted files become age/sops. yadm is a single ~2k-line script;
186
+ this is a larger surface. And yadm's bootstrap is one file, whereas
187
+ provisioning here is spread across `install/provision/`.
@@ -0,0 +1,196 @@
1
+ ---
2
+ render_with_liquid: false
3
+ ---
4
+
5
+ # Performance Budgets
6
+
7
+ Every operation this repo owns falls into one of four performance tiers. Each tier has a **hard budget** enforced by `benches/test_perf_budgets.sh`. If a change pushes an operation past its budget's headroom, CI fails.
8
+
9
+ ## The tiers
10
+
11
+ | Tier | Budget | What belongs here |
12
+ |---|---|---|
13
+ | **INSTANT** | ≤ **500ms** | Anything a human sees at a shell prompt |
14
+ | **FAST** | ≤ **2000ms** | Heavier gates + sandboxed CLI reads |
15
+ | **MEDIUM** | ≤ **5000ms** | Full diagnostic runs |
16
+ | **ACCEPTED-SLOW** | documented | Multi-second ops that are legitimately slow (see below) |
17
+ | **OUT-OF-SCOPE** | not gated | Multi-minute ops we don't gate per-run |
18
+
19
+ ## Reference baselines (2026-08-30, `rousseau-cachyos-geekom-a9`, Ryzen AI 9 HX 370)
20
+
21
+ All medians in milliseconds. Budget = 2× median (or the tier ceiling, whichever is larger).
22
+
23
+ ### INSTANT tier
24
+
25
+ | Operation | Baseline | Budget | Headroom |
26
+ |---|---:|---:|---:|
27
+ | `dot version` | 10 | 500 | 50× |
28
+ | `dot help` | 13 | 500 | 38× |
29
+ | `dot help <cmd>` | 15 | 500 | 33× |
30
+ | `dot search <keyword>` | 15 | 500 | 33× |
31
+ | iCloud script single-run | 15 | 500 | 33× |
32
+
33
+
34
+ ### FAST tier
35
+
36
+ Currently empty.
37
+
38
+ `dot status` and `dot diff` were placed here at 2000ms against reference
39
+ medians of 1510 ms and 1420 ms — 1.3× and 1.4× headroom, short of the ≥ 2×
40
+ this document requires of every budget. They have since moved to MEDIUM; see
41
+ that section for the measurements that prompted it.
42
+
43
+ > The baselines of 28 ms and 32 ms recorded before those were not real. The
44
+ > perf sandbox never created chezmoi's source directory, so both commands
45
+ > aborted immediately with *"no such file or directory"* — and `_measure`
46
+ > discarded exit codes, so the gate timed the failure path and called it
47
+ > excellent. The sandbox now links the repo into `XDG_DATA_HOME`.
48
+
49
+ The QA gates and test suites that used to sit in this tier moved to GATES
50
+ below: they are not interactive operations, so a ceiling defined by
51
+ human-perceived latency never described them.
52
+
53
+ ### MEDIUM tier
54
+
55
+ | Operation | Baseline | Budget | Headroom |
56
+ |---|---:|---:|---:|
57
+ | `dot status` (sandbox) | 2329 | 5000 | 2.1× |
58
+ | `dot diff` (sandbox) | 2157 | 5000 | 2.3× |
59
+ | `dot doctor` | 3892 | 5000 | 1.3× |
60
+ | `bench.sh --quick` | 826 | 5000 | 6× |
61
+
62
+ The `dot status` and `dot diff` baselines are the **worst** of the two hosted
63
+ runners, not the reference machine: macOS measured 2290 ms / 2157 ms and Ubuntu
64
+ 2329 ms / 2136 ms, against 1139 ms / 1174 ms locally. Two unrelated platforms
65
+ agreeing within 8% is a property of the commands rather than of one slow
66
+ runner — both shell out to chezmoi, which walks the whole source tree, and that
67
+ is disk-bound. A budget taken from the faster machine would have been a gate
68
+ that only ever fired on other people's hardware.
69
+
70
+ `dot doctor` and `bench.sh --quick` are diagnostics that report findings through
71
+ their exit status — `dot doctor` exits 1 whenever it finds issues, and `bench.sh` exits 1
72
+ when a shell breaches its own startup threshold. They are gated with
73
+ `_gate_diag`, which permits exit 1 but still fails on 2+ (not-found,
74
+ permission, signal, syntax error). That is far narrower than the blanket
75
+ `|| true` it replaced.
76
+
77
+ ### GATES tier (CI quality gates and test suites)
78
+
79
+ Budgets are 2× the median measured on the **slowest supported platform**,
80
+ not the fastest. Medians below: `rousseau-mbp-m1`, macOS 26 (Darwin 25.6),
81
+ 2026-08-30.
82
+
83
+ | Operation | macOS median | Linux median | Budget | Headroom |
84
+ |---|---:|---:|---:|---:|
85
+ | `check-version-consistency.sh` | 68 | 14 | 500 | 7.4× |
86
+ | `docs-coverage.sh` | 969 | 148 | 2000 | 2.1× |
87
+ | iCloud regression test (12 assertions) | 494 | 94 | 1000 | 2.0× |
88
+ | iCloud unit test (29 assertions) | 1743 | 498 | 3500 | 2.0× |
89
+ | iCloud manifest test (11 assertions) | 1575 | — † | 5000 | 3.2× |
90
+ | `traceability-coverage.sh` | 2389 | 623 | 5000 | 2.1× |
91
+ | `test_dot_subcommand_smoke.sh` | 3785 | 1308 | 7500 | 2.0× |
92
+ | `test_dot_help_registry_symmetry.sh` | 4449 | 1381 | 9000 | 2.0× |
93
+
94
+ † The manifest test hashes an entire sandbox tree before and after every
95
+ scenario, so it is disk-bound in a way the other gates are not. Its budget is
96
+ set at 3.2× the macOS median rather than the 2.0× used above, deliberately:
97
+ `dot status` and `dot diff` were first budgeted at 1.3× and 1.4×, and both
98
+ breached on the first CI run that measured them. The Linux median is left
99
+ unfilled until CI reports one — guessing it would defeat the point of a table
100
+ of measurements.
101
+
102
+ These gates run **3–6× slower on macOS than on Linux** — fork/exec is markedly
103
+ more expensive there and every one of them is fork-heavy shell. CI covers both
104
+ platforms, so the original Linux-only calibration could not hold, and five of
105
+ these sat red as a result. Re-capture the Linux column when convenient; it is
106
+ carried over from the 2026-08-30 Ryzen baseline and is not the binding
107
+ constraint.
108
+
109
+ ### ACCEPTED-SLOW (documented, not gated per-run)
110
+
111
+ | Operation | Baseline | Reason |
112
+ |---|---:|---|
113
+ | `test_dot_help_flag_universal.sh` | ~11s | Invokes `dot help --help` on ~100 commands via subshell each. The coverage it provides justifies the cost; regression is caught by `benches/test_help_gates_wall_clock.sh` at the suite level. |
114
+
115
+ ### OUT-OF-SCOPE (not gated per-run)
116
+
117
+ | Operation | Why we don't gate |
118
+ |---|---|
119
+ | `chezmoi apply` | Fresh macOS: minutes. Depends on iCloud sync + package installs. Gated at suite level only. |
120
+ | `install.sh` full | Downloads + installs packages. Network-bound. |
121
+ | `dot upgrade` | Runs `mise upgrade`, `chezmoi apply`, package manager upgrades. |
122
+ | Full test suite | 15+ minutes on CI. Gated by workflow timeout, not per-run assertion. |
123
+
124
+ ## Ratchet vs aspiration
125
+
126
+ The budgets above are **regression gates**, not aspirations. If a real optimisation lowers a baseline, edit the doc + the perf test to lower the budget too. If a change pushes something over the budget, the test fails and CI blocks the merge.
127
+
128
+ The aspirational shell-startup target (`<30ms`) is tracked separately in `benches/bench.sh` — that's a bench, not a budget.
129
+
130
+ ## Adding a new operation
131
+
132
+ When you add a new script that runs at a shell prompt:
133
+
134
+ 1. Time it 5 runs on a warm system: `for _ in {1..5}; do time bash your-script; done`
135
+ 2. Take the median.
136
+ 3. Place it in the tier where `budget ≥ 2 × median`. If a median is 400ms it goes in FAST (500ms is uncomfortably tight); if it's 300ms, INSTANT is fine.
137
+ 4. Add it to `benches/test_perf_budgets.sh` in the correct tier section.
138
+ 5. Add its baseline to this doc.
139
+
140
+ ## Where the enforcement lives
141
+
142
+ - **Per-op budget test**: `benches/test_perf_budgets.sh`
143
+ - **Suite-level wall-clock ratchet**: `benches/test_help_gates_wall_clock.sh`
144
+ - **CI wiring**: `.github/workflows/ci.yml`, job `quality-performance` — runs on
145
+ ubuntu-latest and macos-latest for every PR, with no `|| true` and no budget
146
+ scaling. On the gates the hosted macOS runner is comparable to or faster than
147
+ the reference machine — docs-coverage 679 ms vs 969, traceability 1775 vs
148
+ 2389, help-registry 3318 vs 4449, `dot doctor` 1746 vs 4423. The exception is
149
+ anything that drives chezmoi over the whole source tree: `dot status` and
150
+ `dot diff` measured ~2× the local median on **both** hosted platforms, which
151
+ is why they sit in MEDIUM rather than FAST. Budget a new operation against the
152
+ slowest platform it will run on, not against this machine.
153
+
154
+ ### How the time is measured
155
+
156
+ `_measure` uses the `time` keyword with `TIMEFORMAT='%3R'`, not `date +%s%N`.
157
+
158
+ `%N` is a GNU extension. BSD `date` — macOS 14 and earlier — copies the literal
159
+ `N` through, so every arithmetic conversion failed, `_measure` returned nothing,
160
+ and an empty median compares as `0` against any budget. The gate reported every
161
+ budget met on those machines, for any command, including one that could not
162
+ parse. `time` is a shell builtin, is millisecond-accurate in bash 3.2, and needs
163
+ no external clock at all.
164
+
165
+ Two consequences worth keeping:
166
+
167
+ - `_gate_max_rc` rejects a non-numeric median outright rather than comparing it.
168
+ Both fields go through `-gt` / `-le`, where bash reads a non-numeric operand
169
+ as `0` — so a gate that cannot measure would otherwise report success.
170
+ - `tests/regression/test_gate_integrity.sh` puts a `date` without `%N` first on
171
+ `PATH` and requires the same verdicts. A future timing rewrite that
172
+ reintroduces the dependency fails there rather than going quiet.
173
+
174
+ ## Environment knobs
175
+
176
+ | Variable | Default | Effect |
177
+ |---|---|---|
178
+ | `PERF_BUDGET_PERCENT` | `100` | Scales every budget. `0` makes them all impossible — that is how `test_gate_integrity.sh` proves the gate actually fires. |
179
+ | `PERF_GATE_FILTER` | *(unset)* | Runs only gates whose label contains this substring. Skipped gates never call `test_start`, so `TESTS_RUN == PASSED + FAILED` still holds. |
180
+ | `DOT_CLI` | `bin/dot` | Points the `dot` gates at another binary, so breakage detection can be exercised against a deliberately corrupted CLI. |
181
+
182
+ ## When a budget fires
183
+
184
+ The error looks like:
185
+
186
+ ```
187
+ ✗ instant_dot_help: median=612ms EXCEEDS budget=500ms
188
+ ```
189
+
190
+ Steps:
191
+
192
+ 1. Bisect the change that pushed it over.
193
+ 2. Fix the regression, or
194
+ 3. If the increase is legitimate (real new work), move the operation to the next tier + update this doc + `test_perf_budgets.sh` in the same PR.
195
+
196
+ **Never silently bump the budget.** The tier a thing lives in is a promise to users.