# file-boundary-guard.ps1 -- PreToolUse hook
# Blocks file writes outside an agent's designated project directory.
# Prevents cross-project contamination and unauthorized file access.
# Exit code 2 = block. Exit code 0 = allow.
#
# Part of @connexum/ai-governance

$ErrorActionPreference = 'Stop'

$rawInput = [Console]::In.ReadToEnd()

if ([string]::IsNullOrWhiteSpace($rawInput)) { exit 0 }

try { $input_obj = $rawInput | ConvertFrom-Json } catch { exit 0 }

$toolName = if ($input_obj.tool_name) { $input_obj.tool_name } else { '' }
$toolInput = $input_obj.tool_input

# Only gate file-write tools
if ($toolName -notin 'Edit', 'Write', 'NotebookEdit') { exit 0 }

# Extract file path
$filePath = if ($toolInput.file_path) { $toolInput.file_path }
             elseif ($toolInput.path) { $toolInput.path }
             else { '' }

if (-not $filePath) { exit 0 }

# Resolve to absolute path
try {
    $resolvedPath = [System.IO.Path]::GetFullPath($filePath)
} catch {
    $resolvedPath = $filePath
}

# Load boundary config
$boundaryFile = $null
foreach ($candidate in @(
    '.claude/governance/project-boundaries.conf',
    '.claude/rules/project-boundaries.conf',
    'GOVERNANCE/project-boundaries.conf'
)) {
    if (Test-Path $candidate) {
        $boundaryFile = $candidate
        break
    }
}

# If no boundary file, allow (no restrictions configured)
if (-not $boundaryFile) { exit 0 }

# Check if file path is within any allowed boundary
$allowed = $false
foreach ($line in Get-Content $boundaryFile) {
    $boundary = $line.Trim()
    # Skip comments and empty lines
    if ($boundary.StartsWith('#') -or -not $boundary) { continue }

    try {
        $resolvedBoundary = [System.IO.Path]::GetFullPath($boundary)
    } catch {
        $resolvedBoundary = $boundary
    }

    if ($resolvedPath.StartsWith($resolvedBoundary, [System.StringComparison]::OrdinalIgnoreCase)) {
        $allowed = $true
        break
    }
}

if (-not $allowed) {
    [Console]::Error.WriteLine("BLOCKED: File write to '$filePath' is outside allowed project boundaries.")
    exit 2
}

exit 0
