Payload/scripts/kaden/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%\<your-local-repo-root>\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"