You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
8.3 KiB
192 lines
8.3 KiB
<#
|
|
.SYNOPSIS
|
|
Reconstruct the state of lights-out runs for Companion polling and Morning Review.
|
|
.DESCRIPTION
|
|
Reads the durable registry at ~\.scout\lights-out\runs.json and, for each run, re-derives
|
|
live facts from git and engine artifacts. Safe to call from a brand-new Scout session
|
|
that has no memory of the conversation that launched the run.
|
|
.EXAMPLE
|
|
.\check-run.ps1 -Latest
|
|
.EXAMPLE
|
|
.\check-run.ps1 -Since 1 -Json
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$Id,
|
|
[switch]$Latest,
|
|
[switch]$Active,
|
|
[int]$Since = 0,
|
|
[int]$NotesTail = 40,
|
|
[switch]$Json
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
. (Join-Path $PSScriptRoot 'lib.ps1')
|
|
|
|
$runs = @(Read-LightsOutRuns)
|
|
if ($runs.Count -eq 0) {
|
|
if ($Json) { '[]' } else { Write-Host 'No lights-out runs have ever been registered.' }
|
|
return
|
|
}
|
|
|
|
$runs = $runs | Sort-Object { [datetime]::Parse($_.startedAt) } -Descending
|
|
|
|
if ($Id) {
|
|
$runs = @($runs | Where-Object {
|
|
$_.id -eq $Id -or
|
|
($_.PSObject.Properties.Name -contains 'registryId' -and $_.registryId -eq $Id) -or
|
|
($_.PSObject.Properties.Name -contains 'launchId' -and $_.launchId -eq $Id) -or
|
|
($_.PSObject.Properties.Name -contains 'engineRunId' -and $_.engineRunId -eq $Id)
|
|
})
|
|
$launchMatches = @($runs | Where-Object {
|
|
$_.PSObject.Properties.Name -contains 'launchId' -and $_.launchId -eq $Id
|
|
} | Sort-Object { [datetime]::Parse($_.startedAt) } -Descending)
|
|
if ($launchMatches.Count -gt 0) {
|
|
$runs = @($launchMatches | Select-Object -First 1)
|
|
}
|
|
}
|
|
if ($Active) { $runs = @($runs | Where-Object { $_.status -in @('running', 'launching', 'finalizing') }) }
|
|
if ($Since -gt 0) {
|
|
$cutoff = (Get-Date).AddDays(-$Since)
|
|
$runs = @($runs | Where-Object { [datetime]::Parse($_.startedAt) -ge $cutoff })
|
|
}
|
|
if ($Latest) { $runs = @($runs | Select-Object -First 1) }
|
|
|
|
$results = foreach ($run in $runs) {
|
|
$alive = $false
|
|
if ($run.PSObject.Properties['workerPid'] -and $run.workerPid) {
|
|
$alive = Test-LightsOutProcessAlive -ProcessId ([int]$run.workerPid) -StartedAtIso $run.startedAt
|
|
}
|
|
|
|
$checkoutPath = Resolve-LightsOutCheckoutPath -Run $run
|
|
$runDir = $null
|
|
if ($checkoutPath -and (Test-Path -LiteralPath $checkoutPath)) {
|
|
$runDir = Resolve-LightsOutRunDir -RepoPath $checkoutPath -RunId $run.id
|
|
}
|
|
|
|
$endState = $null
|
|
if ($runDir) {
|
|
$endStatePath = Join-Path $runDir 'end-state.json'
|
|
if (Test-Path -LiteralPath $endStatePath) {
|
|
try { $endState = Get-Content -LiteralPath $endStatePath -Raw | ConvertFrom-Json } catch { }
|
|
}
|
|
}
|
|
|
|
$commits = @()
|
|
$diffStat = ''
|
|
$branchExists = $false
|
|
$canReadDiff = $checkoutPath -and (Test-Path -LiteralPath $checkoutPath)
|
|
if (($run.PSObject.Properties['worktree'] -and $run.worktree) -and -not $runDir) {
|
|
$canReadDiff = $false
|
|
}
|
|
if ($canReadDiff -and $run.baseCommit) {
|
|
$branchExists = [bool](git -C $checkoutPath rev-parse --verify --quiet $run.branch 2>$null)
|
|
$ref = if ($branchExists) { $run.branch } else { 'HEAD' }
|
|
$commits = @(git -C $checkoutPath --no-pager log --oneline "$($run.baseCommit)..$ref" 2>$null)
|
|
$diffStat = (git -C $checkoutPath --no-pager diff --shortstat "$($run.baseCommit)..$ref" 2>$null) -join ' '
|
|
}
|
|
|
|
$notesTailText = $null
|
|
if ($runDir) {
|
|
$notesPath = Join-Path $runDir 'notes.md'
|
|
if (Test-Path -LiteralPath $notesPath) {
|
|
$notesTailText = (Get-Content -LiteralPath $notesPath -Tail $NotesTail -ErrorAction SilentlyContinue) -join "`n"
|
|
}
|
|
}
|
|
|
|
$elapsed = $null
|
|
try {
|
|
$end = if ($run.PSObject.Properties['endedAt'] -and $run.endedAt) { [datetime]::Parse($run.endedAt) } else { Get-Date }
|
|
$elapsed = '{0:h\hm\m}' -f ($end - [datetime]::Parse($run.startedAt))
|
|
}
|
|
catch { }
|
|
|
|
$vaultNote = Get-RunPropertyValue -InputObject $run -Names @('vaultNote')
|
|
if (-not $vaultNote) {
|
|
$guess = Join-Path (Get-LightsOutVaultFolder -Run $run) 'summary.md'
|
|
if (Test-Path -LiteralPath $guess) { $vaultNote = $guess }
|
|
}
|
|
|
|
$effectiveStatus = $run.status
|
|
if ($run.status -in @('running', 'launching', 'finalizing') -and -not $alive) {
|
|
$hasWorkerPid = $run.PSObject.Properties['workerPid'] -and $run.workerPid
|
|
$startedRecently = $false
|
|
try { $startedRecently = ([datetime]::Parse($run.startedAt)) -gt (Get-Date).AddMinutes(-2) } catch { }
|
|
if ($hasWorkerPid -or -not $startedRecently) {
|
|
$effectiveStatus = 'orphaned'
|
|
}
|
|
}
|
|
|
|
$engine = Get-RunEngineDescriptor -Run $run
|
|
|
|
[pscustomobject]@{
|
|
id = $run.id
|
|
registryId = if ($run.PSObject.Properties['registryId']) { $run.registryId } else { $null }
|
|
launchId = if ($run.PSObject.Properties['launchId']) { $run.launchId } else { $null }
|
|
engineRunId = if ($run.PSObject.Properties['engineRunId']) { $run.engineRunId } else { $null }
|
|
status = $effectiveStatus
|
|
recordedStatus = $run.status
|
|
workerAlive = $alive
|
|
workerPid = if ($run.PSObject.Properties['workerPid']) { $run.workerPid } else { $null }
|
|
mode = $run.mode
|
|
agent = $run.agent
|
|
repoPath = $run.repoPath
|
|
checkoutPath = $checkoutPath
|
|
branch = $run.branch
|
|
branchExists = $branchExists
|
|
startedAt = $run.startedAt
|
|
endedAt = if ($run.PSObject.Properties['endedAt']) { $run.endedAt } else { $null }
|
|
elapsed = $elapsed
|
|
engineExitCode = $engine.ExitCode
|
|
commitCount = $commits.Count
|
|
commits = $commits
|
|
diffStat = $diffStat
|
|
stopWhen = if ($run.PSObject.Properties['stopWhen']) { $run.stopWhen } else { $null }
|
|
endState = $endState
|
|
verifyCommand = if ($run.PSObject.Properties['verifyCommand']) { $run.verifyCommand } else { $null }
|
|
verifyExitCodeBefore = if ($run.PSObject.Properties['verifyExitCodeBefore']) { $run.verifyExitCodeBefore } else { $null }
|
|
verifyExitCodeAfter = if ($run.PSObject.Properties['verifyExitCodeAfter']) { $run.verifyExitCodeAfter } else { $null }
|
|
runDir = $runDir
|
|
engineKind = $engine.Kind
|
|
engineSource = $engine.Source
|
|
engineVersion = $engine.Version
|
|
vaultNote = $vaultNote
|
|
notesTail = $notesTailText
|
|
prompt = $run.prompt
|
|
}
|
|
}
|
|
|
|
if ($Json) {
|
|
$array = @($results)
|
|
$jsonText = $array | ConvertTo-Json -Depth 10
|
|
if ($array.Count -eq 1) { "[$jsonText]" } else { $jsonText }
|
|
return
|
|
}
|
|
|
|
foreach ($result in $results) {
|
|
Write-Host ''
|
|
Write-Host "=== $($result.id) ===" -ForegroundColor Cyan
|
|
Write-Host " status : $($result.status)$(if ($result.status -eq 'orphaned') { ' <- worker died without finalizing' })"
|
|
Write-Host " repo/branch : $($result.repoPath) [$($result.branch)$(if (-not $result.branchExists) { ' MISSING' })]"
|
|
Write-Host " agent/mode : $($result.agent) / $($result.mode)"
|
|
if ($result.engineKind) {
|
|
$engineLine = $result.engineKind
|
|
if ($result.engineVersion) { $engineLine += " v$($result.engineVersion)" }
|
|
if ($null -ne $result.engineExitCode) { $engineLine += " exit=$($result.engineExitCode)" }
|
|
Write-Host " engine : $engineLine"
|
|
}
|
|
Write-Host " started : $($result.startedAt) elapsed: $($result.elapsed)"
|
|
Write-Host " commits : $($result.commitCount) $($result.diffStat)"
|
|
if ($result.stopWhen) { Write-Host " stop-when : $($result.stopWhen)" }
|
|
if ($result.endState) { Write-Host " end-state : $($result.endState.status) (iterations=$($result.endState.iterations))" }
|
|
if ($result.verifyCommand) { Write-Host " verify : '$($result.verifyCommand)' before=$($result.verifyExitCodeBefore) after=$($result.verifyExitCodeAfter)" }
|
|
if ($result.vaultNote) { Write-Host " vault note : $($result.vaultNote)" }
|
|
if ($result.runDir) { Write-Host " run dir : $($result.runDir)" }
|
|
if ($result.notesTail) {
|
|
Write-Host " --- notes tail ---"
|
|
$result.notesTail -split "`n" | ForEach-Object { Write-Host " $_" }
|
|
}
|
|
}
|
|
Write-Host ''
|
|
|