Payload/scripts/kaden/utils/sensor-fail-extract.ps1

<#
.SYNOPSIS
    Extract failure-focused lines from a harness sensor JSON file.

.DESCRIPTION
    Loads a sensor artifact JSON, prints key metadata, then scans selected text
    fields for lines that match a regex pattern. This replaces one-off scripts
    that hardcode a specific sensor path.

.PARAMETER SensorPath
    Path to a sensor JSON file. Relative paths resolve from current directory,
    then from the repository root.

.PARAMETER Pattern
    Regex used to extract interesting lines from text fields.
    Default: lines that start with "[-]".

.PARAMETER Fields
    Sensor fields to scan for Pattern.

.EXAMPLE
    pwsh -NoProfile -File .\scripts\kaden\utils\sensor-fail-extract.ps1 -SensorPath docs\harness\features\harness-event-journal\sensors\harness-run-slice-tests-20260928T221703Z.json

.EXAMPLE
    pwsh -NoProfile -File .\scripts\kaden\utils\sensor-fail-extract.ps1 -SensorPath .\my-sensor.json -Pattern "(?i)exception|error|fail"

.EXAMPLE
    Get-Help .\scripts\kaden\utils\sensor-fail-extract.ps1 -Parameter Pattern
#>


#Requires -Version 5.1
[CmdletBinding(DefaultParameterSetName = "Run")]
param(
    [Parameter(ParameterSetName = "Run", Mandatory = $true, Position = 0)]
    [string]$SensorPath,

    [Parameter(ParameterSetName = "Run")]
    [string]$Pattern = "(?m)^\[-\].+$",

    [Parameter(ParameterSetName = "Run")]
    [string[]]$Fields = @(
        "stdout_tail",
        "stderr_tail",
        "stdout",
        "stderr",
        "output_tail",
        "command_stdout_tail",
        "inner_stdout_tail",
        "failure_summary",
        "evidence_summary"
    ),

    [Parameter(ParameterSetName = "Help")]
    [Alias("h", "help")]
    [switch]$ShowHelp
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Resolve-SensorExtractRepoRoot {
    $current = $PSScriptRoot
    while ($true) {
        $marker = Join-Path $current "package.json"
        if (Test-Path -LiteralPath $marker) {
            return $current
        }
        $parent = Split-Path $current -Parent
        if ([string]::IsNullOrWhiteSpace($parent) -or $parent -eq $current) {
            throw "Could not locate repository root (package.json) from this script."
        }
        $current = $parent
    }
}

function Resolve-SensorExtractPath {
    param(
        [Parameter(Mandatory = $true)][string]$InputPath,
        [Parameter(Mandatory = $true)][string]$RepoRoot
    )

    if (Test-Path -LiteralPath $InputPath) {
        return (Resolve-Path -LiteralPath $InputPath).Path
    }
    if (-not [System.IO.Path]::IsPathRooted($InputPath)) {
        $candidate = Join-Path $RepoRoot $InputPath
        if (Test-Path -LiteralPath $candidate) {
            return (Resolve-Path -LiteralPath $candidate).Path
        }
    }
    throw "Sensor file not found: $InputPath. Get-Help `"$PSCommandPath`" -Parameter SensorPath"
}

$repoRoot = Resolve-SensorExtractRepoRoot
$cliKitManifest = Join-Path $PSScriptRoot "PsCliUiKit\PsCliUiKit.psd1"
if (-not (Test-Path -LiteralPath $cliKitManifest)) {
    throw "PsCliUiKit manifest not found: $cliKitManifest"
}
Import-Module -Name $cliKitManifest -Force

try {
    if ($ShowHelp) {
        (Get-Help -Name $PSCommandPath -Full | Out-String -Width 100) | Out-Host
        return
    }

    $fullPath = Resolve-SensorExtractPath -InputPath $SensorPath -RepoRoot $repoRoot
    if ($Pattern.Length -gt 400) {
        throw "Pattern is too long. Get-Help `"$PSCommandPath`" -Parameter Pattern"
    }
    try {
        $null = [regex]::new($Pattern)
    } catch {
        throw "Invalid pattern: $($_.Exception.Message) Get-Help `"$PSCommandPath`" -Parameter Pattern"
    }
    $sensor = Get-Content -LiteralPath $fullPath -Raw -Encoding UTF8 | ConvertFrom-Json

    Write-CliSection -Title "Sensor" -Style "info"
    Write-CliKeyValue -Label "path" -Value $fullPath -LabelWidth 14
    if ($sensor.PSObject.Properties.Name -contains "name") {
        Write-CliKeyValue -Label "sensor" -Value ([string]$sensor.name) -LabelWidth 14
    }
    if ($sensor.PSObject.Properties.Name -contains "command") {
        Write-CliKeyValue -Label "command" -Value ([string]$sensor.command) -LabelWidth 14
    }
    if ($sensor.PSObject.Properties.Name -contains "inner_command") {
        Write-CliKeyValue -Label "inner command" -Value ([string]$sensor.inner_command) -LabelWidth 14
    }
    if ($sensor.PSObject.Properties.Name -contains "exit_code") {
        Write-CliKeyValue -Label "sensor exit" -Value ([string]$sensor.exit_code) -LabelWidth 14
    }
    if ($sensor.PSObject.Properties.Name -contains "summary") {
        Write-CliKeyValue -Label "failure" -Value ([string]$sensor.summary) -LabelWidth 14
    }

    $totalMatches = 0
    $printedExtracted = $false
    foreach ($field in $Fields) {
        if ($sensor.PSObject.Properties.Name -notcontains $field) {
            continue
        }
        $value = [string]$sensor.$field
        if ([string]::IsNullOrWhiteSpace($value)) {
            continue
        }
        $fieldMatches = [regex]::Matches($value, $Pattern)
        if ($fieldMatches.Count -eq 0) {
            continue
        }

        $totalMatches += $fieldMatches.Count
        if (-not $printedExtracted) {
            Write-CliColor -Message ""
            Write-CliColor -Message " [muted]extracted[-]"
            $printedExtracted = $true
        }
        Write-CliColor -Message " [muted]$field[-]"
        foreach ($match in $fieldMatches) {
            $lines = @(
                $match.Value -split "\r?\n" |
                    ForEach-Object { $_.TrimEnd() } |
                    Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
            )
            Write-CliList -Item $lines -Bullet "!" -Indent 4 -Literal
        }
    }

    if ($totalMatches -eq 0) {
        Write-CliStatus -State "Info" -Message "No lines matched pattern: $Pattern"
    } else {
        Write-CliStatus -State "Success" -Message "$totalMatches matching line(s) extracted."
    }
} catch {
    Write-CliSection -Title "Sensor extract failed" -Style "error"
    Write-CliStatus -State "Error" -Message $_.Exception.Message
    exit 2
} finally {
    Write-CliEnd
}