Payload/scripts/harness/pull-ai-base.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
  Pull synced AI base from the Kaden template into this repository.

.EXAMPLE
  .\scripts\harness\pull-ai-base.ps1 -TemplateRoot %USERPROFILE%\Repos_local\kaden
  .\scripts\harness\setup-ai-overlays.ps1 -TemplateRoot %USERPROFILE%\Repos_local\kaden
  .\scripts\harness\setup-harness-hooks.ps1
#>

[CmdletBinding(SupportsShouldProcess = $true)]
param(
    [Parameter(Mandatory = $true)]
    [string]$TemplateRoot,

    [string]$TargetRoot = ''
)

$ErrorActionPreference = 'Stop'

# Resolve the destination repo root. This script is the bootstrap entry, so the
# target may not have .git, package.json, or scripts/harness yet.
if ([string]::IsNullOrWhiteSpace($TargetRoot)) {
    $scriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path }
    $current = (Resolve-Path $scriptDir).Path
    $resolved = $null

    # Prefer on-disk markers, walking up from the script. A first-time child
    # often has only scripts\ + .github\ (or just scripts\), so .git is optional.
    while ($true) {
        $hasGit = Test-Path -LiteralPath (Join-Path $current '.git')
        $hasNodeMarker = Test-Path -LiteralPath (Join-Path $current 'package.json')
        $hasHarnessInvoke = Test-Path -LiteralPath (Join-Path $current 'scripts\harness-invoke.ps1')
        $hasHarnessScripts = Test-Path -LiteralPath (Join-Path $current 'scripts\harness')
        $hasScriptsDir = Test-Path -LiteralPath (Join-Path $current 'scripts')
        $hasGithubDir = Test-Path -LiteralPath (Join-Path $current '.github')
        $isEstablishedRepo = $hasGit -or ($hasNodeMarker -and $hasHarnessInvoke) -or ($hasHarnessInvoke -and $hasHarnessScripts)
        $isBootstrapRepo = $hasScriptsDir -and $hasGithubDir
        if ($isEstablishedRepo -or $isBootstrapRepo) {
            $resolved = $current
            break
        }
        $parent = Split-Path $current -Parent
        if ([string]::IsNullOrWhiteSpace($parent) -or $parent -eq $current) {
            break
        }
        $current = $parent
    }

    # No markers (empty folder with only this script). Infer root from where
    # the script itself lives:
    # <repo>\scripts\pull-ai-base.ps1
    # <repo>\scripts\harness\pull-ai-base.ps1
    # <repo>\templates\scripts\harness\pull-ai-base.ps1
    if (-not $resolved) {
        $normalized = $scriptDir.TrimEnd('\', '/')
        if ($normalized -match '[\\/]templates[\\/]scripts[\\/]harness$') {
            $resolved = (Resolve-Path (Join-Path $scriptDir '..\..\..')).Path
        }
        elseif ($normalized -match '[\\/]scripts[\\/]harness$') {
            $resolved = (Resolve-Path (Join-Path $scriptDir '..\..')).Path
        }
        elseif ($normalized -match '[\\/]scripts$') {
            $resolved = (Resolve-Path (Join-Path $scriptDir '..')).Path
        }
    }

    if (-not $resolved) {
        throw "Could not locate repository root from $scriptDir. Pass -TargetRoot <repo-root>."
    }
    $TargetRoot = $resolved
}

# Source paths are relative to -TemplateRoot (the Kaden clone).
# Dest paths are relative to the target repo root.
# Exclude skips files that must stay in place while this script is running.
$pathsToCopy = @(
    @{ Source = '.github\agents';                       Dest = '.github\agents' },
    @{ Source = '.github\skills';                       Dest = '.github\skills' },
    @{ Source = '.github\agents\knowledge';             Dest = '.github\agents\knowledge' },
    @{ Source = '.github\prompts';                      Dest = '.github\prompts' },
    @{ Source = '.github\evals';                        Dest = '.github\evals' },
    @{ Source = '.github\copilot-instructions.md';      Dest = '.github\copilot-instructions.md' },
    @{ Source = '.github\instructions';                 Dest = '.github\instructions' },
    @{ Source = '.github\templates';                    Dest = '.github\templates' },
    @{ Source = '.cursor\rules';                        Dest = '.cursor\rules\00-from-template' },
    @{ Source = '.cursor\hooks';                        Dest = '.cursor\hooks' },
    @{ Source = 'docs\kaden';                           Dest = 'docs' },
    @{ Source = 'templates\scripts\harness-invoke.ps1'; Dest = 'scripts\harness-invoke.ps1' },
    @{ Source = 'templates\scripts\verify-local.ps1';   Dest = 'scripts\verify-local.ps1' },
    # Temporary dest: flatten harness package into templates\scripts, not scripts\harness.
    # Do not overwrite the running pull-ai-base.ps1 (Copy-Item -Exclude only applies to src\*).
    @{ Source = 'templates\scripts\harness';            Dest = 'scripts'; Exclude = @('pull-ai-base.ps1') }
)

foreach ($item in $pathsToCopy) {
    $src = Join-Path $TemplateRoot $item.Source
    $dst = Join-Path $TargetRoot $item.Dest

    if (-not (Test-Path $src)) {
        Write-Warning "Skip missing template path: $src"
        continue
    }

    if ($PSCmdlet.ShouldProcess($dst, "Copy from $src")) {
        if (Test-Path $src -PathType Container) {
            # Merge into dest. Do not Remove-Item the dest folder - that deletes
            # the running script and any already-copied siblings.
            if (-not (Test-Path -LiteralPath $dst)) {
                New-Item -ItemType Directory -Path $dst -Force | Out-Null
            }
            $copyParams = @{
                Path        = (Join-Path $src '*')
                Destination = $dst
                Recurse     = $true
                Force       = $true
            }
            if ($item.ContainsKey('Exclude') -and $item.Exclude) {
                $copyParams.Exclude = $item.Exclude
            }
            Copy-Item @copyParams
        }
        else {
            $parent = Split-Path $dst -Parent
            if ($parent -and -not (Test-Path -LiteralPath $parent)) {
                New-Item -ItemType Directory -Path $parent -Force | Out-Null
            }
            Copy-Item -Path $src -Destination $dst -Force
        }
    }
}

# sync-cursor-ai.ps1 currently lands under the temporary templates\scripts dest.
# The historical path is scripts\sync-cursor-ai.ps1 - try both.
$syncCandidates = @(
    (Join-Path $TargetRoot 'scripts\sync-cursor-ai.ps1')
    #(Join-Path $TargetRoot 'templates\scripts\sync-cursor-ai.ps1')
)
$syncScript = $syncCandidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
if ($syncScript) {
    & $syncScript -RepoRoot $TargetRoot
}
else {
    Write-Warning "sync-cursor-ai.ps1 not found under scripts\ or templates\scripts\ - run sync manually."
}

Write-Output "AI base pull complete. Next: .\scripts\harness\setup-ai-overlays.ps1 -TemplateRoot <path-to-kaden>; then .\scripts\harness\setup-harness-hooks.ps1"