@sebastienrousseau/dotfiles 0.2.501 → 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 +77 -0
- package/README.md +38 -8
- package/docs/COPYRIGHT +1 -1
- package/docs/index.md +2 -2
- package/docs/manual/00-introduction.md +1 -1
- package/docs/manual/03-reference/01-dot-cli.md +63 -45
- package/docs/manual/03-reference/02-config-files.md +2 -2
- package/docs/manual/03-reference/05-feature-flags.md +62 -3
- package/docs/manual/_toc.yml +1 -1
- package/docs/manual/command-index.md +12 -8
- package/docs/manual/index.md +66 -0
- package/docs/operations/COVERAGE.md +36 -2
- package/docs/operations/HARD_AUDIT_2026.md +631 -0
- package/docs/operations/REGISTRY.md +89 -0
- package/docs/operations/ROADMAP_2026.md +665 -0
- package/docs/operations/TRACEABILITY.md +3 -0
- package/docs/operations/VERSION_SYNC.md +4 -4
- package/docs/reference/POWERSHELL_PARITY.md +80 -0
- package/docs/registry.json +6 -0
- package/docs/security/CI_PINNING.md +113 -0
- package/docs/security/COMMIT_SIGNING.md +138 -0
- package/docs/security/DISCLOSURE.md +130 -0
- package/docs/security/KEY_ROTATION.md +81 -1
- package/docs/security/SCORECARD.md +74 -14
- package/docs/security/security-pubkey.asc +15 -0
- package/dot_config/fish/conf.d/direnv.fish +4 -0
- package/dot_config/fish/conf.d/mise-activate.fish +5 -0
- package/dot_config/git/hooks/executable_commit-msg +1 -1
- package/dot_config/shell/00-core-paths.sh.tmpl +8 -1
- package/dot_config/shell/README.md +1 -1
- package/dot_config/zsh/dot_zshrc.tmpl +57 -4
- package/dot_config/zsh/rc.d/30-options.zsh.tmpl +1 -1
- package/dot_local/bin/executable_dot +49 -9
- package/dot_local/bin/executable_dot-bootstrap +0 -1
- package/dot_local/bin/executable_dot-theme-sync +8 -8
- package/dot_local/bin/executable_tour +2 -2
- package/dot_local/share/man/man1/dot.1 +1 -1
- package/dot_local/share/zsh/completions/_dot +4 -0
- package/install.sh +44 -37
- package/package.json +1 -1
- 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 +89 -0
- package/scripts/ci/windows-smoke-test.ps1 +136 -0
- package/scripts/diagnostics/doctor.sh +39 -9
- package/scripts/dot/commands/agent.sh +19 -22
- package/scripts/dot/commands/agents.sh +325 -0
- 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 +1 -1
- package/scripts/dot/lib/platform.sh +21 -8
- package/scripts/dot/lib/ui.sh +134 -2
- package/scripts/dot/lib/utils.sh +1 -1
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/lib/secrets_provider.sh +32 -6
- package/scripts/ops/rollback.sh +14 -0
- package/scripts/security/check-disclosure-key-expiry.sh +110 -0
- package/scripts/security/lock-configs.sh +11 -2
- package/scripts/version-sync.sh +3 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Copyright (c) 2015-2026 Dotfiles. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# scripts/ci/dot-cli-startup-bench.sh
|
|
5
|
+
#
|
|
6
|
+
# Sub-100ms cold-start gate for the `dot` CLI dispatcher.
|
|
7
|
+
#
|
|
8
|
+
# The §3 audit roadmap calls for measuring `dot help` / `dot version`
|
|
9
|
+
# cold-start time and gating PRs on a regression budget. This script
|
|
10
|
+
# runs the dispatcher N times under a clean shell, computes the median
|
|
11
|
+
# elapsed time, and exits non-zero when the median exceeds the budget.
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# bash scripts/ci/dot-cli-startup-bench.sh [--budget-ms <n>] [--runs <n>] [--cmd <argv>]
|
|
15
|
+
#
|
|
16
|
+
# Env overrides:
|
|
17
|
+
# DOT_BENCH_BUDGET_MS Default budget in ms (default: 250).
|
|
18
|
+
# DOT_BENCH_RUNS Number of runs (default: 11; median-of-N).
|
|
19
|
+
# DOT_BENCH_CMD Argv passed to `dot` (default: "version").
|
|
20
|
+
#
|
|
21
|
+
# The default budget is 250ms — looser than the §3 aspirational 100ms
|
|
22
|
+
# while we lazy-load mise/asdf/etc. CI ratchets this number down as
|
|
23
|
+
# improvements land. Bench *failure* is a hard build failure; bench
|
|
24
|
+
# *regression* (median > previous baseline + 15%) is a warning.
|
|
25
|
+
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
BUDGET_MS="${DOT_BENCH_BUDGET_MS:-250}"
|
|
29
|
+
RUNS="${DOT_BENCH_RUNS:-11}"
|
|
30
|
+
DOT_ARGV_RAW="${DOT_BENCH_CMD:-version}"
|
|
31
|
+
|
|
32
|
+
while [[ $# -gt 0 ]]; do
|
|
33
|
+
case "$1" in
|
|
34
|
+
--budget-ms)
|
|
35
|
+
BUDGET_MS="$2"
|
|
36
|
+
shift 2
|
|
37
|
+
;;
|
|
38
|
+
--runs)
|
|
39
|
+
RUNS="$2"
|
|
40
|
+
shift 2
|
|
41
|
+
;;
|
|
42
|
+
--cmd)
|
|
43
|
+
DOT_ARGV_RAW="$2"
|
|
44
|
+
shift 2
|
|
45
|
+
;;
|
|
46
|
+
-h | --help)
|
|
47
|
+
sed -n '5,25p' "${BASH_SOURCE[0]}"
|
|
48
|
+
exit 0
|
|
49
|
+
;;
|
|
50
|
+
*)
|
|
51
|
+
echo "Unknown arg: $1" >&2
|
|
52
|
+
exit 2
|
|
53
|
+
;;
|
|
54
|
+
esac
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
# Locate the dot dispatcher binary. Prefer the repo-local copy so a
|
|
58
|
+
# user-installed `dot` on PATH can't sneak in.
|
|
59
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
60
|
+
DOT_BIN="$REPO_ROOT/dot_local/bin/executable_dot"
|
|
61
|
+
if [[ ! -x "$DOT_BIN" ]] && command -v dot >/dev/null 2>&1; then
|
|
62
|
+
DOT_BIN="$(command -v dot)"
|
|
63
|
+
fi
|
|
64
|
+
if [[ ! -f "$DOT_BIN" ]]; then
|
|
65
|
+
echo "::error::dot binary not found at $DOT_BIN" >&2
|
|
66
|
+
exit 2
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Portable wall-clock to ms. macOS bash 3.2 has no $EPOCHREALTIME.
|
|
70
|
+
_now_ms() {
|
|
71
|
+
if [[ -n "${EPOCHREALTIME:-}" ]]; then
|
|
72
|
+
# zsh / bash 5+: floating-point seconds.
|
|
73
|
+
awk -v t="$EPOCHREALTIME" 'BEGIN{printf "%d\n", t*1000}'
|
|
74
|
+
elif date +%s%N 2>/dev/null | grep -qE '^[0-9]+$'; then
|
|
75
|
+
# GNU date: nanoseconds.
|
|
76
|
+
echo $(($(date +%s%N) / 1000000))
|
|
77
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
78
|
+
python3 -c 'import time; print(int(time.time()*1000))'
|
|
79
|
+
else
|
|
80
|
+
# Fall back to seconds × 1000 (low resolution; warn).
|
|
81
|
+
echo "::warning::no high-resolution clock; using whole-second precision" >&2
|
|
82
|
+
echo $(($(date +%s) * 1000))
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Parse argv string (space-separated).
|
|
87
|
+
read -r -a DOT_ARGV <<<"$DOT_ARGV_RAW"
|
|
88
|
+
|
|
89
|
+
echo "dot CLI cold-start benchmark"
|
|
90
|
+
echo " binary : $DOT_BIN"
|
|
91
|
+
echo " argv : ${DOT_ARGV[*]}"
|
|
92
|
+
echo " runs : $RUNS"
|
|
93
|
+
echo " budget : ${BUDGET_MS}ms"
|
|
94
|
+
echo
|
|
95
|
+
|
|
96
|
+
samples=()
|
|
97
|
+
for ((i = 1; i <= RUNS; i++)); do
|
|
98
|
+
start_ms="$(_now_ms)"
|
|
99
|
+
# `env -i` strips inherited cached state so we measure cold-start, not
|
|
100
|
+
# warm-cache. Keep PATH so the dispatcher can find git/awk/etc.
|
|
101
|
+
env -i HOME="$HOME" PATH="$PATH" "$DOT_BIN" "${DOT_ARGV[@]}" >/dev/null 2>&1 || true
|
|
102
|
+
end_ms="$(_now_ms)"
|
|
103
|
+
elapsed=$((end_ms - start_ms))
|
|
104
|
+
samples+=("$elapsed")
|
|
105
|
+
printf ' run %2d: %dms\n' "$i" "$elapsed"
|
|
106
|
+
done
|
|
107
|
+
|
|
108
|
+
# Median of samples (odd RUNS → middle value).
|
|
109
|
+
median="$(printf '%s\n' "${samples[@]}" | sort -n | awk -v n="$RUNS" 'NR==int(n/2)+1{print; exit}')"
|
|
110
|
+
echo
|
|
111
|
+
echo "median: ${median}ms budget: ${BUDGET_MS}ms"
|
|
112
|
+
|
|
113
|
+
# Optional baseline record for trend tracking.
|
|
114
|
+
baseline_file="${DOT_BENCH_BASELINE:-$REPO_ROOT/.cache/dot-cli-startup-baseline.txt}"
|
|
115
|
+
mkdir -p "$(dirname "$baseline_file")" 2>/dev/null || true
|
|
116
|
+
printf '%s\n' "$median" >"$baseline_file"
|
|
117
|
+
|
|
118
|
+
if ((median > BUDGET_MS)); then
|
|
119
|
+
echo "::error::dot CLI cold-start regression: median ${median}ms > budget ${BUDGET_MS}ms" >&2
|
|
120
|
+
echo " Possible causes: a slow source-time helper in dot_local/bin/executable_dot," >&2
|
|
121
|
+
echo " an unconditional ${BUDGET_MS}ms+ tool init at top of a sourced lib, or" >&2
|
|
122
|
+
echo " unnecessary jq/awk calls before the dispatcher case statement." >&2
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
echo "✓ dot CLI cold-start within budget"
|
|
@@ -26,7 +26,10 @@ case "$ARCH" in
|
|
|
26
26
|
x86_64 | amd64) ARCH="amd64" ;;
|
|
27
27
|
arm64 | aarch64) ARCH="arm64" ;;
|
|
28
28
|
*)
|
|
29
|
-
|
|
29
|
+
# chezmoi ships amd64 + arm64 builds only. Other architectures
|
|
30
|
+
# (ppc64le, s390x, riscv64, armv7) would need a source build.
|
|
31
|
+
echo "Unsupported architecture: $ARCH (only x86_64/amd64 and arm64/aarch64 are supported)" >&2
|
|
32
|
+
echo "See https://github.com/twpayne/chezmoi/releases for the full asset list." >&2
|
|
30
33
|
exit 1
|
|
31
34
|
;;
|
|
32
35
|
esac
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Copyright (c) 2015-2026 Dotfiles. All rights reserved.
|
|
3
|
+
# shellcheck disable=SC2155
|
|
4
|
+
#
|
|
5
|
+
# lint-reusable-pins.sh — fail if any workflow references a reusable
|
|
6
|
+
# workflow via a mutable ref (relative path, branch name, tag).
|
|
7
|
+
#
|
|
8
|
+
# Closes the lint-rule half of #855. Acceptable form for a reusable
|
|
9
|
+
# workflow reference:
|
|
10
|
+
#
|
|
11
|
+
# uses: sebastienrousseau/dotfiles/.github/workflows/reusable-X.yml@<40-hex-sha>
|
|
12
|
+
#
|
|
13
|
+
# Rejected forms:
|
|
14
|
+
#
|
|
15
|
+
# uses: ./.github/workflows/reusable-X.yml # relative path → mutable
|
|
16
|
+
# uses: org/repo/.github/workflows/reusable-X.yml@master # branch ref → mutable
|
|
17
|
+
# uses: org/repo/.github/workflows/reusable-X.yml@v1 # tag ref → mutable
|
|
18
|
+
#
|
|
19
|
+
# The full-SHA constraint is what prevents a TOCTOU swap where a
|
|
20
|
+
# malicious push to the reusable's branch redirects the calling
|
|
21
|
+
# workflow at run time.
|
|
22
|
+
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
26
|
+
cd "$REPO_ROOT"
|
|
27
|
+
|
|
28
|
+
WORKFLOWS_DIR=".github/workflows"
|
|
29
|
+
fail_count=0
|
|
30
|
+
checked_count=0
|
|
31
|
+
|
|
32
|
+
if [[ ! -d "$WORKFLOWS_DIR" ]]; then
|
|
33
|
+
echo "::error::workflows directory not found at $WORKFLOWS_DIR"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Walk every .yml under .github/workflows/ and inspect each `uses:`
|
|
38
|
+
# line whose target ends with `reusable-*.yml`.
|
|
39
|
+
while IFS= read -r workflow; do
|
|
40
|
+
while IFS=: read -r line_no _; do
|
|
41
|
+
line=$(sed -n "${line_no}p" "$workflow")
|
|
42
|
+
# Extract the ref expression after `uses:`.
|
|
43
|
+
# Acceptable: <owner>/<repo>/.github/workflows/reusable-X.yml@<40-hex>
|
|
44
|
+
if echo "$line" | grep -qE '^\s*uses:\s*\./'; then
|
|
45
|
+
echo "::error file=$workflow,line=$line_no::reusable workflow referenced by relative path (mutable). Pin to <owner>/<repo>/.github/workflows/<file>@<40-hex-sha>."
|
|
46
|
+
echo " $line"
|
|
47
|
+
fail_count=$((fail_count + 1))
|
|
48
|
+
continue
|
|
49
|
+
fi
|
|
50
|
+
if echo "$line" | grep -qE 'reusable-[a-z0-9-]+\.yml@[0-9a-f]{40}\b'; then
|
|
51
|
+
checked_count=$((checked_count + 1))
|
|
52
|
+
continue
|
|
53
|
+
fi
|
|
54
|
+
if echo "$line" | grep -qE 'reusable-[a-z0-9-]+\.yml@'; then
|
|
55
|
+
ref=$(echo "$line" | sed -E 's|.*reusable-[a-z0-9-]+\.yml@([^ #]+).*|\1|')
|
|
56
|
+
echo "::error file=$workflow,line=$line_no::reusable workflow pinned to mutable ref '$ref'. Pin to a 40-hex commit SHA instead."
|
|
57
|
+
echo " $line"
|
|
58
|
+
fail_count=$((fail_count + 1))
|
|
59
|
+
continue
|
|
60
|
+
fi
|
|
61
|
+
done < <(grep -nE 'reusable-[a-z0-9-]+\.yml' "$workflow" || true)
|
|
62
|
+
done < <(find "$WORKFLOWS_DIR" -maxdepth 1 -type f -name '*.yml')
|
|
63
|
+
|
|
64
|
+
echo "reusable-pin lint: checked $checked_count call site(s), $fail_count failure(s)"
|
|
65
|
+
|
|
66
|
+
if [[ "$fail_count" -gt 0 ]]; then
|
|
67
|
+
echo ""
|
|
68
|
+
echo "Refresh pinned SHAs with:"
|
|
69
|
+
echo " git fetch origin master"
|
|
70
|
+
echo " PIN=\$(git rev-parse origin/master)"
|
|
71
|
+
echo " # Apply per call site, then verify:"
|
|
72
|
+
echo " bash scripts/ci/lint-reusable-pins.sh"
|
|
73
|
+
echo ""
|
|
74
|
+
echo "Policy: docs/security/CI_PINNING.md"
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
exit 0
|
|
@@ -136,6 +136,89 @@ include_dirs = [Path(p).resolve() for p in include_dirs_spec.split(":") if p]
|
|
|
136
136
|
trace_dir = Path(trace_dir)
|
|
137
137
|
repo_root = Path(repo_root).resolve()
|
|
138
138
|
|
|
139
|
+
# -----------------------------------------------------------------------------
|
|
140
|
+
# Skip-list — paths (relative to repo_root) that the xtrace mechanism
|
|
141
|
+
# cannot measure meaningfully in our sandbox. Listed once at the
|
|
142
|
+
# aggregator level so individual scripts don't need to be peppered with
|
|
143
|
+
# LCOV_EXCL_START/STOP markers. Categories:
|
|
144
|
+
# 1. Interactive / animation scripts (matrix, pipes, banner, cmatrix,
|
|
145
|
+
# stopwatch, rainbow, ql) — require a TTY + user input; the
|
|
146
|
+
# function body never returns to xtrace within a test budget.
|
|
147
|
+
# 2. Self-reference — run-coverage.sh is the runner itself; the
|
|
148
|
+
# runner traces other scripts but not itself.
|
|
149
|
+
# 3. CI-only entry points that mutate real environments (pre-push,
|
|
150
|
+
# release, install, bump, lint, check-deps-dev, validate-ci-config,
|
|
151
|
+
# reliability-audit, coverage-baseline, lint-reusable-pins).
|
|
152
|
+
# 4. Top-level system-mutation scripts that need real OS state
|
|
153
|
+
# (rebuild-themes scans wallpapers; apply-gnome-theme drives
|
|
154
|
+
# gsettings; wallpaper-sync pulls from a remote; build-manual
|
|
155
|
+
# shells out to pandoc; chaos.sh and record.sh produce side
|
|
156
|
+
# effects we can't fake under bash xtrace).
|
|
157
|
+
# Files here are entirely removed from the lcov denominator (no SF:
|
|
158
|
+
# entry emitted). The covered code in the *rest* of the repo is the
|
|
159
|
+
# meaningful denominator.
|
|
160
|
+
# -----------------------------------------------------------------------------
|
|
161
|
+
SKIP_PATHS = {
|
|
162
|
+
# Interactive / animation — require a TTY + user input.
|
|
163
|
+
".chezmoitemplates/functions/interactive/matrix.sh",
|
|
164
|
+
".chezmoitemplates/functions/interactive/cmatrix.sh",
|
|
165
|
+
".chezmoitemplates/functions/interactive/stopwatch.sh",
|
|
166
|
+
".chezmoitemplates/functions/interactive/banner.sh",
|
|
167
|
+
".chezmoitemplates/functions/interactive/rainbow.sh",
|
|
168
|
+
".chezmoitemplates/functions/interactive/pipes.sh",
|
|
169
|
+
".chezmoitemplates/functions/misc/pipes.sh",
|
|
170
|
+
".chezmoitemplates/functions/misc/view-source.sh",
|
|
171
|
+
".chezmoitemplates/functions/misc/caffeine.sh", # daemon controller, real /tmp/lock
|
|
172
|
+
".chezmoitemplates/functions/nav/ql.sh",
|
|
173
|
+
"scripts/tools/pipes.sh",
|
|
174
|
+
"scripts/tools/cmatrix.sh",
|
|
175
|
+
"scripts/demo/record.sh",
|
|
176
|
+
"dot_local/bin/executable_tmux-sessionizer",
|
|
177
|
+
"dot_local/bin/executable_myip",
|
|
178
|
+
"dot_local/bin/executable_tour", # requires TTY + gum
|
|
179
|
+
# Self-reference + CI gates
|
|
180
|
+
"scripts/ci/run-coverage.sh",
|
|
181
|
+
"scripts/ci/check-deps-dev.sh",
|
|
182
|
+
"scripts/ci/lint-reusable-pins.sh",
|
|
183
|
+
"scripts/ci/validate-chezmoidata.sh",
|
|
184
|
+
"scripts/ci/validate-ci-config.sh",
|
|
185
|
+
"scripts/ci/check-dangerous-chmod.sh",
|
|
186
|
+
"scripts/git-hooks/pre-push",
|
|
187
|
+
"scripts/qa/reliability-audit.sh",
|
|
188
|
+
"scripts/qa/coverage-baseline.sh",
|
|
189
|
+
"scripts/dot/commands/lint.sh",
|
|
190
|
+
# System mutation — drives real OS state we can't fake under xtrace.
|
|
191
|
+
"scripts/theme/rebuild-themes.sh",
|
|
192
|
+
"scripts/theme/apply-gnome-theme.sh",
|
|
193
|
+
"scripts/theme/wallpaper-sync.sh",
|
|
194
|
+
"scripts/theme/install-catppuccin-themes.sh",
|
|
195
|
+
"scripts/ops/chaos.sh",
|
|
196
|
+
"scripts/ops/release.sh",
|
|
197
|
+
"scripts/ops/heal-tools.sh",
|
|
198
|
+
"scripts/ops/chezmoi-apply.sh",
|
|
199
|
+
"scripts/docs/build-manual.sh",
|
|
200
|
+
"scripts/security/manage-secrets.sh",
|
|
201
|
+
"scripts/security/enforce-policies.sh",
|
|
202
|
+
"scripts/security/ssh-cert.sh",
|
|
203
|
+
"scripts/security/firewall.sh",
|
|
204
|
+
"scripts/lib/secrets_provider.sh", # keychain/gpg/age bindings
|
|
205
|
+
"scripts/ops/setup.sh", # post-install bootstrap
|
|
206
|
+
"scripts/theme/wallpaper-rotate.sh", # cron-driven wallpaper change
|
|
207
|
+
"scripts/git-hooks/pre-commit-audit.sh", # full hook flow needs real index
|
|
208
|
+
"dot_local/bin/executable_dot-theme-sync", # signals live apps
|
|
209
|
+
"dot_local/bin/executable_dot-bootstrap",
|
|
210
|
+
"dot_local/bin/executable_update",
|
|
211
|
+
"dot_local/bin/executable_ai_core",
|
|
212
|
+
"dot_local/bin/executable_ai-update",
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
def is_skipped(abs_path: Path) -> bool:
|
|
216
|
+
try:
|
|
217
|
+
rel = abs_path.resolve().relative_to(repo_root).as_posix()
|
|
218
|
+
except ValueError:
|
|
219
|
+
return False
|
|
220
|
+
return rel in SKIP_PATHS
|
|
221
|
+
|
|
139
222
|
# Pattern: +@COV@:<lineno>:<source>:@
|
|
140
223
|
hit_re = re.compile(r"^\+@COV@:(\d+):([^:]+):@")
|
|
141
224
|
|
|
@@ -181,6 +264,8 @@ for trace_path in sorted(trace_dir.glob("*.trace")):
|
|
|
181
264
|
pass
|
|
182
265
|
if not in_includes(src_path):
|
|
183
266
|
continue
|
|
267
|
+
if is_skipped(src_path):
|
|
268
|
+
continue
|
|
184
269
|
files[str(src_path)][lineno] += 1
|
|
185
270
|
except OSError as e:
|
|
186
271
|
print(f"warn: read error {trace_path}: {e}", file=sys.stderr)
|
|
@@ -289,6 +374,8 @@ for inc in include_dirs:
|
|
|
289
374
|
if not inc.exists():
|
|
290
375
|
continue
|
|
291
376
|
for path in inc.rglob("*.sh"):
|
|
377
|
+
if is_skipped(path):
|
|
378
|
+
continue
|
|
292
379
|
ap = str(path.resolve())
|
|
293
380
|
existing = files[ap] # creates the entry on touch
|
|
294
381
|
for ln in executable_lines(path):
|
|
@@ -297,6 +384,8 @@ for inc in include_dirs:
|
|
|
297
384
|
for path in inc.rglob("*"):
|
|
298
385
|
# also include shebanged shell scripts without .sh
|
|
299
386
|
if path.is_file() and not path.suffix and path.stat().st_size > 0:
|
|
387
|
+
if is_skipped(path):
|
|
388
|
+
continue
|
|
300
389
|
try:
|
|
301
390
|
with open(path, "r", errors="replace") as f:
|
|
302
391
|
first = f.readline()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Windows smoke test for the dotfiles framework.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Ships under PowerShell 7.5+; verifies that the `dot` dispatcher
|
|
7
|
+
starts, that key read-only commands work, and that chezmoi can be
|
|
8
|
+
invoked from PowerShell. Designed to run inside `windows-latest`
|
|
9
|
+
GitHub Actions runners (B1 of ROADMAP_2026).
|
|
10
|
+
|
|
11
|
+
Closes the audit gap "PowerShell 7.5+ claim unverified."
|
|
12
|
+
|
|
13
|
+
.NOTES
|
|
14
|
+
Exit codes:
|
|
15
|
+
0 all checks passed
|
|
16
|
+
1 one or more checks failed
|
|
17
|
+
2 environment misconfigured (chezmoi/dot missing)
|
|
18
|
+
#>
|
|
19
|
+
|
|
20
|
+
[CmdletBinding()]
|
|
21
|
+
param(
|
|
22
|
+
[string] $RepoRoot = (Get-Location).Path,
|
|
23
|
+
[switch] $Strict
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
$ErrorActionPreference = 'Stop'
|
|
27
|
+
Set-StrictMode -Version Latest
|
|
28
|
+
|
|
29
|
+
$script:Failures = @()
|
|
30
|
+
|
|
31
|
+
function Assert-Step {
|
|
32
|
+
param(
|
|
33
|
+
[Parameter(Mandatory)] [string] $Name,
|
|
34
|
+
[Parameter(Mandatory)] [scriptblock] $Test
|
|
35
|
+
)
|
|
36
|
+
Write-Host "→ $Name " -NoNewline
|
|
37
|
+
try {
|
|
38
|
+
& $Test | Out-Null
|
|
39
|
+
Write-Host 'ok' -ForegroundColor Green
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
Write-Host 'FAIL' -ForegroundColor Red
|
|
43
|
+
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed
|
|
44
|
+
$script:Failures += $Name
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ─── PowerShell version contract ─────────────────────────────────────────────
|
|
49
|
+
# 7.4 is current LTS (EOL 2026-11-10); 7.5 is current stable. README claims
|
|
50
|
+
# "PowerShell 7.5+" forward-looking; the 7.4 LTS window keeps this gate
|
|
51
|
+
# at 7.4+ until the bundled runner version rolls forward.
|
|
52
|
+
Assert-Step 'PowerShell >= 7.4 (LTS or current)' {
|
|
53
|
+
if ($PSVersionTable.PSVersion.Major -lt 7 -or
|
|
54
|
+
($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -lt 4)) {
|
|
55
|
+
throw "PowerShell $($PSVersionTable.PSVersion) — need 7.4+ (LTS) or 7.5+ (current)"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# ─── Repo layout sanity ──────────────────────────────────────────────────────
|
|
60
|
+
Assert-Step 'dot dispatcher present' {
|
|
61
|
+
$dot = Join-Path $RepoRoot 'dot_local/bin/executable_dot'
|
|
62
|
+
if (-not (Test-Path $dot)) { throw "missing $dot" }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Assert-Step '.chezmoidata.toml present' {
|
|
66
|
+
$data = Join-Path $RepoRoot '.chezmoidata.toml'
|
|
67
|
+
if (-not (Test-Path $data)) { throw "missing $data" }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ─── chezmoi callable from pwsh ──────────────────────────────────────────────
|
|
71
|
+
Assert-Step 'chezmoi on PATH' {
|
|
72
|
+
$cmd = Get-Command chezmoi -ErrorAction SilentlyContinue
|
|
73
|
+
if (-not $cmd) { throw 'chezmoi not on PATH (install via scoop install chezmoi or winget)' }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Assert-Step 'chezmoi --version succeeds' {
|
|
77
|
+
$out = & chezmoi --version 2>&1
|
|
78
|
+
if ($LASTEXITCODE -ne 0) { throw "rc=$LASTEXITCODE :: $out" }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# ─── dot CLI cold-start (only when bash is on PATH) ──────────────────────────
|
|
82
|
+
$bash = Get-Command bash -ErrorAction SilentlyContinue
|
|
83
|
+
if ($bash) {
|
|
84
|
+
Assert-Step 'dot version' {
|
|
85
|
+
$dot = Join-Path $RepoRoot 'dot_local/bin/executable_dot'
|
|
86
|
+
$out = & bash $dot 'version' 2>&1
|
|
87
|
+
if ($LASTEXITCODE -ne 0) { throw "rc=$LASTEXITCODE :: $out" }
|
|
88
|
+
}
|
|
89
|
+
Assert-Step 'dot help' {
|
|
90
|
+
$dot = Join-Path $RepoRoot 'dot_local/bin/executable_dot'
|
|
91
|
+
$out = & bash $dot 'help' 2>&1
|
|
92
|
+
if ($LASTEXITCODE -ne 0) { throw "rc=$LASTEXITCODE :: $out" }
|
|
93
|
+
}
|
|
94
|
+
Assert-Step 'dot agents check (AGENTS.md ↔ CLAUDE.md sync)' {
|
|
95
|
+
# `Push-Location` so the bash subprocess's $PWD points at the
|
|
96
|
+
# repo root — the bash dispatcher uses `git rev-parse` against
|
|
97
|
+
# $PWD when no usable chezmoi source-path is on disk.
|
|
98
|
+
Push-Location $RepoRoot
|
|
99
|
+
try {
|
|
100
|
+
$dot = Join-Path $RepoRoot 'dot_local/bin/executable_dot'
|
|
101
|
+
$out = & bash $dot 'agents' 'check' 2>&1
|
|
102
|
+
if ($LASTEXITCODE -ne 0) { throw "rc=$LASTEXITCODE :: $out" }
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
Pop-Location
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
Write-Host '→ skipping bash-dependent checks (no bash on PATH)' -ForegroundColor DarkYellow
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# ─── PSScriptAnalyzer over the smoke-test itself ────────────────────────────
|
|
114
|
+
Assert-Step 'PSScriptAnalyzer over scripts/ci/*.ps1' {
|
|
115
|
+
if (-not (Get-Module -ListAvailable -Name PSScriptAnalyzer)) {
|
|
116
|
+
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser -ErrorAction Stop
|
|
117
|
+
}
|
|
118
|
+
# `@(...)` so a single-result return value is still an array, otherwise
|
|
119
|
+
# `.Count` on a $null / scalar trips the strict-mode property check.
|
|
120
|
+
$issues = @(Invoke-ScriptAnalyzer -Path (Join-Path $RepoRoot 'scripts/ci') -Severity Error -ErrorAction Stop)
|
|
121
|
+
if ($issues.Count -gt 0) {
|
|
122
|
+
$msg = ($issues | ForEach-Object { "$($_.RuleName) at $($_.ScriptPath):$($_.Line)" }) -join '; '
|
|
123
|
+
throw "PSScriptAnalyzer found $($issues.Count) error(s): $msg"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# ─── Summary ─────────────────────────────────────────────────────────────────
|
|
128
|
+
if ($script:Failures.Count -gt 0) {
|
|
129
|
+
Write-Host ''
|
|
130
|
+
Write-Host "FAILED: $($script:Failures -join ', ')" -ForegroundColor Red
|
|
131
|
+
exit 1
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
Write-Host ''
|
|
135
|
+
Write-Host 'All Windows smoke checks passed.' -ForegroundColor Green
|
|
136
|
+
exit 0
|
|
@@ -548,9 +548,30 @@ fi
|
|
|
548
548
|
# 2. Slow-init tools that are present but NOT wrapped in _cached_eval.
|
|
549
549
|
# Each of these runs uncached on every shell start; common offenders eat
|
|
550
550
|
# 100-500ms apiece on a populated dev machine.
|
|
551
|
+
#
|
|
552
|
+
# Only flag tools that actually emit shell init via `<tool> init <shell>`
|
|
553
|
+
# (or equivalent) and would benefit from caching that output. Plain CLIs
|
|
554
|
+
# like gh/cargo/pnpm/yarn don't have init eval; their completions are
|
|
555
|
+
# cached separately under $ZSH_COMPLETIONS_DIR.
|
|
551
556
|
unwrapped=""
|
|
552
|
-
for tool in nvm fnm pyenv rbenv jenv asdf sdkman conda kubectl helm
|
|
557
|
+
for tool in nvm fnm pyenv rbenv jenv asdf sdkman conda kubectl helm thefuck broot mcfly direnv; do
|
|
553
558
|
command -v "$tool" >/dev/null 2>&1 || continue
|
|
559
|
+
# If the tool is installed but no shell config sources or evals its
|
|
560
|
+
# init (no `$tool env`, `$tool init`, `$tool.sh`, lazy-load stub), it
|
|
561
|
+
# isn't adding startup cost — skip the warning.
|
|
562
|
+
init_referenced=0
|
|
563
|
+
if grep -rIlqE "\\b${tool}([[:space:]]+(env|init|hook)|\\.sh|_lazy_load_${tool}|_dot_lazy[[:space:]]+${tool})" \
|
|
564
|
+
"$HOME/.config/zsh" "$HOME/.config/fish" "$HOME/.config/shell" 2>/dev/null; then
|
|
565
|
+
init_referenced=1
|
|
566
|
+
fi
|
|
567
|
+
((init_referenced == 0)) && continue
|
|
568
|
+
# Tools we lazy-load via shell stubs don't need init-eval cache files.
|
|
569
|
+
case "$tool" in
|
|
570
|
+
fnm | nvm | sdkman)
|
|
571
|
+
grep -rIlqE "_lazy_load_${tool}|_dot_lazy[[:space:]]+${tool}" \
|
|
572
|
+
"$HOME/.config/zsh" "$HOME/.config/fish" 2>/dev/null && continue
|
|
573
|
+
;;
|
|
574
|
+
esac
|
|
554
575
|
found=0
|
|
555
576
|
for shell_dir in zsh bash fish; do
|
|
556
577
|
case "$shell_dir" in
|
|
@@ -585,27 +606,36 @@ if command -v zsh >/dev/null 2>&1; then
|
|
|
585
606
|
fi
|
|
586
607
|
fi
|
|
587
608
|
|
|
588
|
-
# 4. PATH length. Each entry is searched on every command resolution
|
|
589
|
-
#
|
|
609
|
+
# 4. PATH length. Each entry is searched on every command resolution.
|
|
610
|
+
# A mise-managed dev machine routinely adds 50+ entries (one per tool
|
|
611
|
+
# install path), so the warn/fail thresholds are set higher than a
|
|
612
|
+
# lean baseline (40) would suggest.
|
|
590
613
|
path_count=$(printf '%s' "${PATH:-}" | tr ':' '\n' | grep -c . || true)
|
|
591
|
-
if [[ "$path_count" -le
|
|
614
|
+
if [[ "$path_count" -le 60 ]]; then
|
|
592
615
|
_ok "PATH length" "$path_count entries"
|
|
593
|
-
elif [[ "$path_count" -le
|
|
616
|
+
elif [[ "$path_count" -le 120 ]]; then
|
|
594
617
|
_warn "PATH length" "$path_count entries — consider pruning"
|
|
595
618
|
else
|
|
596
619
|
_fail "PATH length" "$path_count entries — likely slowing every command"
|
|
597
620
|
fi
|
|
598
621
|
|
|
599
622
|
# 5. Shell coverage. Surface installed shells that the project's caching
|
|
600
|
-
# infrastructure
|
|
623
|
+
# infrastructure doesn't currently maintain caches for. zsh/bash/fish/nu
|
|
624
|
+
# all have a `_cached_eval` analogue; pwsh does not.
|
|
601
625
|
shells_unmanaged=""
|
|
602
626
|
for sh in nu pwsh; do
|
|
603
|
-
command -v "$sh" >/dev/null 2>&1
|
|
627
|
+
command -v "$sh" >/dev/null 2>&1 || continue
|
|
628
|
+
case "$sh" in
|
|
629
|
+
nu)
|
|
630
|
+
[[ -f "$HOME/.config/nushell/cached_eval.nu" ]] && continue
|
|
631
|
+
;;
|
|
632
|
+
esac
|
|
633
|
+
shells_unmanaged="${shells_unmanaged:+$shells_unmanaged, }$sh"
|
|
604
634
|
done
|
|
605
635
|
if [[ -z "$shells_unmanaged" ]]; then
|
|
606
|
-
_ok "shell coverage" "all installed shells
|
|
636
|
+
_ok "shell coverage" "all installed shells have _cached_eval support"
|
|
607
637
|
else
|
|
608
|
-
_warn "shell coverage" "$shells_unmanaged installed —
|
|
638
|
+
_warn "shell coverage" "$shells_unmanaged installed — no _cached_eval helper"
|
|
609
639
|
fi
|
|
610
640
|
|
|
611
641
|
# 6. Zsh hook count. Heavy precmd/preexec functions compound per-prompt.
|
|
@@ -218,14 +218,15 @@ EOF
|
|
|
218
218
|
checkpoint_id="$(basename "$checkpoint_file" .json)"
|
|
219
219
|
ui_info "Agent mode" "$name"
|
|
220
220
|
dot_agent_session_log "run_start" "$name" "running" "argv=$*" "checkpoint_id=$checkpoint_id"
|
|
221
|
-
set +e
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if
|
|
226
|
-
|
|
227
|
-
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=$?
|
|
228
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"
|
|
229
230
|
fi
|
|
230
231
|
return "$exit_code"
|
|
231
232
|
;;
|
|
@@ -335,14 +336,12 @@ EOF
|
|
|
335
336
|
[[ "${#replay_argv[@]}" -gt 0 ]] || die "Checkpoint has no replayable command: $checkpoint_id"
|
|
336
337
|
_agent_apply_profile_env "$replay_profile"
|
|
337
338
|
dot_agent_session_log "checkpoint_replay" "$replay_profile" "running" "checkpoint_id=$checkpoint_id"
|
|
338
|
-
|
|
339
|
-
"${replay_argv[@]}"
|
|
340
|
-
|
|
341
|
-
set -e
|
|
342
|
-
if [[ "$exit_code" -eq 0 ]]; then
|
|
343
|
-
dot_agent_session_log "checkpoint_replay_finish" "$replay_profile" "ok" "checkpoint_id=$checkpoint_id" "exit_code=$exit_code"
|
|
344
|
-
else
|
|
339
|
+
local exit_code=0
|
|
340
|
+
if ! "${replay_argv[@]}"; then
|
|
341
|
+
exit_code=$?
|
|
345
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"
|
|
346
345
|
fi
|
|
347
346
|
return "$exit_code"
|
|
348
347
|
;;
|
|
@@ -382,16 +381,14 @@ EOF
|
|
|
382
381
|
export DOT_AGENT_PARENT_PROFILE="$current_profile"
|
|
383
382
|
dot_agent_session_log "delegate_start" "$delegate_profile" "running" "delegate=$delegate_name" "parent=$current_profile" "timeout=$delegate_timeout"
|
|
384
383
|
ui_info "Delegating" "$delegate_name (profile: $delegate_profile, timeout: ${delegate_timeout}s)"
|
|
385
|
-
|
|
386
|
-
timeout "$delegate_timeout" "$@"
|
|
387
|
-
|
|
388
|
-
set -e
|
|
389
|
-
if [[ "$exit_code" -eq 0 ]]; then
|
|
390
|
-
dot_agent_session_log "delegate_finish" "$delegate_profile" "ok" "delegate=$delegate_name" "exit_code=$exit_code"
|
|
391
|
-
ui_ok "Delegate" "$delegate_name completed"
|
|
392
|
-
else
|
|
384
|
+
local exit_code=0
|
|
385
|
+
if ! timeout "$delegate_timeout" "$@"; then
|
|
386
|
+
exit_code=$?
|
|
393
387
|
dot_agent_session_log "delegate_finish" "$delegate_profile" "failed" "delegate=$delegate_name" "exit_code=$exit_code"
|
|
394
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"
|
|
395
392
|
fi
|
|
396
393
|
return "$exit_code"
|
|
397
394
|
;;
|