scripts/internal/instruction-deploy.ps1
|
Set-StrictMode -Version Latest # F-184 iteration 002 (T003; FR-011 / FR-015 / FR-016): manifest-driven, host-neutral # deployment of the Specrew coordinator instruction section into every supported host's # manifest-declared InstructionsFile. # # Host-neutral (FR-015): the InstructionsFile location comes from host.psd1; there are NO # host-name (`agy`/Antigravity/claude/...) branches here. Used by `specrew init` (deploy), # `specrew update` (refresh), and `specrew start` (heal). The host registry # (Get-RegisteredHostKinds / Get-HostManifest from hosts/_registry.ps1) must be loaded by # the caller; the merge primitive is dot-sourced below. . (Join-Path $PSScriptRoot 'instruction-file-merge.ps1') function Deploy-SpecrewCoordinatorInstructions { # Deploy/refresh/heal the coordinator managed section into each supported host's # InstructionsFile under $ProjectRoot. Idempotent (init/update/start-heal converge). # Hosts that share an InstructionsFile (e.g. AGENTS.md across codex/antigravity/cursor) # are written once. Returns one result row per supported host: # { Kind, InstructionsFile, Path, Changed, Created, SharedWith }. param( [Parameter(Mandatory = $true)][string]$ProjectRoot, [AllowNull()][string]$HostKind ) if (-not (Get-Command -Name Get-RegisteredHostKinds -ErrorAction SilentlyContinue)) { throw "Deploy-SpecrewCoordinatorInstructions requires the host registry (hosts/_registry.ps1) to be loaded." } $fragment = Get-SpecrewCoordinatorFragment $kinds = if (-not [string]::IsNullOrWhiteSpace($HostKind)) { @($HostKind.ToLowerInvariant()) } else { @(Get-RegisteredHostKinds) } $results = [System.Collections.Generic.List[object]]::new() $deployedPaths = @{} foreach ($kind in $kinds) { $manifest = Get-HostManifest -Kind $kind $status = if ($manifest.ContainsKey('Status')) { [string]$manifest['Status'] } else { '' } if ($status -ne 'supported') { continue } $instructionsFile = if ($manifest.ContainsKey('InstructionsFile')) { [string]$manifest['InstructionsFile'] } else { '' } if ([string]::IsNullOrWhiteSpace($instructionsFile)) { continue } # host declares no InstructionsFile -> skip gracefully $target = Join-Path $ProjectRoot $instructionsFile $key = $target.ToLowerInvariant() if ($deployedPaths.ContainsKey($key)) { # Another supported host shares this InstructionsFile; the section is already there. $results.Add([pscustomobject]@{ Kind = $kind; InstructionsFile = $instructionsFile; Path = $target Changed = $false; Created = $false; SharedWith = $deployedPaths[$key] }) | Out-Null continue } $res = Set-SpecrewInstructionFileSection -Path $target -ManagedContent $fragment $deployedPaths[$key] = $kind $results.Add([pscustomobject]@{ Kind = $kind; InstructionsFile = $instructionsFile; Path = $target Changed = $res.Changed; Created = $res.Created; SharedWith = $null }) | Out-Null } return $results.ToArray() } function Invoke-SpecrewInstructionDeployment { # Integration entry point for `specrew init` (deploy), `specrew update` (refresh), and # `specrew start` (heal). Self-loads the host registry with a fail-open ladder, deploys # the coordinator section into every supported host's InstructionsFile, and returns # action rows for the caller to report. Fail-open: instruction-deploy problems NEVER fail # init/update/start. param( [Parameter(Mandatory = $true)][string]$ProjectPath ) $actions = [System.Collections.Generic.List[object]]::new() if (-not (Get-Command -Name Get-RegisteredHostKinds -ErrorAction SilentlyContinue)) { $registryPath = Join-Path (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) 'hosts/_registry.ps1' if (Test-Path -LiteralPath $registryPath -PathType Leaf) { try { . $registryPath } catch { $null = $_ } } } if (-not (Get-Command -Name Get-RegisteredHostKinds -ErrorAction SilentlyContinue)) { $actions.Add([pscustomobject]@{ HostKind = '(registry)'; Action = 'coordinator-instructions-skipped'; Detail = 'host registry unavailable' }) | Out-Null return $actions.ToArray() } try { foreach ($r in @(Deploy-SpecrewCoordinatorInstructions -ProjectRoot $ProjectPath)) { $detail = if ($r.SharedWith) { ('{0} (shared with {1})' -f $r.InstructionsFile, $r.SharedWith) } elseif ($r.Created) { ('{0} created' -f $r.InstructionsFile) } elseif ($r.Changed) { ('{0} refreshed' -f $r.InstructionsFile) } else { ('{0} already current' -f $r.InstructionsFile) } $actions.Add([pscustomobject]@{ HostKind = $r.Kind; Action = 'coordinator-instructions'; Detail = $detail }) | Out-Null } } catch { $actions.Add([pscustomobject]@{ HostKind = '(all)'; Action = 'coordinator-instructions-failed'; Detail = $_.Exception.Message }) | Out-Null } return $actions.ToArray() } |