193 lines
7.7 KiB
Bash
Executable File
193 lines
7.7 KiB
Bash
Executable File
#!/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: process/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="process/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
|