Payload/scripts/harness/setup-kaden-ai-overlays.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Apply project-specific AI overlays from Kaden templates (init-safe by default). .DESCRIPTION Copies overlay files from templates/kaden-ai-overlays/<project-id>/ into the target repo. Project id is read from .sync-project-id in the repo root unless -ProjectId is passed. Default: create missing files only (does NOT overwrite existing overlays). Use -Force to reset from template. Use -CleanStale to remove wrong-project overlay files. .PARAMETER TemplateRoot Path to the Kaden clone (contains templates/kaden-ai-overlays/). .PARAMETER ProjectId Override .sync-project-id (e.g. mac-test-suite, xml-migrator). .PARAMETER Force Overwrite existing overlay files from template. .PARAMETER CleanStale Remove 10-project-*.mdc and *-project.instructions.md for other project ids. .EXAMPLE .\scripts\setup-kaden-ai-overlays.ps1 -TemplateRoot %USERPROFILE%\Repos_local\kaden .EXAMPLE .\scripts\setup-kaden-ai-overlays.ps1 -TemplateRoot C:\path\to\kaden -Force -CleanStale #> [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)] [string]$TemplateRoot, [string]$RepoRoot = '', [string]$ProjectId = '', [switch]$Force, [switch]$CleanStale ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ([string]::IsNullOrWhiteSpace($RepoRoot)) { $scriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path } $RepoRoot = (Resolve-Path (Join-Path $scriptDir '..')).Path } $templateRootResolved = Resolve-Path -Path $TemplateRoot $overlayRoot = Join-Path $templateRootResolved.Path 'templates\kaden-ai-overlays' if (-not (Test-Path -LiteralPath $overlayRoot)) { throw "Overlay templates not found at $overlayRoot" } $idFilePath = Join-Path $RepoRoot '.sync-project-id' if ([string]::IsNullOrWhiteSpace($ProjectId)) { if (-not (Test-Path -LiteralPath $idFilePath)) { throw @( "Missing .sync-project-id in repo root and -ProjectId not supplied.", "Add a tracked file containing the project id (e.g. mac-test-suite, xml-migrator),", "or pass -ProjectId explicitly." ) -join ' ' } $ProjectId = (Get-Content -LiteralPath $idFilePath -Raw).Trim() } if ([string]::IsNullOrWhiteSpace($ProjectId)) { throw '.sync-project-id is empty.' } $sourceDir = Join-Path $overlayRoot $ProjectId if (-not (Test-Path -LiteralPath $sourceDir)) { throw "No overlay template for project '$ProjectId' at $sourceDir" } $rulesDir = Join-Path $RepoRoot '.cursor\rules' if (-not (Test-Path -LiteralPath $rulesDir)) { New-Item -ItemType Directory -Path $rulesDir -Force | Out-Null } $instructionsDir = Join-Path $RepoRoot '.github\instructions' if (-not (Test-Path -LiteralPath $instructionsDir)) { New-Item -ItemType Directory -Path $instructionsDir -Force | Out-Null } $utf8NoBom = New-Object System.Text.UTF8Encoding $false function Write-OverlayFile { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)][string]$SourcePath, [Parameter(Mandatory = $true)][string]$DestPath, [Parameter(Mandatory = $true)][string]$Label, [switch]$Force ) if (-not (Test-Path -LiteralPath $SourcePath)) { throw "Missing template file: $SourcePath" } if ((Test-Path -LiteralPath $DestPath) -and -not $Force) { Write-Output "[setup-kaden-ai-overlays] skip existing $Label (use -Force to overwrite): $DestPath" return } if ($PSCmdlet.ShouldProcess($DestPath, "Write $Label from template")) { $content = Get-Content -LiteralPath $SourcePath -Raw -Encoding UTF8 [System.IO.File]::WriteAllText($DestPath, $content, $utf8NoBom) Write-Output "[setup-kaden-ai-overlays] wrote $DestPath" } } function Remove-StaleOverlayFile { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$Reason ) if (-not (Test-Path -LiteralPath $Path)) { return } if ($PSCmdlet.ShouldProcess($Path, $Reason)) { Remove-Item -LiteralPath $Path -Force Write-Warning "[setup-kaden-ai-overlays] removed stale overlay: $Path" } } $destinations = @( @{ Source = Join-Path $sourceDir 'AGENTS.md' Dest = Join-Path $RepoRoot 'AGENTS.md' Label = 'AGENTS.md' }, @{ Source = Join-Path $sourceDir '10-project.mdc' Dest = Join-Path $rulesDir "10-project-$ProjectId.mdc" Label = 'Cursor project rule' }, @{ Source = Join-Path $sourceDir 'copilot-project.instructions.md' Dest = Join-Path $instructionsDir "$ProjectId-project.instructions.md" Label = 'Copilot project instructions' } ) if ($CleanStale) { Get-ChildItem -Path $rulesDir -Filter '10-project-*.mdc' -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne "10-project-$ProjectId.mdc" } | ForEach-Object { Remove-StaleOverlayFile -Path $_.FullName -Reason 'Remove wrong-project Cursor rule' } Get-ChildItem -Path $instructionsDir -Filter '*-project.instructions.md' -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne "$ProjectId-project.instructions.md" } | ForEach-Object { Remove-StaleOverlayFile -Path $_.FullName -Reason 'Remove wrong-project Copilot instructions' } } foreach ($item in $destinations) { Write-OverlayFile -SourcePath $item.Source -DestPath $item.Dest -Label $item.Label -Force:$Force } Write-Output "[setup-kaden-ai-overlays] project=$ProjectId repo=$RepoRoot" |