@sebastienrousseau/dotfiles 0.2.500 → 0.2.501
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 +77 -0
- package/README.md +82 -44
- package/docs/.vitepress/reports/localization-readability-audit.md +4 -0
- package/docs/AI.md +8 -2
- package/docs/CNAME +1 -0
- package/docs/COPYRIGHT +1 -1
- package/docs/NAMING_CONVENTIONS.md +7 -0
- package/docs/README.md +4 -0
- package/docs/_config.yml +59 -0
- package/docs/adr/ADR-001-ci-cd-pipeline.md +17 -0
- package/docs/adr/ADR-002-shell-performance.md +9 -0
- package/docs/adr/ADR-003-security-first.md +18 -0
- package/docs/adr/ADR-004-cli-architecture.md +14 -0
- package/docs/adr/ADR-005-chezmoi-choice.md +10 -0
- package/docs/adr/ADR-006-shell-selection.md +9 -0
- package/docs/adr/ADR-007-multi-shell-parity.md +10 -1
- package/docs/adr/ADR-008-alias-system-architecture.md +10 -0
- package/docs/adr/ADR-009-wallpaper-driven-theming.md +131 -0
- package/docs/adr/ADR-010-starship-transient-prompt.md +144 -0
- package/docs/adr/ADR-011-nushell-tier3-keep.md +144 -0
- package/docs/adr/README.md +7 -0
- package/docs/architecture/ARCHITECTURE.md +4 -0
- package/docs/architecture/INTEROP.md +8 -0
- package/docs/architecture/REPO_LAYOUT.md +5 -1
- package/docs/architecture/WALKTHROUGH.md +4 -0
- package/docs/architecture/fleet-deployment.md +4 -0
- package/docs/archive/EUXIS_2026_REVIEW.md +11 -3
- package/docs/archive/LEGACY_ROADMAP.md +45 -27
- package/docs/archive/MILESTONE_v0.2.493.md +4 -0
- package/docs/archive/PLAN.md +46 -8
- package/docs/archive/REPO_AUDIT.md +8 -0
- package/docs/guides/INSTALL.md +4 -0
- package/docs/guides/NEOVIM_IDE_GUIDE.md +9 -0
- package/docs/guides/THEMING.md +8 -0
- package/docs/guides/TROUBLESHOOTING.md +28 -0
- package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +70 -1
- package/docs/index.md +6 -2
- package/docs/interop/A2A.md +7 -0
- package/docs/interop/POWERSHELL.md +102 -0
- package/docs/manual/00-introduction.md +5 -1
- package/docs/manual/01-concepts/01-architecture.md +4 -0
- package/docs/manual/01-concepts/02-trust-model.md +6 -2
- package/docs/manual/01-concepts/03-theme-engine.md +4 -0
- package/docs/manual/01-concepts/04-fleet.md +7 -1
- package/docs/manual/01-concepts/05-self-healing.md +8 -1
- package/docs/manual/02-tutorials/01-first-install.md +5 -0
- package/docs/manual/02-tutorials/02-add-wallpaper.md +5 -0
- package/docs/manual/02-tutorials/03-create-profile.md +6 -0
- package/docs/manual/02-tutorials/04-encrypt-secret.md +6 -0
- package/docs/manual/02-tutorials/05-deploy-fleet.md +5 -1
- package/docs/manual/03-reference/01-dot-cli.md +4 -0
- package/docs/manual/03-reference/02-config-files.md +8 -2
- package/docs/manual/03-reference/03-environment.md +4 -0
- package/docs/manual/03-reference/04-templates.md +7 -1
- package/docs/manual/03-reference/05-feature-flags.md +10 -0
- package/docs/manual/04-cookbook/01-recipes.md +4 -0
- package/docs/manual/04-cookbook/02-troubleshooting.md +32 -0
- package/docs/manual/04-cookbook/03-faq.md +8 -0
- package/docs/manual/05-appendices/A-platform-matrix.md +4 -0
- package/docs/manual/05-appendices/B-security-checklist.md +6 -0
- package/docs/manual/05-appendices/C-glossary.md +4 -0
- package/docs/manual/05-appendices/D-bibliography.md +5 -1
- package/docs/manual/05-appendices/E-license.md +4 -0
- package/docs/manual/_toc.yml +1 -1
- package/docs/manual/command-index.md +4 -0
- package/docs/manual/concept-index.md +4 -0
- package/docs/operations/ATTESTATION.md +5 -0
- package/docs/operations/CI_CADENCE.md +107 -0
- package/docs/operations/CI_COMPOSITES.md +156 -0
- package/docs/operations/COMPLETIONS.md +123 -0
- package/docs/operations/COVERAGE.md +148 -0
- package/docs/operations/DRIFT.md +107 -0
- package/docs/operations/MAINTENANCE.md +5 -1
- package/docs/operations/MIGRATION.md +14 -6
- package/docs/operations/OPERATIONS.md +24 -0
- package/docs/operations/PERFORMANCE.md +133 -0
- package/docs/operations/RELIABILITY.md +6 -0
- package/docs/operations/ROADMAP.md +36 -18
- package/docs/operations/TESTING.md +4 -0
- package/docs/operations/TRACEABILITY.md +4 -0
- package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +4 -0
- package/docs/operations/VERSION_SYNC.md +54 -5
- package/docs/reference/ALIASES.md +7 -0
- package/docs/reference/ALIASES_CHEATSHEET.md +5 -1
- package/docs/reference/ALIASES_DEPRECATIONS.md +5 -1
- package/docs/reference/FEATURES.md +4 -0
- package/docs/reference/FONTS.md +4 -0
- package/docs/reference/PROFILES.md +4 -0
- package/docs/reference/SCREENSHOTS.md +4 -0
- package/docs/reference/SCRIPTS.md +4 -0
- package/docs/reference/SUPPORT_MATRIX.md +10 -4
- package/docs/reference/THEMES.md +4 -0
- package/docs/reference/TOOLS.md +4 -0
- package/docs/reference/UTILS.md +16 -12
- package/docs/security/AI_ACT_COMPLIANCE.md +4 -0
- package/docs/security/AUDIT_BYPASS.md +103 -0
- package/docs/security/AUTOMATION_SECRETS.md +4 -0
- package/docs/security/CI_EGRESS_ALLOWLIST.md +127 -0
- package/docs/security/COMPLIANCE.md +5 -0
- package/docs/security/DEPS_DEV_EXCEPTIONS.md +86 -0
- package/docs/security/ENCRYPTION.md +5 -0
- package/docs/security/FMEA.md +4 -0
- package/docs/security/HISTORY_FILTERING.md +132 -0
- package/docs/security/INCIDENT_RESPONSE.md +4 -0
- package/docs/security/INSTALL_VERIFICATION.md +122 -0
- package/docs/security/KEYS.md +4 -0
- package/docs/security/KEY_ROTATION.md +4 -0
- package/docs/security/MCP_POLICY.md +9 -0
- package/docs/security/POLICY_RELEASES.md +4 -0
- package/docs/security/README.md +4 -0
- package/docs/security/SCORECARD.md +80 -0
- package/docs/security/SECRETS.md +12 -0
- package/docs/security/SECURITY.md +4 -0
- package/docs/security/SECURITY_CHECKLIST.md +11 -0
- package/docs/security/SHELL_EXEMPTIONS.md +145 -0
- package/docs/security/SOUP_REGISTER.md +4 -0
- package/docs/security/THREAT_MODEL.md +10 -0
- package/docs/security/VERIFICATION_VALIDATION.md +5 -1
- package/docs/themes/README.md +4 -0
- package/docs/themes/VISUAL_INTEGRITY_REPORT.md +4 -0
- package/dot_config/ai/identity.md +3 -0
- package/dot_config/ai/patterns/architect.md +2 -0
- package/dot_config/ai/patterns/hardener.md +2 -0
- package/dot_config/ai/patterns/refactor.md +2 -0
- package/dot_config/alacritty/alacritty.toml.tmpl +3 -3
- package/dot_config/atuin/config.toml.tmpl +47 -0
- package/dot_config/dotfiles/agent-card.json +1 -1
- package/dot_config/dotfiles/boot/README.md +2 -0
- package/dot_config/dotfiles/grub/README.md +2 -0
- package/dot_config/dotfiles/lock/README.md +2 -0
- package/dot_config/fish/conf.d/init.fish.tmpl +21 -0
- package/dot_config/fish/functions/_cached_eval.fish +84 -11
- package/dot_config/fish/functions/_cached_eval_clear.fish +17 -0
- package/dot_config/foot/foot.ini.tmpl +3 -3
- package/dot_config/fuzzel/fuzzel.ini.tmpl +2 -2
- package/dot_config/ghostty/config.tmpl +3 -3
- package/dot_config/git/hooks/executable_commit-msg +146 -0
- package/dot_config/goose/config.yaml +2 -2
- package/dot_config/gtk-3.0/gtk.css.tmpl +2 -2
- package/dot_config/gtk-3.0/settings.ini.tmpl +2 -2
- package/dot_config/gtk-4.0/gtk.css.tmpl +2 -2
- package/dot_config/gtk-4.0/settings.ini.tmpl +2 -2
- package/dot_config/kitty/kitty.conf.tmpl +3 -3
- package/dot_config/mise/config.toml +1 -1
- package/dot_config/niri/config.kdl.tmpl +2 -2
- package/dot_config/nushell/cached_eval.nu +80 -0
- package/dot_config/nushell/env.nu.tmpl +21 -13
- package/dot_config/shell/00-core-paths.sh.tmpl +1 -0
- package/dot_config/shell/05-core-safety.sh +1 -0
- package/dot_config/shell/10-secrets.sh +1 -0
- package/dot_config/shell/40-fzf-defaults.sh.tmpl +1 -0
- package/dot_config/shell/40-ls-colors.sh +1 -0
- package/dot_config/shell/50-logic-functions-core.sh.tmpl +1 -0
- package/dot_config/shell/50-logic-functions.sh.tmpl +1 -0
- package/dot_config/shell/51-logic-functions-extra.sh.tmpl +1 -0
- package/dot_config/shell/90-ux-aliases.sh.tmpl +1 -0
- package/dot_config/shell/91-ux-aliases-lazy.sh.tmpl +1 -0
- package/dot_config/shell/README.md +25 -8
- package/dot_config/starship.toml.tmpl +2 -2
- package/dot_config/tmux/tmux.conf.tmpl +3 -3
- package/dot_config/user-dirs.dirs +1 -0
- package/dot_config/vscode/settings.json.tmpl +2 -2
- package/dot_config/waybar/config.jsonc.tmpl +2 -2
- package/dot_config/waybar/style.css.tmpl +2 -2
- package/dot_config/wezterm/wezterm.lua.tmpl +3 -3
- package/dot_config/zsh/dot_zshrc.tmpl +154 -17
- package/dot_config/zsh/rc.d/00-alias-shims.zsh +28 -6
- package/dot_config/zsh/rc.d/30-options.zsh.tmpl +26 -6
- package/dot_local/bin/executable_bm +2 -0
- package/dot_local/bin/executable_dot +9 -2
- package/dot_local/bin/executable_dot-load-benchmark +1 -1
- package/dot_local/bin/executable_notify +2 -0
- package/dot_local/bin/executable_open +2 -0
- package/dot_local/bin/executable_tour +4 -2
- package/dot_local/bin/executable_up +3 -1
- package/install.sh +12 -4
- package/package.json +1 -1
- package/scripts/ci/check-dangerous-chmod.sh +19 -0
- package/scripts/ci/check-deps-dev.sh +236 -0
- package/scripts/ci/check-insecure-tls.sh +61 -0
- package/scripts/ci/check-regression-traceability.sh +67 -0
- package/scripts/ci/check-shell-preamble.sh +106 -0
- package/scripts/ci/run-coverage.sh +362 -0
- package/scripts/ci/validate-chezmoidata.sh +25 -0
- package/scripts/diagnostics/doctor.sh +173 -5
- package/scripts/diagnostics/drift-dashboard.sh +177 -13
- package/scripts/diagnostics/health.sh +21 -4
- package/scripts/diagnostics/perf.sh +304 -77
- package/scripts/diagnostics/workstation-attestation.sh +6 -1
- package/scripts/dot/commands/agent.sh +14 -5
- package/scripts/dot/commands/ai.sh +75 -10
- package/scripts/dot/lib/bento.sh +2 -1
- package/scripts/dot/lib/log.sh +6 -0
- package/scripts/dot/lib/platform.sh +1 -0
- package/scripts/dot/lib/ui.sh +11 -0
- package/scripts/dot/lib/utils.sh +5 -0
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/git-hooks/pre-push +66 -3
- package/scripts/ops/heal-chezmoi.sh +41 -6
- package/scripts/qa/powershell-contract.ps1 +95 -0
- package/scripts/theme/merge-wallpaper.sh +4 -0
- package/scripts/theme/switch.sh +20 -10
- package/dot_config/atuin/config.toml +0 -40
- package/dot_local/bin/__pycache__/executable_dot-load-benchmark-ptycpython-312.pyc +0 -0
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Copyright (c) 2015-2026 Dotfiles. All rights reserved.
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# drift-dashboard.sh — Consolidated drift report.
|
|
5
|
+
#
|
|
6
|
+
# Surfaces four classes of drift between the chezmoi source and the
|
|
7
|
+
# deployed targets:
|
|
8
|
+
#
|
|
9
|
+
# 1. chezmoi-managed drift — deployed file differs from rendered source
|
|
10
|
+
# (`chezmoi status` output). Standard case; what the original
|
|
11
|
+
# drift-dashboard reported.
|
|
12
|
+
# 2. Untracked source — a file exists in the chezmoi source tree but
|
|
13
|
+
# isn't tracked by git, suggesting in-progress local work that
|
|
14
|
+
# hasn't been committed.
|
|
15
|
+
# 3. Orphan deployed — a file under XDG/HOME that was previously
|
|
16
|
+
# chezmoi-managed but the source has since been removed. Detected
|
|
17
|
+
# by sampling well-known managed paths and asking chezmoi if it
|
|
18
|
+
# claims each.
|
|
19
|
+
# 4. Stale source — source file older than its deployed target,
|
|
20
|
+
# which means someone hand-edited the deployed file and the next
|
|
21
|
+
# `chezmoi apply` will silently revert it. Reverse-drift trap.
|
|
22
|
+
#
|
|
23
|
+
# Output is JSON when --json is passed, otherwise the existing
|
|
24
|
+
# ui-formatted summary. Exit code: 0 if every section is clean; 1 if
|
|
25
|
+
# any drift is found; 2 if a prerequisite (chezmoi, git) is missing.
|
|
26
|
+
#
|
|
27
|
+
# Closes #875.
|
|
28
|
+
# =============================================================================
|
|
29
|
+
|
|
3
30
|
set -euo pipefail
|
|
4
31
|
|
|
5
32
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -7,31 +34,168 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
7
34
|
# shellcheck disable=SC1091
|
|
8
35
|
source "$SCRIPT_DIR/../dot/lib/ui.sh"
|
|
9
36
|
|
|
37
|
+
JSON_MODE=0
|
|
38
|
+
SHOW_DIFF="${DOTFILES_DRIFT_SHOW_DIFF:-0}"
|
|
39
|
+
|
|
40
|
+
for arg in "$@"; do
|
|
41
|
+
case "$arg" in
|
|
42
|
+
--json | -j) JSON_MODE=1 ;;
|
|
43
|
+
--diff | -d) SHOW_DIFF=1 ;;
|
|
44
|
+
--help | -h)
|
|
45
|
+
cat <<EOF
|
|
46
|
+
Usage: drift-dashboard.sh [options]
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--json, -j Emit a single JSON object summarising every drift class.
|
|
50
|
+
--diff, -d Also print the chezmoi diff (excluding scripts/install/tests).
|
|
51
|
+
--help, -h Show this help.
|
|
52
|
+
EOF
|
|
53
|
+
exit 0
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
|
|
10
58
|
ui_init
|
|
11
59
|
|
|
12
60
|
if ! command -v chezmoi >/dev/null; then
|
|
13
|
-
|
|
14
|
-
|
|
61
|
+
if [[ $JSON_MODE -eq 1 ]]; then
|
|
62
|
+
printf '{"error":"chezmoi not found"}\n'
|
|
63
|
+
else
|
|
64
|
+
ui_err "chezmoi" "not found"
|
|
65
|
+
fi
|
|
66
|
+
exit 2
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# -----------------------------------------------------------------------------
|
|
70
|
+
# Class 1: chezmoi-managed drift
|
|
71
|
+
# -----------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
cm_status="$(chezmoi status 2>/dev/null || true)"
|
|
74
|
+
cm_count=0
|
|
75
|
+
if [[ -n "$cm_status" ]]; then
|
|
76
|
+
cm_count=$(printf '%s\n' "$cm_status" | wc -l | tr -d ' ')
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# -----------------------------------------------------------------------------
|
|
80
|
+
# Class 2: untracked source files (chezmoi source tree only)
|
|
81
|
+
# -----------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
untracked=""
|
|
84
|
+
untracked_count=0
|
|
85
|
+
src_dir="$(chezmoi source-path 2>/dev/null || true)"
|
|
86
|
+
if [[ -n "$src_dir" && -d "$src_dir/.git" ]]; then
|
|
87
|
+
untracked="$(git -C "$src_dir" ls-files --others --exclude-standard 2>/dev/null || true)"
|
|
88
|
+
if [[ -n "$untracked" ]]; then
|
|
89
|
+
untracked_count=$(printf '%s\n' "$untracked" | wc -l | tr -d ' ')
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# -----------------------------------------------------------------------------
|
|
94
|
+
# Class 4: stale source (source older than deployed = pending revert risk)
|
|
95
|
+
# Compute by walking chezmoi-managed targets and comparing mtimes.
|
|
96
|
+
# -----------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
stale_list=""
|
|
99
|
+
stale_count=0
|
|
100
|
+
if [[ -n "$src_dir" ]]; then
|
|
101
|
+
while IFS= read -r target; do
|
|
102
|
+
[[ -z "$target" ]] && continue
|
|
103
|
+
target_path="$target"
|
|
104
|
+
[[ ! -e "$target_path" ]] && continue
|
|
105
|
+
src_path="$(chezmoi source-path "$target_path" 2>/dev/null || true)"
|
|
106
|
+
[[ -z "$src_path" || ! -e "$src_path" ]] && continue
|
|
107
|
+
if [[ "$target_path" -nt "$src_path" ]]; then
|
|
108
|
+
stale_list+="$target_path"$'\n'
|
|
109
|
+
stale_count=$((stale_count + 1))
|
|
110
|
+
fi
|
|
111
|
+
done < <(chezmoi managed 2>/dev/null | head -200 | while IFS= read -r rel; do
|
|
112
|
+
printf '%s\n' "$HOME/$rel"
|
|
113
|
+
done)
|
|
114
|
+
fi
|
|
115
|
+
stale_list="${stale_list%$'\n'}"
|
|
116
|
+
|
|
117
|
+
# -----------------------------------------------------------------------------
|
|
118
|
+
# Class 3: orphan deployed files (chezmoi no longer claims them)
|
|
119
|
+
# Sample heuristic — chezmoi doesn't expose a direct "orphan" query.
|
|
120
|
+
# We surface a count of zero unless the user has a pre-populated
|
|
121
|
+
# ~/.local/state/dotfiles/orphans file (drift-history feature in fleet
|
|
122
|
+
# already maintains one); future work tracked under #875.
|
|
123
|
+
# -----------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
orphan_count=0
|
|
126
|
+
orphan_file="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/orphans"
|
|
127
|
+
if [[ -s "$orphan_file" ]]; then
|
|
128
|
+
orphan_count=$(wc -l <"$orphan_file" | tr -d ' ')
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# -----------------------------------------------------------------------------
|
|
132
|
+
# Report
|
|
133
|
+
# -----------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
total=$((cm_count + untracked_count + orphan_count + stale_count))
|
|
136
|
+
|
|
137
|
+
if [[ $JSON_MODE -eq 1 ]]; then
|
|
138
|
+
python3 - <<PY
|
|
139
|
+
import json, sys
|
|
140
|
+
print(json.dumps({
|
|
141
|
+
"managed_drift": $cm_count,
|
|
142
|
+
"untracked_source": $untracked_count,
|
|
143
|
+
"orphan_deployed": $orphan_count,
|
|
144
|
+
"stale_source": $stale_count,
|
|
145
|
+
"total": $total
|
|
146
|
+
}))
|
|
147
|
+
PY
|
|
148
|
+
if ((total > 0)); then exit 1; else exit 0; fi
|
|
15
149
|
fi
|
|
16
150
|
|
|
17
151
|
ui_header "Dotfiles Drift Dashboard"
|
|
18
152
|
|
|
19
|
-
|
|
20
|
-
if
|
|
21
|
-
|
|
22
|
-
|
|
153
|
+
# Class 1
|
|
154
|
+
if ((cm_count > 0)); then
|
|
155
|
+
echo ""
|
|
156
|
+
ui_warn "Managed drift" "$cm_count file(s) — deployed differs from rendered source"
|
|
157
|
+
printf '%s\n' "$cm_status"
|
|
158
|
+
else
|
|
159
|
+
ui_ok "Managed drift" "clean"
|
|
23
160
|
fi
|
|
24
161
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
162
|
+
# Class 2
|
|
163
|
+
if ((untracked_count > 0)); then
|
|
164
|
+
echo ""
|
|
165
|
+
ui_warn "Untracked source" "$untracked_count file(s) in chezmoi source not tracked by git"
|
|
166
|
+
printf '%s\n' "$untracked"
|
|
167
|
+
else
|
|
168
|
+
ui_ok "Untracked source" "clean"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# Class 3
|
|
172
|
+
if ((orphan_count > 0)); then
|
|
173
|
+
echo ""
|
|
174
|
+
ui_warn "Orphan deployed" "$orphan_count file(s) — review $(pretty_path "$orphan_file")"
|
|
175
|
+
else
|
|
176
|
+
ui_ok "Orphan deployed" "clean"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Class 4
|
|
180
|
+
if ((stale_count > 0)); then
|
|
181
|
+
echo ""
|
|
182
|
+
ui_warn "Stale source" "$stale_count target(s) newer than source — next \`chezmoi apply\` would revert"
|
|
183
|
+
printf '%s\n' "$stale_list"
|
|
184
|
+
else
|
|
185
|
+
ui_ok "Stale source" "clean"
|
|
186
|
+
fi
|
|
28
187
|
|
|
29
|
-
count=$(printf '%s\n' "$status" | wc -l | tr -d ' ')
|
|
30
188
|
echo ""
|
|
31
|
-
|
|
189
|
+
if ((total > 0)); then
|
|
190
|
+
ui_warn "Total drift signals" "$total"
|
|
191
|
+
else
|
|
192
|
+
ui_ok "Total" "no drift detected"
|
|
193
|
+
fi
|
|
32
194
|
|
|
33
|
-
if [[ "$
|
|
195
|
+
if [[ "$SHOW_DIFF" = "1" && $cm_count -gt 0 ]]; then
|
|
34
196
|
echo ""
|
|
35
|
-
ui_header "
|
|
197
|
+
ui_header "chezmoi diff (excluding scripts/install/tests)"
|
|
36
198
|
chezmoi diff --exclude scripts --exclude install --exclude tests || true
|
|
37
199
|
fi
|
|
200
|
+
|
|
201
|
+
((total > 0)) && exit 1 || exit 0
|
|
@@ -420,16 +420,33 @@ check_config_directories() {
|
|
|
420
420
|
check_sync_status() {
|
|
421
421
|
check_section "Sync Status"
|
|
422
422
|
|
|
423
|
-
# Chezmoi status
|
|
423
|
+
# Chezmoi status. The two-character prefix encodes both directions:
|
|
424
|
+
# col 1 = last-applied state vs actual (was the file edited in
|
|
425
|
+
# place since last apply?)
|
|
426
|
+
# col 2 = actual state vs target (what `chezmoi apply`
|
|
427
|
+
# would still change)
|
|
428
|
+
# Only col 2 matters for "is $HOME out of sync with the source" —
|
|
429
|
+
# col 1 alone just means the source repo has uncommitted edits,
|
|
430
|
+
# which is normal during development and not something the user
|
|
431
|
+
# needs warned about via the health dashboard.
|
|
424
432
|
if has_command chezmoi; then
|
|
425
433
|
local status_output
|
|
426
434
|
status_output=$(chezmoi status 2>/dev/null || echo "")
|
|
427
435
|
if [[ -z "$status_output" ]]; then
|
|
428
436
|
check "Chezmoi sync" "pass"
|
|
429
437
|
else
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
438
|
+
# Count only entries where column 2 is non-space (apply would do something).
|
|
439
|
+
local applicable
|
|
440
|
+
applicable=$(printf '%s\n' "$status_output" | awk 'substr($0,2,1)!=" "' | wc -l | tr -d ' ')
|
|
441
|
+
if [[ "$applicable" -eq 0 ]]; then
|
|
442
|
+
# All drift is source-only (unstaged edits in the source repo).
|
|
443
|
+
# That's not a sync issue; mention it but pass.
|
|
444
|
+
local source_only
|
|
445
|
+
source_only=$(printf '%s\n' "$status_output" | wc -l | tr -d ' ')
|
|
446
|
+
check "Chezmoi sync" "pass" "$source_only source-only edit(s) (run 'chezmoi diff' to inspect)"
|
|
447
|
+
else
|
|
448
|
+
check "Chezmoi sync" "warn" "$applicable file(s) out of sync"
|
|
449
|
+
fi
|
|
433
450
|
fi
|
|
434
451
|
else
|
|
435
452
|
check "Chezmoi sync" "warn" "Not installed, skipped"
|
|
@@ -29,6 +29,9 @@ PROFILE=false
|
|
|
29
29
|
RUNS=3
|
|
30
30
|
TARGET_MS="${DOTFILES_PERF_TARGET_MS:-250}"
|
|
31
31
|
MAX_MS="${DOTFILES_PERF_MAX_MS:-1000}"
|
|
32
|
+
SHELL_FILTER=""
|
|
33
|
+
BY_TOOL=false
|
|
34
|
+
RESET_TIMINGS=false
|
|
32
35
|
|
|
33
36
|
while [[ $# -gt 0 ]]; do
|
|
34
37
|
case "$1" in
|
|
@@ -48,20 +51,180 @@ while [[ $# -gt 0 ]]; do
|
|
|
48
51
|
TARGET_MS="${2:-$TARGET_MS}"
|
|
49
52
|
shift 2
|
|
50
53
|
;;
|
|
54
|
+
--shell | -s)
|
|
55
|
+
SHELL_FILTER="${2:-}"
|
|
56
|
+
shift 2
|
|
57
|
+
;;
|
|
58
|
+
--by-tool)
|
|
59
|
+
BY_TOOL=true
|
|
60
|
+
shift
|
|
61
|
+
;;
|
|
62
|
+
--reset)
|
|
63
|
+
RESET_TIMINGS=true
|
|
64
|
+
shift
|
|
65
|
+
;;
|
|
66
|
+
--baseline)
|
|
67
|
+
# Write the current per-shell means to the baseline file so
|
|
68
|
+
# subsequent runs can warn on regression. Closes part of #863.
|
|
69
|
+
WRITE_BASELINE=true
|
|
70
|
+
shift
|
|
71
|
+
;;
|
|
72
|
+
--no-baseline-check)
|
|
73
|
+
# Skip the baseline-vs-current comparison entirely. Useful for
|
|
74
|
+
# the very first run on a machine, or for short-lived CI.
|
|
75
|
+
NO_BASELINE_CHECK=true
|
|
76
|
+
shift
|
|
77
|
+
;;
|
|
51
78
|
*)
|
|
52
79
|
shift
|
|
53
80
|
;;
|
|
54
81
|
esac
|
|
55
82
|
done
|
|
56
83
|
|
|
57
|
-
|
|
58
|
-
|
|
84
|
+
WRITE_BASELINE="${WRITE_BASELINE:-false}"
|
|
85
|
+
NO_BASELINE_CHECK="${NO_BASELINE_CHECK:-false}"
|
|
86
|
+
BASELINE_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/dotfiles/perf-baseline.json"
|
|
87
|
+
BASELINE_REGRESSION_PCT="${DOTFILES_PERF_REGRESSION_PCT:-10}"
|
|
88
|
+
|
|
89
|
+
# --by-tool reader: aggregate $XDG_STATE_HOME/dotfiles/eval-timings.jsonl
|
|
90
|
+
# (populated by _cached_eval when EVALCACHE_TIMING=1) and report which
|
|
91
|
+
# tools dominate startup time. Independent of the runtime measurement
|
|
92
|
+
# loop above, so it returns immediately.
|
|
93
|
+
if $BY_TOOL || $RESET_TIMINGS; then
|
|
94
|
+
log_dir="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles"
|
|
95
|
+
log_file="$log_dir/eval-timings.jsonl"
|
|
96
|
+
if $RESET_TIMINGS; then
|
|
97
|
+
: >"$log_file" 2>/dev/null || rm -f "$log_file" 2>/dev/null || true
|
|
98
|
+
ui_ok "eval timings" "cleared $log_file"
|
|
99
|
+
$BY_TOOL || exit 0
|
|
100
|
+
fi
|
|
101
|
+
if [[ ! -s "$log_file" ]]; then
|
|
102
|
+
ui_warn "eval timings" "no data at $log_file"
|
|
103
|
+
echo " Hint: open a new shell with EVALCACHE_TIMING=1 to start collecting." >&2
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
ui_dot_banner "Diagnostics"
|
|
107
|
+
ui_header "Per-tool timing breakdown"
|
|
108
|
+
ui_section "$log_file"
|
|
109
|
+
python3 - "$log_file" <<'PY'
|
|
110
|
+
import json, sys
|
|
111
|
+
from collections import defaultdict
|
|
112
|
+
|
|
113
|
+
def percentile(sorted_vals, p):
|
|
114
|
+
"""Linear-interpolation percentile, matching numpy.percentile defaults."""
|
|
115
|
+
if not sorted_vals:
|
|
116
|
+
return 0
|
|
117
|
+
if len(sorted_vals) == 1:
|
|
118
|
+
return sorted_vals[0]
|
|
119
|
+
k = (len(sorted_vals) - 1) * (p / 100.0)
|
|
120
|
+
f = int(k)
|
|
121
|
+
c = min(f + 1, len(sorted_vals) - 1)
|
|
122
|
+
if f == c:
|
|
123
|
+
return sorted_vals[f]
|
|
124
|
+
return sorted_vals[f] + (sorted_vals[c] - sorted_vals[f]) * (k - f)
|
|
125
|
+
|
|
126
|
+
samples = defaultdict(list) # label -> [ms, ms, ...]
|
|
127
|
+
shells_by_label = defaultdict(set) # label -> {shell, ...}
|
|
128
|
+
|
|
129
|
+
with open(sys.argv[1]) as f:
|
|
130
|
+
for line in f:
|
|
131
|
+
line = line.strip()
|
|
132
|
+
if not line:
|
|
133
|
+
continue
|
|
134
|
+
try:
|
|
135
|
+
ev = json.loads(line)
|
|
136
|
+
except Exception:
|
|
137
|
+
continue
|
|
138
|
+
label = ev.get("label", "?")
|
|
139
|
+
try:
|
|
140
|
+
ms = int(ev.get("ms", 0) or 0)
|
|
141
|
+
except (TypeError, ValueError):
|
|
142
|
+
continue
|
|
143
|
+
samples[label].append(ms)
|
|
144
|
+
shells_by_label[label].add(ev.get("shell", "?"))
|
|
145
|
+
|
|
146
|
+
rows = []
|
|
147
|
+
for label, vals in samples.items():
|
|
148
|
+
vals_sorted = sorted(vals)
|
|
149
|
+
rows.append({
|
|
150
|
+
"label": label,
|
|
151
|
+
"count": len(vals_sorted),
|
|
152
|
+
"total": sum(vals_sorted),
|
|
153
|
+
"mean": sum(vals_sorted) // max(len(vals_sorted), 1),
|
|
154
|
+
"min": vals_sorted[0],
|
|
155
|
+
"max": vals_sorted[-1],
|
|
156
|
+
"p50": int(percentile(vals_sorted, 50)),
|
|
157
|
+
"p95": int(percentile(vals_sorted, 95)),
|
|
158
|
+
"p99": int(percentile(vals_sorted, 99)),
|
|
159
|
+
"shells": ",".join(sorted(shells_by_label[label])),
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
rows.sort(key=lambda r: r["total"], reverse=True)
|
|
163
|
+
header = (f" {'label':<20} {'calls':>5} {'total':>8} "
|
|
164
|
+
f"{'mean':>7} {'p50':>5} {'p95':>5} {'p99':>5} shells")
|
|
165
|
+
print(header)
|
|
166
|
+
print(" " + "-" * (len(header) - 2))
|
|
167
|
+
for r in rows:
|
|
168
|
+
print(f" {r['label']:<20} {r['count']:>5} {r['total']:>6}ms "
|
|
169
|
+
f"{r['mean']:>5}ms {r['p50']:>3}ms {r['p95']:>3}ms {r['p99']:>3}ms {r['shells']}")
|
|
170
|
+
PY
|
|
171
|
+
exit 0
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Per-shell defaults. nu and pwsh are genuinely slower than POSIX shells;
|
|
175
|
+
# bash should be quickest. Override via DOTFILES_PERF_TARGET_<SHELL>_MS.
|
|
176
|
+
shell_target_for() {
|
|
177
|
+
case "$1" in
|
|
178
|
+
zsh) echo "${DOTFILES_PERF_TARGET_ZSH_MS:-$TARGET_MS}" ;;
|
|
179
|
+
bash) echo "${DOTFILES_PERF_TARGET_BASH_MS:-60}" ;;
|
|
180
|
+
fish) echo "${DOTFILES_PERF_TARGET_FISH_MS:-200}" ;;
|
|
181
|
+
nu) echo "${DOTFILES_PERF_TARGET_NU_MS:-500}" ;;
|
|
182
|
+
pwsh) echo "${DOTFILES_PERF_TARGET_PWSH_MS:-600}" ;;
|
|
183
|
+
*) echo "$TARGET_MS" ;;
|
|
184
|
+
esac
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Invoke a near-empty session for the named shell, picking flags that
|
|
188
|
+
# load the user's interactive profile (matches what a fresh terminal does).
|
|
189
|
+
invoke_shell() {
|
|
190
|
+
case "$1" in
|
|
191
|
+
zsh) zsh -i -c exit </dev/null ;;
|
|
192
|
+
bash) bash -i -c exit </dev/null ;;
|
|
193
|
+
fish) fish -i -c exit </dev/null ;;
|
|
194
|
+
nu) nu -c exit </dev/null ;;
|
|
195
|
+
pwsh) pwsh -Command exit </dev/null ;;
|
|
196
|
+
*) return 1 ;;
|
|
197
|
+
esac
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
time_one_run() {
|
|
201
|
+
local shell_name="$1" start end
|
|
59
202
|
start=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
60
|
-
|
|
203
|
+
invoke_shell "$shell_name" >/dev/null 2>&1 || true
|
|
61
204
|
end=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
62
205
|
echo $((end - start))
|
|
63
206
|
}
|
|
64
207
|
|
|
208
|
+
# Existing single-shell helper retained for backward compatibility with
|
|
209
|
+
# callers/tests that reference time_shell_startup; routes to time_one_run.
|
|
210
|
+
time_shell_startup() { time_one_run zsh; }
|
|
211
|
+
|
|
212
|
+
# Measure a shell across $RUNS iterations, return "mean min max".
|
|
213
|
+
measure_shell() {
|
|
214
|
+
local shell_name="$1" sum=0 min=999999 max=0 t
|
|
215
|
+
local times=()
|
|
216
|
+
# One warm-up run discarded — caches a $_SHELL_CACHE on first invocation.
|
|
217
|
+
invoke_shell "$shell_name" >/dev/null 2>&1 || true
|
|
218
|
+
for _ in $(seq 1 "$RUNS"); do
|
|
219
|
+
t=$(time_one_run "$shell_name")
|
|
220
|
+
times+=("$t")
|
|
221
|
+
sum=$((sum + t))
|
|
222
|
+
[[ "$t" -lt "$min" ]] && min="$t"
|
|
223
|
+
[[ "$t" -gt "$max" ]] && max="$t"
|
|
224
|
+
done
|
|
225
|
+
echo "$((sum / RUNS)) $min $max"
|
|
226
|
+
}
|
|
227
|
+
|
|
65
228
|
calc_score() {
|
|
66
229
|
local mean_ms="$1"
|
|
67
230
|
if [[ "$mean_ms" -le "$TARGET_MS" ]]; then
|
|
@@ -86,95 +249,159 @@ run_profile() {
|
|
|
86
249
|
' 2>/dev/null | head -20
|
|
87
250
|
}
|
|
88
251
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
252
|
+
# Discover installed shells. SHELL_FILTER (--shell <name>) restricts to one.
|
|
253
|
+
all_shells=(zsh bash fish nu pwsh)
|
|
254
|
+
shells_to_measure=()
|
|
255
|
+
for s in "${all_shells[@]}"; do
|
|
256
|
+
command -v "$s" >/dev/null 2>&1 || continue
|
|
257
|
+
if [[ -n "$SHELL_FILTER" && "$s" != "$SHELL_FILTER" ]]; then
|
|
258
|
+
continue
|
|
259
|
+
fi
|
|
260
|
+
shells_to_measure+=("$s")
|
|
261
|
+
done
|
|
262
|
+
if ((${#shells_to_measure[@]} == 0)); then
|
|
263
|
+
ui_err "perf" "no measurable shells found${SHELL_FILTER:+ (filter: $SHELL_FILTER)}"
|
|
264
|
+
exit 1
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
# Measure each shell once into parallel arrays.
|
|
268
|
+
declare -a shell_names shell_means shell_mins shell_maxs shell_targets shell_passes
|
|
269
|
+
for s in "${shells_to_measure[@]}"; do
|
|
270
|
+
read -r m mn mx <<<"$(measure_shell "$s")"
|
|
271
|
+
t=$(shell_target_for "$s")
|
|
272
|
+
shell_names+=("$s")
|
|
273
|
+
shell_means+=("$m")
|
|
274
|
+
shell_mins+=("$mn")
|
|
275
|
+
shell_maxs+=("$mx")
|
|
276
|
+
shell_targets+=("$t")
|
|
277
|
+
if [[ "$m" -le "$t" ]]; then
|
|
278
|
+
shell_passes+=("1")
|
|
279
|
+
else
|
|
280
|
+
shell_passes+=("0")
|
|
281
|
+
fi
|
|
282
|
+
done
|
|
283
|
+
|
|
284
|
+
# Primary "score" continues to use the zsh measurement (when available)
|
|
285
|
+
# so existing dashboards / JSON consumers keep their reference number.
|
|
286
|
+
mean=0
|
|
287
|
+
for i in "${!shell_names[@]}"; do
|
|
288
|
+
if [[ "${shell_names[$i]}" == "zsh" ]]; then
|
|
289
|
+
mean="${shell_means[$i]}"
|
|
290
|
+
break
|
|
291
|
+
fi
|
|
292
|
+
done
|
|
293
|
+
[[ "$mean" -eq 0 && "${#shell_means[@]}" -gt 0 ]] && mean="${shell_means[0]}"
|
|
294
|
+
score=$(calc_score "$mean")
|
|
295
|
+
|
|
296
|
+
# -----------------------------------------------------------------------------
|
|
297
|
+
# Baseline persistence + regression detection (closes part of #863).
|
|
298
|
+
# Stored at $XDG_CACHE_HOME/dotfiles/perf-baseline.json. The file is a
|
|
299
|
+
# JSON object mapping shell name → recorded mean (ms). `--baseline`
|
|
300
|
+
# overwrites it with the current measurement; default behavior reads
|
|
301
|
+
# it (if present) and warns when any shell regresses by more than
|
|
302
|
+
# DOTFILES_PERF_REGRESSION_PCT (default 10%).
|
|
303
|
+
# -----------------------------------------------------------------------------
|
|
304
|
+
|
|
305
|
+
declare -a baseline_warnings=()
|
|
306
|
+
if $WRITE_BASELINE; then
|
|
307
|
+
mkdir -p "$(dirname "$BASELINE_FILE")"
|
|
308
|
+
{
|
|
309
|
+
printf '{\n "recorded_at": "%s",\n "regression_pct": %d,\n "shells": {' \
|
|
310
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$BASELINE_REGRESSION_PCT"
|
|
311
|
+
for i in "${!shell_names[@]}"; do
|
|
312
|
+
[[ "$i" -gt 0 ]] && printf ','
|
|
313
|
+
printf '\n "%s": %d' "${shell_names[$i]}" "${shell_means[$i]}"
|
|
314
|
+
done
|
|
315
|
+
printf '\n }\n}\n'
|
|
316
|
+
} >"$BASELINE_FILE"
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
if ! $NO_BASELINE_CHECK && [[ -s "$BASELINE_FILE" ]]; then
|
|
320
|
+
# Read the baseline and compare each measured shell.
|
|
321
|
+
for i in "${!shell_names[@]}"; do
|
|
322
|
+
name="${shell_names[$i]}"
|
|
323
|
+
m="${shell_means[$i]}"
|
|
324
|
+
baseline_ms=$(python3 -c '
|
|
325
|
+
import json, sys
|
|
326
|
+
try:
|
|
327
|
+
d = json.load(open(sys.argv[1]))
|
|
328
|
+
print(d.get("shells", {}).get(sys.argv[2], 0))
|
|
329
|
+
except Exception:
|
|
330
|
+
print(0)
|
|
331
|
+
' "$BASELINE_FILE" "$name" 2>/dev/null)
|
|
332
|
+
[[ -z "$baseline_ms" || "$baseline_ms" -eq 0 ]] && continue
|
|
333
|
+
# Threshold: current > baseline * (1 + pct/100)
|
|
334
|
+
threshold=$((baseline_ms * (100 + BASELINE_REGRESSION_PCT) / 100))
|
|
335
|
+
if ((m > threshold)); then
|
|
336
|
+
delta_pct=$((((m - baseline_ms) * 100) / baseline_ms))
|
|
337
|
+
baseline_warnings+=("$name: $m ms vs baseline $baseline_ms ms (+${delta_pct}%, threshold +${BASELINE_REGRESSION_PCT}%)")
|
|
338
|
+
fi
|
|
93
339
|
done
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
if $JSON_OUTPUT; then
|
|
343
|
+
printf '{\n "runs": %d,\n "target_ms": %d,\n "max_ms_target": %d,\n "score": %d,\n "mean_ms": %d,\n "regression_count": %d,\n "shells": {' \
|
|
344
|
+
"$RUNS" "$TARGET_MS" "$MAX_MS" "$score" "$mean" "${#baseline_warnings[@]}"
|
|
345
|
+
for i in "${!shell_names[@]}"; do
|
|
346
|
+
[[ "$i" -gt 0 ]] && printf ','
|
|
347
|
+
printf '\n "%s": {"mean_ms": %d, "min_ms": %d, "max_ms": %d, "target_ms": %d, "pass": %s}' \
|
|
348
|
+
"${shell_names[$i]}" "${shell_means[$i]}" "${shell_mins[$i]}" "${shell_maxs[$i]}" \
|
|
349
|
+
"${shell_targets[$i]}" "$([[ ${shell_passes[$i]} == 1 ]] && echo true || echo false)"
|
|
101
350
|
done
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
JSON
|
|
351
|
+
printf '\n }'
|
|
352
|
+
if [[ "${#baseline_warnings[@]}" -gt 0 ]]; then
|
|
353
|
+
printf ',\n "regressions": ['
|
|
354
|
+
for i in "${!baseline_warnings[@]}"; do
|
|
355
|
+
[[ "$i" -gt 0 ]] && printf ','
|
|
356
|
+
# JSON-escape the warning text minimally (no embedded quotes expected).
|
|
357
|
+
printf '\n "%s"' "${baseline_warnings[$i]}"
|
|
358
|
+
done
|
|
359
|
+
printf '\n ]'
|
|
360
|
+
fi
|
|
361
|
+
printf '\n}\n'
|
|
115
362
|
exit 0
|
|
116
363
|
fi
|
|
117
364
|
|
|
118
365
|
ui_dot_banner "Diagnostics"
|
|
119
366
|
ui_header "Shell Performance"
|
|
120
367
|
|
|
121
|
-
ui_section "
|
|
368
|
+
ui_section "Per-shell startup ($RUNS runs each, after one warm-up)"
|
|
122
369
|
|
|
123
|
-
|
|
124
|
-
for
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if [[ "$t" -gt "$max" ]]; then max="$t"; fi
|
|
134
|
-
if [[ "$UI_ENABLED" = "1" ]]; then
|
|
135
|
-
ui_kv "Run" "${t}ms"
|
|
370
|
+
# Aligned table: name (6) | mean (8) | min/max (16) | target (12) | status
|
|
371
|
+
for i in "${!shell_names[@]}"; do
|
|
372
|
+
name="${shell_names[$i]}"
|
|
373
|
+
m="${shell_means[$i]}"
|
|
374
|
+
mn="${shell_mins[$i]}"
|
|
375
|
+
mx="${shell_maxs[$i]}"
|
|
376
|
+
t="${shell_targets[$i]}"
|
|
377
|
+
if [[ "${shell_passes[$i]}" == "1" ]]; then
|
|
378
|
+
status_marker="✓"
|
|
379
|
+
detail=""
|
|
136
380
|
else
|
|
137
|
-
|
|
381
|
+
status_marker="✗"
|
|
382
|
+
detail=" — over by $((m - t))ms"
|
|
138
383
|
fi
|
|
384
|
+
printf ' %s %-6s mean %4dms (min %3dms, max %3dms) target %4dms%s\n' \
|
|
385
|
+
"$status_marker" "$name" "$m" "$mn" "$mx" "$t" "$detail"
|
|
139
386
|
done
|
|
140
|
-
mean=$((sum / RUNS))
|
|
141
|
-
score=$(calc_score "$mean")
|
|
142
387
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
388
|
+
# Baseline comparison summary
|
|
389
|
+
if [[ "${#baseline_warnings[@]}" -gt 0 ]]; then
|
|
390
|
+
ui_section "Baseline regressions"
|
|
391
|
+
for w in "${baseline_warnings[@]}"; do
|
|
392
|
+
printf ' ✗ %s\n' "$w"
|
|
393
|
+
done
|
|
394
|
+
printf ' (threshold: >%s%% over the recorded baseline at %s)\n' \
|
|
395
|
+
"$BASELINE_REGRESSION_PCT" "$BASELINE_FILE"
|
|
396
|
+
elif [[ -s "$BASELINE_FILE" ]] && ! $NO_BASELINE_CHECK; then
|
|
397
|
+
ui_section "Baseline"
|
|
398
|
+
printf ' ✓ all measured shells within %s%% of baseline (%s)\n' \
|
|
399
|
+
"$BASELINE_REGRESSION_PCT" "$BASELINE_FILE"
|
|
400
|
+
elif $WRITE_BASELINE; then
|
|
401
|
+
ui_section "Baseline"
|
|
402
|
+
printf ' ✓ wrote baseline to %s\n' "$BASELINE_FILE"
|
|
155
403
|
fi
|
|
156
404
|
|
|
157
|
-
# Per-shell startup comparison
|
|
158
|
-
ui_section "Per-shell startup"
|
|
159
|
-
|
|
160
|
-
for shell_name in zsh bash fish; do
|
|
161
|
-
if command -v "$shell_name" >/dev/null 2>&1; then
|
|
162
|
-
local_start=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
163
|
-
if [[ "$shell_name" == "fish" ]]; then
|
|
164
|
-
fish -c exit >/dev/null 2>&1 || true
|
|
165
|
-
else
|
|
166
|
-
"$shell_name" -i -c exit >/dev/null 2>&1 || true
|
|
167
|
-
fi
|
|
168
|
-
local_end=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
169
|
-
shell_time=$((local_end - local_start))
|
|
170
|
-
if [[ "$UI_ENABLED" = "1" ]]; then
|
|
171
|
-
ui_kv "$shell_name" "${shell_time}ms"
|
|
172
|
-
else
|
|
173
|
-
echo " $shell_name: ${shell_time}ms"
|
|
174
|
-
fi
|
|
175
|
-
fi
|
|
176
|
-
done
|
|
177
|
-
|
|
178
405
|
# Per-component breakdown (Zsh only, uses DOTFILES_DEBUG timing)
|
|
179
406
|
ui_section "Component breakdown (estimated)"
|
|
180
407
|
for component in "bare zsh" "paths+env" "aliases" "functions" "tools"; do
|