AI skills in use in my daily
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

<#
.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 ''