@sebastienrousseau/dotfiles 0.2.500 → 0.2.502
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 +154 -0
- package/README.md +113 -45
- 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 +67 -45
- 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 +72 -3
- 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 +16 -8
- package/docs/manual/concept-index.md +4 -0
- package/docs/manual/index.md +66 -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 +182 -0
- package/docs/operations/DRIFT.md +107 -0
- package/docs/operations/HARD_AUDIT_2026.md +631 -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/REGISTRY.md +89 -0
- package/docs/operations/RELIABILITY.md +6 -0
- package/docs/operations/ROADMAP.md +36 -18
- package/docs/operations/ROADMAP_2026.md +665 -0
- package/docs/operations/TESTING.md +4 -0
- package/docs/operations/TRACEABILITY.md +7 -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/POWERSHELL_PARITY.md +80 -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/registry.json +6 -0
- 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/CI_PINNING.md +113 -0
- package/docs/security/COMMIT_SIGNING.md +138 -0
- package/docs/security/COMPLIANCE.md +5 -0
- package/docs/security/DEPS_DEV_EXCEPTIONS.md +86 -0
- package/docs/security/DISCLOSURE.md +130 -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 +85 -1
- 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 +140 -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/security/security-pubkey.asc +15 -0
- 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/direnv.fish +4 -0
- package/dot_config/fish/conf.d/init.fish.tmpl +21 -0
- package/dot_config/fish/conf.d/mise-activate.fish +5 -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 +9 -1
- 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 +207 -17
- package/dot_config/zsh/rc.d/00-alias-shims.zsh +28 -6
- package/dot_config/zsh/rc.d/30-options.zsh.tmpl +27 -7
- package/dot_local/bin/executable_bm +2 -0
- package/dot_local/bin/executable_dot +56 -9
- package/dot_local/bin/executable_dot-bootstrap +0 -1
- package/dot_local/bin/executable_dot-load-benchmark +1 -1
- package/dot_local/bin/executable_dot-theme-sync +8 -8
- 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/dot_local/share/man/man1/dot.1 +1 -1
- package/dot_local/share/zsh/completions/_dot +4 -0
- package/install.sh +53 -38
- 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/dot-cli-startup-bench.sh +126 -0
- package/scripts/ci/install-chezmoi-verified.sh +4 -1
- package/scripts/ci/lint-reusable-pins.sh +78 -0
- package/scripts/ci/run-coverage.sh +451 -0
- package/scripts/ci/validate-chezmoidata.sh +25 -0
- package/scripts/ci/windows-smoke-test.ps1 +136 -0
- package/scripts/diagnostics/doctor.sh +203 -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 +33 -27
- package/scripts/dot/commands/agents.sh +325 -0
- package/scripts/dot/commands/ai.sh +75 -10
- package/scripts/dot/commands/aliases.sh +10 -8
- package/scripts/dot/commands/core.sh +10 -4
- package/scripts/dot/commands/fleet.sh +278 -3
- package/scripts/dot/commands/init.sh +184 -0
- package/scripts/dot/commands/meta.sh +7 -4
- package/scripts/dot/commands/registry.sh +263 -0
- package/scripts/dot/commands/tools.sh +49 -1
- package/scripts/dot/lib/bento.sh +2 -1
- package/scripts/dot/lib/log.sh +6 -0
- package/scripts/dot/lib/platform.sh +22 -8
- package/scripts/dot/lib/ui.sh +145 -2
- package/scripts/dot/lib/utils.sh +6 -1
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/git-hooks/pre-push +66 -3
- package/scripts/lib/secrets_provider.sh +32 -6
- package/scripts/ops/heal-chezmoi.sh +41 -6
- package/scripts/ops/rollback.sh +14 -0
- package/scripts/qa/powershell-contract.ps1 +95 -0
- package/scripts/security/check-disclosure-key-expiry.sh +110 -0
- package/scripts/security/lock-configs.sh +11 -2
- package/scripts/theme/merge-wallpaper.sh +4 -0
- package/scripts/theme/switch.sh +20 -10
- package/scripts/version-sync.sh +3 -0
- package/dot_config/atuin/config.toml +0 -40
- package/dot_local/bin/__pycache__/executable_dot-load-benchmark-ptycpython-312.pyc +0 -0
|
@@ -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
|
|
@@ -9,6 +9,9 @@ set -euo pipefail
|
|
|
9
9
|
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_ROOT="${REPO_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
12
|
+
# Export so child processes (the mcp-doctor subprocess below) inherit it
|
|
13
|
+
# without the env-prefix dance that confuses shellcheck (SC2097/SC2098).
|
|
14
|
+
export REPO_ROOT
|
|
12
15
|
|
|
13
16
|
# shellcheck source=../dot/lib/ui.sh
|
|
14
17
|
source "$SCRIPT_DIR/../dot/lib/ui.sh"
|
|
@@ -91,7 +94,9 @@ if [[ -z "$current_profile" ]] && [[ -f "$agent_profile_file" ]]; then
|
|
|
91
94
|
current_profile="$(jq -r '.defaultProfile // "ask"' "$agent_profile_file")"
|
|
92
95
|
fi
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
MCP_CONFIG="$REPO_ROOT/dot_config/claude/mcp_servers.json"
|
|
98
|
+
export MCP_CONFIG
|
|
99
|
+
mcp_summary="$(bash "$REPO_ROOT/scripts/diagnostics/mcp-doctor.sh" --strict --json)"
|
|
95
100
|
|
|
96
101
|
attestation_json="$(
|
|
97
102
|
jq -n \
|
|
@@ -76,11 +76,20 @@ _agent_checkpoint_file() {
|
|
|
76
76
|
_agent_apply_profile_env() {
|
|
77
77
|
local name="$1"
|
|
78
78
|
export DOT_AGENT_PROFILE="$name"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
# Split declare-and-assign to avoid masking the field-getter's exit
|
|
80
|
+
# code (SC2155). If the profile JSON is malformed, the assignment
|
|
81
|
+
# now fails the function instead of silently exporting an empty value.
|
|
82
|
+
local approval filesystem network mcp_profile max_steps
|
|
83
|
+
approval="$(_agent_profile_field "$name" "approval")"
|
|
84
|
+
filesystem="$(_agent_profile_field "$name" "filesystem")"
|
|
85
|
+
network="$(_agent_profile_field "$name" "network")"
|
|
86
|
+
mcp_profile="$(_agent_profile_field "$name" "mcpProfile")"
|
|
87
|
+
max_steps="$(_agent_profile_field "$name" "maxSteps")"
|
|
88
|
+
export DOT_AGENT_APPROVAL="$approval"
|
|
89
|
+
export DOT_AGENT_FILESYSTEM="$filesystem"
|
|
90
|
+
export DOT_AGENT_NETWORK="$network"
|
|
91
|
+
export DOT_AGENT_MCP_PROFILE="$mcp_profile"
|
|
92
|
+
export DOT_AGENT_MAX_STEPS="$max_steps"
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
_agent_rbac_enforcement() {
|
|
@@ -209,14 +218,15 @@ EOF
|
|
|
209
218
|
checkpoint_id="$(basename "$checkpoint_file" .json)"
|
|
210
219
|
ui_info "Agent mode" "$name"
|
|
211
220
|
dot_agent_session_log "run_start" "$name" "running" "argv=$*" "checkpoint_id=$checkpoint_id"
|
|
212
|
-
set +e
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if
|
|
217
|
-
|
|
218
|
-
else
|
|
221
|
+
# `if !` instead of `set +e / "$@" / set -e` — bash's errexit
|
|
222
|
+
# is automatically suspended inside a conditional, so we capture
|
|
223
|
+
# the exit code cleanly without toggling shell options.
|
|
224
|
+
local exit_code=0
|
|
225
|
+
if ! "$@"; then
|
|
226
|
+
exit_code=$?
|
|
219
227
|
dot_agent_session_log "run_finish" "$name" "failed" "exit_code=$exit_code" "checkpoint_id=$checkpoint_id"
|
|
228
|
+
else
|
|
229
|
+
dot_agent_session_log "run_finish" "$name" "ok" "exit_code=$exit_code" "checkpoint_id=$checkpoint_id"
|
|
220
230
|
fi
|
|
221
231
|
return "$exit_code"
|
|
222
232
|
;;
|
|
@@ -326,14 +336,12 @@ EOF
|
|
|
326
336
|
[[ "${#replay_argv[@]}" -gt 0 ]] || die "Checkpoint has no replayable command: $checkpoint_id"
|
|
327
337
|
_agent_apply_profile_env "$replay_profile"
|
|
328
338
|
dot_agent_session_log "checkpoint_replay" "$replay_profile" "running" "checkpoint_id=$checkpoint_id"
|
|
329
|
-
|
|
330
|
-
"${replay_argv[@]}"
|
|
331
|
-
|
|
332
|
-
set -e
|
|
333
|
-
if [[ "$exit_code" -eq 0 ]]; then
|
|
334
|
-
dot_agent_session_log "checkpoint_replay_finish" "$replay_profile" "ok" "checkpoint_id=$checkpoint_id" "exit_code=$exit_code"
|
|
335
|
-
else
|
|
339
|
+
local exit_code=0
|
|
340
|
+
if ! "${replay_argv[@]}"; then
|
|
341
|
+
exit_code=$?
|
|
336
342
|
dot_agent_session_log "checkpoint_replay_finish" "$replay_profile" "failed" "checkpoint_id=$checkpoint_id" "exit_code=$exit_code"
|
|
343
|
+
else
|
|
344
|
+
dot_agent_session_log "checkpoint_replay_finish" "$replay_profile" "ok" "checkpoint_id=$checkpoint_id" "exit_code=$exit_code"
|
|
337
345
|
fi
|
|
338
346
|
return "$exit_code"
|
|
339
347
|
;;
|
|
@@ -373,16 +381,14 @@ EOF
|
|
|
373
381
|
export DOT_AGENT_PARENT_PROFILE="$current_profile"
|
|
374
382
|
dot_agent_session_log "delegate_start" "$delegate_profile" "running" "delegate=$delegate_name" "parent=$current_profile" "timeout=$delegate_timeout"
|
|
375
383
|
ui_info "Delegating" "$delegate_name (profile: $delegate_profile, timeout: ${delegate_timeout}s)"
|
|
376
|
-
|
|
377
|
-
timeout "$delegate_timeout" "$@"
|
|
378
|
-
|
|
379
|
-
set -e
|
|
380
|
-
if [[ "$exit_code" -eq 0 ]]; then
|
|
381
|
-
dot_agent_session_log "delegate_finish" "$delegate_profile" "ok" "delegate=$delegate_name" "exit_code=$exit_code"
|
|
382
|
-
ui_ok "Delegate" "$delegate_name completed"
|
|
383
|
-
else
|
|
384
|
+
local exit_code=0
|
|
385
|
+
if ! timeout "$delegate_timeout" "$@"; then
|
|
386
|
+
exit_code=$?
|
|
384
387
|
dot_agent_session_log "delegate_finish" "$delegate_profile" "failed" "delegate=$delegate_name" "exit_code=$exit_code"
|
|
385
388
|
ui_err "Delegate" "$delegate_name failed (exit $exit_code)"
|
|
389
|
+
else
|
|
390
|
+
dot_agent_session_log "delegate_finish" "$delegate_profile" "ok" "delegate=$delegate_name" "exit_code=$exit_code"
|
|
391
|
+
ui_ok "Delegate" "$delegate_name completed"
|
|
386
392
|
fi
|
|
387
393
|
return "$exit_code"
|
|
388
394
|
;;
|