Pre-commit hook

 #!/bin/bash

# Pre-commit hook: Enforce FAIL-FAST philosophy

# See PROJECT_STANDARDS.md and docs/CODING_PHILOSOPHY.md for coding standards

#

# This hook catches common fallback patterns that hide bugs instead of

# exposing them. When in doubt: CRASH LOUD, CRASH EARLY, CRASH CLEAR.


# Check if we're being run standalone or as a hook

if [ "$1" = "--standalone" ]; then

    STANDALONE=1

    shift

    # Use provided files or all .lua files

    if [ -n "$1" ]; then

        STAGED_LUA="$@"

    else

        STAGED_LUA=$(find . -name "*.lua" -not -path "./.git/*")

    fi

else

    STANDALONE=0

    # Find staged .lua files

    STAGED_LUA=$(git diff --cached --name-only --diff-filter=ACM | grep '\.lua$')

fi


if [ -z "$STAGED_LUA" ]; then

    [ $STANDALONE -eq 0 ] && echo "✓ No Lua files to check"

    exit 0

fi


TOTAL_VIOLATIONS=0


# ============================================

# ADDITIONAL CHECKS: Workarounds & Compatibility

# ============================================

# Source additional checks for workarounds, legacy code, etc.

if [ -f ".git/hooks/pre-commit-additions.sh" ]; then

    . .git/hooks/pre-commit-additions.sh

fi


# ============================================

# CHECK 1: Forbidden Fallback Patterns

# ============================================

FALLBACK_VIOLATIONS=0

BOOLEAN_LOGIC_WARNINGS=0


for file in $STAGED_LUA; do

    # Skip if file doesn't exist (deleted)

    if [ ! -f "$file" ]; then

        continue

    fi


    # Find all " or " patterns, excluding ONLY comments

    # We want to catch ALL fallbacks: in functions, returns, expressions, etc.

    # Note: Not filtering string literals because the pattern can be too broad

    ALL_ORS=$(grep -n " or " "$file" | \

              grep -v "^\s*--" | \

              grep -v " -- .*or" | \

              grep -v "\-\- Alternative" | \

              grep -v "\-\- Can be:")


    if [ -n "$ALL_ORS" ]; then

        # Separate boolean logic from fallbacks

        FALLBACKS=""

        BOOLEAN_LOGIC=""


        while IFS= read -r line; do

            # Check if this looks like boolean comparison logic:

            # Pattern: something == something or something == something

            # Pattern: (something) or (something)

            # Pattern: if boolVar or boolVar then (combining boolean variables)

            # Pattern: return boolVar or boolVar (returning combined boolean)

            if echo "$line" | grep -qE '(==|~=|<|>|<=|>=).*or.*(==|~=|<|>|<=|>=)'; then

                # Looks like boolean logic - allow but warn

                BOOLEAN_LOGIC="${BOOLEAN_LOGIC}${line}\n"

            elif echo "$line" | grep -qE '\(.*\)\s+or\s+\(.*\)'; then

                # Parenthesized expressions - likely boolean logic

                BOOLEAN_LOGIC="${BOOLEAN_LOGIC}${line}\n"

            elif echo "$line" | grep -qE '(if|return|while|until)\s+[a-zA-Z_][a-zA-Z0-9_]*\s+or\s+[a-zA-Z_][a-zA-Z0-9_]*\s+(then|$|do)'; then

                # Pattern: if varA or varB then / return varA or varB

                # Combining boolean variables - legitimate boolean logic

                BOOLEAN_LOGIC="${BOOLEAN_LOGIC}${line}\n"

            else

                # Likely a fallback pattern

                FALLBACKS="${FALLBACKS}${line}\n"

            fi

        done <<< "$ALL_ORS"


        # Report fallback violations

        if [ -n "$FALLBACKS" ]; then

            [ $FALLBACK_VIOLATIONS -eq 0 ] && echo "❌ FALLBACK VIOLATIONS:"

            echo "  $file:"

            echo -e "$FALLBACKS" | grep -v '^$' | sed 's/^/    /'

            FALLBACK_VIOLATIONS=$((FALLBACK_VIOLATIONS + 1))

        fi


        # Boolean logic is allowed - skip warnings

    fi

done


[ $FALLBACK_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + FALLBACK_VIOLATIONS))


# ============================================

# CHECK 1B: Hidden Fallback Patterns (if not x then x = y)

# ============================================

HIDDEN_FALLBACK_VIOLATIONS=0


for file in $STAGED_LUA; do

    # Skip if file doesn't exist (deleted)

    if [ ! -f "$file" ]; then

        continue

    fi


    # Look for "if not <var> then" followed by "<var> = " pattern

    # This catches: if not x then x = 0 end

    HIDDEN_FALLBACKS=$(grep -n "if not .* then" "$file" | \

                       grep -v "^\s*--" | \

                       while read -r line; do

                           linenum=$(echo "$line" | cut -d: -f1)

                           varname=$(echo "$line" | sed 's/.*if not \([a-zA-Z_][a-zA-Z0-9_]*\).*/\1/')

                           # Check if next few lines assign to same variable

                           nextlines=$(sed -n "${linenum},$((linenum+3))p" "$file" | grep "^\s*${varname}\s*=")

                           if [ -n "$nextlines" ]; then

                               echo "$line"

                           fi

                       done)


    if [ -n "$HIDDEN_FALLBACKS" ]; then

        [ $HIDDEN_FALLBACK_VIOLATIONS -eq 0 ] && echo "❌ HIDDEN FALLBACKS:"

        echo "  $file:"

        echo "$HIDDEN_FALLBACKS" | sed 's/^/    /'

        HIDDEN_FALLBACK_VIOLATIONS=$((HIDDEN_FALLBACK_VIOLATIONS + 1))

    fi

done


[ $HIDDEN_FALLBACK_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + HIDDEN_FALLBACK_VIOLATIONS))


# ============================================

# CHECK 1C: Semantic Fallback Patterns (local x = default; if config.x then x = config.x)

# ============================================

SEMANTIC_FALLBACK_VIOLATIONS=0


for file in $STAGED_LUA; do

    # Skip if file doesn't exist (deleted)

    if [ ! -f "$file" ]; then

        continue

    fi


    # Use awk to detect semantic fallback patterns:

    # Pattern 1: local var = value; if config.var then var = config.var

    # Pattern 2: local var = value; if config.var and ... then var = ...

    # Both hide defaults from grep while providing fallback behavior

    # Skip counters: local count = 0 ... count = count + 1

    SEMANTIC_FALLBACKS=$(awk '

        /local [a-zA-Z_][a-zA-Z0-9_]* = / {

            # Extract variable name and check if it has a default value

            # Skip if default is nil (that is a valid feature flag pattern)

            if ($0 ~ /= nil/) {

                next

            }

            # Skip if initialized to a number (likely a counter)

            if ($0 ~ /= [0-9]+[[:space:]]*$/ || $0 ~ /= [0-9]+\.?[0-9]*[[:space:]]*$/) {

                next

            }

            match($0, /local ([a-zA-Z_][a-zA-Z0-9_]*) = /, arr)

            if (arr[1]) {

                varname = arr[1]

                line_num = NR

                stored_line = NR": "$0

                # Track we are looking for reassignment of this var

                looking_for_var = varname

            }

        }

        looking_for_var && NR > line_num && NR <= line_num + 10 {

            # Check if this line reassigns the variable we are tracking

            # Pattern: if <something> then ... varname = ...

            if ($0 ~ "if .* then" || $0 ~ "elseif .* then") {

                in_conditional = 1

                conditional_line = NR

            }

            # Only flag if reassignment happens AFTER the conditional starts

            if (in_conditional && NR > conditional_line && $0 ~ "^[[:space:]]*" looking_for_var "[[:space:]]*=") {

                # Found reassignment in conditional - this is a semantic fallback

                print stored_line

                looking_for_var = ""

                in_conditional = 0

            }

            if ($0 ~ "^[[:space:]]*end[[:space:]]*$") {

                in_conditional = 0

            }

        }

        # Reset if we move too far from the local declaration

        looking_for_var && (NR - line_num > 10) {

            looking_for_var = ""

            in_conditional = 0

        }

    ' "$file")


    if [ -n "$SEMANTIC_FALLBACKS" ]; then

        [ $SEMANTIC_FALLBACK_VIOLATIONS -eq 0 ] && echo "❌ SEMANTIC FALLBACKS (local x = default; if config.x then x = config.x):"

        echo "  $file:"

        echo "$SEMANTIC_FALLBACKS" | sed 's/^/    /'

        SEMANTIC_FALLBACK_VIOLATIONS=$((SEMANTIC_FALLBACK_VIOLATIONS + 1))

    fi

done


[ $SEMANTIC_FALLBACK_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + SEMANTIC_FALLBACK_VIOLATIONS))


# ============================================

# CHECK 1D: Type-check Fallbacks (if type(x) == "table" then...)

# ============================================

TYPE_CHECK_VIOLATIONS=0


for file in $STAGED_LUA; do

    if [ ! -f "$file" ]; then

        continue

    fi


    TYPE_CHECKS=$(grep -n 'if type(' "$file" | grep -v "^\s*--")


    if [ -n "$TYPE_CHECKS" ]; then

        [ $TYPE_CHECK_VIOLATIONS -eq 0 ] && echo "❌ TYPE-CHECK FALLBACKS:"

        echo "  $file:"

        echo "$TYPE_CHECKS" | sed 's/^/    /'

        TYPE_CHECK_VIOLATIONS=$((TYPE_CHECK_VIOLATIONS + 1))

    fi

done


[ $TYPE_CHECK_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + TYPE_CHECK_VIOLATIONS))


# ============================================

# CHECK 1E: Metatable Fallbacks (__index with defaults)

# ============================================

METATABLE_VIOLATIONS=0


for file in $STAGED_LUA; do

    if [ ! -f "$file" ]; then

        continue

    fi


    METATABLES=$(grep -n '__index.*=' "$file" | grep -v "^\s*--")


    if [ -n "$METATABLES" ]; then

        [ $METATABLE_VIOLATIONS -eq 0 ] && echo "❌ METATABLE FALLBACKS (__index):"

        echo "  $file:"

        echo "$METATABLES" | sed 's/^/    /'

        METATABLE_VIOLATIONS=$((METATABLE_VIOLATIONS + 1))

    fi

done


[ $METATABLE_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + METATABLE_VIOLATIONS))


# ============================================

# CHECK 1F: Table Access with Fallback (t.key or default)

# ============================================

TABLE_ACCESS_VIOLATIONS=0


for file in $STAGED_LUA; do

    if [ ! -f "$file" ]; then

        continue

    fi


    # Look for table.key or default / table[key] or default

    # This is typically a fallback pattern

    TABLE_FALLBACKS=$(grep -n '\(\..*\|\[.*\]\)\s*or\s*[^=]' "$file" | \

                      grep -v "^\s*--" | \

                      grep -v " -- " | \

                      grep -v "==.*or" | \

                      grep -v "~=.*or")


    if [ -n "$TABLE_FALLBACKS" ]; then

        [ $TABLE_ACCESS_VIOLATIONS -eq 0 ] && echo "❌ TABLE ACCESS FALLBACKS (t.key or default):"

        echo "  $file:"

        echo "$TABLE_FALLBACKS" | sed 's/^/    /'

        TABLE_ACCESS_VIOLATIONS=$((TABLE_ACCESS_VIOLATIONS + 1))

    fi

done


[ $TABLE_ACCESS_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + TABLE_ACCESS_VIOLATIONS))


# ============================================

# CHECK 1G: Function Return Fallbacks (return x or y)

# ============================================

RETURN_FALLBACK_VIOLATIONS=0


for file in $STAGED_LUA; do

    if [ ! -f "$file" ]; then

        continue

    fi


    # Look for "return ... or ..." patterns

    # Exclude boolean logic (return a == b or c == d)

    RETURN_FALLBACKS=$(grep -n 'return.*or' "$file" | \

                       grep -v "^\s*--" | \

                       grep -v "==.*or.*==" | \

                       grep -v "~=.*or.*~=" | \

                       grep -v "(.*) or (.*)")


    if [ -n "$RETURN_FALLBACKS" ]; then

        # Filter out cases that are clearly boolean variable combinations

        FILTERED_FALLBACKS=""

        while IFS= read -r line; do

            # Skip: return boolVar or boolVar

            if echo "$line" | grep -qE 'return\s+[a-zA-Z_][a-zA-Z0-9_]*\s+or\s+[a-zA-Z_][a-zA-Z0-9_]*\s*$'; then

                continue

            fi

            FILTERED_FALLBACKS="${FILTERED_FALLBACKS}${line}\n"

        done <<< "$RETURN_FALLBACKS"


        if [ -n "$FILTERED_FALLBACKS" ]; then

            [ $RETURN_FALLBACK_VIOLATIONS -eq 0 ] && echo "❌ RETURN FALLBACKS (return x or y):"

            echo "  $file:"

            echo -e "$FILTERED_FALLBACKS" | grep -v '^$' | sed 's/^/    /'

            RETURN_FALLBACK_VIOLATIONS=$((RETURN_FALLBACK_VIOLATIONS + 1))

        fi

    fi

done


[ $RETURN_FALLBACK_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + RETURN_FALLBACK_VIOLATIONS))


# ============================================

# CHECK 1H: Assignment Fallbacks (x = y or z)

# ============================================

ASSIGNMENT_FALLBACK_VIOLATIONS=0


for file in $STAGED_LUA; do

    if [ ! -f "$file" ]; then

        continue

    fi


    # Look for "var = ... or ..." patterns

    # Exclude boolean logic and variable combinations

    ASSIGNMENT_FALLBACKS=$(grep -n '=.*or' "$file" | \

                           grep -v "^\s*--" | \

                           grep -v "==.*or" | \

                           grep -v "~=.*or" | \

                           grep -v "(.*) or (.*)" | \

                           grep -v "-- ")


    if [ -n "$ASSIGNMENT_FALLBACKS" ]; then

        # Filter out likely boolean combinations

        FILTERED_FALLBACKS=""

        while IFS= read -r line; do

            # Skip: var = boolVar or boolVar

            if echo "$line" | grep -qE '=\s+[a-zA-Z_][a-zA-Z0-9_]*\s+or\s+[a-zA-Z_][a-zA-Z0-9_]*\s*$'; then

                continue

            fi

            # Skip boolean operators

            if echo "$line" | grep -qE '(==|~=|<|>|<=|>=).*or.*(==|~=|<|>|<=|>=)'; then

                continue

            fi

            FILTERED_FALLBACKS="${FILTERED_FALLBACKS}${line}\n"

        done <<< "$ASSIGNMENT_FALLBACKS"


        if [ -n "$FILTERED_FALLBACKS" ]; then

            [ $ASSIGNMENT_FALLBACK_VIOLATIONS -eq 0 ] && echo "❌ ASSIGNMENT FALLBACKS (x = y or z):"

            echo "  $file:"

            echo -e "$FILTERED_FALLBACKS" | grep -v '^$' | sed 's/^/    /'

            ASSIGNMENT_FALLBACK_VIOLATIONS=$((ASSIGNMENT_FALLBACK_VIOLATIONS + 1))

        fi

    fi

done


[ $ASSIGNMENT_FALLBACK_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + ASSIGNMENT_FALLBACK_VIOLATIONS))


# ============================================

# CHECK 1I: Ternary Operator Fallbacks (x and y or z)

# ============================================

TERNARY_FALLBACK_VIOLATIONS=0


for file in $STAGED_LUA; do

    if [ ! -f "$file" ]; then

        continue

    fi


    # Look for "x and y or z" patterns (Lua's ternary operator idiom)

    # These are often used for fallbacks

    TERNARY_PATTERNS=$(grep -n 'and.*or' "$file" | \

                       grep -v "^\s*--" | \

                       grep -v " -- ")


    if [ -n "$TERNARY_PATTERNS" ]; then

        TERNARY_FALLBACKS=""


        while IFS= read -r line; do

            # Exclude complex boolean logic with multiple comparisons

            if echo "$line" | grep -qE '(==|~=|<|>|<=|>=).*and.*(==|~=|<|>|<=|>=).*or.*(==|~=|<|>|<=|>=)'; then

                # Looks like complex boolean logic - skip

                continue

            else

                # Likely a ternary fallback pattern

                TERNARY_FALLBACKS="${TERNARY_FALLBACKS}${line}\n"

            fi

        done <<< "$TERNARY_PATTERNS"


        if [ -n "$TERNARY_FALLBACKS" ]; then

            [ $TERNARY_FALLBACK_VIOLATIONS -eq 0 ] && echo "❌ TERNARY OPERATOR FALLBACKS (x and y or z):"

            echo "  $file:"

            echo -e "$TERNARY_FALLBACKS" | grep -v '^$' | sed 's/^/    /'

            TERNARY_FALLBACK_VIOLATIONS=$((TERNARY_FALLBACK_VIOLATIONS + 1))

        fi

    fi

done


[ $TERNARY_FALLBACK_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + TERNARY_FALLBACK_VIOLATIONS))


# ============================================

# CHECK 1J: Suspicious Comments (Fallback Thinking)

# ============================================

COMMENT_VIOLATIONS=0


for file in $STAGED_LUA; do

    # Skip if file doesn't exist (deleted)

    if [ ! -f "$file" ]; then

        continue

    fi


    # Look for comments that suggest defensive/fallback thinking

    # But exclude the philosophy doc and this hook itself

    if [[ "$file" == *"CODING_PHILOSOPHY"* ]] || [[ "$file" == *"pre-commit"* ]]; then

        continue

    fi


    SUSPICIOUS_COMMENTS=$(grep -n -i '\-\-.*\(just in case\|might be missing\|might not exist\|could be nil\|backwards compat\|migration\)' "$file" | \

                          grep -v "no fallback" | \

                          grep -v "FAIL FAST")


    if [ -n "$SUSPICIOUS_COMMENTS" ]; then

        [ $COMMENT_VIOLATIONS -eq 0 ] && echo "⚠️  SUSPICIOUS COMMENTS:"

        echo "  $file:"

        echo "$SUSPICIOUS_COMMENTS" | sed 's/^/    /'

        COMMENT_VIOLATIONS=$((COMMENT_VIOLATIONS + 1))

    fi

done


[ $COMMENT_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + COMMENT_VIOLATIONS))


# ============================================

# CHECK 1K: TODO and Placeholder Comments

# ============================================

TODO_VIOLATIONS=0


for file in $STAGED_LUA; do

    # Skip if file doesn't exist (deleted)

    if [ ! -f "$file" ]; then

        continue

    fi


    # Look for TODO, FIXME, HACK, XXX, will implement, placeholder, temporary, not yet implemented, etc.

    TODO_COMMENTS=$(grep -n -i '\-\-.*\(TODO\|FIXME\|HACK\|XXX\|will implement\|placeholder\|temporary\|coming soon\|not implemented\|not yet implemented\)' "$file" || true)


    if [ -n "$TODO_COMMENTS" ]; then

        [ $TODO_VIOLATIONS -eq 0 ] && echo "❌ TODO/PLACEHOLDER COMMENTS:"

        echo "  $file:"

        echo "$TODO_COMMENTS" | sed 's/^/    /'

        TODO_VIOLATIONS=$((TODO_VIOLATIONS + 1))

    fi

done


[ $TODO_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + TODO_VIOLATIONS))


# ============================================

# CHECK 2: Non-ASCII Characters (Emoji, etc.)

# ============================================

NON_ASCII_VIOLATIONS=0


for file in $STAGED_LUA; do

    # Skip if file doesn't exist (deleted)

    if [ ! -f "$file" ]; then

        continue

    fi


    # Find lines with non-ASCII characters (characters with codes > 127)

    # Exclude comment lines (starting with --)

    NON_ASCII=$(grep -n -P '[^\x00-\x7F]' "$file" | grep -v "^\s*[0-9]*:\s*--")


    if [ -n "$NON_ASCII" ]; then

        [ $NON_ASCII_VIOLATIONS -eq 0 ] && echo "❌ NON-ASCII CHARACTERS:"

        echo "  $file:"

        echo "$NON_ASCII" | sed 's/^/    /'

        NON_ASCII_VIOLATIONS=$((NON_ASCII_VIOLATIONS + 1))

    fi

done


[ $NON_ASCII_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + NON_ASCII_VIOLATIONS))


# ============================================

# CHECK 3: Forbidden pcall Fallback Patterns

# ============================================

PCALL_VIOLATIONS=0


for file in $STAGED_LUA; do

    # Skip if file doesn't exist (deleted)

    if [ ! -f "$file" ]; then

        continue

    fi


    # Look for pcall patterns with fallback logic

    # Pattern: "pcall(" on a line, likely storing success/result

    PCALLS=$(grep -n "pcall(" "$file" | grep -v "^\s*--")


    if [ -n "$PCALLS" ]; then

        [ $PCALL_VIOLATIONS -eq 0 ] && echo "❌ PCALL (hides errors):"

        echo "  $file:"

        echo "$PCALLS" | sed 's/^/    /'

        PCALL_VIOLATIONS=$((PCALL_VIOLATIONS + 1))

    fi

done


[ $PCALL_VIOLATIONS -gt 0 ] && TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + PCALL_VIOLATIONS))


# FINAL RESULT

if [ $TOTAL_VIOLATIONS -gt 0 ]; then

    echo ""

    echo "BLOCKED: $TOTAL_VIOLATIONS violation(s). See PROJECT_STANDARDS.md"

    exit 1

else

    echo "✓ Pass: No violations found"

    exit 0

fi


Comments

Popular posts from this blog

Adventures with Vibes