Payload/scripts/kaden/verify-local.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Agent/harness bookend quality gate (full bolus: test-design Semgrep, Vitest, ESLint, PowerShell lint). .DESCRIPTION This is the harness bookend gate used by turn-start, turn-stop, and harness-sensor - NOT the git pre-commit or pre-push hook. Husky runs staged/focused checks separately (.husky/pre-commit, .husky/pre-push). Fail-fast order (core gates): lint:tests, test, lint, lint:powershell. Use -Security for Tier A (security:pre-push). Use -SecurityFull for Tier A + B (security:pr). Each step announces itself as "[verify-local] npm run <name>" so FAIL sensors can attribute the failure to a named step without changing the single exit-code contract. When package.json is absent, Node gates are skipped and lint-powershell.ps1 is the bookend. .PARAMETER Security Run Tier A security gate (security:pre-push) when defined in package.json. .PARAMETER SecurityFull Run full pre-PR security gate (security:pr = Tier A + corporate-parity Semgrep) when defined. .EXAMPLE .\scripts\kaden\verify-local.ps1 .EXAMPLE .\scripts\kaden\verify-local.ps1 -Security .EXAMPLE .\scripts\kaden\verify-local.ps1 -SecurityFull #> [CmdletBinding()] param( [switch]$Security, [switch]$SecurityFull ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot (Join-Path '..' '..'))).Path $runtimeModule = Join-Path $PSScriptRoot 'harness\lib\RepoRuntime.psm1' $contractModule = Join-Path $PSScriptRoot 'harness\lib\HarnessContract.psm1' Import-Module $runtimeModule -Force Import-Module $contractModule -Force # Nested helpers live outside try/finally so PowerShell Editor Services can parse the script. function Write-VerifyLocalLine { param([AllowEmptyString()][string]$Message = '') # Console.Out reaches the process stdout for sensor capture without becoming # PowerShell pipeline output (which would pollute Invoke-HarnessFailFastNamedSequence returns). [Console]::Out.WriteLine($Message) } function Invoke-LocalNpmScript { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)]$ScriptsTable ) if (-not $ScriptsTable.PSObject.Properties[$Name]) { Write-VerifyLocalLine "[verify-local] skip $Name (not in package.json)" return 0 } Write-VerifyLocalLine "[verify-local] npm run $Name" # Start-Process (not `& npm | ForEach-Object`) so Vitest/ESLint stdout is not # enumerated line-by-line on the PowerShell pipeline -- that adds ~1ms per line # and turns a ~15s suite into 100s+. Child inherits this console (sensor capture # still sees announce lines + npm output). Function return stays a single int. $npm = Resolve-HarnessExternalCommandPath -Command 'npm' $proc = Start-Process -FilePath $npm -ArgumentList @('run', $Name) -WorkingDirectory (Get-Location).Path ` -NoNewWindow -Wait -PassThru if ($null -eq $proc -or $null -eq $proc.ExitCode) { return 0 } return [int]$proc.ExitCode } function Invoke-LocalPowerShellLint { $lintScript = Join-Path $repoRoot 'scripts\kaden\harness\lint-powershell.ps1' if (-not (Test-Path -LiteralPath $lintScript)) { Write-VerifyLocalLine '[verify-local] skip lint:powershell (scripts/kaden/harness/lint-powershell.ps1 missing)' return 0 } Write-VerifyLocalLine '[verify-local] lint:powershell' $proc = Start-Process -FilePath (Get-Process -Id $PID).Path -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $lintScript) ` -WorkingDirectory $repoRoot -NoNewWindow -Wait -PassThru if ($null -eq $proc -or $null -eq $proc.ExitCode) { return 0 } return [int]$proc.ExitCode } Push-Location $repoRoot try { Initialize-RepoShellEnvironment -LogPrefix 'verify-local' if (-not (Test-Path 'package.json')) { Write-VerifyLocalLine '[verify-local] skip lint:tests (not in package.json)' Write-VerifyLocalLine '[verify-local] skip test (not in package.json)' Write-VerifyLocalLine '[verify-local] skip lint (not in package.json)' $lintCode = Invoke-LocalPowerShellLint if ($lintCode -ne 0) { throw "lint:powershell failed with exit code $lintCode" } if ($SecurityFull) { Write-VerifyLocalLine '[verify-local] skip security:pr (not in package.json)' } elseif ($Security) { Write-VerifyLocalLine '[verify-local] skip security:pre-push (not in package.json)' } Write-VerifyLocalLine '[verify-local] All requested gates passed.' return } $pkg = Get-Content 'package.json' -Raw | ConvertFrom-Json # StrictMode: package.json may exist without a scripts table (template / deps-only checkouts). $hasScripts = $pkg.PSObject.Properties.Name -contains 'scripts' -and $null -ne $pkg.scripts if (-not $hasScripts) { Write-VerifyLocalLine '[verify-local] skip lint:tests (no package.json scripts)' Write-VerifyLocalLine '[verify-local] skip test (no package.json scripts)' Write-VerifyLocalLine '[verify-local] skip lint (no package.json scripts)' $lintCode = Invoke-LocalPowerShellLint if ($lintCode -ne 0) { throw "lint:powershell failed with exit code $lintCode" } if ($SecurityFull) { Write-VerifyLocalLine '[verify-local] skip security:pr (no package.json scripts)' } elseif ($Security) { Write-VerifyLocalLine '[verify-local] skip security:pre-push (no package.json scripts)' } Write-VerifyLocalLine '[verify-local] All requested gates passed.' return } $scripts = $pkg.scripts # Canonical core gate order (single source of truth for announce lines + fail-fast). # Security switches append after these; they are optional and not part of the core order contract. $coreGateScripts = @('lint:tests', 'test', 'lint', 'lint:powershell') $coreResult = Invoke-HarnessFailFastNamedSequence -Names $coreGateScripts -InvokeName { param($Name) return [int](Invoke-LocalNpmScript -Name $Name -ScriptsTable $scripts) } if ($coreResult.ExitCode -ne 0) { $failed = $coreResult.Ran[-1] throw "npm run $failed failed with exit code $($coreResult.ExitCode)" } if ($SecurityFull) { $code = Invoke-LocalNpmScript -Name 'security:pr' -ScriptsTable $scripts if ($code -ne 0) { throw "npm run security:pr failed with exit code $code" } } elseif ($Security) { $code = Invoke-LocalNpmScript -Name 'security:pre-push' -ScriptsTable $scripts if ($code -ne 0) { throw "npm run security:pre-push failed with exit code $code" } } Write-VerifyLocalLine '[verify-local] All requested gates passed.' } finally { Pop-Location } |