From d8b2d7f51beb31e539a6dd55a5edda2e89bbbdf3 Mon Sep 17 00:00:00 2001 From: Garret Premo Date: Tue, 4 Aug 2026 02:04:31 -0400 Subject: [PATCH 1/3] chore: add shellcheck gate to CI (#132) Adds a shellcheck job pinned to v0.10.0 (sha256-verified download) that runs --severity=warning over scripts/**/*.sh and .claude/skills/**/scripts/*.sh (37 files), and extends the pull_request paths filter so shell-only PRs trigger CI. Fixes both pre-existing SC2034 findings in scripts/ship.sh by renaming the unused retry-loop counters to `_`. Each glob group is checked for an empty match separately so a moved or renamed directory fails the job loudly instead of silently checking a subset of files. The download uses --fail/--retry so a transient GitHub outage errors clearly instead of being mistaken for a checksum mismatch, and stages the tarball/extraction under $RUNNER_TEMP to keep the checkout tree clean. --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++++++++++++++ scripts/ship.sh | 8 ++++---- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33fc1b2..afa3cd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,8 @@ on: - 'bun.lockb' - 'tsconfig*.json' - '.github/workflows/ci.yml' + - 'scripts/**' + - '.claude/skills/**/scripts/**' jobs: lint: @@ -26,6 +28,47 @@ jobs: - run: bun install - run: bun run lint + shellcheck: + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v6 + - name: Install shellcheck v0.10.0 + run: | + set -euo pipefail + url="https://github.com/koalaman/shellcheck/releases/download/v0.10.0/shellcheck-v0.10.0.linux.x86_64.tar.xz" + sha256="6c881ab0698e4e6ea235245f22832860544f17ba386442fe7e9d629f8cbedf87" + curl -sSL --fail --retry 3 --retry-all-errors "$url" -o "$RUNNER_TEMP/shellcheck.tar.xz" + echo "$sha256 $RUNNER_TEMP/shellcheck.tar.xz" | sha256sum -c - + tar -xJf "$RUNNER_TEMP/shellcheck.tar.xz" -C "$RUNNER_TEMP" + echo "$RUNNER_TEMP/shellcheck-v0.10.0" >> "$GITHUB_PATH" + - name: Run shellcheck + run: | + set -euo pipefail + # severity=warning: `error` is a no-op gate (0 findings measured against + # this tree); `warning` catches the real issues (2 SC2034 in scripts/ship.sh, + # both fixed) without the noise of `style` (5). No --enable=all: + # SC2310/SC2311 fire zero times here, so enabling `all` would only add + # ~35 info + ~592 style findings for no signal on the case it was + # proposed for. + shopt -s nullglob globstar + + script_files=(scripts/**/*.sh) + if [ "${#script_files[@]}" -eq 0 ]; then + echo "::error::scripts/**/*.sh matched no files" + exit 1 + fi + + skill_files=(.claude/skills/**/scripts/*.sh) + if [ "${#skill_files[@]}" -eq 0 ]; then + echo "::error::.claude/skills/**/scripts/*.sh matched no files" + exit 1 + fi + + shellcheck --severity=warning "${script_files[@]}" "${skill_files[@]}" + unit-tests: strategy: matrix: diff --git a/scripts/ship.sh b/scripts/ship.sh index 7b4ae6a..1355cf1 100755 --- a/scripts/ship.sh +++ b/scripts/ship.sh @@ -38,9 +38,9 @@ promote_issues() { # GraphQL, which is eventually consistent — mergeCommit is briefly null. Retry # rather than hard-stop: past this point a bail leaves the release unpublished. resolve_merge_sha() { - local pr="$1" attempt + local pr="$1" _ MERGE_SHA="" - for attempt in $(seq 1 5); do + for _ in $(seq 1 5); do MERGE_SHA=$(gh pr view "$pr" --json mergeCommit --jq '.mergeCommit.oid // empty' 2>/dev/null || true) [ -n "$MERGE_SHA" ] && [ "$MERGE_SHA" != "null" ] && return 0 sleep 3 @@ -58,7 +58,7 @@ resolve_merge_sha() { tag_and_publish() { local sha="$1" version="$2" local tag="v$version" - local existing run attempt + local existing run _ # The merge commit is created server-side — fetch before tagging it. git fetch origin main --quiet || { @@ -102,7 +102,7 @@ tag_and_publish() { # registration lag, then fail loudly rather than exiting 0 on a guess. info "Waiting for publish workflow..." run="" - for attempt in $(seq 1 30); do + for _ in $(seq 1 30); do run=$(gh run list --workflow publish.yml --commit "$sha" --limit 1 --json databaseId --jq '.[0].databaseId // empty' 2>/dev/null || true) [ -n "$run" ] && break sleep 10 From 0805c6dd8ca2b26acd883a07ab434cd750face69 Mon Sep 17 00:00:00 2001 From: Garret Premo Date: Tue, 4 Aug 2026 02:25:47 -0400 Subject: [PATCH 2/3] chore: close glob asymmetry and log checked counts (#132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses non-blocking review feedback on #143: - .claude/skills/**/scripts/*.sh was not recursive while scripts/**/*.sh was, so a future .claude/skills/foo/scripts/lib/helper.sh would have been skipped silently — the per-group emptiness guard cannot catch it because the group still has 26 other files. Same failure mode the guards were added to prevent. Still resolves to the same 37 files today. - shellcheck is silent when clean, leaving no record in the log of what was actually checked. Echo the per-group counts so the run is self-auditing and a partial-coverage regression that leaves both groups non-empty is visible. - The 'style (5)' figure in the rationale comment was ambiguous about which tree it described. Measured on the same 37-file invocation CI uses: pre-change was warning=1 style=6, post-change is warning=0 style=5. The comment now states it is the current-tree number and names the codes. --- .github/workflows/ci.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afa3cd9..6e4d62e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,12 +47,13 @@ jobs: - name: Run shellcheck run: | set -euo pipefail - # severity=warning: `error` is a no-op gate (0 findings measured against - # this tree); `warning` catches the real issues (2 SC2034 in scripts/ship.sh, - # both fixed) without the noise of `style` (5). No --enable=all: - # SC2310/SC2311 fire zero times here, so enabling `all` would only add - # ~35 info + ~592 style findings for no signal on the case it was - # proposed for. + # severity=warning: `error` is a no-op gate (0 findings against this + # tree, before and after); `warning` caught the real issues (2 SC2034 + # in scripts/ship.sh, both fixed here) without the noise of `style` + # (5 on the current tree — all SC1091/SC2016/SC2317, none real + # defects). No --enable=all: SC2310/SC2311 fire zero times here, so + # enabling `all` would only add ~35 info + ~592 style findings for no + # signal on the case it was proposed for. shopt -s nullglob globstar script_files=(scripts/**/*.sh) @@ -61,12 +62,18 @@ jobs: exit 1 fi - skill_files=(.claude/skills/**/scripts/*.sh) + skill_files=(.claude/skills/**/scripts/**/*.sh) if [ "${#skill_files[@]}" -eq 0 ]; then - echo "::error::.claude/skills/**/scripts/*.sh matched no files" + echo "::error::.claude/skills/**/scripts/**/*.sh matched no files" exit 1 fi + # shellcheck is silent when clean, so without this the log records no + # evidence of WHAT was checked. It also surfaces a partial-coverage + # regression that leaves both groups non-empty — the case the guards + # above cannot see. + echo "shellcheck: ${#script_files[@]} in scripts/, ${#skill_files[@]} in .claude/skills/" + shellcheck --severity=warning "${script_files[@]}" "${skill_files[@]}" unit-tests: From 6859876e85fbc5bde4ddf69fcdff812fa3544eb2 Mon Sep 17 00:00:00 2001 From: Garret Premo Date: Tue, 4 Aug 2026 02:36:06 -0400 Subject: [PATCH 3/3] chore: make the --enable=all figures in the CI comment drift-proof (#132) The comment cited '~35 info + ~592 style'; measured against the current tree they are 37 and 614. Both were tilde-hedged and the ~4% gap never touched the conclusion they support, but precise counts in a comment will keep drifting as scripts are added. State the magnitude, which is what the argument actually rests on, and mark the exact figures as a point-in-time measurement. --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e4d62e..dcd7cdd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,8 +52,9 @@ jobs: # in scripts/ship.sh, both fixed here) without the noise of `style` # (5 on the current tree — all SC1091/SC2016/SC2317, none real # defects). No --enable=all: SC2310/SC2311 fire zero times here, so - # enabling `all` would only add ~35 info + ~592 style findings for no - # signal on the case it was proposed for. + # enabling `all` would only add tens of info and hundreds of style + # findings (37 and 614 when this was written) for no signal on the + # case it was proposed for. shopt -s nullglob globstar script_files=(scripts/**/*.sh)