scripts/specrew-hooks-doctor.ps1
|
<#
.SYNOPSIS `specrew hooks doctor` — the host-integration doctor: host-support tiers + hook-health receipt evidence + the Codex untrusted-headless governance preflight, in ONE report (F-198 iteration 005, T038/T039; FR-050 / FR-053 / FR-051). .DESCRIPTION The three Beta2 host-integration truths a maintainer needs at a glance are produced by three sibling leaf modules and stitched by the aggregator scripts/internal/continuous-co-review/host-support-doctor.ps1 (Format-SpecrewHostSupportDoctorReport). This is the AUTHORIZED production seam that surfaces them: the natural home (`specrew hooks status` in scripts/specrew-hooks.ps1) is F-184-protected, so the doctor rides its own routed, non-protected script here (dispatched from the non-protected scripts/specrew.ps1 `hooks` arm). Read-only and fail-open: it NEVER writes a receipt, a config, or ~/.codex. Missing / stale / drifted evidence renders HONESTLY (unverified/degraded), never health-washed - it only FORMATS what the resolvers return. Dispatcher-only command - it does NOT gate on project setup (it is a diagnostic surface, useful even in a broken project). Flags (Unix-style, parsed from the forwarded args): --project-path <path> Project whose hook-health receipts to resolve (default: cwd). --host <h> Narrow the hook-health rows to this host (repeatable). Default: the CLI-first hosts. --help | -h Usage. #> [CmdletBinding()] param( [Parameter(ValueFromRemainingArguments = $true)] [string[]]$Rest ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # --- Unix-style flag parse (the CLI dispatcher forwards --flag tokens, which do not bind PowerShell-style) --- $projectPath = $null $hosts = New-Object System.Collections.Generic.List[string] $showHelp = $false $remaining = @($Rest | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) for ($i = 0; $i -lt $remaining.Count; $i++) { $arg = $remaining[$i] if ($arg -match '^--project-path=(.+)$') { $projectPath = $Matches[1] } elseif ($arg -ieq '--project-path' -and ($i + 1) -lt $remaining.Count) { $projectPath = $remaining[++$i] } elseif ($arg -match '^--host=(.+)$') { $hosts.Add($Matches[1]) | Out-Null } elseif ($arg -ieq '--host' -and ($i + 1) -lt $remaining.Count) { $hosts.Add($remaining[++$i]) | Out-Null } elseif ($arg -ieq '--help' -or $arg -ieq '-h') { $showHelp = $true } } if ($showHelp) { Write-Host 'Usage: specrew hooks doctor [--project-path <path>] [--host <claude|codex|copilot> ...]' Write-Host '' Write-Host 'Surfaces, in one report:' Write-Host ' - host+surface support tiers (FR-050)' Write-Host ' - hook-health receipt evidence (FR-053) - a deployed hook config is NOT proof it fired' Write-Host ' - the Codex untrusted-headless governance preflight (FR-051)' Write-Host '' Write-Host 'Read-only and fail-open; missing / stale / drifted evidence renders unverified/degraded, never healthy.' exit 0 } if ([string]::IsNullOrWhiteSpace($projectPath)) { $projectPath = (Get-Location).Path } # Normalize to an ABSOLUTE path: the health resolvers use .NET file APIs, which resolve a relative path against # the PROCESS cwd, not the PowerShell location. Fail-open if the path does not exist (keep the user's value). try { $resolved = (Resolve-Path -LiteralPath $projectPath -ErrorAction Stop).Path; if ($resolved) { $projectPath = $resolved } } catch { $null = $_ } # Load the aggregator (it self-loads its two sibling leaf modules fail-open, so this single dot-source is enough). $doctorModule = Join-Path $PSScriptRoot 'internal/continuous-co-review/host-support-doctor.ps1' if (-not (Test-Path -LiteralPath $doctorModule -PathType Leaf)) { Write-Host "ERROR: host-support doctor module not found at $doctorModule" -ForegroundColor Red exit 1 } . $doctorModule if (-not (Get-Command -Name 'Format-SpecrewHostSupportDoctorReport' -ErrorAction SilentlyContinue)) { Write-Host 'ERROR: Format-SpecrewHostSupportDoctorReport is unavailable (aggregator failed to load).' -ForegroundColor Red exit 1 } $reportArgs = @{ ProjectRoot = $projectPath } if ($hosts.Count -gt 0) { $reportArgs.Hosts = @($hosts) } # The aggregator is fail-open per section and returns a STRING; write it as-is (no console coloring so the # report stays copy-paste faithful and testable). Write-Host (Format-SpecrewHostSupportDoctorReport @reportArgs) exit 0 |