Merge pull request #118 from dan-claude-bot/fix/shellcheck-dotglob

fix: lint the release path — globstar does not descend into dot-directories
This commit is contained in:
Daniel Marin 2026-07-20 14:53:46 +01:00 committed by GitHub
commit cc0a1a9a79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 56 additions and 2 deletions

View file

@ -14,10 +14,35 @@ jobs:
# globstar so a script in a new subdirectory is linted without anyone # globstar so a script in a new subdirectory is linted without anyone
# remembering to edit this list; bin/* covers the extensionless entrypoint # remembering to edit this list; bin/* covers the extensionless entrypoint
# (bin/box). The file list is printed so under-coverage shows up in the log. # (bin/box). The file list is printed so under-coverage shows up in the log.
#
# dotglob is not decoration (#116): globstar makes `**` descend, but a
# glob still does not MATCH a dot-prefixed name, so `**/` never entered
# `.github/` — and the whole release path (changelog-armed.sh, which
# gates every PR, release-notes.sh, labels-reconcile.sh) went unlinted
# while the comment above told the next author it was covered.
# Measured on this tree: dotglob adds exactly those three and nothing
# else — a checkout's `.git` carries no `*.sh` (its hooks ship as
# `*.sample`), so `**/*.sh` does not wander into it.
#
# The sweep below is the CLASS check, same shape as the eof_guard_sweep
# in test/cli.sh (#112): the one-time fix is `dotglob`, but what keeps
# the gap from reopening is asserting that every TRACKED script is in
# the set actually handed to shellcheck. `git ls-files` is the authority
# on what the repo contains; if the glob ever drifts from it again —
# another dot-directory, another shopt subtlety — CI says which files
# escaped instead of quietly linting a subset and passing.
run: | run: |
shopt -s globstar shopt -s globstar dotglob
files=(bin/* **/*.sh) files=(bin/* **/*.sh)
printf 'shellcheck: %s\n' "${files[@]}" printf 'shellcheck: %s\n' "${files[@]}"
missing="$(comm -13 \
<(printf '%s\n' "${files[@]}" | sort -u) \
<(git ls-files '*.sh' | sort -u))"
if [ -n "$missing" ]; then
echo "tracked scripts the shellcheck sweep does not cover (#116):"
printf '%s\n' "$missing" | sed 's/^/ /'
exit 1
fi
shellcheck -x "${files[@]}" shellcheck -x "${files[@]}"
- name: cli tests - name: cli tests
run: bash test/cli.sh run: bash test/cli.sh

View file

@ -59,6 +59,29 @@ which records not just what changed but what each drill run proved.
operator copy-pastes is as wrong as a role box executes, and it fails operator copy-pastes is as wrong as a role box executes, and it fails
later and further from the cause. later and further from the cause.
### Fixed
- **CI's shellcheck sweep never lints `.github/scripts/*.sh`** (#116) —
`globstar` makes `**` descend into subdirectories, but a glob still does
not *match* a dot-prefixed name, so `**/` never entered `.github/`. The
three scripts that escaped are the release path: `changelog-armed.sh` (the
#108/#110 guard that gates every PR, and had never been linted),
`release-notes.sh` (which produces the published release body), and
`labels-reconcile.sh` (the label state machine) — while the step's own
comment promised that "a script in a new subdirectory is linted without
anyone remembering to edit this list". Latent, not broken: all three pass
shellcheck as-is, so this lands as a no-op on current code and the fix is
that a regression in them would now be caught. `dotglob` alongside
`globstar` closes it, measured rather than assumed — it adds exactly those
three and nothing else, a checkout's `.git` carrying no `*.sh` (its hooks
ship as `*.sample`). Paired with a CLASS check in the same shape as the
`eof_guard_sweep` of #112: the sweep now compares the globbed set against
`git ls-files '*.sh'` and fails naming any tracked script it does not
cover, so the gap cannot reopen silently the next time a dot-directory or
a shopt subtlety hides one. `eof_guard_sweep` itself carried the identical
blind spot — it rebuilds the same glob — and is widened the same way.
## 0.8.0 — 2026-07-19 ## 0.8.0 — 2026-07-19
### Added ### Added

View file

@ -967,7 +967,13 @@ fi
# guard is not. # guard is not.
eof_guard_sweep() { eof_guard_sweep() {
local f n line bad=0 files local f n line bad=0 files
files="$(cd "$ROOT" && shopt -s globstar && printf '%s\n' bin/* ./**/*.sh | sed 's|^\./||' | sort -u)" # dotglob alongside globstar for the same reason CI's shellcheck step carries
# it (#116): globstar descends, but a glob does not MATCH a dot-prefixed name,
# so this sweep skipped '.github/scripts/*.sh' — the release path — exactly as
# the linter did. Those three set errexit, so they are in scope for this class
# by construction; today none of them reads at all, which is why widening the
# set is a no-op on current code rather than a bug fix.
files="$(cd "$ROOT" && shopt -s globstar dotglob && printf '%s\n' bin/* ./**/*.sh | sed 's|^\./||' | sort -u)"
while IFS= read -r f; do while IFS= read -r f; do
[ -f "$ROOT/$f" ] || continue [ -f "$ROOT/$f" ] || continue
grep -qE '^[[:space:]]*set[[:space:]]+-[a-zA-Z]*e' "$ROOT/$f" || continue grep -qE '^[[:space:]]*set[[:space:]]+-[a-zA-Z]*e' "$ROOT/$f" || continue