#!/usr/bin/env bash # # CascadingDev Pre-commit Hook # ============================= # This hook orchestrates AI-powered automation during git commits. # # What it does: # 1. Creates companion summary files (.sum.md) for discussion files # 2. Validates that discussion files are append-only (no deletions) # 3. Runs automation/runner.py to generate AI-powered outputs # 4. Runs automation/workflow.py to update vote summaries # # Safety: Exits on errors to prevent broken commits set -euo pipefail # Navigate to git repository root so all file paths work correctly # regardless of where the user ran the commit command ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo ".")" cd "$ROOT" # ============================================================================ # FUNCTION: check_append_only_discussion # ============================================================================ # Validates that changes to discussion files only add content (no deletions). # # Discussion files are append-only to maintain conversation history. # YAML header fields (status, timestamps, etc.) are allowed to change. # # Args: # $1 - Path to discussion file (e.g., "Docs/features/FR_*/discussions/*.discussion.md") # # Returns: # 0 if valid (append-only or header changes only) # 1 if invalid (deletions detected in body content) # # Note: This is a basic implementation using regex. A more robust version # would parse YAML front matter and validate append-only at the line level. # ============================================================================ check_append_only_discussion() { local disc_file="$1" local diff_output # Get the staged diff for this file diff_output=$(git diff --cached "$disc_file") # Look for deletion lines (lines starting with "-" but not "---" file markers) # If we find any, the user may have deleted existing content if echo "$diff_output" | grep -E "^-[^-]" | grep -Ev "^--- a/" | grep -Ev "^\+\+\+ b/"; then echo >&2 "[pre-commit] Error: Deletions or modifications detected in existing lines of $disc_file." echo >&2 " Discussion files must be append-only, except for allowed header fields." return 1 fi # Check for modifications to YAML header fields # These are allowed (e.g., updating status: OPEN → READY_FOR_IMPLEMENTATION) # A more robust check would parse YAML front matter instead of using regex local header_modified=0 if echo "$diff_output" | grep -E "^[-+]" | grep -E "^(status|created|updated|feature_id|stage_id):" > /dev/null; then header_modified=1 fi # If there are additions and no body deletions, we're good # This assumes additions are at the end (append-only) if echo "$diff_output" | grep -E "^\+[^+]" | grep -Ev "^\+\+\+ b/" > /dev/null && [ "$header_modified" -eq 0 ]; then : # Valid: additions only (append-only pattern) fi return 0 } # ============================================================================ # FUNCTION: ensure_summary # ============================================================================ # Creates a companion summary file (.sum.md) for a discussion file if missing. # # Summary files provide structured views of discussions: # - Vote counts and latest votes per participant # - Action items, questions, decisions # - Timeline of major updates # # The Python automation (workflow.py) updates these files, but it expects # them to already exist. This function ensures the initial template is created. # # Args: # $1 - Path to discussion file (e.g., "feature.discussion.md") # # Creates: # - {discussion}.sum.md (e.g., "feature.discussion.sum.md") # - Auto-stages the new file for inclusion in the commit # # Template location: assets/templates/feature.discussion.sum.md # ============================================================================ ensure_summary() { local disc="$1" local dir; dir="$(dirname "$disc")" local sum="$dir/$(basename "$disc" .md).sum.md" local template_path="assets/templates/feature.discussion.sum.md" # Only create if it doesn't exist if [ ! -f "$sum" ]; then # Copy template content to the summary file cat "$template_path" > "$sum" # Auto-stage so it's included in this commit git add "$sum" echo >&2 "[pre-commit] Created summary file: $sum" fi } # ============================================================================ # STEP 1: Collect Staged Files # ============================================================================ # Get all files staged for commit (Added or Modified only) # Exit early if there's nothing to process mapfile -t STAGED < <(git diff --cached --name-only --diff-filter=AM || true) [ "${#STAGED[@]}" -eq 0 ] && exit 0 # ============================================================================ # STEP 2: Process Discussion Files # ============================================================================ # For each staged discussion file: # 1. Ensure companion .sum.md file exists (create from template if needed) # 2. Validate append-only constraint (no deletions to conversation history) # # Discussion file pattern: Docs/features/FR_*/discussions/*.discussion.md # ============================================================================ for f in "${STAGED[@]}"; do case "$f" in Docs/features/*/discussions/*.discussion.md) # Create .sum.md companion file if it doesn't exist ensure_summary "$f" # Validate append-only constraint if ! check_append_only_discussion "$f"; then exit 1 # Abort commit if deletions detected fi ;; esac done # ============================================================================ # STEP 3: Run AI Automation (runner.py) # ============================================================================ # The runner processes staged files according to .ai-rules.yml configuration: # - Reads staged file diffs # - Generates AI prompts with context # - Calls Claude API to generate patches # - Applies patches to output files # - Auto-stages generated files # # Examples: # - request.md → generates feature.discussion.md (initial AI comment + vote) # - feature.discussion.md → may update implementation.discussion.md # # Exit codes: # - 0: Success (or no rules matched, or graceful error handling) # - 1: Fatal error (malformed YAML, missing dependencies, etc.) # # Debug artifacts: .git/ai-rules-debug/*.{raw.out,clean.diff,sanitized.diff,final.diff} # ============================================================================ if [ -f "automation/runner.py" ]; then if ! python3 -m automation.runner; then echo "[pre-commit] automation.runner failed" >&2 exit 1 fi fi # ============================================================================ # STEP 4: Run Workflow Automation (workflow.py) # ============================================================================ # The workflow parses discussion files and updates summary files: # - Extracts VOTE: lines (READY, CHANGES, REJECT) # - Counts votes per participant (latest vote wins) # - Extracts questions, action items, decisions (Q:, TODO:, DECISION:) # - Updates .sum.md files with structured data # - Auto-stages updated summary files # # This runs AFTER runner.py so it can process any discussion files that # were just generated by the AI automation. # # Exit behavior: Always returns 0 (|| true) so vote tracking never blocks commits. # This is intentional - vote summaries are informational only. # ============================================================================ if [ -x "automation/workflow.py" ]; then python3 automation/workflow.py --status || true fi # ============================================================================ # Success - Allow Commit to Proceed # ============================================================================ exit 0