Payload/scripts/kaden/harness/harness-turn-start.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Start a slice turn with journal + baseline verify-local evidence. .DESCRIPTION This is the harness-first default entrypoint for implementation turns. It preflights verify-local (retry once) BEFORE ActivityStarted so a transient capture FAIL cannot poison turn-discipline baseline checks. On preflight success it records ActivityStarted, appends an authoritative verify-local sensor row, and can optionally mark one slice phase in_progress. For ATDD -Phase and -FeaturePhase turns, when a PASS verify-local sensor exists within -FreshVerifyMinutes and the production tree is clean, baseline verify is skipped and the fresh PASS is reused (same TTL + fingerprint). .EXAMPLE .\scripts\kaden\harness-turn-start.ps1 -Feature saved-search-queries -Slice mo-save-list -Phase RED -Summary "start slice turn" #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Feature, [Parameter(Mandatory = $true)] [string]$Slice, [ValidateSet('RED', 'GREEN', 'MUTATE', 'REFACTOR', 'COMMIT', '')] [string]$Phase = '', [ValidateSet('quality-gates', 'slice-review', '')] [string]$FeaturePhase = '', [string]$Summary = 'Turn start (baseline verify-local)', [ValidateSet('cursor', 'copilot', 'other')] [string]$ModelProvider = 'copilot', [string]$ModelSlug = 'unknown', [ValidateRange(1, 1440)] [int]$FreshVerifyMinutes = 20, [string]$FeatureRoot = '', [string]$RepoRoot = '', [switch]$AllowArbitraryFeatureRoot, # When set, skip `git status --short` and use -GitStatusShort (empty = clean). Pester CM-A seam. [switch]$OverrideGitStatus, [string]$GitStatusShort = '' ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' Import-Module (Join-Path $PSScriptRoot 'lib\RepoRuntime.psm1') -Force Import-Module (Join-Path $PSScriptRoot 'lib\HarnessContract.psm1') -Force Import-Module (Join-Path $PSScriptRoot 'lib\HarnessJournal.psm1') -Force $recordPhase = Join-Path $PSScriptRoot 'harness-record-phase.ps1' $sensorScript = Join-Path $PSScriptRoot 'harness-sensor.ps1' $journalScript = Join-Path $PSScriptRoot 'harness-journal.ps1' $verifyScript = Join-Path (Split-Path $PSScriptRoot -Parent) 'verify-local.ps1' if ([string]::IsNullOrWhiteSpace($RepoRoot)) { $RepoRoot = Get-HarnessRepoRoot -StartPath $PSScriptRoot } Initialize-RepoShellEnvironment -LogPrefix 'harness-turn-start' | Out-Null if (-not [string]::IsNullOrWhiteSpace($Phase) -and -not [string]::IsNullOrWhiteSpace($FeaturePhase)) { throw 'harness-turn-start: -Phase and -FeaturePhase are mutually exclusive.' } $featureRootPath = Get-HarnessFeatureRoot -RepoRoot $RepoRoot -Feature $Feature ` -FeatureRootOverride $FeatureRoot -AllowArbitraryFeatureRoot:$AllowArbitraryFeatureRoot $gitStatusShort = '' if ($OverrideGitStatus) { $gitStatusShort = $GitStatusShort } else { Push-Location $RepoRoot try { $gitStatusShort = (& git status --short 2>$null | Out-String) } finally { Pop-Location } } $reuse = Resolve-HarnessTurnStartVerifyReuse ` -FeaturePhase $FeaturePhase ` -Phase $Phase ` -FeatureRoot $featureRootPath ` -FreshVerifyMinutes $FreshVerifyMinutes ` -GitStatusShort $gitStatusShort ` -RepoRoot $RepoRoot ` -UtcNow ([datetime]::UtcNow) $recordPhaseCommon = @{ Feature = $Feature Slice = $Slice ModelProvider = $ModelProvider ModelSlug = $ModelSlug RepoRoot = $RepoRoot } $childCommon = @{ Feature = $Feature Slice = $Slice ModelProvider = $ModelProvider ModelSlug = $ModelSlug RepoRoot = $RepoRoot } if (-not [string]::IsNullOrWhiteSpace($FeatureRoot)) { $recordPhaseCommon.FeatureRoot = $featureRootPath $recordPhaseCommon.AllowArbitraryFeatureRoot = $true $childCommon.FeatureRoot = $featureRootPath $childCommon.AllowArbitraryFeatureRoot = $true } $turnAlreadyOpen = Test-HarnessSliceTurnOpen -FeatureRoot $featureRootPath -Slice $Slice if ($turnAlreadyOpen -and -not [string]::IsNullOrWhiteSpace($FeaturePhase)) { Write-Output '[harness-turn-start] turn already open; skipping ActivityStarted and baseline verify. Record FeaturePhase only.' & $recordPhase @recordPhaseCommon -FeaturePhase $FeaturePhase -FeaturePhaseStatus in_progress try { $ownReport = Get-HarnessOwnSliceReport -FeatureRoot $featureRootPath -Feature $Feature -Slice $Slice ` -RepoRoot $RepoRoot $ownPath = Write-HarnessOwnSliceNextArtifact -FeatureRoot $featureRootPath -Report $ownReport Format-HarnessOwnSliceHumanReport -Report $ownReport -Header 'OWN after turn-start' | ForEach-Object { Write-Output $_ } Write-Output " Wrote: $ownPath" Write-Output '' } catch { Write-Warning "harness-turn-start: OWN context unavailable: $($_.Exception.Message)" } return } $readyReport = Assert-HarnessTurnStartSandboxReady -RepoRoot $RepoRoot Write-Output ("[harness-turn-start] {0}" -f $readyReport.Summary) $skippedBaseline = $false if ($reuse.ShouldSkip) { $skippedBaseline = $true Write-Output "[harness-turn-start] $($reuse.Message)" } else { $preflight = Invoke-HarnessVerifyLocalBookendPreflight -RepoRoot $RepoRoot -VerifyScript $verifyScript if ($preflight.ExitCode -ne 0) { $stepLabel = if (-not [string]::IsNullOrWhiteSpace([string]$preflight.FailedStep)) { [string]$preflight.FailedStep } else { 'verify-local' } throw "harness-turn-start: preflight verify-local failed at step '$stepLabel' after $($preflight.MaxAttempts) attempt(s). Fix the suite before starting a turn so baseline journal evidence stays PASS." } } & $journalScript @childCommon -Action start -Agent atdd-developer -Summary $Summary ` -ActivityType turn -ActivityName start if ($skippedBaseline) { Write-Output '[harness-turn-start] skipped baseline verify-local (fresh PASS reused); no new verify-local sensor written' $reusedSensor = $reuse.Sensor $reusedArtifact = [string](Get-HarnessProperty -Object $reusedSensor -Name 'RelativeName') $reusedActivity = New-HarnessJournalActivity -Id (New-HarnessJournalActivityId) -Type 'sensor' -Name 'verify-local' -Role 'instant' $reusedActor = New-HarnessJournalActor -Agent 'harness-turn-start' -ParentAgent $null -Tool 'harness-script' ` -Provider $ModelProvider -Slug $ModelSlug -Role 'primary' Add-HarnessJournalEvent -FeatureRoot $featureRootPath -Feature $Feature -Slice $Slice ` -Kind 'SensorRunRecorded' -Activity $reusedActivity -Actor $reusedActor -Outcome 'PASS' ` -Summary ("reused fresh verify-local sensor {0}" -f $reusedArtifact) ` -Payload ([pscustomobject]@{ name = 'verify-local' type = 'computational' verdict = 'PASS' evidence_artifact = ('sensors/' + $reusedArtifact) skip_reason_code = 'REUSED_FRESH_SENSOR' skip_rationale = 'fresh-pass-reuse' }) ` -Refs ([pscustomobject]@{ amends_event_id = $null pair_event_id = $null artifact = ('sensors/' + $reusedArtifact) command = 'harness-turn-start.ps1' inner_command = 'reused fresh verify-local sensor' }) | Out-Null } else { & $sensorScript @childCommon -Run verify-local -Append $sensorExit = $LASTEXITCODE # Immediate baseline discipline check -- fail fast instead of 60 minutes later. # harness-sensor.ps1 exits 1 when verify-local returned FAIL; we surface that now # rather than letting a poisoned baseline propagate silently through RED -> REFACTOR. if ($sensorExit -ne 0) { Write-Warning 'harness-turn-start: verify-local sensor recorded FAIL. The turn ActivityStarted is in the journal but the baseline is poisoned.' Write-Warning 'Fix: resolve the suite failure (npm test / npm run lint), then run harness-turn-stop.ps1 to close this turn, and start a fresh turn with harness-turn-start.ps1.' throw 'harness-turn-start: baseline verify-local FAIL -- turn is open but poisoned. See warnings above.' } } if (-not [string]::IsNullOrWhiteSpace($Phase)) { & $recordPhase @recordPhaseCommon -Phase $Phase -Status in_progress } elseif (-not [string]::IsNullOrWhiteSpace($FeaturePhase)) { & $recordPhase @recordPhaseCommon -FeaturePhase $FeaturePhase -FeaturePhaseStatus in_progress } # OWN context -- same terminal blob as ActivityStarted so agents see NEXT without a second memory hop. try { $ownReport = Get-HarnessOwnSliceReport -FeatureRoot $featureRootPath -Feature $Feature -Slice $Slice ` -RepoRoot $RepoRoot $ownPath = Write-HarnessOwnSliceNextArtifact -FeatureRoot $featureRootPath -Report $ownReport Format-HarnessOwnSliceHumanReport -Report $ownReport -Header 'OWN after turn-start' | ForEach-Object { Write-Output $_ } Write-Output " Wrote: $ownPath" Write-Output '' } catch { Write-Warning "harness-turn-start: OWN context unavailable: $($_.Exception.Message)" } |