#!/usr/bin/env bash
#
# Code Correctness Guard - PostToolUse hook for itp-hooks
#
# Detects code correctness issues that cause runtime failures:
# 1. Silent failure patterns (swallowed exceptions, missing error handling)
# 2. Cross-language syntax errors (shell variables in Python, etc.)
#
# Uses the "decision: block" JSON format for visibility (per ADR 2025-12-17).
#
# Detection:
# - Bash tool: Non-zero exit codes with stderr
# - Write/Edit on .sh/.bash: ShellCheck analysis (SC2155, SC2164, SC2181, SC2086 etc.)
# - Write/Edit on .py: Ruff silent failure rules + shell variable syntax detection
# - Write/Edit on .js/.ts: Oxlint + custom floating promise detection
#
# Exit behavior:
# - Always exits 0 (non-blocking) - Claude continues but sees the warning
# - Uses "decision: block" in JSON for visibility, not execution blocking
#

set -euo pipefail

# Iter-35 bash-5.2-patsub-replacement-defense (cross-plugin sweep):
# disable bash 5.2+ `&`-as-backreference in ${VAR//PATTERN/REPLACEMENT}.
# See plugins/autoloop/hooks/heartbeat-tick.sh for full rationale +
# upstream sources (bash maintainer + Arch pacman patch).
shopt -u patsub_replacement 2>/dev/null || true

# Read JSON input from Claude Code
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null) || exit 0

# Emit warning in Claude-visible format with FIX GUIDANCE
# Per ADR 2025-12-17: "decision: block" is required for Claude to see PostToolUse output
# IMPORTANT: Only the "reason" field is visible to Claude - all content must go there
emit_warning() {
    local category="$1"
    local message="$2"
    local file_path="${3:-}"
    local details="${4:-}"
    local fix_guidance="${5:-}"

    # Build the complete reason string - this is ALL Claude sees
    local full_reason="[$category] $message"

    if [[ -n "$file_path" ]]; then
        full_reason="$full_reason

FILE: $file_path"
    fi

    if [[ -n "$details" ]]; then
        full_reason="$full_reason

ISSUES DETECTED:
$details"
    fi

    if [[ -n "$fix_guidance" ]]; then
        full_reason="$full_reason

$fix_guidance"
    fi

    # Output JSON - ONLY decision and reason are read by Claude Code
    jq -n --arg reason "$full_reason" '{decision: "block", reason: $reason}'
}

# === BASH TOOL: Check exit code and stderr patterns ===
if [[ "$TOOL_NAME" == "Bash" ]]; then
    # Iter-56 bash-branch-early-exit-jq-batching optimization:
    #
    # Pre-iter-56 the Bash branch unconditionally spawned 3 separate jq
    # processes (EXIT_CODE + STDERR + COMMAND) BEFORE checking whether
    # EXIT_CODE was 0. Since most Bash commands exit 0 in normal use,
    # 2 of those 3 jq spawns (STDERR + COMMAND) were pure waste on the
    # dominant code path — costing ~10-14ms per Bash call to extract
    # data the early-exit-on-0 check would immediately discard.
    #
    # New structure (cheap-field-first + batched-jq-on-demand):
    #   1. Spawn ONE jq for EXIT_CODE alone (1 jq).
    #   2. If exit code is 0, bail immediately (most common case).
    #   3. Only when exit code is non-zero, spawn ONE BATCHED jq that
    #      emits STDERR + COMMAND together via TSV (1 jq).
    #
    # Net: within the Bash branch, 3 jq spawns → 1 jq spawn on exit-0
    # hot path (also: 3 → 2 on exit-nonzero path, since STDERR+COMMAND
    # are now batched via @tsv). Matches the autoloop iter-25/26/27/28/29
    # batched-jq + early-exit pattern.
    #
    # Measured (A/B benchmark, 50 iters, Bash exit-0 hot path):
    #   BASELINE (pre-iter-56, 4 total jq spawns): 23.36 ms/call
    #   ITER-56  (early-exit, 2 total jq spawns) : 15.44 ms/call
    #   --------------------------------------------------------
    #   Speedup: 1.51x, ~7.9 ms saved per call.
    #
    # Over a session with hundreds of Bash invocations, this is seconds
    # of saved cumulative hook overhead. The remaining ~15ms is the
    # bash-process-spawn floor (1 spawn per hook call by Claude Code) +
    # the 2 surviving jq spawns (TOOL_NAME at line 32 + EXIT_CODE here).
    EXIT_CODE=$(echo "$INPUT" | jq -r '.tool_output.exit_code // 0' 2>/dev/null) || exit 0

    # Skip if exit code is 0 (success) — most-common path, exit before
    # paying for the STDERR + COMMAND batched-jq spawn below.
    if [[ "$EXIT_CODE" -eq 0 ]]; then
        exit 0
    fi

    # Non-zero exit: batch STDERR + COMMAND extraction into one jq call.
    # Uses TSV via @tsv to safely round-trip newlines/quotes inside
    # STDERR (@tsv escapes \t and \n as literals; we restore them when
    # reading). Falls back to empty fields if jq fails.
    BATCHED_TSV=$(echo "$INPUT" | jq -r '[(.tool_output.stderr // ""), (.tool_input.command // "")] | @tsv' 2>/dev/null) || BATCHED_TSV=$'\t'
    # Restore embedded newlines (jq @tsv encodes them as the 2-char "\n").
    # Round-trip is lossless: jq escapes raw newline → "\\n", printf %b
    # decodes "\\n" → newline AND decodes "\\\\n" → "\\n" so literal
    # backslash-n sequences in the original payload are preserved.
    STDERR=$(printf '%b' "${BATCHED_TSV%%$'\t'*}")
    COMMAND=$(printf '%b' "${BATCHED_TSV#*$'\t'}")

    # Skip certain expected failures (grep no match, diff has differences, etc.)
    if [[ "$EXIT_CODE" -eq 1 ]] && [[ "$COMMAND" =~ ^(grep|diff|test|\[) ]]; then
        exit 0
    fi

    # Non-zero exit with meaningful stderr indicates potential failure
    if [[ -n "$STDERR" ]]; then
        # Truncate long stderr for readability
        STDERR_TRUNCATED="${STDERR:0:500}"
        if [[ ${#STDERR} -gt 500 ]]; then
            STDERR_TRUNCATED="${STDERR_TRUNCATED}..."
        fi

        BASH_FIX_GUIDANCE="SILENT FAILURE PRINCIPLE: Commands that fail silently cause cascading issues. YOU MUST:
1. ACKNOWLEDGE the failure explicitly - do not proceed as if it succeeded
2. DIAGNOSE the root cause from the stderr message above
3. FIX the underlying issue (missing dependency, wrong path, permission, etc.)
4. RE-RUN the command to verify it succeeds (exit code 0)
5. If the command is expected to fail sometimes, handle it explicitly with '|| true' or conditionals

NEVER ignore non-zero exit codes. Every failure has a cause that must be addressed."

        emit_warning "BASH-FAILURE" \
            "Command exited with code $EXIT_CODE - STOP and fix before continuing" \
            "" \
            "$STDERR_TRUNCATED" \
            "$BASH_FIX_GUIDANCE"
    fi

    exit 0
fi

# === WRITE/EDIT TOOL: Run static analysis on new/modified files ===
if [[ "$TOOL_NAME" == "Write" || "$TOOL_NAME" == "Edit" ]]; then
    FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')

    # Early exit for empty paths or non-existent files
    [[ -z "$FILE_PATH" ]] && exit 0
    [[ ! -f "$FILE_PATH" ]] && exit 0

    # Verify file is readable before running analysis
    if [[ ! -r "$FILE_PATH" ]]; then
        exit 0
    fi

    # For Edit tool: determine changed line range to scope linter output.
    # Only reports issues on lines that were actually modified (± buffer).
    # This prevents flagging pre-existing issues elsewhere in the file.
    # Write tool checks the entire file (it's all new content).
    EDIT_LINE_START=""
    EDIT_LINE_END=""
    if [[ "$TOOL_NAME" == "Edit" ]]; then
        EDIT_REPLACE_ALL=$(echo "$INPUT" | jq -r '.tool_input.replace_all // false')
        if [[ "$EDIT_REPLACE_ALL" != "true" ]]; then
            EDIT_NEW_STRING=$(echo "$INPUT" | jq -r '.tool_input.new_string // ""')
            if [[ -n "$EDIT_NEW_STRING" ]]; then
                # Use first non-empty line of new_string to locate the edit
                EDIT_FIRST_LINE=$(echo "$EDIT_NEW_STRING" | grep -m1 '.' || true)
                if [[ -n "$EDIT_FIRST_LINE" ]]; then
                    # Only scope if we can uniquely identify the location
                    EDIT_MATCH_COUNT=$(grep -c -F -- "$EDIT_FIRST_LINE" "$FILE_PATH" 2>/dev/null || echo "0")
                    if [[ "$EDIT_MATCH_COUNT" -eq 1 ]]; then
                        EDIT_LINE_START=$(grep -n -F -- "$EDIT_FIRST_LINE" "$FILE_PATH" 2>/dev/null | head -1 | cut -d: -f1)
                        if [[ -n "$EDIT_LINE_START" ]]; then
                            EDIT_NEW_LINE_COUNT=$(echo "$EDIT_NEW_STRING" | wc -l | tr -d ' ')
                            EDIT_LINE_END=$((EDIT_LINE_START + EDIT_NEW_LINE_COUNT - 1))
                            # Buffer: check 3 lines before and after the edit
                            EDIT_BUFFER=3
                            EDIT_LINE_START=$((EDIT_LINE_START > EDIT_BUFFER ? EDIT_LINE_START - EDIT_BUFFER : 1))
                            EDIT_LINE_END=$((EDIT_LINE_END + EDIT_BUFFER))
                        fi
                    fi
                fi
            fi
        fi
    fi

    # Principle-based fix guidance for each language
    SHELL_FIX_GUIDANCE="SHELL SILENT FAILURE PRINCIPLES:
1. SC2155: SPLIT declaration from assignment - 'local var=\$(cmd)' masks exit code
   FIX: 'local var; var=\$(cmd)' - now \$? reflects command exit status
2. SC2164: ALWAYS handle cd/pushd failures - 'cd dir' silently continues if dir missing
   FIX: 'cd dir || exit 1' or 'cd dir || { echo 'Failed'; exit 1; }'
3. SC2181: DON'T use \$? in conditionals - 'if [ \$? -eq 0 ]' is fragile
   FIX: 'if command; then' - directly test the command
4. SC2086: QUOTE variable expansions - unquoted vars cause word splitting bugs
   FIX: Use \"\$var\" instead of \$var
5. USE 'set -euo pipefail' at script start to fail fast on errors

Fix each issue at the line indicated. The error message explains what pattern to fix."

    PYTHON_FIX_GUIDANCE="PYTHON SILENT FAILURE PRINCIPLES:
1. NEVER use bare 'except:' - it catches KeyboardInterrupt, SystemExit, and hides real bugs
2. NEVER use 'except: pass' - errors must be logged, re-raised, or explicitly handled
3. CATCH SPECIFIC exceptions - use 'except ValueError:' or 'except (TypeError, KeyError):'
4. ALWAYS log or handle exceptions - at minimum: logging.exception('Context message')
5. RE-RAISE if you can't handle - 'except SomeError: logger.error(...); raise'
6. ALWAYS use subprocess.run(..., check=True) - without check=True, non-zero exits are silent
   - PLW1510: subprocess.run() without check= silently ignores command failures

Fix each pattern to make failures VISIBLE and ACTIONABLE, not silent."

    SHELL_VAR_FIX_GUIDANCE="CROSS-LANGUAGE SYNTAX ERROR:
Python does NOT expand shell variables like \$HOME, \$USER, \$PATH.
The string \"\$HOME/.config\" is interpreted LITERALLY, creating a directory named '\$HOME'.

CORRECT PATTERNS:
  Path.home() / '.config'              # pathlib (recommended)
  os.path.expanduser('~/.config')      # os module
  os.environ['HOME'] + '/.config'      # explicit env var
  os.path.expandvars('\$HOME/.config')  # if you must use \$HOME syntax

WRONG PATTERNS:
  Path('\$HOME/.config')               # Creates literal '\$HOME' directory!
  open('\$HOME/.config/file')          # FileNotFoundError or wrong location
  subprocess.run(cwd='\$HOME/...')     # Wrong working directory

This is a common mistake when doing bulk find-replace across polyglot codebases."

    JS_FIX_GUIDANCE="JAVASCRIPT/TYPESCRIPT SILENT FAILURE PRINCIPLES:
1. EMPTY CATCH: Never use 'catch (e) {}' - it hides all errors completely
   FIX: 'catch (e) { console.error('Context:', e); throw e; }'
2. FLOATING PROMISES: .then() without .catch() silently drops rejections
   FIX: Always chain .catch() OR use try/await/catch pattern
3. UNHANDLED ASYNC: async functions without try/catch lose errors
   FIX: Wrap await in try/catch: 'try { await fn(); } catch (e) { handle(e); }'
4. SWALLOWED ERRORS: 'catch (e) { /* ignore */ }' hides bugs
   FIX: At minimum log: 'catch (e) { console.error('Failed:', e); }'
5. RE-THROW UNKNOWN: If you can't handle it, re-throw
   FIX: 'catch (e) { if (e instanceof Expected) handle(); else throw e; }'

Silent JS failures are debugging nightmares. Make every error VISIBLE."

    case "$FILE_PATH" in
        # === Shell Scripts: ShellCheck ===
        *.sh|*.bash)
            if command -v shellcheck &>/dev/null; then
                SHELLCHECK_OUTPUT=$(shellcheck \
                    -f json \
                    -e SC1091 \
                    "$FILE_PATH" 2>/dev/null || true)

                if [[ -n "$SHELLCHECK_OUTPUT" ]] && [[ "$SHELLCHECK_OUTPUT" != "[]" ]]; then
                    # For Edit tool: scope to changed lines only (skip pre-existing issues)
                    if [[ -n "$EDIT_LINE_START" && -n "$EDIT_LINE_END" ]]; then
                        SHELLCHECK_OUTPUT=$(echo "$SHELLCHECK_OUTPUT" | jq \
                            --argjson start "$EDIT_LINE_START" \
                            --argjson end "$EDIT_LINE_END" \
                            '[.[] | select(.line >= $start and .line <= $end)]')
                    fi

                    ISSUE_COUNT=$(echo "$SHELLCHECK_OUTPUT" | jq 'length')

                    ISSUES_SUMMARY=$(echo "$SHELLCHECK_OUTPUT" | jq -r '
                        .[0:3] | .[] |
                        "Line \(.line): SC\(.code) - \(.message)"
                    ' | head -5)

                    if [[ "$ISSUE_COUNT" -gt 0 ]]; then
                        emit_warning "SHELLCHECK" \
                            "Found $ISSUE_COUNT shell issue(s) - fix before continuing" \
                            "$FILE_PATH" \
                            "$ISSUES_SUMMARY" \
                            "$SHELL_FIX_GUIDANCE"
                    fi
                fi
            fi
            ;;

        # === Python: Ruff for silent failure patterns + shell variable detection ===
        *.py)
            # Check 1: Shell variables in Python strings (e.g., Path("$HOME/..."))
            # This catches the bug where bulk sed replace puts shell syntax in Python
            # Pattern 1: Function calls like Path("$HOME"), open("$HOME"), chdir("$HOME")
            # Pattern 2: Keyword args like cwd="$HOME", path="$HOME"
            SHELL_VAR_ISSUES=$(grep -nE '(Path|open|chdir)\s*\(\s*["\x27]\$[A-Z_]+|(cwd|path|dir|directory|folder|home)\s*=\s*["\x27]\$[A-Z_]+' "$FILE_PATH" 2>/dev/null || true)
            if [[ -n "$SHELL_VAR_ISSUES" ]]; then
                SHELL_VAR_COUNT=$(echo "$SHELL_VAR_ISSUES" | wc -l | tr -d ' ')
                SHELL_VAR_SUMMARY=$(echo "$SHELL_VAR_ISSUES" | head -3 | sed 's/^\([0-9]*\):.*/Line \1: Shell variable in Python string/')
                emit_warning "SHELL-VAR-IN-PYTHON" \
                    "Found $SHELL_VAR_COUNT shell variable(s) in Python - Python doesn't expand \$HOME" \
                    "$FILE_PATH" \
                    "$SHELL_VAR_SUMMARY" \
                    "$SHELL_VAR_FIX_GUIDANCE"
            fi

            # Check 2: PEP 723 shebang on library modules (causes uv interpreter storms)
            # PEP 723 is ONLY for standalone scripts, NEVER for library modules that get imported.
            # When uv sees the shebang on import, it spawns thousands of get_interpreter_info processes.
            if grep -q '#!/usr/bin/env.*uv run' "$FILE_PATH" 2>/dev/null && grep -q '# /// script' "$FILE_PATH" 2>/dev/null; then
                HAS_MAIN_GUARD=$(grep -c 'if __name__.*==.*"__main__"' "$FILE_PATH" 2>/dev/null || echo "0")
                HAS_ALL_EXPORT=$(grep -c '^__all__' "$FILE_PATH" 2>/dev/null || echo "0")
                IS_IN_LIB=$(echo "$FILE_PATH" | grep -c '/lib/' || echo "0")

                # Library indicators: no __main__ guard, has __all__ export, or in lib/ directory
                if [[ "$HAS_MAIN_GUARD" -eq 0 ]] || [[ "$HAS_ALL_EXPORT" -gt 0 ]] || [[ "$IS_IN_LIB" -gt 0 ]]; then
                    PEP723_FIX="PEP 723 LIBRARY MODULE STORM:
The '#!/usr/bin/env -S uv run' shebang causes uv to probe interpreters
every time the file is imported or scanned. This spawns THOUSANDS of
'python get_interpreter_info' processes, freezing your system.

PEP 723 inline script metadata is ONLY for standalone scripts that you
run directly (python script.py). It must NEVER be used on library modules.

FIX: Remove the shebang and script metadata block entirely:
  1. Delete line: #!/usr/bin/env -S uv run
  2. Delete block: # /// script ... # ///
  3. Document dependencies in docstring or requirements.txt instead

INDICATORS THIS FILE IS A LIBRARY (not a script):
  - Has __all__ = [...] export list
  - Located in a lib/ directory
  - Missing 'if __name__ == \"__main__\":' entry point
  - Designed to be imported by other modules

Reference: CLAUDE.md Process Storm Prevention section"

                    emit_warning "PEP723-LIBRARY-STORM" \
                        "PEP 723 shebang on library module - WILL CAUSE PROCESS STORM" \
                        "$FILE_PATH" \
                        "File has 'uv run' shebang but is a library module (no __main__, has __all__, or in lib/)" \
                        "$PEP723_FIX"
                fi
            fi

            # Check 3: Ruff for silent failure patterns
            if command -v ruff &>/dev/null; then
                RUFF_OUTPUT=$(ruff check \
                    --select=E722,S110,S112,PLW1510 \
                    --output-format=json \
                    "$FILE_PATH" 2>/dev/null || true)

                if [[ -n "$RUFF_OUTPUT" ]] && [[ "$RUFF_OUTPUT" != "[]" ]]; then
                    # For Edit tool: scope to changed lines only (skip pre-existing issues)
                    if [[ -n "$EDIT_LINE_START" && -n "$EDIT_LINE_END" ]]; then
                        RUFF_OUTPUT=$(echo "$RUFF_OUTPUT" | jq \
                            --argjson start "$EDIT_LINE_START" \
                            --argjson end "$EDIT_LINE_END" \
                            '[.[] | select(.location.row >= $start and .location.row <= $end)]')
                    fi

                    ISSUE_COUNT=$(echo "$RUFF_OUTPUT" | jq 'length')

                    ISSUES_SUMMARY=$(echo "$RUFF_OUTPUT" | jq -r '
                        .[0:3] | .[] |
                        "Line \(.location.row): \(.code) - \(.message)"
                    ' | head -5)

                    if [[ "$ISSUE_COUNT" -gt 0 ]]; then
                        emit_warning "RUFF-SILENT-FAILURE" \
                            "Found $ISSUE_COUNT Python silent failure pattern(s) - fix before continuing" \
                            "$FILE_PATH" \
                            "$ISSUES_SUMMARY" \
                            "$PYTHON_FIX_GUIDANCE"
                    fi
                fi
            fi
            ;;

        # === JavaScript/TypeScript: Oxlint + Custom patterns ===
        *.js|*.ts|*.mjs|*.tsx|*.jsx)
            ISSUES_FOUND=""
            ISSUE_COUNT=0

            # Run Oxlint if available
            if command -v oxlint &>/dev/null; then
                OXLINT_OUTPUT=$(oxlint "$FILE_PATH" 2>&1 || true)

                # Extract actual issue lines (format: " × error" or " ⚠ warning")
                # Exclude summary line like "Found 0 warnings and 0 errors."
                OXLINT_ISSUES=$(echo "$OXLINT_OUTPUT" | grep -E '^\s*(×|⚠)\s+(error|warning)' || true)
                OXLINT_COUNT=0
                if [[ -n "$OXLINT_ISSUES" ]]; then
                    OXLINT_COUNT=$(echo "$OXLINT_ISSUES" | wc -l | tr -d ' ')
                fi

                if [[ "$OXLINT_COUNT" -gt 0 ]]; then
                    ISSUES_FOUND=$(echo "$OXLINT_ISSUES" | head -3)
                    ISSUE_COUNT=$((ISSUE_COUNT + OXLINT_COUNT))
                fi
            fi

            # Custom: Detect floating promises (.then without .catch on same logical block)
            # Pattern: .then( without .catch( within 3 lines
            FLOATING_PROMISES=$(grep -n '\.then(' "$FILE_PATH" 2>/dev/null | while read -r line; do
                LINE_NUM=$(echo "$line" | cut -d: -f1)
                # Check if .catch exists within 3 lines after
                if ! sed -n "${LINE_NUM},$((LINE_NUM + 3))p" "$FILE_PATH" | grep -q '\.catch('; then
                    echo "Line $LINE_NUM: Floating promise - .then() without .catch()"
                fi
            done || true)

            if [[ -n "$FLOATING_PROMISES" ]]; then
                FLOATING_COUNT=$(echo "$FLOATING_PROMISES" | wc -l | tr -d ' ')
                ISSUE_COUNT=$((ISSUE_COUNT + FLOATING_COUNT))
                if [[ -n "$ISSUES_FOUND" ]]; then
                    ISSUES_FOUND="$ISSUES_FOUND
$FLOATING_PROMISES"
                else
                    ISSUES_FOUND="$FLOATING_PROMISES"
                fi
            fi

            # Custom: Detect empty catch blocks
            EMPTY_CATCH=$(grep -nE 'catch\s*\([^)]*\)\s*\{\s*\}' "$FILE_PATH" 2>/dev/null | \
                head -3 | sed 's/^\([0-9]*\):.*/Line \1: Empty catch block - errors silently swallowed/' || true)

            if [[ -n "$EMPTY_CATCH" ]]; then
                EMPTY_COUNT=$(echo "$EMPTY_CATCH" | wc -l | tr -d ' ')
                ISSUE_COUNT=$((ISSUE_COUNT + EMPTY_COUNT))
                if [[ -n "$ISSUES_FOUND" ]]; then
                    ISSUES_FOUND="$ISSUES_FOUND
$EMPTY_CATCH"
                else
                    ISSUES_FOUND="$EMPTY_CATCH"
                fi
            fi

            # Emit warning if any issues found
            if [[ "$ISSUE_COUNT" -gt 0 ]]; then
                ISSUES_SUMMARY=$(echo "$ISSUES_FOUND" | head -5)
                emit_warning "JS-SILENT-FAILURE" \
                    "Found $ISSUE_COUNT JS/TS silent failure pattern(s) - fix before continuing" \
                    "$FILE_PATH" \
                    "$ISSUES_SUMMARY" \
                    "$JS_FIX_GUIDANCE"
            fi
            ;;
    esac

    # === INLINE IGNORE AUDIT (all supported file types, full-file scan) ===
    # Warns about existing inline ignore comments that should be in config files.
    # This is informational only — the PreToolUse guard blocks NEW additions.
    # Skip test files — they legitimately contain ignore patterns as test fixtures.
    SKIP_INLINE_AUDIT=false
    case "$(basename "$FILE_PATH")" in
        test_*|*_test.*|*.test.*|*_spec.*|*.spec.*) SKIP_INLINE_AUDIT=true ;;
    esac
    case "$FILE_PATH" in
        */__tests__/*|*/tests/*|*/test/*|*/fixtures/*) SKIP_INLINE_AUDIT=true ;;
    esac

    if [[ "$SKIP_INLINE_AUDIT" != "true" ]]; then
    INLINE_IGNORE_FIX_GUIDANCE="INLINE IGNORE POLICY:
Inline ignore comments should be in config files, not source code.

HIERARCHY:
1. FIX THE ERROR (preferred) - add type annotations, casts, __all__ for re-exports
2. CONFIG-LEVEL IGNORE (only for tool/library limitations):
   - ruff: [lint.per-file-ignores] in ruff.toml
   - ty: [[overrides]] in ty.toml with include pattern
   - oxlint: .oxlintrc.json rules section
   - biome: biome.json linter.rules section
3. NEVER: Inline # noqa / # type: ignore / // eslint-disable

Escape hatch: Add INLINE-IGNORE-OK on the same line if truly unavoidable."

    case "$FILE_PATH" in
        *.py|*.pyi)
            INLINE_IGNORES=$(grep -nE '# (ruff: )?noqa\b|# type:\s*ignore\b|# ty:\s*ignore\b' "$FILE_PATH" 2>/dev/null | grep -v 'INLINE-IGNORE-OK' || true)
            ;;
        *.js|*.ts|*.mjs|*.tsx|*.jsx)
            INLINE_IGNORES=$(grep -nE '//\s*eslint-disable(-next)?-line\b|/\*\s*eslint-disable\b|//\s*biome-ignore\b|//\s*oxlint-ignore\b' "$FILE_PATH" 2>/dev/null | grep -v 'INLINE-IGNORE-OK' || true)
            ;;
        *)
            INLINE_IGNORES=""
            ;;
    esac

    # For Edit tool: scope inline ignore audit to changed lines only.
    # Pre-existing ignores elsewhere in the file are not the editor's problem.
    if [[ -n "$EDIT_LINE_START" && -n "$EDIT_LINE_END" && -n "$INLINE_IGNORES" ]]; then
        INLINE_IGNORES=$(echo "$INLINE_IGNORES" | while IFS=: read -r line_num rest; do
            if [[ "$line_num" -ge "$EDIT_LINE_START" && "$line_num" -le "$EDIT_LINE_END" ]]; then
                echo "$line_num:$rest"
            fi
        done)
    fi

    if [[ -n "$INLINE_IGNORES" ]]; then
        IGNORE_COUNT=$(echo "$INLINE_IGNORES" | wc -l | tr -d ' ')
        # Cap at 5 lines
        IGNORE_SUMMARY=$(echo "$INLINE_IGNORES" | head -5 | sed 's/^\([0-9]*\):.*/Line \1/')
        if [[ "$IGNORE_COUNT" -gt 5 ]]; then
            IGNORE_SUMMARY="$IGNORE_SUMMARY
...and $((IGNORE_COUNT - 5)) more"
        fi
        emit_warning "INLINE-IGNORE-AUDIT" \
            "Found $IGNORE_COUNT inline ignore comment(s) - move to config files" \
            "$FILE_PATH" \
            "$IGNORE_SUMMARY" \
            "$INLINE_IGNORE_FIX_GUIDANCE"
    fi
    fi # SKIP_INLINE_AUDIT
fi

# Always exit 0 - we're non-blocking, visibility comes from JSON format
exit 0
