mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
* fix(hooks): stamp gsd-hook-version in .sh hooks and fix stale detection regex (#2136, #2206) Three-part fix for the persistent "⚠ stale hooks — run /gsd-update" false positive that appeared on every session after a fresh install. Root cause: the stale-hook detector (gsd-check-update.js) could only match the JS comment syntax // in its version regex — never the bash # syntax used in .sh hooks. And the bash hooks had no version header at all, so they always landed in the "unknown / stale" branch regardless. Neither partial fix (PR #2207 regex only, PR #2215 install stamping only) was sufficient alone: - Regex fix without install stamping: hooks install with literal "{{GSD_VERSION}}", the {{-guard silently skips them, bash hook staleness permanently undetectable after future updates. - Install stamping without regex fix: hooks are stamped correctly with "# gsd-hook-version: 1.36.0" but the detector's // regex can't read it; still falls to the unknown/stale branch on every session. Fix: 1. Add "# gsd-hook-version: {{GSD_VERSION}}" header to gsd-phase-boundary.sh, gsd-session-state.sh, gsd-validate-commit.sh 2. Extend install.js (both bundled and Codex paths) to substitute {{GSD_VERSION}} in .sh files at install time (same as .js hooks) 3. Extend gsd-check-update.js versionMatch regex to handle bash "#" comment syntax: /(?:\/\/|#) gsd-hook-version:\s*(.+)/ Tests: 11 new assertions across 5 describe blocks covering all three fix parts independently plus an E2E install+detect round-trip. 3885/3885 pass. Approach credit: PR #2207 (j2h4u / Maxim Brashenko) for the regex fix; PR #2215 (nitsan2dots) for the install.js substitution approach. Closes #2136, #2206, #2209, #2210, #2212 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(hooks): extract check-update worker to dedicated file, eliminating template-literal regex escaping Move stale-hook detection logic from inline `node -e '<template literal>'` subprocess to a standalone gsd-check-update-worker.js. Benefits: - Regex is plain JS with no double-escaping (root cause of the (?:\\/\\/|#) confusion) - Worker is independently testable and can be read directly by tests - Uses execFileSync (array args) to satisfy security hook that blocks execSync - MANAGED_HOOKS now includes gsd-check-update-worker.js itself Update tests to read worker file instead of main hook for regex/configDir assertions. All 3886 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
49 lines
2.0 KiB
Bash
Executable File
49 lines
2.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# gsd-hook-version: {{GSD_VERSION}}
|
|
# gsd-validate-commit.sh — PreToolUse hook: enforce Conventional Commits format
|
|
# Blocks git commit commands with non-conforming messages (exit 2).
|
|
# Allows conforming messages and all non-commit commands (exit 0).
|
|
# Uses Node.js for JSON parsing (always available in GSD projects, no jq dependency).
|
|
#
|
|
# OPT-IN: This hook is a no-op unless config.json has hooks.community: true.
|
|
# Enable with: "hooks": { "community": true } in .planning/config.json
|
|
|
|
# Check opt-in config — exit silently if not enabled
|
|
if [ -f .planning/config.json ]; then
|
|
ENABLED=$(node -e "try{const c=require('./.planning/config.json');process.stdout.write(c.hooks?.community===true?'1':'0')}catch{process.stdout.write('0')}" 2>/dev/null)
|
|
if [ "$ENABLED" != "1" ]; then exit 0; fi
|
|
else
|
|
exit 0
|
|
fi
|
|
|
|
INPUT=$(cat)
|
|
|
|
# Extract command from JSON using Node (handles escaping correctly, no jq needed)
|
|
CMD=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{process.stdout.write(JSON.parse(d).tool_input?.command||'')}catch{}})" 2>/dev/null)
|
|
|
|
# Only check git commit commands
|
|
if [[ "$CMD" =~ ^git[[:space:]]+commit ]]; then
|
|
# Extract message from -m flag
|
|
MSG=""
|
|
if [[ "$CMD" =~ -m[[:space:]]+\"([^\"]+)\" ]]; then
|
|
MSG="${BASH_REMATCH[1]}"
|
|
elif [[ "$CMD" =~ -m[[:space:]]+\'([^\']+)\' ]]; then
|
|
MSG="${BASH_REMATCH[1]}"
|
|
fi
|
|
|
|
if [ -n "$MSG" ]; then
|
|
SUBJECT=$(echo "$MSG" | head -1)
|
|
# Validate Conventional Commits format
|
|
if ! [[ "$SUBJECT" =~ ^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\(.+\))?:[[:space:]].+ ]]; then
|
|
echo '{"decision": "block", "reason": "Commit message must follow Conventional Commits: <type>(<scope>): <subject>. Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore. Subject must be <=72 chars, lowercase, imperative mood, no trailing period."}'
|
|
exit 2
|
|
fi
|
|
if [ ${#SUBJECT} -gt 72 ]; then
|
|
echo '{"decision": "block", "reason": "Commit subject must be 72 characters or less."}'
|
|
exit 2
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
exit 0
|