Public/Search.ps1

#### # Search-KeywordInFile
function Search-KeywordInFile {
    #### Walks a directory tree and returns every file whose contents match a regex pattern.
    ####
    #### `Select-String` runs in parallel with eight worker threads. The matched paths are
    #### written to `FilesFound.json` under `OutPath` and also returned on the pipeline as a
    #### compact JSON string.
    ####
    #### **Parameters**
    #### - `[string]`: __Directory__
    #### - *Root of the recursive walk. Resolved against the current location.*
    #### - `[string]`: __Pattern__
    #### - *Regex passed to `Select-String -Pattern`.*
    #### - `[string]`: __OutPath__
    #### - *Directory the `FilesFound.json` result lands in. Created if missing. Defaults to the current location.*
    ####
    #### **Returns**
    #### - `[string]`
    #### - *Compact JSON array of matching file paths.*
    ####
    #### **Requires**
    #### - *PowerShell 7+ for `ForEach-Object -Parallel`.*
    ####
    #### **Example**
    #### ```powershell
    #### Search-KeywordInFile -Directory ./src -Pattern 'TODO'
    #### ```
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Directory,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Pattern,

        [Parameter(Mandatory = $false)]
        [string]$OutPath = (Get-Location).Path
    )

    # Resolve once so the parallel block sees an absolute path; relative paths bind to
    # each worker's own working directory, not guaranteed to match the caller.
    $resolvedDirectory = Resolve-Path -Path $Directory

    # SilentlyContinue plus Force keeps the walk alive across hidden, system, and
    # access-denied items. ThrottleLimit 8 fits most laptop core counts without
    # saturating the disk queue.
    $results = @(
        Get-ChildItem -Path $resolvedDirectory -Recurse -File -ErrorAction SilentlyContinue -Force |
            ForEach-Object -ThrottleLimit 8 -Parallel {
                $pattern = $using:Pattern
                # Quiet returns a bool so each worker ships one value back to the parent
                # runspace per file instead of the full set of match objects.
                $matched = Select-String -Path $_.FullName -Pattern $pattern -Encoding utf8 -Quiet -ErrorAction SilentlyContinue -AllMatches
                [PSCustomObject]@{
                    Path = $_.FullName
                    Matched = [bool]$matched
                }
            }
    )

    # Project the matched paths and persist them under OutPath so the JSON survives the
    # session. The same string is also returned so callers can pipe it forward.
    $matchedFiles = @($results | Where-Object Matched | Select-Object -ExpandProperty Path)
    if (-not (Test-Path -Path $OutPath)) {
        [void](New-Item -Path $OutPath -ItemType Directory -Force)
    }
    $jsonPath = Join-Path $OutPath 'FilesFound.json'
    $jsonOut = ConvertTo-Json -InputObject $matchedFiles -Compress
    $jsonOut | Out-File -FilePath $jsonPath -Encoding utf8
    Write-Host -BackgroundColor Cyan "Results json file has been written to $jsonPath" -ForegroundColor DarkRed
    return $jsonOut
}