#!/usr/bin/env bash # # KNEL-Football Secure OS - Pre-Commit Hook # Enforces SDLC.md requirements automatically # # This hook runs BEFORE every commit and ensures: # 1. All tests pass # 2. Zero lint warnings # 3. Tests exist for modified code # 4. Documentation is updated for changes # # Reference: docs/SDLC.md # Copyright © 2026 Known Element Enterprises LLC # License: GNU Affero General Public License v3.0 only set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color echo -e "${YELLOW}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${YELLOW}║ SDLC ENFORCEMENT - Pre-Commit Check ║${NC}" echo -e "${YELLOW}╚══════════════════════════════════════════════════════════════╝${NC}" echo "" # Track if any check fails FAILED=0 # Get list of staged files STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM) STAGED_SHELL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(sh|bash)$' || true) # Skip checks if only documentation changes ONLY_DOCS=1 for file in $STAGED_FILES; do if [[ ! "$file" =~ ^docs/ && ! "$file" =~ \.md$ && ! "$file" =~ ^LICENSE ]]; then ONLY_DOCS=0 break fi done if [[ "$ONLY_DOCS" == "1" ]]; then echo -e "${YELLOW}Only documentation changes detected - skipping code checks${NC}" exit 0 fi # ============================================================================= # CHECK 1: Lint (ShellCheck) - Zero warnings required # ============================================================================= echo -e "${YELLOW}[1/4] Running lint checks (shellcheck)...${NC}" if [[ -n "$STAGED_SHELL_FILES" ]]; then LINT_OUTPUT=$(./run.sh lint 2>&1) || { echo -e "${RED}✗ LINT FAILED${NC}" echo "$LINT_OUTPUT" echo "" echo -e "${RED}SDLC VIOLATION: Zero lint warnings required${NC}" echo -e "${RED}Reference: docs/SDLC.md - Code Quality Standards${NC}" FAILED=1 } if [[ $FAILED -eq 0 ]]; then echo -e "${GREEN}✓ Lint passed${NC}" fi else echo -e "${GREEN}✓ No shell files to lint${NC}" fi # ============================================================================= # CHECK 2: Unit Tests - All must pass # ============================================================================= echo -e "${YELLOW}[2/4] Running unit tests...${NC}" TEST_OUTPUT=$(./run.sh test:unit 2>&1) || { echo -e "${RED}✗ UNIT TESTS FAILED${NC}" echo "$TEST_OUTPUT" echo "" echo -e "${RED}SDLC VIOLATION: All tests must pass before commit${NC}" echo -e "${RED}Reference: docs/SDLC.md - TDD Workflow${NC}" FAILED=1 } if [[ $FAILED -eq 0 ]]; then echo -e "${GREEN}✓ Unit tests passed${NC}" fi # ============================================================================= # CHECK 3: Test Coverage - Tests must exist for modified code # ============================================================================= echo -e "${YELLOW}[3/4] Checking test coverage for modified files...${NC}" MISSING_TESTS="" for file in $STAGED_FILES; do # Check if this is a source file that needs tests if [[ "$file" =~ ^src/.*\.sh$ ]]; then basename=$(basename "$file" .sh) test_file="tests/unit/${basename}_test.bats" if [[ ! -f "$test_file" ]]; then MISSING_TESTS="$MISSING_TESTS\n - $file -> expected: $test_file" fi fi # Check if this is a config hook that needs tests if [[ "$file" =~ ^config/hooks/.*\.sh$ ]]; then hookname=$(basename "$file" .sh) # Hooks are tested via integration tests if [[ ! -f "tests/integration/config_test.bats" ]]; then MISSING_TESTS="$MISSING_TESTS\n - $file -> integration tests missing" fi fi done if [[ -n "$MISSING_TESTS" ]]; then echo -e "${RED}✗ MISSING TEST COVERAGE${NC}" echo -e "The following files lack corresponding tests:" echo -e "$MISSING_TESTS" echo "" echo -e "${RED}SDLC VIOLATION: TDD requires tests for all code${NC}" echo -e "${RED}Reference: docs/SDLC.md - Test-Driven Development${NC}" FAILED=1 else echo -e "${GREEN}✓ All modified files have tests${NC}" fi # ============================================================================= # CHECK 4: Documentation Sync - PRD updated for new features # ============================================================================= echo -e "${YELLOW}[4/4] Checking documentation synchronization...${NC}" # Check for new function definitions in staged shell files NEW_FUNCTIONS="" for file in $STAGED_SHELL_FILES; do # Extract function names from staged changes FUNCTIONS=$(git diff --cached "$file" | grep -E '^\+.*\(\)\s*\{' | sed 's/^\+//;s/().*//;s/\s//g' || true) if [[ -n "$FUNCTIONS" ]]; then NEW_FUNCTIONS="$NEW_FUNCTIONS\n $file: $(echo "$FUNCTIONS" | tr '\n' ' ')" fi done # If new functions added, check if PRD, docs, or JOURNAL were updated if [[ -n "$NEW_FUNCTIONS" ]]; then DOCS_UPDATED=$(echo "$STAGED_FILES" | grep -E '^(docs/|PRD\.md|JOURNAL\.md)' || true) if [[ -z "$DOCS_UPDATED" ]]; then echo -e "${YELLOW}⚠ New functions detected without documentation updates:${NC}" echo -e "$NEW_FUNCTIONS" echo -e "${YELLOW}Note: Consider updating PRD.md, docs/, or JOURNAL.md${NC}" # This is a warning, not a hard failure else echo -e "${GREEN}✓ Documentation appears to be updated${NC}" fi else echo -e "${GREEN}✓ No new functions to document${NC}" fi # ============================================================================= # Final Result # ============================================================================= echo "" echo -e "${YELLOW}╔══════════════════════════════════════════════════════════════╗${NC}" if [[ $FAILED -eq 1 ]]; then echo -e "${YELLOW}║ COMMIT BLOCKED ║${NC}" echo -e "${YELLOW}╚══════════════════════════════════════════════════════════════╝${NC}" echo "" echo -e "${RED}SDLC requirements not met. Please fix the above issues.${NC}" echo "" echo -e "${YELLOW}Quick fix commands:${NC}" echo " ./run.sh lint # Fix lint warnings" echo " ./run.sh test:unit # Run unit tests" echo " ./run.sh test # Run all tests" echo "" echo -e "${YELLOW}Reference: docs/SDLC.md${NC}" exit 1 else echo -e "${YELLOW}║ ALL CHECKS PASSED ║${NC}" echo -e "${YELLOW}╚══════════════════════════════════════════════════════════════╝${NC}" echo "" echo -e "${GREEN}✓ SDLC requirements verified${NC}" echo -e "${GREEN}✓ Commit allowed${NC}" exit 0 fi