@sebastienrousseau/dotfiles 0.2.503 → 0.2.504

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.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,33 @@
2
2
 
3
3
  This file documents all notable changes to this project.
4
4
 
5
- ## v0.2.503 (unreleased)
5
+ ## v0.2.504 — 2026-06-03
6
+
7
+ First-class **stock-macOS** support: the installer and the entire `dot` CLI
8
+ now run correctly under the system `/bin/bash` 3.2, and the mise↔chezmoi
9
+ ownership conflict is resolved structurally. Found by performing a clean
10
+ end-to-end install and exercising every command. Full suite: 4821/4821.
11
+
12
+ ### Fixed
13
+
14
+ - **macOS bash 3.2 compatibility** across the CLI, which had assumed bash 4+:
15
+ - iTerm2 profile `run_onchange` used `declare -A` and aborted `chezmoi apply`; rewritten with indexed arrays.
16
+ - `dot ai` used `local -n` namerefs + `declare -A`; now passes arrays by value and reads probe results from the cached TSV via `awk`.
17
+ - `dot security-score` and the `matrix` effect dropped `declare -A` (parallel arrays / `case`).
18
+ - `dot fleet apply` replaced the bash-4.3 `wait -n` throttle with a portable PID-FIFO; `dot theme rebuild` now requires bash 4 and re-execs under a newer bash when available.
19
+ - `dot perf` / `dot bundle` / `dot benchmark` / `dot doctor` / `dot sync` and the pre-commit hook: empty-array `"${arr[@]}"` under `set -u` aborted on bash 3.2; cleanup traps now use the `set +u` idiom.
20
+ - `.bashrc` guards bash-4-only `shopt` options (`globstar`, `dirspell`, `autocd`).
21
+ - **Installer:** backup loop no longer recurses into live `~/.ssh/agent` sockets; seeds git identity from `git config --global` and prints a hint when none is set. The v0.2.503 migration no longer exits non-zero under `set -e`.
22
+ - **mise:** the chezmoi-managed base config moved to `conf.d/00-dotfiles.toml`; `~/.config/mise/config.toml` is now mise's `.chezmoiignore`'d writable global, so `mise use -g` no longer drifts the source. An absent `gh` token no longer breaks the whole config, and the x86_64-only hyperfine aqua package (unrunnable on Apple Silicon) is no longer managed by mise. `version-sync.sh` no longer hangs on a non-TTY stdin (rg now searches the tree, not stdin) and respects `EXCLUDE_FILES`.
23
+ - **`Brewfile.cli`:** add `zellij` and `sops` (checked by `dot doctor`, previously absent).
24
+ - **`dot version-locks`** is `.chezmoiroot`-aware again (had reported "config not found" since the v0.2.503 reorg).
25
+ - **Tests:** robust large-HEIC wallpaper dimension probe (`-ping`); `test_perf_percentiles` trusts mise in its isolated environment.
26
+
27
+ ### Changed
28
+
29
+ - Homebrew/Scoop release installers now actually install (sha256 substitution, Scoop `extract_dir` + Windows entrypoint).
30
+
31
+ ## v0.2.503 — 2026-05-30
6
32
 
7
33
  Polish + cross-platform + Scorecard release **plus** the
8
34
  incremental repo reorganisation per
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://github.com/sebastienrousseau/dotfiles/actions"><img src="https://img.shields.io/github/actions/workflow/status/sebastienrousseau/dotfiles/ci.yml?style=for-the-badge&logo=github" alt="Build" /></a>
13
- <a href="https://github.com/sebastienrousseau/dotfiles/releases/latest"><img src="https://img.shields.io/badge/Version-v0.2.503-blue?style=for-the-badge" alt="Version" /></a>
13
+ <a href="https://github.com/sebastienrousseau/dotfiles/releases/latest"><img src="https://img.shields.io/badge/Version-v0.2.504-blue?style=for-the-badge" alt="Version" /></a>
14
14
  <a href="https://github.com/sebastienrousseau/dotfiles/releases"><img src="https://img.shields.io/github/downloads/sebastienrousseau/dotfiles/total?style=for-the-badge" alt="Downloads" /></a>
15
15
  <a href="https://codespaces.new/sebastienrousseau/dotfiles"><img src="https://img.shields.io/badge/Open%20in-Codespaces-blue?style=for-the-badge&logo=github" alt="Open in GitHub Codespaces" /></a>
16
16
  <a href="https://scorecard.dev/viewer/?uri=github.com/sebastienrousseau/dotfiles"><img src="https://img.shields.io/ossf-scorecard/github.com/sebastienrousseau/dotfiles?style=for-the-badge&label=OpenSSF%20Scorecard" alt="OpenSSF Scorecard" /></a>
@@ -52,8 +52,8 @@
52
52
 
53
53
  ```bash
54
54
  curl -fsSL -o /tmp/dotfiles-install.sh \
55
- https://raw.githubusercontent.com/sebastienrousseau/dotfiles/v0.2.503/install.sh
56
- echo "4c0303a2d88d5aed98428ab0da37618c9795af4dae0e6549646c2fce5235c280 /tmp/dotfiles-install.sh" \
55
+ https://raw.githubusercontent.com/sebastienrousseau/dotfiles/v0.2.504/install.sh
56
+ echo "d5a04c5e2813a93a63c8ecce9655cf3d107f6068862c6eba84a92cf22f801c7e /tmp/dotfiles-install.sh" \
57
57
  | shasum -a 256 -c
58
58
  bash /tmp/dotfiles-install.sh
59
59
  ```
package/docs/COPYRIGHT CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * 🅳🅾🆃🅵🅸🅻🅴🆂 (v0.2.503) - <https://github.com/sebastienrousseau/dotfiles>
2
+ * 🅳🅾🆃🅵🅸🅻🅴🆂 (v0.2.504) - <https://github.com/sebastienrousseau/dotfiles>
3
3
  * Made With ❤️ in London, United Kingdom
4
4
  * Designed by
5
5
  * Copyright (c) 2015-2026. All rights reserved.
package/docs/OPENCODE.md CHANGED
@@ -117,7 +117,7 @@ install.sh # Bootstrap installer
117
117
  ### Key Data Files
118
118
 
119
119
  - `.chezmoidata.toml` — All configuration: profiles, feature flags, tool settings
120
- - `dot_config/mise/config.toml` — mise tool versions (node, python, go, rust, etc.)
120
+ - `dot_config/mise/conf.d/00-dotfiles.toml` — mise tool versions (node, python, go, rust, etc.); the writable `~/.config/mise/config.toml` is mise-owned and not chezmoi-managed
121
121
  - `dot_config/starship.toml.tmpl` — Prompt configuration
122
122
 
123
123
  ### Testing
package/docs/index.md CHANGED
@@ -7,7 +7,7 @@ render_with_liquid: false
7
7
  Cross-platform, signed, local-first dotfiles for macOS, Linux, and WSL.
8
8
 
9
9
  [![Build](https://img.shields.io/github/actions/workflow/status/sebastienrousseau/dotfiles/ci.yml?style=for-the-badge&logo=github)](https://github.com/sebastienrousseau/dotfiles/actions)
10
- [![Version](https://img.shields.io/badge/Version-v0.2.503-blue?style=for-the-badge)](https://github.com/sebastienrousseau/dotfiles/releases/tag/v0.2.503)
10
+ [![Version](https://img.shields.io/badge/Version-v0.2.504-blue?style=for-the-badge)](https://github.com/sebastienrousseau/dotfiles/releases/tag/v0.2.504)
11
11
  [![Downloads](https://img.shields.io/github/downloads/sebastienrousseau/dotfiles/total?style=for-the-badge)](https://github.com/sebastienrousseau/dotfiles/releases)
12
12
 
13
13
  This site is published from the `docs/` directory on `master` and tracks the current workstation baseline, operational guidance, and security material for the repository.
@@ -38,6 +38,6 @@ This site is published from the `docs/` directory on `master` and tracks the cur
38
38
 
39
39
  ## Current Release
40
40
 
41
- - Current tagged release: [`v0.2.503`](https://github.com/sebastienrousseau/dotfiles/releases/tag/v0.2.503)
41
+ - Current tagged release: [`v0.2.504`](https://github.com/sebastienrousseau/dotfiles/releases/tag/v0.2.504)
42
42
  - Latest release feed: [GitHub releases](https://github.com/sebastienrousseau/dotfiles/releases/latest)
43
43
  - Source repository: [sebastienrousseau/dotfiles](https://github.com/sebastienrousseau/dotfiles)
@@ -4,7 +4,7 @@ render_with_liquid: false
4
4
 
5
5
  # Introduction
6
6
 
7
- This manual describes `.dotfiles` v0.2.503 — a trusted agent workstation baseline for macOS, Linux, and WSL.
7
+ This manual describes `.dotfiles` v0.2.504 — a trusted agent workstation baseline for macOS, Linux, and WSL.
8
8
 
9
9
  The repository is more than a personal dotfiles collection. It ships as workstation infrastructure: signed, attested, multi-platform, AI-aware, and self-healing. Chezmoi handles templating and platform differences. The `dot` CLI sits on top and coordinates lifecycle operations.
10
10
 
package/install.sh CHANGED
@@ -59,21 +59,26 @@ show_help() {
59
59
  Usage: install.sh [version] [options]
60
60
 
61
61
  Arguments:
62
- version The version (tag or branch) to install (default: v0.2.503)
62
+ version The version (tag or branch) to install (default: v0.2.504)
63
63
 
64
64
  Options:
65
65
  --help Show this help message
66
66
  --force Non-interactive mode (sets DOTFILES_NONINTERACTIVE=1)
67
67
  --silent Quiet mode (sets DOTFILES_SILENT=1)
68
68
  --minimal Minimal profile (disable nvim, tmux, zellij)
69
+ --provision After applying configs, install the toolchain (packages,
70
+ fonts, language tools) via the install/provision scripts.
71
+ Also enabled by DOTFILES_PROVISION=1. Opt-in because it
72
+ installs packages and may change OS defaults.
69
73
 
70
74
  EOF
71
75
  }
72
76
 
73
77
  main() {
74
- local version="v0.2.503"
78
+ local version="v0.2.504"
75
79
  local version_set=0
76
80
  local minimal=0
81
+ local provision="${DOTFILES_PROVISION:-0}"
77
82
  local _cleanup_files=()
78
83
  trap 'set +u; rm -f "${_cleanup_files[@]}" 2>/dev/null; set -u' EXIT
79
84
 
@@ -86,6 +91,7 @@ main() {
86
91
  --silent) export DOTFILES_SILENT=1 ;;
87
92
  --force) export DOTFILES_NONINTERACTIVE=1 ;;
88
93
  --minimal) minimal=1 ;;
94
+ --provision) provision=1 ;;
89
95
  -*)
90
96
  # Catch single-dash and double-dash unknowns the same way.
91
97
  error "Unknown option: $arg"
@@ -99,7 +105,7 @@ main() {
99
105
  # like `foobar` doesn't trigger a 30s+ network download attempt.
100
106
  # Caught by the install.sh fuzz harness (#881).
101
107
  if [[ ! "$arg" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+([-+][a-zA-Z0-9.-]+)?$ ]]; then
102
- error "Unrecognized positional argument '$arg' — expected a semver version (e.g. v0.2.503)."
108
+ error "Unrecognized positional argument '$arg' — expected a semver version (e.g. v0.2.504)."
103
109
  fi
104
110
  version="$arg"
105
111
  version_set=1
@@ -257,6 +263,35 @@ main() {
257
263
  sed_in_place 's/^zellij = true/zellij = false/' "$data_file"
258
264
  }
259
265
 
266
+ # Carry the user's existing global git identity into chezmoi's data.
267
+ #
268
+ # This installer sets up chezmoi via sourceDir rather than `chezmoi init`,
269
+ # so the config template's prompts for git_name / git_email never run. The
270
+ # gitconfig template coalesces git_name->name->"" (likewise email,
271
+ # signingkey) and only emits a [user] block when non-empty — so without
272
+ # this, a fresh install produces a ~/.gitconfig with no identity at all,
273
+ # and commits fail with "Author identity unknown". Seed name/email/
274
+ # signingkey from `git config --global` when present. Idempotent: skips if
275
+ # the config already carries a [data] block.
276
+ toml_escape() { printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'; }
277
+ seed_git_identity() {
278
+ local cfg="$CHEZMOI_CONFIG_FILE"
279
+ [[ -f "$cfg" ]] || return 0
280
+ grep -q '^\[data\]' "$cfg" 2>/dev/null && return 0
281
+ local gname gemail gkey
282
+ gname="$(git config --global user.name 2>/dev/null || true)"
283
+ gemail="$(git config --global user.email 2>/dev/null || true)"
284
+ gkey="$(git config --global user.signingkey 2>/dev/null || true)"
285
+ [[ -z "$gname" && -z "$gemail" ]] && return 0
286
+ {
287
+ printf '\n[data]\n'
288
+ [[ -n "$gname" ]] && printf 'name = "%s"\n' "$(toml_escape "$gname")"
289
+ [[ -n "$gemail" ]] && printf 'email = "%s"\n' "$(toml_escape "$gemail")"
290
+ [[ -n "$gkey" ]] && printf 'signingkey = "%s"\n' "$(toml_escape "$gkey")"
291
+ } >>"$cfg"
292
+ echo " Seeded git identity ($gname <$gemail>) into chezmoi data."
293
+ }
294
+
260
295
  # 5. Backup existing dotfiles that chezmoi will overwrite
261
296
  step "Backing up existing dotfiles..."
262
297
  BACKUP_DIR="$HOME/.dotfiles.bak.$(date +"%Y%m%d_%H%M%S")"
@@ -273,6 +308,17 @@ main() {
273
308
  if command -v chezmoi >/dev/null && [[ -f "$CHEZMOI_CONFIG_FILE" ]]; then
274
309
  while IFS= read -r file; do
275
310
  [[ -z "$file" ]] && continue
311
+ # Skip managed *directories*: chezmoi apply never clobbers a
312
+ # directory's existing contents, so they don't need backing up, and
313
+ # `cp -a` on one recurses into things it can't copy — e.g. the live
314
+ # ssh-agent sockets under ~/.ssh/agent, which spew
315
+ # "is a socket (not copied)" warnings. The managed files chezmoi
316
+ # would actually overwrite are listed (and backed up) individually.
317
+ # `-d` without `-L` excludes only real dirs; symlinks are copied as
318
+ # links by `cp -a`, so they never recurse.
319
+ if [[ -d "$file" && ! -L "$file" ]]; then
320
+ continue
321
+ fi
276
322
  if [[ -e "$file" ]]; then
277
323
  rel="${file#"$HOME"/}"
278
324
  mkdir -p "$BACKUP_DIR/$(dirname "$rel")"
@@ -304,7 +350,7 @@ main() {
304
350
  # 6. Initialize & Apply
305
351
  step "Applying Configuration..."
306
352
 
307
- # ── Auto-migration for v0.2.503 reorg ─────────────────────────────────
353
+ # ── Auto-migration for v0.2.504 reorg ─────────────────────────────────
308
354
  # If the user is upgrading from a pre-0.2.503 install, run the
309
355
  # migration script BEFORE `chezmoi apply` so the reorg's source-
310
356
  # path moves don't cause chezmoi to delete deployed files.
@@ -313,7 +359,7 @@ main() {
313
359
  for migrate_src in "$SOURCE_DIR" "$LEGACY_SOURCE_DIR"; do
314
360
  migrate_script="$migrate_src/install/migrate/migrate-v0_2-to-v0_2_503.sh"
315
361
  if [[ -x "$migrate_script" ]]; then
316
- echo " Running v0.2.503 migration (idempotent; safe on fresh installs)..."
362
+ echo " Running v0.2.504 migration (idempotent; safe on fresh installs)..."
317
363
  "$migrate_script" || echo " migration exited non-zero — continuing apply"
318
364
  break
319
365
  fi
@@ -323,6 +369,7 @@ main() {
323
369
  if [[ -d "$SOURCE_DIR/.git" ]]; then
324
370
  echo " Applying from local source: $SOURCE_DIR"
325
371
  ensure_chezmoi_source "$SOURCE_DIR"
372
+ seed_git_identity
326
373
  APPLY_FLAGS=()
327
374
  if [[ "${DOTFILES_NONINTERACTIVE:-0}" = "1" ]]; then
328
375
  APPLY_FLAGS=(--force --no-tty)
@@ -332,6 +379,7 @@ main() {
332
379
  echo " Migrating from legacy source: $LEGACY_SOURCE_DIR"
333
380
  mv "$LEGACY_SOURCE_DIR" "$SOURCE_DIR"
334
381
  ensure_chezmoi_source "$SOURCE_DIR"
382
+ seed_git_identity
335
383
  APPLY_FLAGS=()
336
384
  if [[ "${DOTFILES_NONINTERACTIVE:-0}" = "1" ]]; then
337
385
  APPLY_FLAGS=(--force --no-tty)
@@ -362,6 +410,7 @@ main() {
362
410
  fi
363
411
 
364
412
  ensure_chezmoi_source "$SOURCE_DIR"
413
+ seed_git_identity
365
414
  APPLY_FLAGS=()
366
415
  if [[ "${DOTFILES_NONINTERACTIVE:-0}" = "1" ]]; then
367
416
  APPLY_FLAGS=(--force --no-tty)
@@ -369,7 +418,49 @@ main() {
369
418
  chezmoi apply "${APPLY_FLAGS[@]}"
370
419
  fi
371
420
 
421
+ # Toolchain provisioning (opt-in). The install/provision/ scripts (package
422
+ # managers, fonts, language + AI tools) are chezmoi run_ scripts, but they
423
+ # live OUTSIDE the .chezmoiroot source dir (defaults/), so `chezmoi apply`
424
+ # never runs them — a fresh install otherwise ships configs but zero tools.
425
+ # Run them here only when explicitly requested: they install packages and
426
+ # can change OS defaults, so we never do it implicitly (CI / Docker /
427
+ # unattended automation must opt in via --provision or DOTFILES_PROVISION=1).
428
+ if [[ "$provision" = "1" && $minimal -eq 0 ]]; then
429
+ step "Provisioning toolchain (packages, fonts, tools)..."
430
+ local prov_dir="$SOURCE_DIR/install/provision"
431
+ if [[ -d "$prov_dir" ]]; then
432
+ local script
433
+ # run_once_install_* (shells) first, then numbered run_onchange_* in order.
434
+ while IFS= read -r script; do
435
+ [[ -f "$script" ]] || continue
436
+ echo " → $(basename "$script")"
437
+ if [[ "$script" == *.tmpl ]]; then
438
+ chezmoi execute-template <"$script" | bash ||
439
+ echo " (step exited non-zero — continuing)"
440
+ else
441
+ bash "$script" || echo " (step exited non-zero — continuing)"
442
+ fi
443
+ done < <(ls "$prov_dir"/run_once_install_* "$prov_dir"/run_onchange_* 2>/dev/null | sort)
444
+ fi
445
+ elif [[ $minimal -eq 0 ]]; then
446
+ step "Configs deployed. To also install the toolchain (packages, fonts, tools):"
447
+ echo " bash \"$SOURCE_DIR/install.sh\" --provision # or DOTFILES_PROVISION=1"
448
+ echo " (or: brew bundle --file ~/.config/shell/Brewfile.cli)"
449
+ fi
450
+
372
451
  success "Configuration loaded. Please restart your shell."
452
+
453
+ # If no git identity ended up configured, commits will fail with
454
+ # "Author identity unknown". Point the user at the one-line fix rather
455
+ # than letting them discover it on their first commit.
456
+ if [[ -z "$(git config --global user.email 2>/dev/null || true)" ]] &&
457
+ ! grep -q '^email' "$CHEZMOI_CONFIG_FILE" 2>/dev/null; then
458
+ step "No git identity detected. Set yours so commits are attributed:"
459
+ echo " git config --global user.name \"Your Name\""
460
+ echo " git config --global user.email \"you@example.com\""
461
+ echo " then re-run: chezmoi apply"
462
+ fi
463
+
373
464
  step "Run 'dot learn' for an interactive tour of your new dotfiles."
374
465
  }
375
466
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sebastienrousseau/dotfiles",
3
- "version": "0.2.503",
3
+ "version": "0.2.504",
4
4
  "description": "The Trusted Shell Platform — Universal dotfiles managed by Chezmoi. Features Bash & Zsh for macOS, Linux & WSL. Rust modern tooling & enterprise-grade security.",
5
5
  "main": "install.sh",
6
6
  "bin": {
package/scripts/README.md CHANGED
@@ -7,7 +7,7 @@ workflows, by `chezmoi apply` hooks, or by hand.
7
7
 
8
8
  ## Subtree map
9
9
 
10
- Per the v0.2.503 reorg (RFC `docs/operations/RFC_v0_2_503_reorganization.md`),
10
+ Per the repository reorg (RFC `docs/operations/RFC_v0_2_503_reorganization.md`),
11
11
  **repo-only ops** (CI, release, maintenance, docs-generation) have
12
12
  moved to the top-level `tools/` tree. `scripts/` retains the
13
13
  **runtime-invoked** scripts that the `dot` CLI dispatches to plus a
@@ -17,7 +17,7 @@ few specialised subtrees.
17
17
 
18
18
  | Path | Purpose | Touched by |
19
19
  |---|---|---|
20
- | `scripts/dot/commands/` | Per-subcommand handlers for the `dot` CLI. Sources `lib/dot/*.sh` (moved out of this tree in v0.2.503 Phase 1). | Dispatched by `bin/dot` |
20
+ | `scripts/dot/commands/` | Per-subcommand handlers for the `dot` CLI. Sources `lib/dot/*.sh` (moved out of this tree in the Phase 1 reorg). | Dispatched by `bin/dot` |
21
21
  | `scripts/dot/powershell/` | Native PowerShell module (`Dot.psm1`) exporting `Get-DotVersion` / `Invoke-DotHelp` / `Test-DotAgentsSync`. | `bin/dot.ps1` |
22
22
  | `scripts/dot/data/` | Per-command static data (registry seeds, palette tables). | `scripts/dot/commands/*` |
23
23
  | `scripts/lib/` | Cross-cutting library helpers reused across multiple scripts. Example: `secrets_provider.sh` (keychain / pass / age dispatch). | `scripts/dot/commands/*`, ops scripts |
@@ -49,7 +49,7 @@ few specialised subtrees.
49
49
  The `dot` CLI is implemented as `bin/dot`
50
50
  (dispatcher) + `scripts/dot/commands/<cmd>.sh` (per-command
51
51
  handlers) + `lib/dot/*.sh` (shared helpers — moved out of
52
- `scripts/dot/lib/` in v0.2.503 Phase 1 per the
52
+ `scripts/dot/lib/` in the Phase 1 reorg per the
53
53
  [reorganisation RFC](../docs/operations/RFC_v0_2_503_reorganization.md)).
54
54
 
55
55
  | Path | Purpose |
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
3
- # Compat shim: the canonical script moved to tools/ci/ in v0.2.503.
3
+ # Compat shim: the canonical script moved to tools/ci/ during the repo reorg.
4
4
  # Pinned reusable workflows on master still reference the old path;
5
5
  # this delegates so they keep working until the next pin bump.
6
6
  set -euo pipefail
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
3
- # Compat shim: the canonical script moved to tools/ci/ in v0.2.503.
3
+ # Compat shim: the canonical script moved to tools/ci/ during the repo reorg.
4
4
  # Pinned reusable workflows on master still reference the old path;
5
5
  # this delegates so they keep working until the next pin bump.
6
6
  set -euo pipefail
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
3
- # Compat shim: the canonical script moved to tools/ci/ in v0.2.503.
3
+ # Compat shim: the canonical script moved to tools/ci/ during the repo reorg.
4
4
  # Pinned reusable workflows on master still reference the old path;
5
5
  # this delegates so they keep working until the next pin bump.
6
6
  set -euo pipefail
@@ -41,7 +41,7 @@
41
41
  set -euo pipefail
42
42
 
43
43
  _cleanup_files=()
44
- trap 'rm -f "${_cleanup_files[@]}"' EXIT
44
+ trap 'set +u; rm -f "${_cleanup_files[@]}" 2>/dev/null; set -u' EXIT
45
45
 
46
46
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
47
47
  # shellcheck source=../../lib/dot/ui.sh
@@ -4,7 +4,7 @@
4
4
  set -euo pipefail
5
5
 
6
6
  _cleanup_files=()
7
- trap 'rm -f "${_cleanup_files[@]}"' EXIT
7
+ trap 'set +u; rm -f "${_cleanup_files[@]}" 2>/dev/null; set -u' EXIT
8
8
 
9
9
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
10
  REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
 
8
8
  _cleanup_files=()
9
- trap 'rm -f "${_cleanup_files[@]}"' EXIT
9
+ trap 'set +u; rm -f "${_cleanup_files[@]}" 2>/dev/null; set -u' EXIT
10
10
 
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  # shellcheck source=../../lib/dot/ui.sh
@@ -64,7 +64,25 @@ YELLOW="${YELLOW:-}"
64
64
  # Scoring
65
65
  TOTAL_POINTS=0
66
66
  MAX_POINTS=0
67
- declare -A CATEGORY_SCORES
67
+ # Parallel indexed arrays instead of `declare -A` (bash 4+; macOS ships
68
+ # bash 3.2). CAT_NAMES[i] holds a category name, CAT_SCORES[i] its running
69
+ # total. Category names can contain spaces, so an emulation via dynamic
70
+ # variable names wouldn't work — a linear scan over the handful of
71
+ # categories is fine.
72
+ CAT_NAMES=()
73
+ CAT_SCORES=()
74
+
75
+ _cat_add() {
76
+ local name="$1" pts="$2" i
77
+ for ((i = 0; i < ${#CAT_NAMES[@]}; i++)); do
78
+ if [[ "${CAT_NAMES[$i]}" == "$name" ]]; then
79
+ CAT_SCORES[i]=$((CAT_SCORES[i] + pts))
80
+ return 0
81
+ fi
82
+ done
83
+ CAT_NAMES+=("$name")
84
+ CAT_SCORES+=("$pts")
85
+ }
68
86
 
69
87
  add_points() {
70
88
  local category="$1"
@@ -74,7 +92,7 @@ add_points() {
74
92
 
75
93
  TOTAL_POINTS=$((TOTAL_POINTS + points))
76
94
  MAX_POINTS=$((MAX_POINTS + max))
77
- CATEGORY_SCORES["$category"]=$((${CATEGORY_SCORES[$category]:-0} + points))
95
+ _cat_add "$category" "$points"
78
96
 
79
97
  if ! $JSON_OUTPUT && $VERBOSE; then
80
98
  local icon
@@ -339,10 +357,11 @@ print_summary() {
339
357
  echo " \"grade\": \"$(get_grade $score)\","
340
358
  echo " \"categories\": {"
341
359
  local first=true
342
- for cat in "${!CATEGORY_SCORES[@]}"; do
360
+ local _ci
361
+ for ((_ci = 0; _ci < ${#CAT_NAMES[@]}; _ci++)); do
343
362
  $first || echo ","
344
363
  first=false
345
- printf " \"%s\": %d" "$cat" "${CATEGORY_SCORES[$cat]}"
364
+ printf " \"%s\": %d" "${CAT_NAMES[$_ci]}" "${CAT_SCORES[$_ci]}"
346
365
  done
347
366
  echo ""
348
367
  echo " }"
@@ -30,10 +30,20 @@ resolve_source_dir() {
30
30
 
31
31
  src_dir=$(resolve_source_dir)
32
32
 
33
+ # resolve_source_dir returns the repo root, but since the defaults/ reorg the
34
+ # deployable tree lives under the .chezmoiroot subdir (defaults/). Descend
35
+ # into it so paths like dot_config/... resolve. (Without this the mise lookup
36
+ # below silently reported "config not found" on every run.)
37
+ cm_src="$src_dir"
38
+ if [[ -f "$src_dir/.chezmoiroot" ]]; then
39
+ _cm_root="$(tr -d '[:space:]' <"$src_dir/.chezmoiroot")"
40
+ [[ -n "$_cm_root" && -d "$src_dir/$_cm_root" ]] && cm_src="$src_dir/$_cm_root"
41
+ fi
42
+
33
43
  ui_header "Version Locks"
34
44
 
35
45
  ui_section "mise toolchain"
36
- mise_config="$src_dir/dot_config/mise/config.toml"
46
+ mise_config="$cm_src/dot_config/mise/conf.d/00-dotfiles.toml"
37
47
  if [[ -f "$mise_config" ]]; then
38
48
  awk -F= '
39
49
  /^\[tools\]/ {in_tools=1; next}
@@ -53,7 +53,8 @@ _ai_extract_version() {
53
53
  }
54
54
 
55
55
  _ai_refresh_status_cache() {
56
- local -n ai_entries=$1
56
+ # Entries passed by value (not a `local -n` nameref — bash 4.3+; macOS bash is 3.2).
57
+ local ai_entries=("$@")
57
58
  local tmp_file
58
59
  tmp_file="$(mktemp)"
59
60
  mkdir -p "$AI_CACHE_DIR"
@@ -134,6 +135,12 @@ _ai_get_cached_status() {
134
135
  cat "$AI_STATUS_CACHE_FILE"
135
136
  }
136
137
 
138
+ # Look up field $2 (2=present 0/1, 3=version) for binary $1 from the
139
+ # cached TSV. Replaces a bash-4 associative array for macOS bash 3.2.
140
+ _ai_status_field() {
141
+ awk -F'\t' -v b="$1" -v f="$2" '$1==b{print $f;exit}' "$AI_STATUS_CACHE_FILE" 2>/dev/null
142
+ }
143
+
137
144
  # binary -> mise package mapping
138
145
  _ai_mise_pkg() {
139
146
  case "$1" in
@@ -177,17 +184,9 @@ cmd_ai_status() {
177
184
  )
178
185
 
179
186
  if ! _ai_cache_fresh "$AI_STATUS_CACHE_FILE"; then
180
- _ai_refresh_status_cache ai_clis
187
+ _ai_refresh_status_cache "${ai_clis[@]}"
181
188
  fi
182
189
 
183
- declare -A ai_present=()
184
- declare -A ai_version=()
185
- local cached_bin cached_present cached_version
186
- while IFS=$'\t' read -r cached_bin cached_present cached_version; do
187
- ai_present["$cached_bin"]="$cached_present"
188
- ai_version["$cached_bin"]="$cached_version"
189
- done < <(_ai_get_cached_status)
190
-
191
190
  local -a installed=()
192
191
  local -a missing=()
193
192
  local current_category=""
@@ -199,8 +198,9 @@ cmd_ai_status() {
199
198
  ui_section "$category"
200
199
  current_category="$category"
201
200
  fi
202
- if [[ "${ai_present[$bin]:-0}" == "1" ]]; then
203
- ver="${ai_version[$bin]:-installed}"
201
+ if [[ "$(_ai_status_field "$bin" 2)" == "1" ]]; then
202
+ ver="$(_ai_status_field "$bin" 3)"
203
+ [[ -z "$ver" ]] && ver="installed"
204
204
  [[ "$bin" == "claude" ]] && ver="${ver%% *}"
205
205
  ui_ok "$name" "$ver — $desc"
206
206
  installed+=("$name|$bin|$role")
@@ -583,16 +583,21 @@ EOF
583
583
  fi
584
584
  }
585
585
 
586
- local running=0
586
+ # Throttle to `jobs` concurrent workers. `wait -n` (wait for the next
587
+ # job to finish) is bash 4.3+, but macOS ships bash 3.2 — there it
588
+ # silently fails and the throttle collapses to unbounded parallelism.
589
+ # Track PIDs and block on the oldest when at capacity instead; works on
590
+ # bash 3.2 and 4+ alike.
591
+ local _pids=()
587
592
  while IFS=$'\t' read -r name ssh profile; do
588
593
  [[ -n "$name" && -n "$ssh" ]] || continue
589
594
  total=$((total + 1))
590
- while ((running >= jobs)); do
591
- wait -n 2>/dev/null || break
592
- running=$((running - 1))
595
+ while ((${#_pids[@]} >= jobs)); do
596
+ wait "${_pids[0]}" 2>/dev/null || true
597
+ _pids=("${_pids[@]:1}")
593
598
  done
594
599
  _fleet_apply_one "$name" "$ssh" "$effective_cmd" "$tmpdir" &
595
- running=$((running + 1))
600
+ _pids+=("$!")
596
601
  done <<<"$entries"
597
602
  wait
598
603
 
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
 
8
8
  _cleanup_files=()
9
- trap 'rm -f "${_cleanup_files[@]}"' EXIT
9
+ trap 'set +u; rm -f "${_cleanup_files[@]}" 2>/dev/null; set -u' EXIT
10
10
 
11
11
  # ANSI Colors
12
12
  RED='\033[0;31m'
@@ -140,6 +140,6 @@ if [[ $FAILED -eq 1 ]]; then
140
140
  printf '%b\\n' " (Use --no-verify to bypass if absolutely necessary)"
141
141
  exit 1
142
142
  else
143
- printf '%b\n' "${GREEN}${BOLD}✅ Audit passed.${NC} v0.2.503 standards maintained."
143
+ printf '%b\n' "${GREEN}${BOLD}✅ Audit passed.${NC} v0.2.504 standards maintained."
144
144
  exit 0
145
145
  fi
@@ -5,7 +5,7 @@
5
5
  set -euo pipefail
6
6
 
7
7
  _cleanup_files=()
8
- trap 'rm -f "${_cleanup_files[@]}"' EXIT
8
+ trap 'set +u; rm -f "${_cleanup_files[@]}" 2>/dev/null; set -u' EXIT
9
9
 
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
11
11
  # shellcheck disable=SC1091
@@ -11,9 +11,15 @@ source "$SCRIPT_DIR/../../lib/dot/ui.sh"
11
11
  source "$SCRIPT_DIR/../../lib/dot/log.sh"
12
12
  export DOT_COMMAND="apply"
13
13
 
14
- # Temp file cleanup
14
+ # Temp file cleanup. `set +u` guards the array expansion: on bash 3.2
15
+ # (macOS) expanding an empty array under `set -u` is an "unbound
16
+ # variable" error, which would fire on every clean `dot sync`.
15
17
  _TMPFILES=()
16
- cleanup() { rm -f "${_TMPFILES[@]}"; }
18
+ cleanup() {
19
+ set +u
20
+ rm -f "${_TMPFILES[@]}" 2>/dev/null
21
+ set -u
22
+ }
17
23
  trap cleanup EXIT
18
24
 
19
25
  # Help flag
@@ -12,11 +12,33 @@
12
12
  # bash rebuild-themes.sh --list # List discovered wallpapers without rebuilding
13
13
  set -euo pipefail
14
14
 
15
+ # This script relies on associative arrays (`declare -A`) and `wait -n`,
16
+ # both of which need bash >= 4. macOS ships /bin/bash 3.2, where it would
17
+ # otherwise fail with cryptic "invalid option" / "bad array subscript"
18
+ # errors mid-run. Re-exec under a newer bash when one is available
19
+ # (Homebrew, MacPorts, mise), and fail with a clear, actionable message
20
+ # otherwise rather than corrupting a half-built themes.toml.
21
+ if ((BASH_VERSINFO[0] < 4)); then
22
+ for _newer_bash in \
23
+ /opt/homebrew/bin/bash \
24
+ /usr/local/bin/bash \
25
+ "${HOMEBREW_PREFIX:-}/bin/bash" \
26
+ "$(command -v bash 2>/dev/null || true)"; do
27
+ if [[ -n "$_newer_bash" && -x "$_newer_bash" ]] &&
28
+ "$_newer_bash" -c '((BASH_VERSINFO[0] >= 4))' 2>/dev/null; then
29
+ exec "$_newer_bash" "$0" "$@"
30
+ fi
31
+ done
32
+ echo "Error: 'dot theme rebuild' needs bash >= 4 (macOS ships bash 3.2)." >&2
33
+ echo " Install a newer bash and retry: brew install bash" >&2
34
+ exit 1
35
+ fi
36
+
15
37
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
38
  EXTRACT_SCRIPT="$SCRIPT_DIR/extract-theme.py"
17
39
  DOTFILES_DIR="${HOME}/.dotfiles"
18
40
 
19
- # Descend into the chezmoi source subdir when .chezmoiroot is present (v0.2.503+)
41
+ # Descend into the chezmoi source subdir when .chezmoiroot is present (post-reorg, chezmoi files under defaults/)
20
42
  CHEZMOI_SRC="$DOTFILES_DIR"
21
43
  if [[ -f "$DOTFILES_DIR/.chezmoiroot" ]]; then
22
44
  _sub="$(head -1 "$DOTFILES_DIR/.chezmoiroot" | tr -d '[:space:]')"
@@ -266,7 +288,7 @@ if [[ $TOTAL_WORK -gt 0 ]]; then
266
288
  fi
267
289
  ) &
268
290
 
269
- # Limit parallel jobs
291
+ # Limit parallel jobs (bash >= 4 guaranteed by the guard at the top).
270
292
  while [[ $(jobs -r | wc -l) -ge $JOBS ]]; do
271
293
  wait -n 2>/dev/null || true
272
294
  done
@@ -58,7 +58,7 @@ if [ -z "$SRC_DIR" ]; then
58
58
  exit 1
59
59
  fi
60
60
 
61
- # Descend into the chezmoi source subdir when .chezmoiroot is present (v0.2.503+)
61
+ # Descend into the chezmoi source subdir when .chezmoiroot is present (post-reorg, chezmoi files under defaults/)
62
62
  CHEZMOI_SRC="$SRC_DIR"
63
63
  if [[ -f "$SRC_DIR/.chezmoiroot" ]]; then
64
64
  _sub="$(head -1 "$SRC_DIR/.chezmoiroot" | tr -d '[:space:]')"
@@ -14,7 +14,7 @@ WALLPAPER_DIR="${DOTFILES_WALLPAPER_DIR:-$HOME/Pictures/Wallpapers}"
14
14
  CHEZMOI_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/chezmoi/chezmoi.toml"
15
15
 
16
16
  # Resolve dotfiles repo root, then descend into chezmoi source subdir if
17
- # .chezmoiroot is present (v0.2.503+, where chezmoi files live in defaults/)
17
+ # .chezmoiroot is present (where chezmoi files live in defaults/)
18
18
  _DOTFILES_ROOT="${HOME}/.dotfiles"
19
19
  [[ ! -d "$_DOTFILES_ROOT" && -d "${HOME}/.local/share/chezmoi" ]] && _DOTFILES_ROOT="${HOME}/.local/share/chezmoi"
20
20
  _CHEZMOI_SRC="$_DOTFILES_ROOT"
@@ -7,7 +7,7 @@
7
7
  set -euo pipefail
8
8
 
9
9
  _cleanup_files=()
10
- trap 'rm -f "${_cleanup_files[@]}"' EXIT
10
+ trap 'set +u; rm -f "${_cleanup_files[@]}" 2>/dev/null; set -u' EXIT
11
11
 
12
12
  # Configuration
13
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -63,6 +63,17 @@ EXCLUDE_FILES=(
63
63
  "docs/manual/05-appendices/D-bibliography.md"
64
64
  "docs/operations/MAINTENANCE.md"
65
65
 
66
+ # Release-process / structural docs that reference *historical* dotfiles
67
+ # versions (the repo reorg, past release tags, migration scenarios).
68
+ # Bumping these each release would corrupt accurate history.
69
+ "docs/operations/RELEASE_PIPELINE.md"
70
+ "docs/operations/TESTING.md"
71
+ "docs/STRUCTURE.md"
72
+ "install/migrate/README.md"
73
+ "install/README.md"
74
+ "tools/README.md"
75
+ "defaults/README.md"
76
+
66
77
  # CI_COMPOSITES.md cites third-party action versions (e.g. v5.0.5),
67
78
  # not dotfiles_version. False-positive pattern match.
68
79
  "docs/operations/CI_COMPOSITES.md"
@@ -154,9 +165,14 @@ find_version_files() {
154
165
 
155
166
  log_info "Scanning for markdown files with version references..."
156
167
 
157
- # Find all markdown files with version patterns
168
+ # Find all markdown files with version patterns.
169
+ # The "." path is required: with no path and a non-TTY stdin (CI, pipes,
170
+ # background jobs) rg reads from stdin instead of searching the tree, so
171
+ # the whole script hangs forever. The `sed` strips rg's leading "./" so
172
+ # the paths match the EXCLUDE_FILES entries (which have no "./"),
173
+ # otherwise excluded historical docs get rewritten.
158
174
  cd "$PROJECT_ROOT"
159
- rg -l "v?$VERSION_PATTERN" --type md >"$temp_file" 2>/dev/null || true
175
+ rg -l "v?$VERSION_PATTERN" --type md . 2>/dev/null | sed 's|^\./||' >"$temp_file" || true
160
176
 
161
177
  # Add known files that should be checked even if they don't have versions yet
162
178
  echo "README.md" >>"$temp_file"