chore(claude): add hooks, skills, and agents for Claude Code (#496)

* chore(claude): add hooks, skills, and agents for Claude Code

Add auto-formatting hooks (ruff, shfmt, prettier, actionlint),
rules.yml edit blocker, 5 skills (/release, /test-action,
/new-action, /validate, /check-pins), and 2 subagents
(action-validator, test-coverage-reviewer). Update CLAUDE.md
with hook documentation.

* fix(claude): add tool availability guards and fix skill docs

Add jq availability checks to hook scripts (block-rules-yml.sh,
post-edit-write.sh) and wrap actionlint call in command -v guard,
consistent with project rules #2 and #10. Fix validate skill to
reflect actual make all pipeline order and note that make test
runs separately.

* fix(claude): correct skill docs per PR review feedback

Fix validate skill description to say "precommit" instead of "test",
and fix check-pins SHA guidance to use origin/main instead of HEAD.

* feat(tools): add SHA-pinning enforcement to check-version-refs

The check-version-refs script previously only displayed existing
SHA-pinned refs but silently skipped non-SHA references. Add a
validation pass that detects and reports any ivuorinen/actions/*
references not using a 40-char hex SHA, exiting 1 on violations.

* fix(tools): fix temp file leak in check-version-refs.sh

Write find output directly to $violations_file instead of
$violations_file.all so the EXIT trap covers cleanup on all
exit paths, not just the happy path.
This commit is contained in:
2026-03-08 04:22:02 +02:00
committed by GitHub
parent 242ecca8f0
commit f995f89a21
13 changed files with 440 additions and 8 deletions

View File

@@ -23,19 +23,43 @@ for tool in find grep sed printf sort cut tr wc; do
fi
done
# --- Validation pass: detect non-SHA-pinned references ---
violations_file=$(safe_mktemp)
trap 'rm -f "$violations_file"' EXIT
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" \
! -path "./_*" ! -path "./.github/*" \
-exec grep -nE '^\s+uses:\s+ivuorinen/actions/' {} /dev/null \; \
>"$violations_file"
violations_found=false
while IFS= read -r match; do
if ! printf '%s\n' "$match" | grep -qE '@[0-9a-f]{40}'; then
if [ "$violations_found" = false ]; then
msg_error "Non-SHA-pinned action references found:"
violations_found=true
fi
printf ' %s\n' "$match" >&2
fi
done <"$violations_file"
if [ "$violations_found" = true ]; then
rm -f "$violations_file"
exit 1
fi
rm -f "$violations_file"
printf '%b' "${BLUE}Current SHA-pinned action references:${NC}\n"
printf '\n'
# Create temp files for processing
temp_file=$(safe_mktemp)
trap 'rm -f "$temp_file"' EXIT
temp_input=$(safe_mktemp)
trap 'rm -f "$temp_file" "$temp_input"' EXIT
# Find all action references and collect SHA|action pairs
# Use input redirection to avoid subshell issues with pipeline
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_input"
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; >"$temp_input"
while IFS= read -r line; do
# Extract action name and SHA using sed
@@ -43,9 +67,9 @@ while IFS= read -r line; do
sha=$(echo "$line" | sed -n 's|.*@\([a-f0-9]\{40\}\).*|\1|p')
if [ -n "$action" ] && [ -n "$sha" ]; then
printf '%s\n' "$sha|$action" >> "$temp_file"
printf '%s\n' "$sha|$action" >>"$temp_file"
fi
done < "$temp_input"
done <"$temp_input"
# Check if we found any references
if [ ! -s "$temp_file" ]; then
@@ -54,7 +78,7 @@ if [ ! -s "$temp_file" ]; then
fi
# Sort by SHA and group
sort "$temp_file" | uniq > "${temp_file}.sorted"
sort "$temp_file" | uniq >"${temp_file}.sorted"
mv "${temp_file}.sorted" "$temp_file"
# Count unique SHAs
@@ -95,7 +119,7 @@ while IFS='|' read -r sha action; do
# Add to current SHA group
actions_list="$actions_list, $action"
fi
done < "$temp_file"
done <"$temp_file"
# Print last SHA group
if [ -n "$current_sha" ]; then