private/Add-DynamicContextToPrompt.ps1

function Add-DynamicContextToPrompt {
    <#
    .SYNOPSIS
        Executes ContextFilter scriptblock and adds derived context files to the prompt.
 
    .DESCRIPTION
        Processes each input file through the ContextFilter scriptblock to derive related
        context files (e.g., finding English originals for French translations). Handles
        path resolution, deduplication, and adds the content to the prompt.
 
    .PARAMETER BasePrompt
        The prompt text to add context to.
 
    .PARAMETER InputFiles
        Array of input file paths to process through the ContextFilter.
 
    .PARAMETER ContextFilter
        A scriptblock that transforms each input filename to derive a matching context file.
        Receives the file path via $_ and should return the derived filename or path.
 
    .PARAMETER ContextFilterBase
        Base directory or directories to search for files derived by ContextFilter.
        Searched in order, with the source file's directory as fallback.
 
    .PARAMETER WhatIf
        If specified, returns info about what would be added without modifying the prompt.
 
    .PARAMETER PSCmdlet
        The PSCmdlet object for ShouldProcess support.
 
    .OUTPUTS
        [hashtable] with keys:
        - Prompt: The modified prompt with context added
        - AddedFiles: Hashtable of files that were added (for deduplication tracking)
        - Count: Number of unique context files added
 
    .EXAMPLE
        $params = @{
            BasePrompt = $prompt
            InputFiles = @("file.fr.md")
            ContextFilter = { $_ -replace '\.fr\.md$', '.md' }
        }
        $result = Add-DynamicContextToPrompt @params
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [string]$BasePrompt,

        [Parameter(Mandatory)]
        [string[]]$InputFiles,

        [Parameter(Mandatory)]
        [scriptblock]$ContextFilter,

        [Parameter()]
        [string[]]$ContextFilterBase,

        [Parameter()]
        [System.Management.Automation.PSCmdlet]$PSCmdlet
    )

    $modifiedPrompt = $BasePrompt
    $addedDynamicContext = @{}

    Write-PSFMessage -Level Verbose -Message "Processing ContextFilter for $($InputFiles.Count) file(s)"
    Write-PSFMessage -Level Debug -Message "ContextFilter scriptblock: $($ContextFilter.ToString())"
    if ($ContextFilterBase) {
        Write-PSFMessage -Level Debug -Message "ContextFilterBase directories: $($ContextFilterBase -join ', ')"
    }

    foreach ($fileInBatch in $InputFiles) {
        Write-PSFMessage -Level Debug -Message "ContextFilter: Processing input file: $fileInBatch"
        try {
            # Run the scriptblock with $_ set to current file
            $derivedName = $fileInBatch | ForEach-Object $ContextFilter
            Write-PSFMessage -Level Debug -Message "ContextFilter: Scriptblock returned: '$derivedName'"

            if (-not $derivedName) {
                Write-PSFMessage -Level Debug -Message "ContextFilter: Empty result, skipping"
                continue
            }

            # Resolve path
            $derivedPath = $null
            if ([System.IO.Path]::IsPathRooted($derivedName)) {
                Write-PSFMessage -Level Debug -Message "ContextFilter: Derived name is absolute path"
                if (Test-Path $derivedName) {
                    $derivedPath = $derivedName
                    Write-PSFMessage -Level Debug -Message "ContextFilter: Absolute path exists: $derivedPath"
                } else {
                    Write-PSFMessage -Level Debug -Message "ContextFilter: Absolute path does not exist: $derivedName"
                }
            } else {
                # Build list of directories to search
                $searchDirs = @()
                if ($ContextFilterBase) {
                    $searchDirs += $ContextFilterBase
                }
                $searchDirs += Split-Path $fileInBatch -Parent
                Write-PSFMessage -Level Debug -Message "ContextFilter: Search directories (in order): $($searchDirs -join ', ')"

                # Search each directory until we find the file
                foreach ($baseDir in $searchDirs) {
                    $candidatePath = Join-Path $baseDir $derivedName
                    Write-PSFMessage -Level Debug -Message "ContextFilter: Checking candidate path: $candidatePath"
                    if (Test-Path $candidatePath) {
                        $derivedPath = (Resolve-Path $candidatePath).Path
                        Write-PSFMessage -Level Debug -Message "ContextFilter: Found at: $derivedPath"
                        break
                    }
                }
            }

            # Skip if file not found
            if (-not $derivedPath -or -not (Test-Path $derivedPath)) {
                $searchedPath = if ($derivedPath) { $derivedPath } else { $derivedName }
                Write-PSFMessage -Level Warning -Message "ContextFilter derived file not found: $searchedPath (from $fileInBatch)"
                Write-PSFMessage -Level Debug -Message "ContextFilter: File not found after searching all directories"
                continue
            }

            # Skip if derived file is same as input file
            $normalizedDerived = (Resolve-Path $derivedPath).Path -replace '\\', '/'
            $normalizedInput = (Resolve-Path $fileInBatch).Path -replace '\\', '/'
            if ($normalizedDerived -eq $normalizedInput) {
                Write-PSFMessage -Level Debug -Message "ContextFilter: Skipping - derived file same as input: $derivedPath"
                continue
            }

            # Skip if already added (deduplication)
            if ($addedDynamicContext.ContainsKey($normalizedDerived)) {
                Write-PSFMessage -Level Debug -Message "ContextFilter: Skipping duplicate: $derivedPath (already added)"
                continue
            }

            # Add to prompt with ShouldProcess
            $shouldProcess = if ($PSCmdlet) {
                $PSCmdlet.ShouldProcess($derivedPath, "Add dynamic context from ContextFilter")
            } else {
                $true
            }

            if ($shouldProcess) {
                $content = Get-Content -Path $derivedPath -Raw
                $contentLength = if ($content) { $content.Length } else { 0 }
                Write-PSFMessage -Level Debug -Message "ContextFilter: Reading content from $derivedPath ($contentLength chars)"
                $modifiedPrompt += "`n`n--- Dynamic Context from $derivedPath (for $fileInBatch) ---`n$content"
                $addedDynamicContext[$normalizedDerived] = $true
                Write-PSFMessage -Level Verbose -Message "Added dynamic context: $derivedPath (from $fileInBatch)"
            }

        } catch {
            Write-PSFMessage -Level Warning -Message "ContextFilter error for $fileInBatch : $_"
            Write-PSFMessage -Level Debug -Message "ContextFilter: Exception details: $($_.Exception.GetType().Name) - $($_.Exception.Message)"
        }
    }

    if ($addedDynamicContext.Count -gt 0) {
        Write-PSFMessage -Level Verbose -Message "Added $($addedDynamicContext.Count) dynamic context file(s) to prompt"
        Write-PSFMessage -Level Debug -Message "ContextFilter: Dynamic context files added: $($addedDynamicContext.Keys -join ', ')"
    }

    return @{
        Prompt     = $modifiedPrompt
        AddedFiles = $addedDynamicContext
        Count      = $addedDynamicContext.Count
    }
}