@sebastienrousseau/dotfiles 0.2.520 → 0.2.521
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 +18 -0
- package/LICENSE-APACHE +190 -0
- package/LICENSE-MIT +21 -0
- package/README.md +43 -37
- package/install.sh +76 -10
- package/package.json +7 -7
- package/tools/README.md +49 -0
- package/tools/ci/install-chezmoi-verified.sh +68 -0
- package/docs/.vitepress/reports/localization-readability-audit.md +0 -73
- package/docs/AI.md +0 -179
- package/docs/ARCHITECTURE.md +0 -117
- package/docs/CNAME +0 -1
- package/docs/CONFIG_STRATEGY.md +0 -124
- package/docs/COPYRIGHT +0 -7
- package/docs/ECOSYSTEM.md +0 -220
- package/docs/GOLD-STANDARD-AUDIT.md +0 -352
- package/docs/GOVERNANCE.md +0 -98
- package/docs/MAINTAINERS.md +0 -41
- package/docs/MINIMUM-TOOLCHAIN.md +0 -100
- package/docs/NAMING_CONVENTIONS.md +0 -102
- package/docs/OPENCODE.md +0 -127
- package/docs/README.md +0 -84
- package/docs/STRUCTURE.md +0 -102
- package/docs/adr/ADR-001-ci-cd-pipeline.md +0 -118
- package/docs/adr/ADR-002-shell-performance.md +0 -130
- package/docs/adr/ADR-003-security-first.md +0 -158
- package/docs/adr/ADR-004-cli-architecture.md +0 -171
- package/docs/adr/ADR-005-chezmoi-choice.md +0 -99
- package/docs/adr/ADR-006-shell-selection.md +0 -124
- package/docs/adr/ADR-007-multi-shell-parity.md +0 -62
- package/docs/adr/ADR-008-alias-system-architecture.md +0 -95
- package/docs/adr/ADR-009-wallpaper-driven-theming.md +0 -131
- package/docs/adr/ADR-010-starship-transient-prompt.md +0 -144
- package/docs/adr/ADR-011-nushell-tier3-keep.md +0 -144
- package/docs/adr/ADR-012-ai-fleet-local-proxy.md +0 -79
- package/docs/adr/README.md +0 -40
- package/docs/architecture/AI_COST_OPTIMIZATION.md +0 -144
- package/docs/architecture/ARCHITECTURE.md +0 -20
- package/docs/architecture/INTEROP.md +0 -44
- package/docs/architecture/REPO_LAYOUT.md +0 -241
- package/docs/architecture/WALKTHROUGH.md +0 -86
- package/docs/architecture/fleet-deployment.md +0 -77
- package/docs/archive/EUXIS_2026_REVIEW.md +0 -127
- package/docs/archive/LEGACY_ROADMAP.md +0 -6
- package/docs/archive/MILESTONE_v0.2.493.md +0 -47
- package/docs/archive/PLAN.md +0 -199
- package/docs/archive/REPO_AUDIT.md +0 -31
- package/docs/articles/.pages +0 -6
- package/docs/articles/2026-07-05-custom-mkdocs-material-dark-theme.md +0 -216
- package/docs/articles/2026-07-05-fish-startup-abbr.md +0 -153
- package/docs/articles/2026-07-05-master-to-main-rename-runbook.md +0 -128
- package/docs/articles/index.md +0 -36
- package/docs/guides/INSTALL.md +0 -144
- package/docs/guides/MACOS_ICLOUD_SYMLINKS.md +0 -121
- package/docs/guides/NEOVIM_IDE_GUIDE.md +0 -61
- package/docs/guides/THEMING.md +0 -230
- package/docs/guides/TROUBLESHOOTING.md +0 -176
- package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +0 -792
- package/docs/index.md +0 -132
- package/docs/interop/A2A.md +0 -39
- package/docs/interop/POWERSHELL.md +0 -102
- package/docs/manual/00-introduction.md +0 -89
- package/docs/manual/01-concepts/01-architecture.md +0 -138
- package/docs/manual/01-concepts/02-trust-model.md +0 -183
- package/docs/manual/01-concepts/03-theme-engine.md +0 -186
- package/docs/manual/01-concepts/04-fleet.md +0 -148
- package/docs/manual/01-concepts/05-self-healing.md +0 -204
- package/docs/manual/02-tutorials/01-first-install.md +0 -197
- package/docs/manual/02-tutorials/02-add-wallpaper.md +0 -216
- package/docs/manual/02-tutorials/03-create-profile.md +0 -244
- package/docs/manual/02-tutorials/04-encrypt-secret.md +0 -281
- package/docs/manual/02-tutorials/05-deploy-fleet.md +0 -283
- package/docs/manual/03-reference/01-dot-cli.md +0 -475
- package/docs/manual/03-reference/02-config-files.md +0 -265
- package/docs/manual/03-reference/03-environment.md +0 -124
- package/docs/manual/03-reference/04-templates.md +0 -190
- package/docs/manual/03-reference/05-feature-flags.md +0 -187
- package/docs/manual/04-cookbook/01-recipes.md +0 -285
- package/docs/manual/04-cookbook/02-troubleshooting.md +0 -351
- package/docs/manual/04-cookbook/03-faq.md +0 -175
- package/docs/manual/05-appendices/A-platform-matrix.md +0 -101
- package/docs/manual/05-appendices/B-security-checklist.md +0 -85
- package/docs/manual/05-appendices/C-glossary.md +0 -40
- package/docs/manual/05-appendices/D-bibliography.md +0 -58
- package/docs/manual/05-appendices/E-license.md +0 -38
- package/docs/manual/_toc.yml +0 -58
- package/docs/manual/command-index.md +0 -175
- package/docs/manual/concept-index.md +0 -170
- package/docs/manual/index.md +0 -66
- package/docs/migration/README.md +0 -81
- package/docs/migration/from-bare-git-repo.md +0 -156
- package/docs/migration/from-gnu-stow.md +0 -165
- package/docs/migration/from-plain-chezmoi.md +0 -148
- package/docs/migration/from-yadm.md +0 -187
- package/docs/operations/ARCHITECTURE_ROADMAP.md +0 -7
- package/docs/operations/ATTESTATION.md +0 -44
- package/docs/operations/CI_CADENCE.md +0 -107
- package/docs/operations/CI_COMPOSITES.md +0 -156
- package/docs/operations/COMPLETIONS.md +0 -123
- package/docs/operations/COVERAGE.md +0 -204
- package/docs/operations/DRIFT.md +0 -107
- package/docs/operations/HARD_AUDIT_2026.md +0 -631
- package/docs/operations/MAINTENANCE.md +0 -63
- package/docs/operations/MANIFEST.md +0 -127
- package/docs/operations/MIGRATION.md +0 -109
- package/docs/operations/OPERATIONS.md +0 -188
- package/docs/operations/PERFORMANCE.md +0 -133
- package/docs/operations/PERFORMANCE_BUDGETS.md +0 -196
- package/docs/operations/REGISTRY.md +0 -90
- package/docs/operations/RELEASE_PIPELINE.md +0 -128
- package/docs/operations/RELIABILITY.md +0 -122
- package/docs/operations/RFC_v0_2_503_reorganization.md +0 -280
- package/docs/operations/ROADMAP.md +0 -10
- package/docs/operations/ROADMAP_2026.md +0 -7
- package/docs/operations/ROADMAP_V0_2_503.md +0 -10
- package/docs/operations/TESTING.md +0 -216
- package/docs/operations/TRACEABILITY.md +0 -44
- package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +0 -65
- package/docs/operations/VERSION_SYNC.md +0 -393
- package/docs/packaging.md +0 -222
- package/docs/reference/ALIASES.md +0 -131
- package/docs/reference/ALIASES_CHEATSHEET.md +0 -32
- package/docs/reference/ALIASES_DEPRECATIONS.md +0 -13
- package/docs/reference/FEATURE-MATRIX.md +0 -646
- package/docs/reference/FEATURES.md +0 -66
- package/docs/reference/FONTS.md +0 -112
- package/docs/reference/POWERSHELL_PARITY.md +0 -82
- package/docs/reference/PROFILES.md +0 -69
- package/docs/reference/SCREENSHOTS.md +0 -121
- package/docs/reference/SCRIPTS.md +0 -71
- package/docs/reference/SUPPORT_MATRIX.md +0 -80
- package/docs/reference/THEMES.md +0 -117
- package/docs/reference/TOOLS.md +0 -110
- package/docs/reference/UTILS.md +0 -243
- package/docs/registry.json +0 -6
- package/docs/schema/dot-env-v1.json +0 -110
- package/docs/schema/dot-registry-v1.json +0 -33
- package/docs/security/AI_ACT_COMPLIANCE.md +0 -94
- package/docs/security/AUDIT_BYPASS.md +0 -103
- package/docs/security/AUTOMATION_SECRETS.md +0 -26
- package/docs/security/CI_EGRESS_ALLOWLIST.md +0 -127
- package/docs/security/CI_PINNING.md +0 -129
- package/docs/security/COMMIT_SIGNING.md +0 -138
- package/docs/security/COMPLIANCE.md +0 -458
- package/docs/security/DEPS_DEV_EXCEPTIONS.md +0 -86
- package/docs/security/DISCLOSURE.md +0 -130
- package/docs/security/ENCRYPTION.md +0 -57
- package/docs/security/FMEA.md +0 -159
- package/docs/security/FUZZING.md +0 -209
- package/docs/security/HISTORY_FILTERING.md +0 -132
- package/docs/security/INCIDENT_RESPONSE.md +0 -579
- package/docs/security/INSTALL_VERIFICATION.md +0 -122
- package/docs/security/KEYS.md +0 -49
- package/docs/security/KEY_ROTATION.md +0 -303
- package/docs/security/MCP_POLICY.md +0 -78
- package/docs/security/POLICY_RELEASES.md +0 -37
- package/docs/security/README.md +0 -28
- package/docs/security/SCORECARD.md +0 -195
- package/docs/security/SECRETS.md +0 -158
- package/docs/security/SECURITY.md +0 -45
- package/docs/security/SECURITY_CHECKLIST.md +0 -55
- package/docs/security/SHELL_EXEMPTIONS.md +0 -145
- package/docs/security/SOUP_REGISTER.md +0 -36
- package/docs/security/THREAT_MODEL.md +0 -130
- package/docs/security/VERIFICATION_VALIDATION.md +0 -228
- package/docs/security/VERIFY_RELEASE.md +0 -201
- package/docs/security/security-pubkey.asc +0 -15
- package/docs/stylesheets/extra.css +0 -444
- package/docs/themes/README.md +0 -10
- package/docs/themes/VISUAL_INTEGRITY_REPORT.md +0 -30
- package/docs/themes/hero-shot.svg +0 -78
- package/scripts/README.md +0 -123
- package/scripts/ci/check-copyright-headers.sh +0 -8
- package/scripts/ci/check-shell-preamble.sh +0 -8
- package/scripts/ci/guard-gitleaks-checkout.sh +0 -8
- package/scripts/demo/record.sh +0 -43
- package/scripts/diagnostics/a2a-conformance.sh +0 -163
- package/scripts/diagnostics/alias-governance.sh +0 -165
- package/scripts/diagnostics/aliases-cheatsheet.sh +0 -74
- package/scripts/diagnostics/aliases-manifest.sh +0 -77
- package/scripts/diagnostics/attest-verify.sh +0 -147
- package/scripts/diagnostics/benchmark.sh +0 -408
- package/scripts/diagnostics/conflicts.sh +0 -73
- package/scripts/diagnostics/doctor-unified.sh +0 -43
- package/scripts/diagnostics/doctor.sh +0 -797
- package/scripts/diagnostics/drift-dashboard.sh +0 -203
- package/scripts/diagnostics/health.sh +0 -656
- package/scripts/diagnostics/history-analysis.sh +0 -86
- package/scripts/diagnostics/mcp-doctor.sh +0 -582
- package/scripts/diagnostics/perf.sh +0 -453
- package/scripts/diagnostics/scorecard.sh +0 -120
- package/scripts/diagnostics/secret-governance.sh +0 -65
- package/scripts/diagnostics/security-score.sh +0 -467
- package/scripts/diagnostics/smoke-test.sh +0 -88
- package/scripts/diagnostics/snapshot.sh +0 -90
- package/scripts/diagnostics/verify.sh +0 -108
- package/scripts/diagnostics/verify_state.sh +0 -73
- package/scripts/diagnostics/version-locks.sh +0 -94
- package/scripts/diagnostics/workstation-attestation.sh +0 -212
- package/scripts/dot/commands/agent.sh +0 -535
- package/scripts/dot/commands/agents.sh +0 -352
- package/scripts/dot/commands/ai.sh +0 -600
- package/scripts/dot/commands/aliases.sh +0 -277
- package/scripts/dot/commands/appearance.sh +0 -110
- package/scripts/dot/commands/completion.sh +0 -171
- package/scripts/dot/commands/core.sh +0 -217
- package/scripts/dot/commands/diagnostics.sh +0 -265
- package/scripts/dot/commands/env-emit.sh +0 -203
- package/scripts/dot/commands/fleet.sh +0 -711
- package/scripts/dot/commands/init.sh +0 -185
- package/scripts/dot/commands/lint.sh +0 -208
- package/scripts/dot/commands/manual.sh +0 -169
- package/scripts/dot/commands/meta.sh +0 -438
- package/scripts/dot/commands/patterns.sh +0 -55
- package/scripts/dot/commands/registry.sh +0 -455
- package/scripts/dot/commands/restore.sh +0 -232
- package/scripts/dot/commands/secrets.sh +0 -296
- package/scripts/dot/commands/security.sh +0 -102
- package/scripts/dot/commands/tools.sh +0 -570
- package/scripts/dot/data/alias-deprecations.tsv +0 -2
- package/scripts/dot/powershell/Dot.psm1 +0 -319
- package/scripts/fonts/install-nerd-fonts.sh +0 -75
- package/scripts/fonts/patch-fonts.sh +0 -36
- package/scripts/git-hooks/install.sh +0 -12
- package/scripts/git-hooks/pre-commit +0 -12
- package/scripts/git-hooks/pre-commit-audit.sh +0 -146
- package/scripts/git-hooks/pre-push +0 -105
- package/scripts/git-hooks/prepare-commit-msg +0 -29
- package/scripts/lib/secrets_provider.sh +0 -200
- package/scripts/nvim/headless-upgrade.lua +0 -81
- package/scripts/ops/ai-setup.sh +0 -71
- package/scripts/ops/bundle.sh +0 -104
- package/scripts/ops/chaos.sh +0 -50
- package/scripts/ops/chezmoi-apply.sh +0 -333
- package/scripts/ops/chezmoi-diff.sh +0 -16
- package/scripts/ops/chezmoi-remove.sh +0 -46
- package/scripts/ops/chezmoi-update.sh +0 -67
- package/scripts/ops/heal-chezmoi.sh +0 -87
- package/scripts/ops/heal-system.sh +0 -129
- package/scripts/ops/heal-tools.sh +0 -297
- package/scripts/ops/heal.sh +0 -223
- package/scripts/ops/post-apply-repair.sh +0 -107
- package/scripts/ops/prewarm.sh +0 -128
- package/scripts/ops/release.sh +0 -262
- package/scripts/ops/rollback.sh +0 -613
- package/scripts/ops/setup.sh +0 -138
- package/scripts/ops/teleport.sh +0 -34
- package/scripts/qa/check-feature-matrix.sh +0 -296
- package/scripts/qa/check-version-consistency.sh +0 -12
- package/scripts/qa/coverage-baseline.sh +0 -61
- package/scripts/qa/docs-coverage.sh +0 -118
- package/scripts/qa/examples-coverage.sh +0 -94
- package/scripts/qa/powershell-contract.ps1 +0 -95
- package/scripts/qa/reliability-audit.sh +0 -139
- package/scripts/qa/scorecard-snapshot.sh +0 -128
- package/scripts/qa/traceability-coverage.sh +0 -124
- package/scripts/qa/validate-examples.sh +0 -90
- package/scripts/qa/wsl-contract.sh +0 -12
- package/scripts/secrets/age-init.sh +0 -82
- package/scripts/secrets/create-secrets-file.sh +0 -46
- package/scripts/secrets/encrypt-ssh-key.sh +0 -44
- package/scripts/security/backup.sh +0 -58
- package/scripts/security/check-disclosure-key-expiry.sh +0 -111
- package/scripts/security/dns-doh.sh +0 -52
- package/scripts/security/encryption-check.sh +0 -55
- package/scripts/security/enforce-policies.sh +0 -552
- package/scripts/security/firewall.sh +0 -91
- package/scripts/security/lock-configs.sh +0 -67
- package/scripts/security/lock-screen.sh +0 -56
- package/scripts/security/manage-secrets.sh +0 -429
- package/scripts/security/ssh-cert.sh +0 -204
- package/scripts/security/telemetry-kill.sh +0 -51
- package/scripts/security/usb-safety.sh +0 -52
- package/scripts/theme/apply-gnome-theme.sh +0 -333
- package/scripts/theme/extract-heic-frames.sh +0 -115
- package/scripts/theme/extract-theme.py +0 -1020
- package/scripts/theme/install-boot-logo.sh +0 -63
- package/scripts/theme/install-catppuccin-themes.sh +0 -371
- package/scripts/theme/install-cursors.sh +0 -26
- package/scripts/theme/install-file-icons.sh +0 -27
- package/scripts/theme/install-grub-theme.sh +0 -62
- package/scripts/theme/install-lock-icon.sh +0 -31
- package/scripts/theme/merge-wallpaper.sh +0 -146
- package/scripts/theme/rebuild-themes.sh +0 -603
- package/scripts/theme/switch.sh +0 -476
- package/scripts/theme/wallpaper-rotate.sh +0 -137
- package/scripts/theme/wallpaper-sync.sh +0 -690
- package/scripts/tools/cmatrix.sh +0 -22
- package/scripts/tools/detect-collisions.py +0 -103
- package/scripts/tools/emoji-picker.sh +0 -49
- package/scripts/tools/figlet-banner.sh +0 -19
- package/scripts/tools/log-rotate.sh +0 -31
- package/scripts/tools/lolcat-wrap.sh +0 -20
- package/scripts/tools/pipes.sh +0 -49
- package/scripts/tuning/linux.sh +0 -186
- package/scripts/tuning/macos.sh +0 -56
- package/scripts/uninstall.sh +0 -86
- package/scripts/verify-release-versions +0 -156
- package/scripts/version-sync.sh +0 -714
- package/templates/chezmoi-data/geekom-a9.toml.example +0 -21
- package/templates/chezmoi-data/mac-m1.toml.example +0 -16
- package/templates/chezmoi-data/mac-t2-linux.toml.example +0 -21
- package/templates/chezmoi-data/surface-pro-7p.toml.example +0 -21
- package/templates/projects/go/.github/workflows/ci.yml +0 -31
- package/templates/projects/go/README.md +0 -7
- package/templates/projects/go/cmd/__PROJECT_NAME__/main.go +0 -8
- package/templates/projects/go/go.mod +0 -3
- package/templates/projects/go/go.sum +0 -0
- package/templates/projects/molecule/README.md +0 -7
- package/templates/projects/molecule/converge.yml +0 -7
- package/templates/projects/molecule/molecule.yml +0 -16
- package/templates/projects/node/.github/workflows/ci.yml +0 -30
- package/templates/projects/node/README.md +0 -7
- package/templates/projects/node/package-lock.json +0 -12
- package/templates/projects/node/package.json +0 -10
- package/templates/projects/node/src/index.js +0 -3
- package/templates/projects/packer/README.md +0 -15
- package/templates/projects/packer/main.pkr.hcl +0 -15
- package/templates/projects/python/.github/workflows/ci.yml +0 -34
- package/templates/projects/python/README.md +0 -7
- package/templates/projects/python/pyproject.toml +0 -25
- package/templates/projects/python/src/__PROJECT_NAME__/__init__.py +0 -2
- package/templates/projects/python/tests/test_basic.py +0 -3
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
|
-
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
|
-
# Shell Performance Profiling
|
|
5
|
-
# Usage: dot perf [--json|-j] [--profile|-p] [--runs|-r N] [--target|-t MS]
|
|
6
|
-
|
|
7
|
-
set -euo pipefail
|
|
8
|
-
|
|
9
|
-
_cleanup_files=()
|
|
10
|
-
trap 'set +u; rm -f "${_cleanup_files[@]}" 2>/dev/null; set -u' EXIT
|
|
11
|
-
|
|
12
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
-
# shellcheck source=../../lib/dot/ui.sh
|
|
14
|
-
# shellcheck disable=SC1091
|
|
15
|
-
source "$SCRIPT_DIR/../../lib/dot/ui.sh"
|
|
16
|
-
# shellcheck source=../../lib/dot/log.sh
|
|
17
|
-
# shellcheck disable=SC1091
|
|
18
|
-
source "$SCRIPT_DIR/../../lib/dot/log.sh"
|
|
19
|
-
export DOT_COMMAND="perf"
|
|
20
|
-
|
|
21
|
-
ui_init
|
|
22
|
-
|
|
23
|
-
if ! command -v python3 >/dev/null 2>&1; then
|
|
24
|
-
ui_err "python3" "required for perf timing"
|
|
25
|
-
exit 1
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
JSON_OUTPUT=false
|
|
29
|
-
PROFILE=false
|
|
30
|
-
RUNS=3
|
|
31
|
-
TARGET_MS="${DOTFILES_PERF_TARGET_MS:-250}"
|
|
32
|
-
MAX_MS="${DOTFILES_PERF_MAX_MS:-1000}"
|
|
33
|
-
SHELL_FILTER=""
|
|
34
|
-
BY_TOOL=false
|
|
35
|
-
RESET_TIMINGS=false
|
|
36
|
-
|
|
37
|
-
while [[ $# -gt 0 ]]; do
|
|
38
|
-
case "$1" in
|
|
39
|
-
--json | -j)
|
|
40
|
-
JSON_OUTPUT=true
|
|
41
|
-
shift
|
|
42
|
-
;;
|
|
43
|
-
--profile | -p)
|
|
44
|
-
PROFILE=true
|
|
45
|
-
shift
|
|
46
|
-
;;
|
|
47
|
-
--runs | -r)
|
|
48
|
-
RUNS="${2:-3}"
|
|
49
|
-
shift 2
|
|
50
|
-
;;
|
|
51
|
-
--target | -t)
|
|
52
|
-
TARGET_MS="${2:-$TARGET_MS}"
|
|
53
|
-
shift 2
|
|
54
|
-
;;
|
|
55
|
-
--shell | -s)
|
|
56
|
-
SHELL_FILTER="${2:-}"
|
|
57
|
-
shift 2
|
|
58
|
-
;;
|
|
59
|
-
--by-tool)
|
|
60
|
-
BY_TOOL=true
|
|
61
|
-
shift
|
|
62
|
-
;;
|
|
63
|
-
--reset)
|
|
64
|
-
RESET_TIMINGS=true
|
|
65
|
-
shift
|
|
66
|
-
;;
|
|
67
|
-
--baseline)
|
|
68
|
-
# Write the current per-shell means to the baseline file so
|
|
69
|
-
# subsequent runs can warn on regression. Closes part of #863.
|
|
70
|
-
WRITE_BASELINE=true
|
|
71
|
-
shift
|
|
72
|
-
;;
|
|
73
|
-
--no-baseline-check)
|
|
74
|
-
# Skip the baseline-vs-current comparison entirely. Useful for
|
|
75
|
-
# the very first run on a machine, or for short-lived CI.
|
|
76
|
-
NO_BASELINE_CHECK=true
|
|
77
|
-
shift
|
|
78
|
-
;;
|
|
79
|
-
*)
|
|
80
|
-
shift
|
|
81
|
-
;;
|
|
82
|
-
esac
|
|
83
|
-
done
|
|
84
|
-
|
|
85
|
-
WRITE_BASELINE="${WRITE_BASELINE:-false}"
|
|
86
|
-
NO_BASELINE_CHECK="${NO_BASELINE_CHECK:-false}"
|
|
87
|
-
BASELINE_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/dotfiles/perf-baseline.json"
|
|
88
|
-
BASELINE_REGRESSION_PCT="${DOTFILES_PERF_REGRESSION_PCT:-10}"
|
|
89
|
-
|
|
90
|
-
# --by-tool reader: aggregate $XDG_STATE_HOME/dotfiles/eval-timings.jsonl
|
|
91
|
-
# (populated by _cached_eval when EVALCACHE_TIMING=1) and report which
|
|
92
|
-
# tools dominate startup time. Independent of the runtime measurement
|
|
93
|
-
# loop above, so it returns immediately.
|
|
94
|
-
if $BY_TOOL || $RESET_TIMINGS; then
|
|
95
|
-
log_dir="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles"
|
|
96
|
-
log_file="$log_dir/eval-timings.jsonl"
|
|
97
|
-
if $RESET_TIMINGS; then
|
|
98
|
-
: >"$log_file" 2>/dev/null || rm -f "$log_file" 2>/dev/null || true
|
|
99
|
-
ui_ok "eval timings" "cleared $log_file"
|
|
100
|
-
$BY_TOOL || exit 0
|
|
101
|
-
fi
|
|
102
|
-
if [[ ! -s "$log_file" ]]; then
|
|
103
|
-
ui_warn "eval timings" "no data at $log_file"
|
|
104
|
-
echo " Hint: open a new shell with EVALCACHE_TIMING=1 to start collecting." >&2
|
|
105
|
-
exit 0
|
|
106
|
-
fi
|
|
107
|
-
ui_dot_banner "Diagnostics"
|
|
108
|
-
ui_header "Per-tool timing breakdown"
|
|
109
|
-
ui_section "$log_file"
|
|
110
|
-
python3 - "$log_file" <<'PY'
|
|
111
|
-
import json, sys
|
|
112
|
-
from collections import defaultdict
|
|
113
|
-
|
|
114
|
-
def percentile(sorted_vals, p):
|
|
115
|
-
"""Linear-interpolation percentile, matching numpy.percentile defaults."""
|
|
116
|
-
if not sorted_vals:
|
|
117
|
-
return 0
|
|
118
|
-
if len(sorted_vals) == 1:
|
|
119
|
-
return sorted_vals[0]
|
|
120
|
-
k = (len(sorted_vals) - 1) * (p / 100.0)
|
|
121
|
-
f = int(k)
|
|
122
|
-
c = min(f + 1, len(sorted_vals) - 1)
|
|
123
|
-
if f == c:
|
|
124
|
-
return sorted_vals[f]
|
|
125
|
-
return sorted_vals[f] + (sorted_vals[c] - sorted_vals[f]) * (k - f)
|
|
126
|
-
|
|
127
|
-
samples = defaultdict(list) # label -> [ms, ms, ...]
|
|
128
|
-
shells_by_label = defaultdict(set) # label -> {shell, ...}
|
|
129
|
-
|
|
130
|
-
with open(sys.argv[1]) as f:
|
|
131
|
-
for line in f:
|
|
132
|
-
line = line.strip()
|
|
133
|
-
if not line:
|
|
134
|
-
continue
|
|
135
|
-
try:
|
|
136
|
-
ev = json.loads(line)
|
|
137
|
-
except Exception:
|
|
138
|
-
continue
|
|
139
|
-
label = ev.get("label", "?")
|
|
140
|
-
try:
|
|
141
|
-
ms = int(ev.get("ms", 0) or 0)
|
|
142
|
-
except (TypeError, ValueError):
|
|
143
|
-
continue
|
|
144
|
-
samples[label].append(ms)
|
|
145
|
-
shells_by_label[label].add(ev.get("shell", "?"))
|
|
146
|
-
|
|
147
|
-
rows = []
|
|
148
|
-
for label, vals in samples.items():
|
|
149
|
-
vals_sorted = sorted(vals)
|
|
150
|
-
rows.append({
|
|
151
|
-
"label": label,
|
|
152
|
-
"count": len(vals_sorted),
|
|
153
|
-
"total": sum(vals_sorted),
|
|
154
|
-
"mean": sum(vals_sorted) // max(len(vals_sorted), 1),
|
|
155
|
-
"min": vals_sorted[0],
|
|
156
|
-
"max": vals_sorted[-1],
|
|
157
|
-
"p50": int(percentile(vals_sorted, 50)),
|
|
158
|
-
"p95": int(percentile(vals_sorted, 95)),
|
|
159
|
-
"p99": int(percentile(vals_sorted, 99)),
|
|
160
|
-
"shells": ",".join(sorted(shells_by_label[label])),
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
rows.sort(key=lambda r: r["total"], reverse=True)
|
|
164
|
-
header = (f" {'label':<20} {'calls':>5} {'total':>8} "
|
|
165
|
-
f"{'mean':>7} {'p50':>5} {'p95':>5} {'p99':>5} shells")
|
|
166
|
-
print(header)
|
|
167
|
-
print(" " + "-" * (len(header) - 2))
|
|
168
|
-
for r in rows:
|
|
169
|
-
print(f" {r['label']:<20} {r['count']:>5} {r['total']:>6}ms "
|
|
170
|
-
f"{r['mean']:>5}ms {r['p50']:>3}ms {r['p95']:>3}ms {r['p99']:>3}ms {r['shells']}")
|
|
171
|
-
PY
|
|
172
|
-
exit 0
|
|
173
|
-
fi
|
|
174
|
-
|
|
175
|
-
# Per-shell defaults. nu and pwsh are genuinely slower than POSIX shells;
|
|
176
|
-
# bash should be quickest. Override via DOTFILES_PERF_TARGET_<SHELL>_MS.
|
|
177
|
-
shell_target_for() {
|
|
178
|
-
case "$1" in
|
|
179
|
-
zsh) echo "${DOTFILES_PERF_TARGET_ZSH_MS:-$TARGET_MS}" ;;
|
|
180
|
-
bash) echo "${DOTFILES_PERF_TARGET_BASH_MS:-60}" ;;
|
|
181
|
-
fish) echo "${DOTFILES_PERF_TARGET_FISH_MS:-200}" ;;
|
|
182
|
-
nu) echo "${DOTFILES_PERF_TARGET_NU_MS:-500}" ;;
|
|
183
|
-
pwsh) echo "${DOTFILES_PERF_TARGET_PWSH_MS:-600}" ;;
|
|
184
|
-
*) echo "$TARGET_MS" ;;
|
|
185
|
-
esac
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
# Invoke a near-empty session for the named shell, picking flags that
|
|
189
|
-
# load the user's interactive profile (matches what a fresh terminal does).
|
|
190
|
-
invoke_shell() {
|
|
191
|
-
case "$1" in
|
|
192
|
-
zsh) zsh -i -c exit </dev/null ;;
|
|
193
|
-
bash) bash -i -c exit </dev/null ;;
|
|
194
|
-
fish) fish -i -c exit </dev/null ;;
|
|
195
|
-
nu) nu -c exit </dev/null ;;
|
|
196
|
-
pwsh) pwsh -Command exit </dev/null ;;
|
|
197
|
-
*) return 1 ;;
|
|
198
|
-
esac
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
time_one_run() {
|
|
202
|
-
local shell_name="$1" start end
|
|
203
|
-
start=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
204
|
-
invoke_shell "$shell_name" >/dev/null 2>&1 || true
|
|
205
|
-
end=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
206
|
-
echo $((end - start))
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
# Existing single-shell helper retained for backward compatibility with
|
|
210
|
-
# callers/tests that reference time_shell_startup; routes to time_one_run.
|
|
211
|
-
time_shell_startup() { time_one_run zsh; }
|
|
212
|
-
|
|
213
|
-
# Measure a shell across $RUNS iterations, return "mean min max".
|
|
214
|
-
measure_shell() {
|
|
215
|
-
local shell_name="$1" sum=0 min=999999 max=0 t
|
|
216
|
-
local times=()
|
|
217
|
-
# One warm-up run discarded — caches a $_SHELL_CACHE on first invocation.
|
|
218
|
-
invoke_shell "$shell_name" >/dev/null 2>&1 || true
|
|
219
|
-
for _ in $(seq 1 "$RUNS"); do
|
|
220
|
-
t=$(time_one_run "$shell_name")
|
|
221
|
-
times+=("$t")
|
|
222
|
-
sum=$((sum + t))
|
|
223
|
-
[[ "$t" -lt "$min" ]] && min="$t"
|
|
224
|
-
[[ "$t" -gt "$max" ]] && max="$t"
|
|
225
|
-
done
|
|
226
|
-
echo "$((sum / RUNS)) $min $max"
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
calc_score() {
|
|
230
|
-
local mean_ms="$1"
|
|
231
|
-
if [[ "$mean_ms" -le "$TARGET_MS" ]]; then
|
|
232
|
-
echo 100
|
|
233
|
-
return
|
|
234
|
-
fi
|
|
235
|
-
if [[ "$mean_ms" -ge "$MAX_MS" ]]; then
|
|
236
|
-
echo 0
|
|
237
|
-
return
|
|
238
|
-
fi
|
|
239
|
-
echo $((100 - (mean_ms - TARGET_MS) * 100 / (MAX_MS - TARGET_MS)))
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
run_profile() {
|
|
243
|
-
zsh -c '
|
|
244
|
-
zmodload zsh/zprof
|
|
245
|
-
# shellcheck disable=SC1091
|
|
246
|
-
source ~/.zshenv 2>/dev/null
|
|
247
|
-
# shellcheck disable=SC1091
|
|
248
|
-
source ~/.config/zsh/.zshrc 2>/dev/null
|
|
249
|
-
zprof
|
|
250
|
-
' 2>/dev/null | head -20
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
# Discover installed shells. SHELL_FILTER (--shell <name>) restricts to one.
|
|
254
|
-
all_shells=(zsh bash fish nu pwsh)
|
|
255
|
-
shells_to_measure=()
|
|
256
|
-
for s in "${all_shells[@]}"; do
|
|
257
|
-
command -v "$s" >/dev/null 2>&1 || continue
|
|
258
|
-
if [[ -n "$SHELL_FILTER" && "$s" != "$SHELL_FILTER" ]]; then
|
|
259
|
-
continue
|
|
260
|
-
fi
|
|
261
|
-
shells_to_measure+=("$s")
|
|
262
|
-
done
|
|
263
|
-
if ((${#shells_to_measure[@]} == 0)); then
|
|
264
|
-
ui_err "perf" "no measurable shells found${SHELL_FILTER:+ (filter: $SHELL_FILTER)}"
|
|
265
|
-
exit 1
|
|
266
|
-
fi
|
|
267
|
-
|
|
268
|
-
# Measure each shell once into parallel arrays.
|
|
269
|
-
declare -a shell_names shell_means shell_mins shell_maxs shell_targets shell_passes
|
|
270
|
-
for s in "${shells_to_measure[@]}"; do
|
|
271
|
-
read -r m mn mx <<<"$(measure_shell "$s")"
|
|
272
|
-
t=$(shell_target_for "$s")
|
|
273
|
-
shell_names+=("$s")
|
|
274
|
-
shell_means+=("$m")
|
|
275
|
-
shell_mins+=("$mn")
|
|
276
|
-
shell_maxs+=("$mx")
|
|
277
|
-
shell_targets+=("$t")
|
|
278
|
-
if [[ "$m" -le "$t" ]]; then
|
|
279
|
-
shell_passes+=("1")
|
|
280
|
-
else
|
|
281
|
-
shell_passes+=("0")
|
|
282
|
-
fi
|
|
283
|
-
done
|
|
284
|
-
|
|
285
|
-
# Primary "score" continues to use the zsh measurement (when available)
|
|
286
|
-
# so existing dashboards / JSON consumers keep their reference number.
|
|
287
|
-
mean=0
|
|
288
|
-
for i in "${!shell_names[@]}"; do
|
|
289
|
-
if [[ "${shell_names[$i]}" == "zsh" ]]; then
|
|
290
|
-
mean="${shell_means[$i]}"
|
|
291
|
-
break
|
|
292
|
-
fi
|
|
293
|
-
done
|
|
294
|
-
[[ "$mean" -eq 0 && "${#shell_means[@]}" -gt 0 ]] && mean="${shell_means[0]}"
|
|
295
|
-
score=$(calc_score "$mean")
|
|
296
|
-
|
|
297
|
-
# -----------------------------------------------------------------------------
|
|
298
|
-
# Baseline persistence + regression detection (closes part of #863).
|
|
299
|
-
# Stored at $XDG_CACHE_HOME/dotfiles/perf-baseline.json. The file is a
|
|
300
|
-
# JSON object mapping shell name → recorded mean (ms). `--baseline`
|
|
301
|
-
# overwrites it with the current measurement; default behavior reads
|
|
302
|
-
# it (if present) and warns when any shell regresses by more than
|
|
303
|
-
# DOTFILES_PERF_REGRESSION_PCT (default 10%).
|
|
304
|
-
# -----------------------------------------------------------------------------
|
|
305
|
-
|
|
306
|
-
declare -a baseline_warnings=()
|
|
307
|
-
if $WRITE_BASELINE; then
|
|
308
|
-
mkdir -p "$(dirname "$BASELINE_FILE")"
|
|
309
|
-
{
|
|
310
|
-
printf '{\n "recorded_at": "%s",\n "regression_pct": %d,\n "shells": {' \
|
|
311
|
-
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$BASELINE_REGRESSION_PCT"
|
|
312
|
-
for i in "${!shell_names[@]}"; do
|
|
313
|
-
[[ "$i" -gt 0 ]] && printf ','
|
|
314
|
-
printf '\n "%s": %d' "${shell_names[$i]}" "${shell_means[$i]}"
|
|
315
|
-
done
|
|
316
|
-
printf '\n }\n}\n'
|
|
317
|
-
} >"$BASELINE_FILE"
|
|
318
|
-
fi
|
|
319
|
-
|
|
320
|
-
if ! $NO_BASELINE_CHECK && [[ -s "$BASELINE_FILE" ]]; then
|
|
321
|
-
# Read the baseline and compare each measured shell.
|
|
322
|
-
for i in "${!shell_names[@]}"; do
|
|
323
|
-
name="${shell_names[$i]}"
|
|
324
|
-
m="${shell_means[$i]}"
|
|
325
|
-
baseline_ms=$(python3 -c '
|
|
326
|
-
import json, sys
|
|
327
|
-
try:
|
|
328
|
-
d = json.load(open(sys.argv[1]))
|
|
329
|
-
print(d.get("shells", {}).get(sys.argv[2], 0))
|
|
330
|
-
except Exception:
|
|
331
|
-
print(0)
|
|
332
|
-
' "$BASELINE_FILE" "$name" 2>/dev/null)
|
|
333
|
-
[[ -z "$baseline_ms" || "$baseline_ms" -eq 0 ]] && continue
|
|
334
|
-
# Threshold: current > baseline * (1 + pct/100)
|
|
335
|
-
threshold=$((baseline_ms * (100 + BASELINE_REGRESSION_PCT) / 100))
|
|
336
|
-
if ((m > threshold)); then
|
|
337
|
-
delta_pct=$((((m - baseline_ms) * 100) / baseline_ms))
|
|
338
|
-
baseline_warnings+=("$name: $m ms vs baseline $baseline_ms ms (+${delta_pct}%, threshold +${BASELINE_REGRESSION_PCT}%)")
|
|
339
|
-
fi
|
|
340
|
-
done
|
|
341
|
-
fi
|
|
342
|
-
|
|
343
|
-
if $JSON_OUTPUT; then
|
|
344
|
-
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": {' \
|
|
345
|
-
"$RUNS" "$TARGET_MS" "$MAX_MS" "$score" "$mean" "${#baseline_warnings[@]}"
|
|
346
|
-
for i in "${!shell_names[@]}"; do
|
|
347
|
-
[[ "$i" -gt 0 ]] && printf ','
|
|
348
|
-
printf '\n "%s": {"mean_ms": %d, "min_ms": %d, "max_ms": %d, "target_ms": %d, "pass": %s}' \
|
|
349
|
-
"${shell_names[$i]}" "${shell_means[$i]}" "${shell_mins[$i]}" "${shell_maxs[$i]}" \
|
|
350
|
-
"${shell_targets[$i]}" "$([[ ${shell_passes[$i]} == 1 ]] && echo true || echo false)"
|
|
351
|
-
done
|
|
352
|
-
printf '\n }'
|
|
353
|
-
if [[ "${#baseline_warnings[@]}" -gt 0 ]]; then
|
|
354
|
-
printf ',\n "regressions": ['
|
|
355
|
-
for i in "${!baseline_warnings[@]}"; do
|
|
356
|
-
[[ "$i" -gt 0 ]] && printf ','
|
|
357
|
-
# JSON-escape the warning text minimally (no embedded quotes expected).
|
|
358
|
-
printf '\n "%s"' "${baseline_warnings[$i]}"
|
|
359
|
-
done
|
|
360
|
-
printf '\n ]'
|
|
361
|
-
fi
|
|
362
|
-
printf '\n}\n'
|
|
363
|
-
exit 0
|
|
364
|
-
fi
|
|
365
|
-
|
|
366
|
-
ui_dot_banner "Diagnostics"
|
|
367
|
-
ui_header "Shell Performance"
|
|
368
|
-
|
|
369
|
-
ui_section "Per-shell startup ($RUNS runs each, after one warm-up)"
|
|
370
|
-
|
|
371
|
-
# Aligned table: name (6) | mean (8) | min/max (16) | target (12) | status
|
|
372
|
-
for i in "${!shell_names[@]}"; do
|
|
373
|
-
name="${shell_names[$i]}"
|
|
374
|
-
m="${shell_means[$i]}"
|
|
375
|
-
mn="${shell_mins[$i]}"
|
|
376
|
-
mx="${shell_maxs[$i]}"
|
|
377
|
-
t="${shell_targets[$i]}"
|
|
378
|
-
if [[ "${shell_passes[$i]}" == "1" ]]; then
|
|
379
|
-
status_marker="✓"
|
|
380
|
-
detail=""
|
|
381
|
-
else
|
|
382
|
-
status_marker="✗"
|
|
383
|
-
detail=" — over by $((m - t))ms"
|
|
384
|
-
fi
|
|
385
|
-
printf ' %s %-6s mean %4dms (min %3dms, max %3dms) target %4dms%s\n' \
|
|
386
|
-
"$status_marker" "$name" "$m" "$mn" "$mx" "$t" "$detail"
|
|
387
|
-
done
|
|
388
|
-
|
|
389
|
-
# Baseline comparison summary
|
|
390
|
-
if [[ "${#baseline_warnings[@]}" -gt 0 ]]; then
|
|
391
|
-
ui_section "Baseline regressions"
|
|
392
|
-
for w in "${baseline_warnings[@]}"; do
|
|
393
|
-
printf ' ✗ %s\n' "$w"
|
|
394
|
-
done
|
|
395
|
-
printf ' (threshold: >%s%% over the recorded baseline at %s)\n' \
|
|
396
|
-
"$BASELINE_REGRESSION_PCT" "$BASELINE_FILE"
|
|
397
|
-
elif [[ -s "$BASELINE_FILE" ]] && ! $NO_BASELINE_CHECK; then
|
|
398
|
-
ui_section "Baseline"
|
|
399
|
-
printf ' ✓ all measured shells within %s%% of baseline (%s)\n' \
|
|
400
|
-
"$BASELINE_REGRESSION_PCT" "$BASELINE_FILE"
|
|
401
|
-
elif $WRITE_BASELINE; then
|
|
402
|
-
ui_section "Baseline"
|
|
403
|
-
printf ' ✓ wrote baseline to %s\n' "$BASELINE_FILE"
|
|
404
|
-
fi
|
|
405
|
-
|
|
406
|
-
# Per-component breakdown (Zsh only, uses DOTFILES_DEBUG timing)
|
|
407
|
-
ui_section "Component breakdown (estimated)"
|
|
408
|
-
for component in "bare zsh" "paths+env" "aliases" "functions" "tools"; do
|
|
409
|
-
case "$component" in
|
|
410
|
-
"bare zsh")
|
|
411
|
-
c_start=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
412
|
-
zsh --no-rcs -c exit >/dev/null 2>&1 || true
|
|
413
|
-
c_end=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
414
|
-
;;
|
|
415
|
-
"paths+env")
|
|
416
|
-
c_start=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
417
|
-
DOTFILES_ULTRA_FAST=1 zsh -i -c exit >/dev/null 2>&1 || true
|
|
418
|
-
c_end=$(python3 -c 'import time; print(int(time.time() * 1000))')
|
|
419
|
-
;;
|
|
420
|
-
*)
|
|
421
|
-
# Approximate — full minus ultra gives the delta
|
|
422
|
-
c_start=0
|
|
423
|
-
c_end=0
|
|
424
|
-
;;
|
|
425
|
-
esac
|
|
426
|
-
c_time=$((c_end - c_start))
|
|
427
|
-
if [[ "$c_time" -gt 0 ]]; then
|
|
428
|
-
if [[ "$UI_ENABLED" = "1" ]]; then
|
|
429
|
-
ui_kv "$component" "${c_time}ms"
|
|
430
|
-
else
|
|
431
|
-
echo " $component: ${c_time}ms"
|
|
432
|
-
fi
|
|
433
|
-
fi
|
|
434
|
-
done
|
|
435
|
-
|
|
436
|
-
if $PROFILE; then
|
|
437
|
-
echo ""
|
|
438
|
-
ui_section "Top contributors (zprof)"
|
|
439
|
-
run_profile
|
|
440
|
-
echo ""
|
|
441
|
-
fi
|
|
442
|
-
|
|
443
|
-
dot_log info "perf_complete" "mean_ms=$mean" "score=$score"
|
|
444
|
-
dot_metric "shell_startup_mean" "$mean" "ms"
|
|
445
|
-
dot_metric "perf_score" "$score" "percent"
|
|
446
|
-
|
|
447
|
-
if [[ "$score" -eq 100 ]]; then
|
|
448
|
-
ui_ok "Performance" "Excellent"
|
|
449
|
-
elif [[ "$score" -ge 80 ]]; then
|
|
450
|
-
ui_warn "Performance" "Good (tune to reach 100)"
|
|
451
|
-
else
|
|
452
|
-
ui_err "Performance" "Needs attention"
|
|
453
|
-
fi
|
|
@@ -1,120 +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 Scorecard
|
|
5
|
-
# Usage: dot scorecard [--json|-j]
|
|
6
|
-
|
|
7
|
-
set -euo pipefail
|
|
8
|
-
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
-
# shellcheck source=../../lib/dot/ui.sh
|
|
11
|
-
# shellcheck disable=SC1091
|
|
12
|
-
source "$SCRIPT_DIR/../../lib/dot/ui.sh"
|
|
13
|
-
# shellcheck source=../../lib/dot/log.sh
|
|
14
|
-
# shellcheck disable=SC1091
|
|
15
|
-
source "$SCRIPT_DIR/../../lib/dot/log.sh"
|
|
16
|
-
export DOT_COMMAND="scorecard"
|
|
17
|
-
|
|
18
|
-
ui_init
|
|
19
|
-
set +x
|
|
20
|
-
|
|
21
|
-
if ! command -v python3 >/dev/null 2>&1; then
|
|
22
|
-
ui_err "python3" "required for scorecard"
|
|
23
|
-
exit 1
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
JSON_OUTPUT=false
|
|
27
|
-
if [[ "${1:-}" == "--json" || "${1:-}" == "-j" ]]; then
|
|
28
|
-
JSON_OUTPUT=true
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
extract_json() {
|
|
32
|
-
# strip any leading noise before the JSON payload
|
|
33
|
-
awk 'BEGIN{p=0} /^[[:space:]]*[{]/{p=1} p{print}'
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
health_json=$(env -u SHELLOPTS bash "$SCRIPT_DIR/health.sh" --json 2>/dev/null | extract_json)
|
|
37
|
-
security_json=$(env -u SHELLOPTS bash "$SCRIPT_DIR/security-score.sh" --json 2>/dev/null | extract_json)
|
|
38
|
-
perf_json=$(env -u SHELLOPTS bash "$SCRIPT_DIR/perf.sh" --json 2>/dev/null | extract_json)
|
|
39
|
-
|
|
40
|
-
if [[ -z "$health_json" || -z "$security_json" || -z "$perf_json" ]]; then
|
|
41
|
-
ui_err "scorecard" "failed to collect JSON metrics"
|
|
42
|
-
exit 1
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
health_score=$(python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); print(j.get("score",0))' <<<"$health_json")
|
|
46
|
-
health_warnings=$(python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); print(j.get("warnings",0))' <<<"$health_json")
|
|
47
|
-
health_failures=$(python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); print(j.get("failures",0))' <<<"$health_json")
|
|
48
|
-
security_score=$(python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); print(j.get("score",0))' <<<"$security_json")
|
|
49
|
-
perf_score=$(python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); print(j.get("score",0))' <<<"$perf_json")
|
|
50
|
-
perf_mean=$(python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); print(j.get("mean_ms",0))' <<<"$perf_json")
|
|
51
|
-
perf_target=$(python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); print(j.get("target_ms",0))' <<<"$perf_json")
|
|
52
|
-
|
|
53
|
-
# Drift count
|
|
54
|
-
if command -v chezmoi >/dev/null 2>&1; then
|
|
55
|
-
# --exclude=always: always-run scripts are pending by design, not drift.
|
|
56
|
-
drift_count=$(chezmoi status --exclude=always 2>/dev/null | wc -l | tr -d ' ')
|
|
57
|
-
else
|
|
58
|
-
drift_count=0
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
dot_log info "scorecard_complete" "health=$health_score" "security=$security_score" "perf=$perf_score"
|
|
62
|
-
dot_metric "scorecard_health" "$health_score" "percent"
|
|
63
|
-
dot_metric "scorecard_security" "$security_score" "percent"
|
|
64
|
-
dot_metric "scorecard_perf" "$perf_score" "percent"
|
|
65
|
-
|
|
66
|
-
if $JSON_OUTPUT; then
|
|
67
|
-
cat <<JSON
|
|
68
|
-
{
|
|
69
|
-
"health": {"score": $health_score, "warnings": $health_warnings, "failures": $health_failures},
|
|
70
|
-
"security": {"score": $security_score},
|
|
71
|
-
"performance": {"score": $perf_score, "mean_ms": $perf_mean, "target_ms": $perf_target},
|
|
72
|
-
"drift": {"count": $drift_count}
|
|
73
|
-
}
|
|
74
|
-
JSON
|
|
75
|
-
exit 0
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
ui_dot_banner "Diagnostics"
|
|
79
|
-
ui_header "Dotfiles Scorecard"
|
|
80
|
-
|
|
81
|
-
ui_section "Scores"
|
|
82
|
-
ui_kv "Health" "${health_score}/100"
|
|
83
|
-
ui_kv "Security" "${security_score}/100"
|
|
84
|
-
ui_kv "Performance" "${perf_score}/100 (avg ${perf_mean}ms, target ${perf_target}ms)"
|
|
85
|
-
ui_kv "Drift" "${drift_count} file(s)"
|
|
86
|
-
|
|
87
|
-
if [[ "$security_score" -lt 100 ]]; then
|
|
88
|
-
ui_warn "Security" "Run 'dot security-score' to reach 100/100"
|
|
89
|
-
else
|
|
90
|
-
ui_ok "Security" "100/100"
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
if [[ "$perf_score" -lt 100 ]]; then
|
|
94
|
-
ui_warn "Performance" "Tune startup to reach 100/100"
|
|
95
|
-
else
|
|
96
|
-
ui_ok "Performance" "100/100"
|
|
97
|
-
fi
|
|
98
|
-
|
|
99
|
-
if [[ "$health_failures" -gt 0 ]]; then
|
|
100
|
-
ui_err "Health" "Failures detected (run 'dot heal')"
|
|
101
|
-
elif [[ "$health_warnings" -gt 0 ]]; then
|
|
102
|
-
ui_warn "Health" "Warnings detected (run 'dot heal')"
|
|
103
|
-
else
|
|
104
|
-
ui_ok "Health" "All checks passing"
|
|
105
|
-
fi
|
|
106
|
-
|
|
107
|
-
ui_section "Tips"
|
|
108
|
-
ui_info "Fix" "dot heal"
|
|
109
|
-
ui_info "Profile" "dot perf --profile"
|
|
110
|
-
ui_info "Security" "dot security-score"
|
|
111
|
-
ui_info "Drift" "dot drift"
|
|
112
|
-
|
|
113
|
-
exit_code=0
|
|
114
|
-
if [[ "$security_score" -lt 100 ]]; then
|
|
115
|
-
exit_code=1
|
|
116
|
-
fi
|
|
117
|
-
if [[ "$perf_score" -lt 100 ]]; then
|
|
118
|
-
exit_code=1
|
|
119
|
-
fi
|
|
120
|
-
exit "$exit_code"
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
|
-
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
8
|
-
# shellcheck source=../lib/secrets_provider.sh
|
|
9
|
-
# shellcheck disable=SC1091
|
|
10
|
-
source "$REPO_ROOT/scripts/lib/secrets_provider.sh"
|
|
11
|
-
|
|
12
|
-
STRICT="${DOTFILES_ALIAS_STRICT_MODE:-${DOTFILES_SECRETS_STRICT_MODE:-0}}"
|
|
13
|
-
|
|
14
|
-
cd "$REPO_ROOT"
|
|
15
|
-
|
|
16
|
-
# Portable read — `mapfile` is bash 4 only, and this runs from the
|
|
17
|
-
# pre-commit hook on macOS where /bin/bash is 3.2. Without this the
|
|
18
|
-
# hook exits 127 and the secret scan is skipped entirely.
|
|
19
|
-
staged_files=()
|
|
20
|
-
while IFS= read -r _line; do
|
|
21
|
-
[[ -n "$_line" ]] && staged_files+=("$_line")
|
|
22
|
-
done < <(git diff --cached --name-only --diff-filter=ACM || true)
|
|
23
|
-
if [[ "${#staged_files[@]}" -eq 0 ]]; then
|
|
24
|
-
echo "Secret governance: no staged files."
|
|
25
|
-
exit 0
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
staged_content() {
|
|
29
|
-
local f
|
|
30
|
-
for f in "${staged_files[@]}"; do
|
|
31
|
-
git show -- ":$f" 2>/dev/null || true
|
|
32
|
-
done
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
violations=0
|
|
36
|
-
tmp_content="$(umask 077 && mktemp)"
|
|
37
|
-
trap 'rm -f "$tmp_content"' EXIT
|
|
38
|
-
staged_content >"$tmp_content"
|
|
39
|
-
|
|
40
|
-
scan_patterns="(api[_-]?key|api[_-]?secret|token|password)[[:space:]]*[:=][[:space:]]*[\"'][^\"']{8,}[\"']"
|
|
41
|
-
if grep -Eqi "$scan_patterns" "$tmp_content"; then
|
|
42
|
-
echo "Secret governance: potential plaintext secret pattern detected in staged content."
|
|
43
|
-
violations=$((violations + 1))
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
if [[ "$STRICT" == "1" ]]; then
|
|
47
|
-
while IFS= read -r key; do
|
|
48
|
-
[[ -n "$key" ]] || continue
|
|
49
|
-
value="$(dot_secrets_get "$key" || true)"
|
|
50
|
-
[[ -n "$value" ]] || continue
|
|
51
|
-
[[ "${#value}" -ge 8 ]] || continue
|
|
52
|
-
|
|
53
|
-
if grep -Fq "$value" "$tmp_content"; then
|
|
54
|
-
echo "Secret governance: exact managed secret value leaked for key: $key"
|
|
55
|
-
violations=$((violations + 1))
|
|
56
|
-
fi
|
|
57
|
-
done < <(dot_secrets_index_list)
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
if [[ "$violations" -gt 0 ]]; then
|
|
61
|
-
echo "Secret governance failed: $violations violation(s)."
|
|
62
|
-
exit 1
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
echo "Secret governance passed."
|