Skip to content

Commit 93d7254

Browse files
committed
feat: add gh-aw compilation hooks for workflow editing
- PostToolUse hook reminds to compile after .md workflow edits - Stop hook blocks if workflows have stale/missing lock files - Both hooks detect version drift between installed gh-aw and lock files
1 parent 5da296f commit 93d7254

3 files changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env bash
2+
# Stop hook: check for uncompiled gh-aw workflow changes before Claude finishes.
3+
# Blocks if any .github/workflows/*.md files have been modified but their
4+
# corresponding .lock.yml files are stale or missing.
5+
6+
set -euo pipefail
7+
8+
INPUT=$(cat)
9+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
10+
11+
if [[ -z "$CWD" ]]; then
12+
exit 0
13+
fi
14+
15+
cd "$CWD"
16+
17+
# Check if this repo has any gh-aw workflow .md files
18+
WORKFLOW_DIR=".github/workflows"
19+
if [[ ! -d "$WORKFLOW_DIR" ]]; then
20+
exit 0
21+
fi
22+
23+
# Find .md workflow files (skip if none exist)
24+
MD_FILES=$(find "$WORKFLOW_DIR" -maxdepth 1 -name '*.md' -type f 2>/dev/null || true)
25+
if [[ -z "$MD_FILES" ]]; then
26+
exit 0
27+
fi
28+
29+
# Check for modified .md files whose .lock.yml is older or missing
30+
STALE=()
31+
MISSING=()
32+
33+
while IFS= read -r md; do
34+
wf_id=$(basename "$md" .md)
35+
lock="${WORKFLOW_DIR}/${wf_id}.lock.yml"
36+
37+
if [[ ! -f "$lock" ]]; then
38+
MISSING+=("$wf_id")
39+
elif [[ "$md" -nt "$lock" ]]; then
40+
STALE+=("$wf_id")
41+
fi
42+
done <<<"$MD_FILES"
43+
44+
# Also check version drift
45+
INSTALLED=""
46+
if command -v gh &>/dev/null && gh extension list 2>/dev/null | grep -q 'gh-aw'; then
47+
INSTALLED=$(gh aw version 2>&1 | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true)
48+
fi
49+
50+
DRIFT=()
51+
if [[ -n "$INSTALLED" ]]; then
52+
while IFS= read -r md; do
53+
wf_id=$(basename "$md" .md)
54+
lock="${WORKFLOW_DIR}/${wf_id}.lock.yml"
55+
if [[ -f "$lock" ]]; then
56+
lock_ver=$(grep -oE 'compiler_version":"v[0-9]+\.[0-9]+\.[0-9]+"' "$lock" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true)
57+
if [[ -n "$lock_ver" && "$lock_ver" != "$INSTALLED" ]]; then
58+
DRIFT+=("${wf_id} (${lock_ver})")
59+
fi
60+
fi
61+
done <<<"$MD_FILES"
62+
fi
63+
64+
# If nothing to report, exit clean
65+
if [[ ${#STALE[@]} -eq 0 && ${#MISSING[@]} -eq 0 && ${#DRIFT[@]} -eq 0 ]]; then
66+
exit 0
67+
fi
68+
69+
# Build report
70+
MSG="[gh-aw] Uncompiled workflow changes detected:"
71+
72+
if [[ ${#MISSING[@]} -gt 0 ]]; then
73+
MSG+="\n MISSING lock files (never compiled):"
74+
for wf in "${MISSING[@]}"; do
75+
MSG+="\n - ${wf}.md (run: gh aw compile ${wf})"
76+
done
77+
fi
78+
79+
if [[ ${#STALE[@]} -gt 0 ]]; then
80+
MSG+="\n STALE lock files (.md newer than .lock.yml):"
81+
for wf in "${STALE[@]}"; do
82+
MSG+="\n - ${wf}.md (run: gh aw compile ${wf})"
83+
done
84+
fi
85+
86+
if [[ ${#DRIFT[@]} -gt 0 ]]; then
87+
MSG+="\n VERSION DRIFT (compiled with older gh-aw, installed: ${INSTALLED}):"
88+
for wf in "${DRIFT[@]}"; do
89+
MSG+="\n - ${wf}"
90+
done
91+
fi
92+
93+
# Output the report — Claude will see this and can act on it
94+
echo -e "$MSG"
95+
96+
# Return structured decision to keep Claude working
97+
cat <<'EOF'
98+
{"decision": "block", "reason": "gh-aw workflow .md files have uncompiled changes or version drift. Run `gh aw compile <workflow-id>` for each stale workflow and stage the resulting .lock.yml files before finishing."}
99+
EOF
100+
101+
exit 0

hooks/check-workflow-edit.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env bash
2+
# PostToolUse hook: remind to compile gh-aw workflows after .md edits
3+
# and check for version drift between installed gh-aw and lock files.
4+
5+
set -euo pipefail
6+
7+
INPUT=$(cat)
8+
9+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
10+
11+
# Only fire for .github/workflows/*.md files
12+
if ! echo "$FILE_PATH" | grep -qE '\.github/workflows/.*\.md$'; then
13+
exit 0
14+
fi
15+
16+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
17+
WF_ID=$(basename "$FILE_PATH" .md)
18+
LOCK_FILE=".github/workflows/${WF_ID}.lock.yml"
19+
20+
# Resolve paths
21+
if [[ -n "$CWD" ]]; then
22+
LOCK_PATH="${CWD}/${LOCK_FILE}"
23+
ACTIONS_LOCK="${CWD}/.github/aw/actions-lock.json"
24+
else
25+
LOCK_PATH="$LOCK_FILE"
26+
ACTIONS_LOCK=".github/aw/actions-lock.json"
27+
fi
28+
29+
# Get installed gh-aw version
30+
INSTALLED=""
31+
if command -v gh &>/dev/null && gh extension list 2>/dev/null | grep -q 'gh-aw'; then
32+
INSTALLED=$(gh aw version 2>&1 | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true)
33+
fi
34+
35+
# Get lock file compiler version
36+
LOCK_VER=""
37+
if [[ -f "$LOCK_PATH" ]]; then
38+
LOCK_VER=$(grep -oE 'compiler_version":"v[0-9]+\.[0-9]+\.[0-9]+"' "$LOCK_PATH" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true)
39+
fi
40+
41+
# Get actions-lock.json pinned version
42+
PINNED=""
43+
if [[ -f "$ACTIONS_LOCK" ]]; then
44+
PINNED=$(grep -oE '"github/gh-aw/actions/setup@v[0-9]+\.[0-9]+\.[0-9]+"' "$ACTIONS_LOCK" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true)
45+
fi
46+
47+
# Build reminder
48+
MSG="[gh-aw] Workflow source modified: ${WF_ID}.md"
49+
MSG+="\n Run: gh aw compile ${WF_ID}"
50+
51+
if [[ -n "$INSTALLED" ]]; then
52+
MSG+="\n Installed gh-aw: ${INSTALLED}"
53+
fi
54+
55+
if [[ -n "$LOCK_VER" && -n "$INSTALLED" && "$LOCK_VER" != "$INSTALLED" ]]; then
56+
MSG+="\n VERSION DRIFT: ${LOCK_FILE} compiled with ${LOCK_VER}, installed is ${INSTALLED}"
57+
fi
58+
59+
if [[ -n "$PINNED" && -n "$INSTALLED" && "$PINNED" != "$INSTALLED" ]]; then
60+
MSG+="\n VERSION DRIFT: actions-lock.json pins ${PINNED}, installed is ${INSTALLED}"
61+
fi
62+
63+
if [[ ! -f "$LOCK_PATH" ]]; then
64+
MSG+="\n WARNING: No lock file exists yet — compilation required before commit"
65+
fi
66+
67+
echo -e "$MSG"
68+
exit 0

hooks/hooks.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"hooks": {
3+
"PostToolUse": [
4+
{
5+
"matcher": "Edit|Write",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/check-workflow-edit.sh",
10+
"statusMessage": "Checking gh-aw workflow status..."
11+
}
12+
]
13+
}
14+
],
15+
"Stop": [
16+
{
17+
"matcher": "",
18+
"hooks": [
19+
{
20+
"type": "command",
21+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/check-uncompiled-workflows.sh",
22+
"statusMessage": "Checking for uncompiled gh-aw workflows..."
23+
}
24+
]
25+
}
26+
]
27+
}
28+
}

0 commit comments

Comments
 (0)