@sebastienrousseau/dotfiles 0.2.520 → 0.2.522

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 (323) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE-APACHE +190 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +43 -37
  5. package/install.sh +76 -10
  6. package/package.json +7 -7
  7. package/tools/README.md +49 -0
  8. package/tools/ci/install-chezmoi-verified.sh +68 -0
  9. package/docs/.vitepress/reports/localization-readability-audit.md +0 -73
  10. package/docs/AI.md +0 -179
  11. package/docs/ARCHITECTURE.md +0 -117
  12. package/docs/CNAME +0 -1
  13. package/docs/CONFIG_STRATEGY.md +0 -124
  14. package/docs/COPYRIGHT +0 -7
  15. package/docs/ECOSYSTEM.md +0 -220
  16. package/docs/GOLD-STANDARD-AUDIT.md +0 -352
  17. package/docs/GOVERNANCE.md +0 -98
  18. package/docs/MAINTAINERS.md +0 -41
  19. package/docs/MINIMUM-TOOLCHAIN.md +0 -100
  20. package/docs/NAMING_CONVENTIONS.md +0 -102
  21. package/docs/OPENCODE.md +0 -127
  22. package/docs/README.md +0 -84
  23. package/docs/STRUCTURE.md +0 -102
  24. package/docs/adr/ADR-001-ci-cd-pipeline.md +0 -118
  25. package/docs/adr/ADR-002-shell-performance.md +0 -130
  26. package/docs/adr/ADR-003-security-first.md +0 -158
  27. package/docs/adr/ADR-004-cli-architecture.md +0 -171
  28. package/docs/adr/ADR-005-chezmoi-choice.md +0 -99
  29. package/docs/adr/ADR-006-shell-selection.md +0 -124
  30. package/docs/adr/ADR-007-multi-shell-parity.md +0 -62
  31. package/docs/adr/ADR-008-alias-system-architecture.md +0 -95
  32. package/docs/adr/ADR-009-wallpaper-driven-theming.md +0 -131
  33. package/docs/adr/ADR-010-starship-transient-prompt.md +0 -144
  34. package/docs/adr/ADR-011-nushell-tier3-keep.md +0 -144
  35. package/docs/adr/ADR-012-ai-fleet-local-proxy.md +0 -79
  36. package/docs/adr/README.md +0 -40
  37. package/docs/architecture/AI_COST_OPTIMIZATION.md +0 -144
  38. package/docs/architecture/ARCHITECTURE.md +0 -20
  39. package/docs/architecture/INTEROP.md +0 -44
  40. package/docs/architecture/REPO_LAYOUT.md +0 -241
  41. package/docs/architecture/WALKTHROUGH.md +0 -86
  42. package/docs/architecture/fleet-deployment.md +0 -77
  43. package/docs/archive/EUXIS_2026_REVIEW.md +0 -127
  44. package/docs/archive/LEGACY_ROADMAP.md +0 -6
  45. package/docs/archive/MILESTONE_v0.2.493.md +0 -47
  46. package/docs/archive/PLAN.md +0 -199
  47. package/docs/archive/REPO_AUDIT.md +0 -31
  48. package/docs/articles/.pages +0 -6
  49. package/docs/articles/2026-07-05-custom-mkdocs-material-dark-theme.md +0 -216
  50. package/docs/articles/2026-07-05-fish-startup-abbr.md +0 -153
  51. package/docs/articles/2026-07-05-master-to-main-rename-runbook.md +0 -128
  52. package/docs/articles/index.md +0 -36
  53. package/docs/guides/INSTALL.md +0 -144
  54. package/docs/guides/MACOS_ICLOUD_SYMLINKS.md +0 -121
  55. package/docs/guides/NEOVIM_IDE_GUIDE.md +0 -61
  56. package/docs/guides/THEMING.md +0 -230
  57. package/docs/guides/TROUBLESHOOTING.md +0 -176
  58. package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +0 -792
  59. package/docs/index.md +0 -132
  60. package/docs/interop/A2A.md +0 -39
  61. package/docs/interop/POWERSHELL.md +0 -102
  62. package/docs/manual/00-introduction.md +0 -89
  63. package/docs/manual/01-concepts/01-architecture.md +0 -138
  64. package/docs/manual/01-concepts/02-trust-model.md +0 -183
  65. package/docs/manual/01-concepts/03-theme-engine.md +0 -186
  66. package/docs/manual/01-concepts/04-fleet.md +0 -148
  67. package/docs/manual/01-concepts/05-self-healing.md +0 -204
  68. package/docs/manual/02-tutorials/01-first-install.md +0 -197
  69. package/docs/manual/02-tutorials/02-add-wallpaper.md +0 -216
  70. package/docs/manual/02-tutorials/03-create-profile.md +0 -244
  71. package/docs/manual/02-tutorials/04-encrypt-secret.md +0 -281
  72. package/docs/manual/02-tutorials/05-deploy-fleet.md +0 -283
  73. package/docs/manual/03-reference/01-dot-cli.md +0 -475
  74. package/docs/manual/03-reference/02-config-files.md +0 -265
  75. package/docs/manual/03-reference/03-environment.md +0 -124
  76. package/docs/manual/03-reference/04-templates.md +0 -190
  77. package/docs/manual/03-reference/05-feature-flags.md +0 -187
  78. package/docs/manual/04-cookbook/01-recipes.md +0 -285
  79. package/docs/manual/04-cookbook/02-troubleshooting.md +0 -351
  80. package/docs/manual/04-cookbook/03-faq.md +0 -175
  81. package/docs/manual/05-appendices/A-platform-matrix.md +0 -101
  82. package/docs/manual/05-appendices/B-security-checklist.md +0 -85
  83. package/docs/manual/05-appendices/C-glossary.md +0 -40
  84. package/docs/manual/05-appendices/D-bibliography.md +0 -58
  85. package/docs/manual/05-appendices/E-license.md +0 -38
  86. package/docs/manual/_toc.yml +0 -58
  87. package/docs/manual/command-index.md +0 -175
  88. package/docs/manual/concept-index.md +0 -170
  89. package/docs/manual/index.md +0 -66
  90. package/docs/migration/README.md +0 -81
  91. package/docs/migration/from-bare-git-repo.md +0 -156
  92. package/docs/migration/from-gnu-stow.md +0 -165
  93. package/docs/migration/from-plain-chezmoi.md +0 -148
  94. package/docs/migration/from-yadm.md +0 -187
  95. package/docs/operations/ARCHITECTURE_ROADMAP.md +0 -7
  96. package/docs/operations/ATTESTATION.md +0 -44
  97. package/docs/operations/CI_CADENCE.md +0 -107
  98. package/docs/operations/CI_COMPOSITES.md +0 -156
  99. package/docs/operations/COMPLETIONS.md +0 -123
  100. package/docs/operations/COVERAGE.md +0 -204
  101. package/docs/operations/DRIFT.md +0 -107
  102. package/docs/operations/HARD_AUDIT_2026.md +0 -631
  103. package/docs/operations/MAINTENANCE.md +0 -63
  104. package/docs/operations/MANIFEST.md +0 -127
  105. package/docs/operations/MIGRATION.md +0 -109
  106. package/docs/operations/OPERATIONS.md +0 -188
  107. package/docs/operations/PERFORMANCE.md +0 -133
  108. package/docs/operations/PERFORMANCE_BUDGETS.md +0 -196
  109. package/docs/operations/REGISTRY.md +0 -90
  110. package/docs/operations/RELEASE_PIPELINE.md +0 -128
  111. package/docs/operations/RELIABILITY.md +0 -122
  112. package/docs/operations/RFC_v0_2_503_reorganization.md +0 -280
  113. package/docs/operations/ROADMAP.md +0 -10
  114. package/docs/operations/ROADMAP_2026.md +0 -7
  115. package/docs/operations/ROADMAP_V0_2_503.md +0 -10
  116. package/docs/operations/TESTING.md +0 -216
  117. package/docs/operations/TRACEABILITY.md +0 -44
  118. package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +0 -65
  119. package/docs/operations/VERSION_SYNC.md +0 -393
  120. package/docs/packaging.md +0 -222
  121. package/docs/reference/ALIASES.md +0 -131
  122. package/docs/reference/ALIASES_CHEATSHEET.md +0 -32
  123. package/docs/reference/ALIASES_DEPRECATIONS.md +0 -13
  124. package/docs/reference/FEATURE-MATRIX.md +0 -646
  125. package/docs/reference/FEATURES.md +0 -66
  126. package/docs/reference/FONTS.md +0 -112
  127. package/docs/reference/POWERSHELL_PARITY.md +0 -82
  128. package/docs/reference/PROFILES.md +0 -69
  129. package/docs/reference/SCREENSHOTS.md +0 -121
  130. package/docs/reference/SCRIPTS.md +0 -71
  131. package/docs/reference/SUPPORT_MATRIX.md +0 -80
  132. package/docs/reference/THEMES.md +0 -117
  133. package/docs/reference/TOOLS.md +0 -110
  134. package/docs/reference/UTILS.md +0 -243
  135. package/docs/registry.json +0 -6
  136. package/docs/schema/dot-env-v1.json +0 -110
  137. package/docs/schema/dot-registry-v1.json +0 -33
  138. package/docs/security/AI_ACT_COMPLIANCE.md +0 -94
  139. package/docs/security/AUDIT_BYPASS.md +0 -103
  140. package/docs/security/AUTOMATION_SECRETS.md +0 -26
  141. package/docs/security/CI_EGRESS_ALLOWLIST.md +0 -127
  142. package/docs/security/CI_PINNING.md +0 -129
  143. package/docs/security/COMMIT_SIGNING.md +0 -138
  144. package/docs/security/COMPLIANCE.md +0 -458
  145. package/docs/security/DEPS_DEV_EXCEPTIONS.md +0 -86
  146. package/docs/security/DISCLOSURE.md +0 -130
  147. package/docs/security/ENCRYPTION.md +0 -57
  148. package/docs/security/FMEA.md +0 -159
  149. package/docs/security/FUZZING.md +0 -209
  150. package/docs/security/HISTORY_FILTERING.md +0 -132
  151. package/docs/security/INCIDENT_RESPONSE.md +0 -579
  152. package/docs/security/INSTALL_VERIFICATION.md +0 -122
  153. package/docs/security/KEYS.md +0 -49
  154. package/docs/security/KEY_ROTATION.md +0 -303
  155. package/docs/security/MCP_POLICY.md +0 -78
  156. package/docs/security/POLICY_RELEASES.md +0 -37
  157. package/docs/security/README.md +0 -28
  158. package/docs/security/SCORECARD.md +0 -195
  159. package/docs/security/SECRETS.md +0 -158
  160. package/docs/security/SECURITY.md +0 -45
  161. package/docs/security/SECURITY_CHECKLIST.md +0 -55
  162. package/docs/security/SHELL_EXEMPTIONS.md +0 -145
  163. package/docs/security/SOUP_REGISTER.md +0 -36
  164. package/docs/security/THREAT_MODEL.md +0 -130
  165. package/docs/security/VERIFICATION_VALIDATION.md +0 -228
  166. package/docs/security/VERIFY_RELEASE.md +0 -201
  167. package/docs/security/security-pubkey.asc +0 -15
  168. package/docs/stylesheets/extra.css +0 -444
  169. package/docs/themes/README.md +0 -10
  170. package/docs/themes/VISUAL_INTEGRITY_REPORT.md +0 -30
  171. package/docs/themes/hero-shot.svg +0 -78
  172. package/scripts/README.md +0 -123
  173. package/scripts/ci/check-copyright-headers.sh +0 -8
  174. package/scripts/ci/check-shell-preamble.sh +0 -8
  175. package/scripts/ci/guard-gitleaks-checkout.sh +0 -8
  176. package/scripts/demo/record.sh +0 -43
  177. package/scripts/diagnostics/a2a-conformance.sh +0 -163
  178. package/scripts/diagnostics/alias-governance.sh +0 -165
  179. package/scripts/diagnostics/aliases-cheatsheet.sh +0 -74
  180. package/scripts/diagnostics/aliases-manifest.sh +0 -77
  181. package/scripts/diagnostics/attest-verify.sh +0 -147
  182. package/scripts/diagnostics/benchmark.sh +0 -408
  183. package/scripts/diagnostics/conflicts.sh +0 -73
  184. package/scripts/diagnostics/doctor-unified.sh +0 -43
  185. package/scripts/diagnostics/doctor.sh +0 -797
  186. package/scripts/diagnostics/drift-dashboard.sh +0 -203
  187. package/scripts/diagnostics/health.sh +0 -656
  188. package/scripts/diagnostics/history-analysis.sh +0 -86
  189. package/scripts/diagnostics/mcp-doctor.sh +0 -582
  190. package/scripts/diagnostics/perf.sh +0 -453
  191. package/scripts/diagnostics/scorecard.sh +0 -120
  192. package/scripts/diagnostics/secret-governance.sh +0 -65
  193. package/scripts/diagnostics/security-score.sh +0 -467
  194. package/scripts/diagnostics/smoke-test.sh +0 -88
  195. package/scripts/diagnostics/snapshot.sh +0 -90
  196. package/scripts/diagnostics/verify.sh +0 -108
  197. package/scripts/diagnostics/verify_state.sh +0 -73
  198. package/scripts/diagnostics/version-locks.sh +0 -94
  199. package/scripts/diagnostics/workstation-attestation.sh +0 -212
  200. package/scripts/dot/commands/agent.sh +0 -535
  201. package/scripts/dot/commands/agents.sh +0 -352
  202. package/scripts/dot/commands/ai.sh +0 -600
  203. package/scripts/dot/commands/aliases.sh +0 -277
  204. package/scripts/dot/commands/appearance.sh +0 -110
  205. package/scripts/dot/commands/completion.sh +0 -171
  206. package/scripts/dot/commands/core.sh +0 -217
  207. package/scripts/dot/commands/diagnostics.sh +0 -265
  208. package/scripts/dot/commands/env-emit.sh +0 -203
  209. package/scripts/dot/commands/fleet.sh +0 -711
  210. package/scripts/dot/commands/init.sh +0 -185
  211. package/scripts/dot/commands/lint.sh +0 -208
  212. package/scripts/dot/commands/manual.sh +0 -169
  213. package/scripts/dot/commands/meta.sh +0 -438
  214. package/scripts/dot/commands/patterns.sh +0 -55
  215. package/scripts/dot/commands/registry.sh +0 -455
  216. package/scripts/dot/commands/restore.sh +0 -232
  217. package/scripts/dot/commands/secrets.sh +0 -296
  218. package/scripts/dot/commands/security.sh +0 -102
  219. package/scripts/dot/commands/tools.sh +0 -570
  220. package/scripts/dot/data/alias-deprecations.tsv +0 -2
  221. package/scripts/dot/powershell/Dot.psm1 +0 -319
  222. package/scripts/fonts/install-nerd-fonts.sh +0 -75
  223. package/scripts/fonts/patch-fonts.sh +0 -36
  224. package/scripts/git-hooks/install.sh +0 -12
  225. package/scripts/git-hooks/pre-commit +0 -12
  226. package/scripts/git-hooks/pre-commit-audit.sh +0 -146
  227. package/scripts/git-hooks/pre-push +0 -105
  228. package/scripts/git-hooks/prepare-commit-msg +0 -29
  229. package/scripts/lib/secrets_provider.sh +0 -200
  230. package/scripts/nvim/headless-upgrade.lua +0 -81
  231. package/scripts/ops/ai-setup.sh +0 -71
  232. package/scripts/ops/bundle.sh +0 -104
  233. package/scripts/ops/chaos.sh +0 -50
  234. package/scripts/ops/chezmoi-apply.sh +0 -333
  235. package/scripts/ops/chezmoi-diff.sh +0 -16
  236. package/scripts/ops/chezmoi-remove.sh +0 -46
  237. package/scripts/ops/chezmoi-update.sh +0 -67
  238. package/scripts/ops/heal-chezmoi.sh +0 -87
  239. package/scripts/ops/heal-system.sh +0 -129
  240. package/scripts/ops/heal-tools.sh +0 -297
  241. package/scripts/ops/heal.sh +0 -223
  242. package/scripts/ops/post-apply-repair.sh +0 -107
  243. package/scripts/ops/prewarm.sh +0 -128
  244. package/scripts/ops/release.sh +0 -262
  245. package/scripts/ops/rollback.sh +0 -613
  246. package/scripts/ops/setup.sh +0 -138
  247. package/scripts/ops/teleport.sh +0 -34
  248. package/scripts/qa/check-feature-matrix.sh +0 -296
  249. package/scripts/qa/check-version-consistency.sh +0 -12
  250. package/scripts/qa/coverage-baseline.sh +0 -61
  251. package/scripts/qa/docs-coverage.sh +0 -118
  252. package/scripts/qa/examples-coverage.sh +0 -94
  253. package/scripts/qa/powershell-contract.ps1 +0 -95
  254. package/scripts/qa/reliability-audit.sh +0 -139
  255. package/scripts/qa/scorecard-snapshot.sh +0 -128
  256. package/scripts/qa/traceability-coverage.sh +0 -124
  257. package/scripts/qa/validate-examples.sh +0 -90
  258. package/scripts/qa/wsl-contract.sh +0 -12
  259. package/scripts/secrets/age-init.sh +0 -82
  260. package/scripts/secrets/create-secrets-file.sh +0 -46
  261. package/scripts/secrets/encrypt-ssh-key.sh +0 -44
  262. package/scripts/security/backup.sh +0 -58
  263. package/scripts/security/check-disclosure-key-expiry.sh +0 -111
  264. package/scripts/security/dns-doh.sh +0 -52
  265. package/scripts/security/encryption-check.sh +0 -55
  266. package/scripts/security/enforce-policies.sh +0 -552
  267. package/scripts/security/firewall.sh +0 -91
  268. package/scripts/security/lock-configs.sh +0 -67
  269. package/scripts/security/lock-screen.sh +0 -56
  270. package/scripts/security/manage-secrets.sh +0 -429
  271. package/scripts/security/ssh-cert.sh +0 -204
  272. package/scripts/security/telemetry-kill.sh +0 -51
  273. package/scripts/security/usb-safety.sh +0 -52
  274. package/scripts/theme/apply-gnome-theme.sh +0 -333
  275. package/scripts/theme/extract-heic-frames.sh +0 -115
  276. package/scripts/theme/extract-theme.py +0 -1020
  277. package/scripts/theme/install-boot-logo.sh +0 -63
  278. package/scripts/theme/install-catppuccin-themes.sh +0 -371
  279. package/scripts/theme/install-cursors.sh +0 -26
  280. package/scripts/theme/install-file-icons.sh +0 -27
  281. package/scripts/theme/install-grub-theme.sh +0 -62
  282. package/scripts/theme/install-lock-icon.sh +0 -31
  283. package/scripts/theme/merge-wallpaper.sh +0 -146
  284. package/scripts/theme/rebuild-themes.sh +0 -603
  285. package/scripts/theme/switch.sh +0 -476
  286. package/scripts/theme/wallpaper-rotate.sh +0 -137
  287. package/scripts/theme/wallpaper-sync.sh +0 -690
  288. package/scripts/tools/cmatrix.sh +0 -22
  289. package/scripts/tools/detect-collisions.py +0 -103
  290. package/scripts/tools/emoji-picker.sh +0 -49
  291. package/scripts/tools/figlet-banner.sh +0 -19
  292. package/scripts/tools/log-rotate.sh +0 -31
  293. package/scripts/tools/lolcat-wrap.sh +0 -20
  294. package/scripts/tools/pipes.sh +0 -49
  295. package/scripts/tuning/linux.sh +0 -186
  296. package/scripts/tuning/macos.sh +0 -56
  297. package/scripts/uninstall.sh +0 -86
  298. package/scripts/verify-release-versions +0 -156
  299. package/scripts/version-sync.sh +0 -714
  300. package/templates/chezmoi-data/geekom-a9.toml.example +0 -21
  301. package/templates/chezmoi-data/mac-m1.toml.example +0 -16
  302. package/templates/chezmoi-data/mac-t2-linux.toml.example +0 -21
  303. package/templates/chezmoi-data/surface-pro-7p.toml.example +0 -21
  304. package/templates/projects/go/.github/workflows/ci.yml +0 -31
  305. package/templates/projects/go/README.md +0 -7
  306. package/templates/projects/go/cmd/__PROJECT_NAME__/main.go +0 -8
  307. package/templates/projects/go/go.mod +0 -3
  308. package/templates/projects/go/go.sum +0 -0
  309. package/templates/projects/molecule/README.md +0 -7
  310. package/templates/projects/molecule/converge.yml +0 -7
  311. package/templates/projects/molecule/molecule.yml +0 -16
  312. package/templates/projects/node/.github/workflows/ci.yml +0 -30
  313. package/templates/projects/node/README.md +0 -7
  314. package/templates/projects/node/package-lock.json +0 -12
  315. package/templates/projects/node/package.json +0 -10
  316. package/templates/projects/node/src/index.js +0 -3
  317. package/templates/projects/packer/README.md +0 -15
  318. package/templates/projects/packer/main.pkr.hcl +0 -15
  319. package/templates/projects/python/.github/workflows/ci.yml +0 -34
  320. package/templates/projects/python/README.md +0 -7
  321. package/templates/projects/python/pyproject.toml +0 -25
  322. package/templates/projects/python/src/__PROJECT_NAME__/__init__.py +0 -2
  323. package/templates/projects/python/tests/test_basic.py +0 -3
@@ -1,797 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SPDX-License-Identifier: Apache-2.0 OR MIT
3
- # Copyright (c) 2015-2026 Sebastien Rousseau
4
- ## Dotfiles Doctor.
5
- ##
6
- ## Diagnoses dotfiles environment health by checking dependencies, paths,
7
- ## and configuration integrity. Reports errors, warnings, and suggests
8
- ## remediation steps.
9
- ##
10
- ## # Usage
11
- ## dot doctor
12
- ##
13
- ## # Exit Codes
14
- ## - 0: All checks passed (may have warnings)
15
- ## - 1: Critical errors detected
16
- ##
17
- ## # Idempotency
18
- ## Safe to run repeatedly. Read-only checks.
19
-
20
- set -euo pipefail
21
-
22
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
- # shellcheck source=../../lib/dot/ui.sh
24
- # shellcheck disable=SC1091
25
- source "$SCRIPT_DIR/../../lib/dot/ui.sh"
26
- # shellcheck source=../../lib/dot/platform.sh
27
- # shellcheck disable=SC1091
28
- source "$SCRIPT_DIR/../../lib/dot/platform.sh"
29
- # shellcheck source=../../lib/dot/log.sh
30
- # shellcheck disable=SC1091
31
- source "$SCRIPT_DIR/../../lib/dot/log.sh"
32
- export DOT_COMMAND="doctor"
33
-
34
- ui_init
35
-
36
- # Extend PATH to include common non-standard install locations
37
- export PATH="$HOME/.atuin/bin:$HOME/.local/bin:$PATH"
38
-
39
- Errors=0
40
- Warnings=0
41
- AI_DEBUG=0
42
-
43
- for arg in "$@"; do
44
- case "$arg" in
45
- --ai) AI_DEBUG=1 ;;
46
- esac
47
- done
48
-
49
- # --- Output helpers (delegate to shared ui.sh) ---
50
- _ok() { ui_ok "$1" "${2:-}"; }
51
- _fail() {
52
- ui_err "$1" "${2:-}"
53
- Errors=$((Errors + 1))
54
- }
55
- _warn() {
56
- ui_warn "$1" "${2:-}"
57
- Warnings=$((Warnings + 1))
58
- }
59
- _section() {
60
- echo ""
61
- ui_section "$1"
62
- }
63
-
64
- pretty_path() {
65
- local value="${1:-}"
66
- if [[ -n "$value" ]]; then
67
- printf '%s' "${value/#$HOME/\~}"
68
- fi
69
- }
70
-
71
- tool_source() {
72
- local path="${1:-}"
73
- if [[ "$path" == "$HOME/.local/share/mise/"* ]]; then
74
- echo "mise"
75
- elif [[ "$path" == /usr/bin/* || "$path" == /bin/* || "$path" == /usr/sbin/* || "$path" == /sbin/* ]]; then
76
- echo "system"
77
- else
78
- echo "custom"
79
- fi
80
- }
81
-
82
- check_cmd() {
83
- local cmd="$1"
84
- if command -v "$cmd" &>/dev/null; then return 0; fi
85
- if command -v mise &>/dev/null; then
86
- if mise ls --installed 2>/dev/null | grep -qE "($cmd|aqua:.*$cmd)"; then
87
- return 0
88
- fi
89
- fi
90
- return 1
91
- }
92
-
93
- get_cmd_path() {
94
- local cmd="$1"
95
- if command -v "$cmd" &>/dev/null; then
96
- command -v "$cmd"
97
- elif command -v mise &>/dev/null; then
98
- local bin_path
99
- bin_path=$(mise bin-paths 2>/dev/null | grep -E "/$cmd(/|$)" | head -n 1)
100
- if [ -n "$bin_path" ]; then
101
- echo "$bin_path/$cmd"
102
- else
103
- echo "$cmd"
104
- fi
105
- else
106
- echo "$cmd"
107
- fi
108
- }
109
-
110
- # --- Header ---
111
- ui_header "Dotfiles Doctor"
112
-
113
- # --- Core Shells ---
114
- _section "Core Shells"
115
- for cmd in zsh fish starship; do
116
- if check_cmd "$cmd"; then
117
- cmd_path="$(get_cmd_path "$cmd")"
118
- _ok "$cmd" "$(pretty_path "$cmd_path") ($(tool_source "$cmd_path"))"
119
- elif [[ "$cmd" == "fish" ]]; then
120
- _warn "$cmd" "optional"
121
- else
122
- _fail "$cmd" "missing"
123
- fi
124
- done
125
-
126
- if check_cmd "nu"; then
127
- cmd_path="$(get_cmd_path "nu")"
128
- _ok "nu" "$(pretty_path "$cmd_path") ($(tool_source "$cmd_path"))"
129
- elif check_cmd "nushell"; then
130
- cmd_path="$(get_cmd_path "nushell")"
131
- _ok "nu" "$(pretty_path "$cmd_path") ($(tool_source "$cmd_path"))"
132
- else
133
- _warn "nu" "optional"
134
- fi
135
-
136
- # --- Modern CLI Tools ---
137
- _section "Modern CLI Tools"
138
- for cmd in rg bat chezmoi fzf zoxide atuin yazi zellij; do
139
- if check_cmd "$cmd"; then
140
- _ok "$cmd" "$(pretty_path "$(get_cmd_path "$cmd")")"
141
- elif [[ "$cmd" == "bat" ]] && check_cmd "batcat"; then
142
- _ok "$cmd" "$(pretty_path "$(get_cmd_path "batcat")") (batcat)"
143
- else
144
- _fail "$cmd" "missing"
145
- fi
146
- done
147
-
148
- # --- Infrastructure ---
149
- _section "Infrastructure"
150
- for cmd in pueue wasmtime nix sops age hyperfine; do
151
- if check_cmd "$cmd"; then
152
- _ok "$cmd" "$(pretty_path "$(get_cmd_path "$cmd")")"
153
- elif [[ "$cmd" == "nix" ]]; then
154
- _ok "$cmd" "optional (not installed)"
155
- else
156
- _fail "$cmd" "missing"
157
- fi
158
- done
159
-
160
- if check_cmd pueue; then
161
- if "$(get_cmd_path pueue)" status >/dev/null 2>&1; then
162
- _ok "pueue daemon" "running"
163
- elif command -v pueued >/dev/null 2>&1 && pueued -d >/dev/null 2>&1 && "$(get_cmd_path pueue)" status >/dev/null 2>&1; then
164
- _ok "pueue daemon" "started"
165
- else
166
- _warn "pueue daemon" "not running (pueued -d)"
167
- fi
168
- fi
169
-
170
- # --- AI CLIs ---
171
- _section "AI CLIs"
172
- for cmd in codex claude copilot kimi agy sgpt ollama opencode aider kiro-cli; do
173
- if check_cmd "$cmd"; then
174
- _ok "$cmd" "$(pretty_path "$(get_cmd_path "$cmd")")"
175
- else
176
- ui_info "$cmd" "optional (not installed)"
177
- fi
178
- done
179
-
180
- # --- Environment ---
181
- _section "Environment"
182
- if [[ -n "${XDG_CONFIG_HOME:-}" ]]; then
183
- _ok "XDG_CONFIG_HOME" "$(pretty_path "$XDG_CONFIG_HOME")"
184
- else
185
- _warn "XDG_CONFIG_HOME" "defaulting to ~/.config"
186
- fi
187
-
188
- # Validate XDG paths are absolute
189
- for var in XDG_CONFIG_HOME XDG_DATA_HOME XDG_CACHE_HOME XDG_STATE_HOME; do
190
- val="${!var:-}"
191
- if [[ -n "$val" ]] && [[ "$val" != /* ]]; then
192
- _warn "$var" "not absolute: $val"
193
- fi
194
- done
195
-
196
- if [[ -n "${PIPX_HOME:-}" ]]; then
197
- _ok "PIPX_HOME" "$(pretty_path "$PIPX_HOME")"
198
- else
199
- _warn "PIPX_HOME" "not set"
200
- fi
201
-
202
- # --- Platform ---
203
- _section "Platform"
204
-
205
- platform_id="$(dot_platform_id)"
206
- _os_name="$(uname -s)"
207
- _kernel="$(uname -sr)"
208
- _arch="$(uname -m)"
209
- _user="$(whoami 2>/dev/null || echo "${USER:-unknown}")"
210
- _hostname_val="$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo "unknown")"
211
-
212
- # Shell version
213
- _shell_name="${SHELL##*/}"
214
- _shell_ver="$("$SHELL" --version 2>&1 | head -1 | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1 || true)"
215
- _shell="${_shell_name}${_shell_ver:+ $_shell_ver}"
216
-
217
- # Terminal
218
- _terminal="${TERM_PROGRAM:-${TERM:-unknown}}"
219
-
220
- # Uptime (portable)
221
- if uptime -p >/dev/null 2>&1; then
222
- _uptime="$(uptime -p 2>/dev/null | sed 's/^up //')"
223
- else
224
- _uptime="$(uptime 2>/dev/null | sed -E 's/^.* up ([^,]+(, [^,]+){0,2}), [0-9]+ users?.*$/\1/' || true)"
225
- fi
226
-
227
- # --- OS-specific detection ---
228
- _os="" _host="" _cpu="" _cpu_cores="" _gpu="" _mem="" _resolution="" _packages="" _de=""
229
- os_release_file="${DOT_DOCTOR_OS_RELEASE:-/etc/os-release}"
230
- proc_root="${DOT_DOCTOR_PROC_ROOT:-/proc}"
231
- sys_root="${DOT_DOCTOR_SYS_ROOT:-/sys}"
232
-
233
- if [[ "$_os_name" == "Darwin" ]]; then
234
- # macOS
235
- _os="macOS $(sw_vers -productVersion 2>/dev/null || echo "unknown")"
236
- _host="$(/usr/sbin/system_profiler SPHardwareDataType 2>/dev/null | awk -F': ' '/Model Name/{print $2}' || sysctl -n hw.model 2>/dev/null || echo "Mac")"
237
- _cpu="$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo "$_arch")"
238
- _cpu_cores="$(sysctl -n hw.ncpu 2>/dev/null || echo "?")"
239
- _gpu="$(system_profiler SPDisplaysDataType 2>/dev/null | awk -F': ' '/Chipset Model|Chip/{print $2; exit}' || echo "n/a")"
240
- _mem_total="$(sysctl -n hw.memsize 2>/dev/null || echo 0)"
241
- _mem_total_gb="$(awk "BEGIN{printf \"%.2f\", ${_mem_total}/1073741824}")"
242
- _mem_pages="$(vm_stat 2>/dev/null | awk '/Pages active/{gsub(/\./,"",$3); print $3}')"
243
- _mem_used_gb="$(awk "BEGIN{printf \"%.2f\", ${_mem_pages:-0}*4096/1073741824}")"
244
- _mem="${_mem_used_gb} GiB / ${_mem_total_gb} GiB"
245
- _resolution="$(system_profiler SPDisplaysDataType 2>/dev/null | awk '/Resolution/{gsub(/^ +/,""); print; exit}' | sed 's/Resolution: //' || echo "n/a")"
246
- _packages="$(brew list --formula 2>/dev/null | wc -l | tr -d ' ') (brew)"
247
- _de="Aqua"
248
-
249
- elif [[ -r "$os_release_file" ]]; then
250
- # Linux (Debian, Ubuntu, Arch, Fedora, RHEL, etc.)
251
- # shellcheck disable=SC1091
252
- . "$os_release_file"
253
- _os="${PRETTY_NAME:-$ID}"
254
-
255
- _host="$(cat "$sys_root/devices/virtual/dmi/id/product_name" 2>/dev/null || cat "$sys_root/firmware/devicetree/base/model" 2>/dev/null || echo "Linux")"
256
-
257
- if command -v lscpu >/dev/null 2>&1; then
258
- _cpu="$(lscpu | awk -F': +' '/Model name/{print $2}')"
259
- _cpu_cores="$(lscpu | awk -F': +' '/^CPU\(s\):/{print $2}')"
260
- else
261
- _cpu="$(grep -m1 'model name' "$proc_root/cpuinfo" 2>/dev/null | cut -d: -f2 | sed 's/^ //' || uname -p)"
262
- _cpu_cores="$(grep -c '^processor' "$proc_root/cpuinfo" 2>/dev/null || echo "?")"
263
- fi
264
-
265
- if command -v lspci >/dev/null 2>&1; then
266
- # Headless hosts (VMs, containers, CI runners) expose no display
267
- # controller, so `grep` exits 1 and — under `set -euo pipefail` —
268
- # would abort the whole report mid-section. Absorb the miss.
269
- _gpu="$(lspci 2>/dev/null | grep -iE 'vga|3d|display' | head -1 | sed 's/.*: //' || true)"
270
- _gpu="${_gpu:-n/a}"
271
- else
272
- _gpu="n/a"
273
- fi
274
-
275
- if command -v free >/dev/null 2>&1; then
276
- _mem="$(free -b | awk '/Mem:/{printf "%.2f GiB / %.2f GiB", $3/1073741824, $2/1073741824}')"
277
- elif [[ -r "$proc_root/meminfo" ]]; then
278
- _mem_total_kb="$(awk '/MemTotal/{print $2}' "$proc_root/meminfo")"
279
- _mem_avail_kb="$(awk '/MemAvailable/{print $2}' "$proc_root/meminfo")"
280
- _mem_used_kb=$((_mem_total_kb - _mem_avail_kb))
281
- _mem="$(awk "BEGIN{printf \"%.2f GiB / %.2f GiB\", ${_mem_used_kb}/1048576, ${_mem_total_kb}/1048576}")"
282
- else
283
- _mem="n/a"
284
- fi
285
-
286
- # Resolution (Wayland or X11)
287
- if command -v wlr-randr >/dev/null 2>&1; then
288
- _resolution="$(wlr-randr 2>/dev/null | awk '/current/{print $1; exit}' || echo "n/a")"
289
- elif command -v xrandr >/dev/null 2>&1; then
290
- _resolution="$(xrandr 2>/dev/null | awk '/\*/{print $1; exit}' || echo "n/a")"
291
- elif command -v xdpyinfo >/dev/null 2>&1; then
292
- _resolution="$(xdpyinfo 2>/dev/null | awk '/dimensions/{print $2}' || echo "n/a")"
293
- else
294
- _resolution="n/a"
295
- fi
296
-
297
- # Packages (multi-distro)
298
- _pkg_count=0
299
- _pkg_mgr="pkg"
300
- if command -v dpkg >/dev/null 2>&1; then
301
- _pkg_count="$(dpkg --get-selections 2>/dev/null | wc -l | tr -d ' ')"
302
- _pkg_mgr="dpkg"
303
- elif command -v rpm >/dev/null 2>&1; then
304
- _pkg_count="$(rpm -qa 2>/dev/null | wc -l | tr -d ' ')"
305
- _pkg_mgr="rpm"
306
- elif command -v pacman >/dev/null 2>&1; then
307
- _pkg_count="$(pacman -Q 2>/dev/null | wc -l | tr -d ' ')"
308
- _pkg_mgr="pacman"
309
- fi
310
- _packages="${_pkg_count} (${_pkg_mgr})"
311
-
312
- _de="${XDG_CURRENT_DESKTOP:-${DESKTOP_SESSION:-n/a}}"
313
-
314
- else
315
- # Fallback (unknown OS)
316
- _os="$(uname -sr)"
317
- _host="unknown"
318
- _cpu="$(uname -p 2>/dev/null || echo "unknown")"
319
- _cpu_cores="?"
320
- _gpu="n/a"
321
- _mem="n/a"
322
- _resolution="n/a"
323
- _packages="n/a"
324
- _de="n/a"
325
- fi
326
-
327
- # WSL detection and overrides
328
- _wsl=""
329
- if [[ -f "$proc_root/version" ]] && grep -qi microsoft "$proc_root/version" 2>/dev/null; then
330
- _wsl="yes"
331
- _os="${_os} (WSL)"
332
- _de="Windows Desktop (WSL)"
333
- _terminal="${TERM_PROGRAM:-Windows Terminal}"
334
- fi
335
-
336
- # Print platform info
337
- _C='\033[0;36m'
338
- _W='\033[1;37m'
339
- _N='\033[0m'
340
- _D='\033[2m'
341
-
342
- printf '\n'
343
- printf ' %b%s%b@%b%s%b\n' "$_C" "$_user" "$_N" "$_C" "$_hostname_val" "$_N"
344
- printf ' %b%s%b\n' "$_D" "$(printf '%*s' "$((${#_user} + 1 + ${#_hostname_val}))" '' | tr ' ' '-')" "$_N"
345
- printf ' %bOS:%b %s\n' "$_W" "$_N" "$_os"
346
- printf ' %bHost:%b %s\n' "$_W" "$_N" "$_host"
347
- printf ' %bKernel:%b %s\n' "$_W" "$_N" "$_kernel"
348
- printf ' %bUptime:%b %s\n' "$_W" "$_N" "${_uptime:-n/a}"
349
- printf ' %bPackages:%b %s\n' "$_W" "$_N" "$_packages"
350
- printf ' %bShell:%b %s\n' "$_W" "$_N" "$_shell"
351
- printf ' %bResolution:%b %s\n' "$_W" "$_N" "$_resolution"
352
- printf ' %bDE:%b %s\n' "$_W" "$_N" "$_de"
353
- printf ' %bTerminal:%b %s\n' "$_W" "$_N" "$_terminal"
354
- printf ' %bCPU:%b %s (%s)\n' "$_W" "$_N" "$_cpu" "$_cpu_cores"
355
- printf ' %bGPU:%b %s\n' "$_W" "$_N" "${_gpu:-n/a}"
356
- printf ' %bMemory:%b %s\n' "$_W" "$_N" "$_mem"
357
- printf ' %bArch:%b %s\n' "$_W" "$_N" "$_arch"
358
-
359
- if [[ -n "$_wsl" ]]; then
360
- echo ""
361
- if command -v wslpath >/dev/null 2>&1; then
362
- _ok "WSL bridge" "wslpath available"
363
- else
364
- _warn "WSL bridge" "wslpath missing"
365
- fi
366
- if [[ "$PWD" == /mnt/* ]]; then
367
- _warn "WSL filesystem" "/mnt causes IO latency"
368
- else
369
- _ok "WSL filesystem" "native"
370
- fi
371
- fi
372
-
373
- # --- State ---
374
- _section "State"
375
- # --exclude=always: a run_before_/run_after_ script (the macOS iCloud hook)
376
- # is "pending" on every apply by design, so plain `verify` exits 1 forever
377
- # on a fully synchronised machine. Files, dirs, symlinks and run_once/
378
- # run_onchange scripts still count.
379
- if chezmoi verify --exclude=always &>/dev/null; then
380
- _ok "chezmoi" "synchronized"
381
- else
382
- _fail "chezmoi" "drifted (run dot drift)"
383
- fi
384
-
385
- if [[ -f "$HOME/.zshrc" ]]; then
386
- _ok ".zshrc" "present"
387
- else
388
- _fail ".zshrc" "missing"
389
- fi
390
-
391
- if command -v dot >/dev/null 2>&1; then
392
- dot_path="$(command -v dot)"
393
- if [[ "$dot_path" == "$HOME/.local/bin/dot" ]]; then
394
- _ok "dot" "$(pretty_path "$dot_path")"
395
- else
396
- _warn "dot" "$(pretty_path "$dot_path") (expected ~/.local/bin/dot)"
397
- fi
398
- else
399
- _fail "dot" "not found in PATH"
400
- fi
401
-
402
- # --- Pre-push audit bypass log (closes #871) ---
403
- _section "Pre-Push Audit Bypass"
404
-
405
- bypass_log="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/audit-bypass.log"
406
- if [[ -s "$bypass_log" ]]; then
407
- # Count entries in the last 7 days (timestamps are ISO-8601 UTC).
408
- seven_days_ago=$(date -u -v-7d +%Y-%m-%dT00:00:00Z 2>/dev/null ||
409
- date -u -d '7 days ago' +%Y-%m-%dT00:00:00Z 2>/dev/null ||
410
- echo "1970-01-01T00:00:00Z")
411
- recent=$(awk -v cutoff="$seven_days_ago" '$1 >= cutoff' "$bypass_log" | wc -l | tr -d ' ')
412
- if ((recent > 0)); then
413
- _warn "audit bypass" "$recent push(es) bypassed in last 7 days — see $(pretty_path "$bypass_log")"
414
- else
415
- _ok "audit bypass" "log exists; no recent entries (last 7d)"
416
- fi
417
- else
418
- _ok "audit bypass" "clean — no pre-push audits bypassed"
419
- fi
420
-
421
- # --- Atuin history-filter (closes #872) ---
422
- _section "Atuin History Filter"
423
-
424
- atuin_cfg="$HOME/.config/atuin/config.toml"
425
- if [[ ! -f "$atuin_cfg" ]]; then
426
- _warn "atuin config" "not found at $(pretty_path "$atuin_cfg")"
427
- else
428
- if grep -Eq "^history_filter[[:space:]]*=" "$atuin_cfg"; then
429
- pattern_count=$(awk '
430
- /^history_filter[[:space:]]*=[[:space:]]*\[/ { inside = 1; next }
431
- inside && /^\]/ { inside = 0 }
432
- inside && /^[[:space:]]*"/ { count++ }
433
- END { print count + 0 }
434
- ' "$atuin_cfg")
435
- if ((pattern_count >= 10)); then
436
- _ok "history_filter" "$pattern_count patterns (chezmoi-managed)"
437
- else
438
- _warn "history_filter" "$pattern_count patterns (expected ≥10 from secrets-patterns.toml)"
439
- fi
440
- else
441
- _fail "history_filter" "no history_filter block in $(pretty_path "$atuin_cfg") — secrets may leak into shell history"
442
- fi
443
- fi
444
-
445
- # --- Topgrade Integration ---
446
- _section "Topgrade Integration"
447
-
448
- if command -v antigravity >/dev/null 2>&1; then
449
- ag_path="$(command -v antigravity)"
450
- if [[ "$ag_path" == "$HOME/.local/bin/antigravity" ]]; then
451
- _ok "antigravity wrapper" "$(pretty_path "$ag_path")"
452
- else
453
- _warn "antigravity wrapper" "$(pretty_path "$ag_path") (expected ~/.local/bin/antigravity)"
454
- fi
455
- else
456
- _warn "antigravity" "optional"
457
- fi
458
-
459
- if [[ -f "$HOME/.config/fish/fish_plugins" ]]; then
460
- if grep -qx "jorgebucaran/fisher" "$HOME/.config/fish/fish_plugins"; then
461
- _ok "fish_plugins" "contains jorgebucaran/fisher"
462
- else
463
- _warn "fish_plugins" "present but missing jorgebucaran/fisher"
464
- fi
465
- else
466
- _fail "fish_plugins" "missing (~/.config/fish/fish_plugins)"
467
- fi
468
-
469
- # Only meaningful when there is a cargo for topgrade to run it through; a
470
- # machine without a Rust toolchain has nothing to update, so warning about
471
- # the missing helper there is noise that never goes away.
472
- if command -v cargo-install-update >/dev/null 2>&1; then
473
- _ok "cargo-install-update" "$(pretty_path "$(command -v cargo-install-update)")"
474
- elif command -v cargo >/dev/null 2>&1; then
475
- _warn "cargo-install-update" "missing (install: cargo install cargo-update)"
476
- else
477
- _ok "cargo-install-update" "not needed (cargo not installed)"
478
- fi
479
-
480
- # --- Symlinks ---
481
- broken_links=0
482
- broken_list=""
483
- for root in "$HOME/.config" "$HOME/.local/bin" "$HOME/.local/share" "$HOME/.ssh"; do
484
- [[ -d "$root" ]] || continue
485
- while IFS= read -r -d '' link; do
486
- link_name="$(basename "$link")"
487
- [[ "$link" == *"google-chrome-backup"* ]] && continue
488
- [[ "$link_name" == SingletonLock || "$link_name" == SingletonCookie || "$link_name" == SingletonSocket ]] && continue
489
- [[ -e "$link" ]] && continue
490
- # A link into ~/Library/Caches dangling is macOS working as designed, not a
491
- # health problem: the OS purges that directory whenever it wants the space,
492
- # and the owning tool recreates its cache on next use. ~/.config/swiftpm/cache
493
- # -> ~/Library/Caches/org.swift.swiftpm is the usual one; it was "fixed" by
494
- # recreating the target earlier the same day and had broken again by evening,
495
- # which is the tell that it is not fixable, only re-reported.
496
- case "$(readlink "$link" 2>/dev/null)" in
497
- "$HOME/Library/Caches/"*) continue ;;
498
- esac
499
- broken_links=$((broken_links + 1))
500
- broken_list="${broken_list:+$broken_list, }$(pretty_path "$link")"
501
- done < <(find "$root" -maxdepth 3 -type l -print0 2>/dev/null)
502
- done
503
-
504
- if [[ $broken_links -eq 0 ]]; then
505
- _ok "symlinks" "none broken"
506
- else
507
- # Name them. "1 broken" with no path meant hunting for it by hand, twice.
508
- _warn "symlinks" "$broken_links broken: $broken_list"
509
- fi
510
-
511
- # --- Portability ---
512
- ghost_paths=0
513
- if command -v chezmoi >/dev/null 2>&1; then
514
- while IFS= read -r managed_path; do
515
- [[ "$managed_path" == "$HOME/.config/"* ]] || continue
516
- [[ -f "$managed_path" ]] || continue
517
- grep -Iq . "$managed_path" 2>/dev/null || continue
518
-
519
- match_count=$(
520
- grep -nE '"/home/(linuxbrew)?[^$]|/Users/[^$]' "$managed_path" 2>/dev/null |
521
- grep -v "linuxbrew" |
522
- grep -v "/mozilla/firefox" |
523
- grep -v "/google-chrome" |
524
- grep -v "/chromium" |
525
- grep -v "/chezmoi/chezmoi.toml" |
526
- grep -v "/bun/" |
527
- grep -v "/.bun/" |
528
- grep -v "/noctalia/" |
529
- grep -c -v -- "-backup/" ||
530
- true
531
- )
532
-
533
- ghost_paths=$((ghost_paths + ${match_count:-0}))
534
- done < <(chezmoi managed --path-style=absolute 2>/dev/null || true)
535
-
536
- if [[ $ghost_paths -gt 0 ]]; then
537
- _warn "portability" "$ghost_paths hardcoded paths in managed ~/.config files"
538
- else
539
- _ok "portability" "no hardcoded paths in managed ~/.config files"
540
- fi
541
- else
542
- _warn "portability" "chezmoi not found (scan skipped)"
543
- fi
544
-
545
- # --- Performance ---
546
- _section "Performance"
547
-
548
- cache_base="${XDG_CACHE_HOME:-$HOME/.cache}"
549
-
550
- # 1. Shell cache freshness for tools the project already wraps in _cached_eval.
551
- # Stale caches force runtime regeneration on next shell start.
552
- stale_caches=0
553
- stale_tools=""
554
- for tool in mise starship zoxide atuin fzf direnv; do
555
- tool_bin="$(command -v "$tool" 2>/dev/null || true)"
556
- [[ -n "$tool_bin" ]] || continue
557
- for shell_dir in zsh bash fish; do
558
- case "$shell_dir" in
559
- fish) cache_file="$cache_base/$shell_dir/${tool}-init.fish" ;;
560
- *) cache_file="$cache_base/$shell_dir/${tool}-init.$shell_dir" ;;
561
- esac
562
- if [[ ! -f "$cache_file" ]] || [[ "$tool_bin" -nt "$cache_file" ]]; then
563
- stale_caches=$((stale_caches + 1))
564
- case "$stale_tools" in
565
- *"$tool"*) ;;
566
- *) stale_tools="${stale_tools:+$stale_tools, }$tool" ;;
567
- esac
568
- break
569
- fi
570
- done
571
- done
572
- if [[ $stale_caches -eq 0 ]]; then
573
- caches_fresh="fresh"
574
- _ok "shell caches" "fresh"
575
- else
576
- caches_fresh="stale"
577
- _warn "shell caches" "stale ($stale_tools) — run dot prewarm"
578
- fi
579
-
580
- # 2. Slow-init tools that are present but NOT wrapped in _cached_eval.
581
- # Each of these runs uncached on every shell start; common offenders eat
582
- # 100-500ms apiece on a populated dev machine.
583
- #
584
- # Only flag tools that actually emit shell init via `<tool> init <shell>`
585
- # (or equivalent) and would benefit from caching that output. Plain CLIs
586
- # like gh/cargo/pnpm/yarn don't have init eval; their completions are
587
- # cached separately under $ZSH_COMPLETIONS_DIR.
588
- unwrapped=""
589
- for tool in nvm fnm pyenv rbenv jenv asdf sdkman conda kubectl helm thefuck broot mcfly direnv; do
590
- command -v "$tool" >/dev/null 2>&1 || continue
591
- # If the tool is installed but no shell config sources or evals its
592
- # init (no `$tool env`, `$tool init`, `$tool.sh`, lazy-load stub), it
593
- # isn't adding startup cost — skip the warning.
594
- init_referenced=0
595
- if grep -rIlqE "\\b${tool}([[:space:]]+(env|init|hook)|\\.sh|_lazy_load_${tool}|_dot_lazy[[:space:]]+${tool})" \
596
- "$HOME/.config/zsh" "$HOME/.config/fish" "$HOME/.config/shell" 2>/dev/null; then
597
- init_referenced=1
598
- fi
599
- ((init_referenced == 0)) && continue
600
- # Tools we lazy-load via shell stubs don't need init-eval cache files.
601
- case "$tool" in
602
- fnm | nvm | sdkman)
603
- grep -rIlqE "_lazy_load_${tool}|_dot_lazy[[:space:]]+${tool}" \
604
- "$HOME/.config/zsh" "$HOME/.config/fish" 2>/dev/null && continue
605
- ;;
606
- esac
607
- found=0
608
- for shell_dir in zsh bash fish; do
609
- case "$shell_dir" in
610
- fish) [[ -f "$cache_base/$shell_dir/${tool}-init.fish" ]] && found=1 ;;
611
- *) [[ -f "$cache_base/$shell_dir/${tool}-init.$shell_dir" ]] && found=1 ;;
612
- esac
613
- ((found == 1)) && break
614
- done
615
- ((found == 0)) && unwrapped="${unwrapped:+$unwrapped, }$tool"
616
- done
617
- if [[ -z "$unwrapped" ]]; then
618
- _ok "uncached slow-init tools" "none detected"
619
- else
620
- _warn "uncached slow-init tools" "$unwrapped — consider wrapping in _cached_eval"
621
- fi
622
-
623
- # 3. Zsh completion dump health. compinit is usually the single biggest
624
- # cost on a zsh startup; a stale or uncompiled .zcompdump compounds it.
625
- if command -v zsh >/dev/null 2>&1; then
626
- zcompdump="${HOME}/.zcompdump"
627
- if [[ -f "$zcompdump" ]]; then
628
- dump_mtime=$(stat -c %Y "$zcompdump" 2>/dev/null || stat -f %m "$zcompdump" 2>/dev/null || echo 0)
629
- age_days=$((($(date +%s) - dump_mtime) / 86400))
630
- if ((age_days > 7)); then
631
- _warn ".zcompdump" "${age_days}d old — refresh: rm ~/.zcompdump* && zsh -ic exit"
632
- else
633
- _ok ".zcompdump" "fresh (${age_days}d)"
634
- fi
635
- if [[ ! -f "${zcompdump}.zwc" ]]; then
636
- _warn ".zcompdump.zwc" "missing — completion init slower than necessary"
637
- fi
638
- fi
639
- fi
640
-
641
- # 4. PATH length. Each entry is searched on every command resolution.
642
- # A mise-managed 2026 dev machine routinely adds 60-90 entries (one per
643
- # installed tool version + per-shim path), so the warn/fail thresholds
644
- # reflect that baseline rather than a lean default (~40).
645
- #
646
- # The OK ceiling was 75, which contradicted the 60-90 baseline stated right
647
- # above it and warned on every healthy mise machine. Worse, the implied remedy
648
- # was backwards: those per-tool entries are what let a command resolve to the
649
- # real binary instead of falling through to a mise shim. Measured 2026-08-20,
650
- # 20 invocations of ripgrep --version:
651
- #
652
- # via shim 2741ms (137ms per call)
653
- # direct install dir 62ms (3.1ms per call)
654
- #
655
- # ~44x. "Pruning" those entries to satisfy a length check would trade
656
- # microseconds of PATH scan for ~134ms on every single tool invocation. The
657
- # ceiling now matches the documented baseline; >90 still warns, because past
658
- # that the entries are worth auditing for tools you no longer use.
659
- path_count=$(printf '%s' "${PATH:-}" | tr ':' '\n' | grep -c . || true)
660
- if [[ "$path_count" -le 90 ]]; then
661
- _ok "PATH length" "$path_count entries"
662
- elif [[ "$path_count" -le 120 ]]; then
663
- _warn "PATH length" "$path_count entries — consider pruning"
664
- else
665
- _fail "PATH length" "$path_count entries — likely slowing every command"
666
- fi
667
-
668
- # 5. Shell coverage. Surface installed shells that the project's caching
669
- # infrastructure doesn't currently maintain caches for.
670
- shells_unmanaged=""
671
- for sh in nu pwsh; do
672
- command -v "$sh" >/dev/null 2>&1 || continue
673
- case "$sh" in
674
- nu)
675
- [[ -f "$HOME/.config/nushell/cached_eval.nu" ]] && continue
676
- ;;
677
- pwsh)
678
- pwsh -NoLogo -NoProfile -NonInteractive -Command 'exit 0' >/dev/null 2>&1 || continue
679
- pwsh_profile="$HOME/.config/powershell/Microsoft.PowerShell_profile.ps1"
680
- [[ -f "$pwsh_profile" ]] && grep -q 'Get-DotfilesCachedInit' "$pwsh_profile" && continue
681
- ;;
682
- esac
683
- shells_unmanaged="${shells_unmanaged:+$shells_unmanaged, }$sh"
684
- done
685
- if [[ -z "$shells_unmanaged" ]]; then
686
- _ok "shell coverage" "all installed shells have _cached_eval support"
687
- else
688
- _warn "shell coverage" "$shells_unmanaged installed — no _cached_eval helper"
689
- fi
690
-
691
- # 6. Zsh hook count. Heavy precmd/preexec functions compound per-prompt.
692
- # Probe an interactive zsh with a hard timeout so a broken zshrc doesn't
693
- # stall doctor; skip cleanly if the probe fails.
694
- if command -v zsh >/dev/null 2>&1 && command -v timeout >/dev/null 2>&1; then
695
- hook_counts=$(timeout 5 zsh -i -c 'echo "$#precmd_functions $#preexec_functions"' 2>/dev/null || echo "")
696
- if [[ -n "$hook_counts" ]]; then
697
- read -r precmd_n preexec_n <<<"$hook_counts"
698
- if [[ "${precmd_n:-0}" -le 5 && "${preexec_n:-0}" -le 5 ]]; then
699
- _ok "zsh hooks" "precmd=$precmd_n preexec=$preexec_n"
700
- else
701
- _warn "zsh hooks" "precmd=$precmd_n preexec=$preexec_n — heavy per-prompt work"
702
- fi
703
- fi
704
- fi
705
-
706
- if command -v hyperfine >/dev/null 2>&1; then
707
- if bash "$SCRIPT_DIR/../../benches/bench.sh" 2>/dev/null; then
708
- _ok "startup latency" "within target thresholds"
709
- else
710
- # Only suggest prewarm when it could actually help. The caches were
711
- # already reported fresh above in the common case, and telling someone to
712
- # re-run a no-op sends them in a circle — as it did on 2026-08-20, where
713
- # prewarm changed nothing because nothing was cold.
714
- if [[ "${caches_fresh:-unknown}" == "fresh" ]]; then
715
- _warn "startup latency" "threshold exceeded (caches already fresh — profile with 'dot benchmark')"
716
- else
717
- _warn "startup latency" "threshold exceeded (run dot prewarm)"
718
- fi
719
- fi
720
- else
721
- _warn "hyperfine" "missing (benchmark skipped)"
722
- fi
723
-
724
- # 7. Baseline check + top-3 slowest tools from EVALCACHE_TIMING.
725
- # Closes part of #863. Reads the same baseline file `dot perf` writes,
726
- # and the same eval-timings.jsonl _cached_eval populates. Skipped
727
- # silently when either file is absent (first-run state).
728
- baseline_file="$cache_base/dotfiles/perf-baseline.json"
729
- if [[ -s "$baseline_file" ]] && command -v python3 >/dev/null 2>&1; then
730
- baseline_age_days=$(python3 -c '
731
- import json, sys, datetime
732
- try:
733
- d = json.load(open(sys.argv[1]))
734
- rec = d.get("recorded_at", "")
735
- if not rec: print(-1); sys.exit(0)
736
- rec = rec.replace("Z", "+00:00")
737
- age = (datetime.datetime.now(datetime.timezone.utc) - datetime.datetime.fromisoformat(rec)).days
738
- print(age)
739
- except Exception:
740
- print(-1)
741
- ' "$baseline_file" 2>/dev/null)
742
- if [[ "$baseline_age_days" -ge 0 ]]; then
743
- _ok "perf baseline" "recorded ${baseline_age_days}d ago — run \`dot perf\` to compare"
744
- fi
745
- fi
746
-
747
- timings_file="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/eval-timings.jsonl"
748
- if [[ -s "$timings_file" ]] && command -v python3 >/dev/null 2>&1; then
749
- top_tools=$(python3 -c '
750
- import json, sys
751
- from collections import defaultdict
752
- samples = defaultdict(list)
753
- try:
754
- for line in open(sys.argv[1]):
755
- try:
756
- ev = json.loads(line)
757
- ms = int(ev.get("ms", 0) or 0)
758
- samples[ev.get("label", "?")].append(ms)
759
- except Exception:
760
- pass
761
- rows = sorted(samples.items(), key=lambda kv: sum(kv[1]) // max(len(kv[1]), 1), reverse=True)[:3]
762
- print(", ".join(f"{lbl}({sum(v)//max(len(v),1)}ms)" for lbl, v in rows))
763
- except Exception:
764
- pass
765
- ' "$timings_file" 2>/dev/null)
766
- if [[ -n "$top_tools" ]]; then
767
- _ok "perf top-tools" "$top_tools"
768
- fi
769
- fi
770
-
771
- # --- Summary ---
772
- dot_log info "doctor_complete" "errors=$Errors" "warnings=$Warnings"
773
- dot_metric "doctor_errors" "$Errors" "count"
774
- dot_metric "doctor_warnings" "$Warnings" "count"
775
- echo ""
776
- if [[ $Errors -eq 0 ]]; then
777
- if [[ $Warnings -eq 0 ]]; then
778
- ui_ok "Healthy" "All checks passed."
779
- else
780
- ui_ok "Healthy" "$Warnings warning(s)."
781
- fi
782
- else
783
- ui_err "$Errors error(s)" "$Warnings warning(s). Run 'dot heal' to repair."
784
-
785
- if [[ $AI_DEBUG -eq 1 ]]; then
786
- _section "AI Problem Analysis"
787
- doctor_report=$(~/.local/bin/dot doctor | grep -E "(✗|⚠)")
788
- ai_prompt="The dotfiles diagnostic 'dot doctor' found the following issues:
789
- ---
790
- $doctor_report
791
- ---
792
- Suggest specific shell commands to fix these issues according to our architectural standards."
793
- dot ai claude --style hardener "$ai_prompt"
794
- fi
795
- exit 1
796
- fi
797
- echo ""