CascadingDev_simplified/assets/hooks/pre-commit

125 lines
3.8 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#
# CascadingDev Pre-commit Hook
# =============================
# This hook provides safety checks during git commits.
#
# What it does:
# 1. Scans for potential secrets in staged changes
# 2. Creates companion summary files (.sum.md) for discussion files
#
# Environment Variables:
# CDEV_SKIP_HOOK=1 Skip all checks (hook exits immediately)
# CDEV_SKIP_SUMMARIES=1 Skip summary file generation
#
# Safety: Exits on errors to prevent broken commits
set -euo pipefail
if [[ -n "${CDEV_SKIP_HOOK:-}" ]]; then
exit 0
fi
# Navigate to git repository root so all file paths work correctly
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo ".")"
cd "$ROOT"
# ============================================================================
# CRITICAL: Acquire Hook Execution Lock
# ============================================================================
# Prevents concurrent hook executions from corrupting Git repository.
# Race condition scenario:
# - Process A runs `git add file1.md`, computes blob SHA, starts writing to .git/objects/
# - Process B runs `git add file2.md` concurrently
# - Blob object creation fails, leaving orphaned SHA in index
# - Result: "error: invalid object 100644 <SHA> for '<file>'"
#
# Solution: Use flock to ensure only one hook instance runs at a time.
# The lock is automatically released when this script exits.
# ============================================================================
LOCK_FILE="${ROOT}/.git/hooks/pre-commit.lock"
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
echo >&2 "[pre-commit] Another pre-commit hook is running. Waiting for lock..."
flock 9 # Block until lock is available
echo >&2 "[pre-commit] Lock acquired, continuing..."
fi
# Cleanup: Remove lock file on exit
trap 'rm -f "$LOCK_FILE"' EXIT
# -------- collect staged files ----------
mapfile -t STAGED < <(git diff --cached --name-only --diff-filter=AM || true)
[ "${#STAGED[@]}" -eq 0 ] && exit 0
# -------- tiny secret scan (fast, regex only) ----------
# Abort commit if staged changes contain potential secrets matching common patterns
DIFF="$(git diff --cached)"
if echo "$DIFF" | grep -Eqi '(api[_-]?key|secret|access[_-]?token|private[_-]?key)[:=]\s*[A-Za-z0-9_\-]{12,}'; then
echo >&2 "[pre-commit] Possible secret detected in staged changes."
echo >&2 " If false positive, commit with --no-verify and add an allowlist later."
exit 11
fi
# -------- ensure discussion summaries exist (companion files) ----------
if [[ -z "${CDEV_SKIP_SUMMARIES:-}" ]]; then
ensure_summary() {
local disc="$1"
local dir; dir="$(dirname "$disc")"
local sum="$dir/$(basename "$disc" .md).sum.md"
if [ ! -f "$sum" ]; then
cat > "$sum" <<'EOF'
# Summary — <Stage Title>
<!-- SUMMARY:DECISIONS START -->
## Decisions (ADR-style)
- (none yet)
<!-- SUMMARY:DECISIONS END -->
<!-- SUMMARY:OPEN_QUESTIONS START -->
## Open Questions
- (none yet)
<!-- SUMMARY:OPEN_QUESTIONS END -->
<!-- SUMMARY:AWAITING START -->
## Awaiting Replies
- (none yet)
<!-- SUMMARY:AWAITING END -->
<!-- SUMMARY:ACTION_ITEMS START -->
## Action Items
- (none yet)
<!-- SUMMARY:ACTION_ITEMS END -->
<!-- SUMMARY:VOTES START -->
## Votes (latest per participant)
READY: 0 • CHANGES: 0 • REJECT: 0
- (no votes yet)
<!-- SUMMARY:VOTES END -->
<!-- SUMMARY:TIMELINE START -->
## Timeline (most recent first)
- <YYYY-MM-DD HH:MM> <name>: <one-liner>
<!-- SUMMARY:TIMELINE END -->
<!-- SUMMARY:LINKS START -->
## Links
- Related PRs:
- Commits:
- Design/Plan: ../design/design.md
<!-- SUMMARY:LINKS END -->
EOF
git add "$sum"
fi
}
# Process each staged discussion file and ensure it has a summary
for f in "${STAGED[@]}"; do
case "$f" in
Docs/features/*/discussions/*.discussion.md) ensure_summary "$f";;
esac
done
fi
exit 0