param(
  [string]$ProjectRoot = "",
  [ValidateSet("full", "user", "project", "vendor")]
  [string]$Mode = "full",
  [ValidateSet("user", "project", "vendor")]
  [string]$Scope = "",
  [Alias("DryRun")]
  [switch]$Plan,
  [switch]$UpgradePlan,
  [switch]$UninstallPlan,
  [switch]$MigratePlan,
  [switch]$MigrateApply,
  [switch]$Upgrade,
  [switch]$Uninstall,
  [switch]$Confirm,
  [switch]$Force,
  [switch]$InitSmoke,
  [switch]$CommitBaseline,
  [switch]$SkipBaselineCommit,
  [switch]$InitGitIfNeeded
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Get-PackageRoot {
  return [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\.."))
}

function Write-Step {
  param([string]$Message)
  Write-Host ("[install-agent] {0}" -f $Message)
}

function Normalize-ManifestPath {
  param(
    [string]$Path,
    [string]$Label,
    [switch]$AllowEnvironmentPath
  )

  $raw = if ($null -eq $Path) { "" } else { [string]$Path }
  $normalized = $raw.Trim().Replace('\', '/')
  while ($normalized.StartsWith('./', [System.StringComparison]::Ordinal)) {
    $normalized = $normalized.Substring(2)
  }

  if (-not $AllowEnvironmentPath) {
    $normalized = $normalized.TrimStart('/')
  }

  if ([string]::IsNullOrWhiteSpace($normalized)) {
    throw "$Label 不能为空。"
  }

  if ((-not $AllowEnvironmentPath) -and ([System.IO.Path]::IsPathRooted($normalized) -or $normalized -match '^[A-Za-z]:')) {
    throw "$Label 不能是 rooted path: $normalized"
  }

  $pathBody = $normalized.TrimEnd('/')
  foreach ($segment in ($pathBody -split '/')) {
    if ($segment -eq '..') {
      throw "$Label 不能包含 .. 段: $normalized"
    }
  }

  return $normalized
}

function Resolve-ManifestSourcePath {
  param(
    [string]$SourceRoot,
    [string]$SourceRelativePath
  )

  $normalizedSource = Normalize-ManifestPath -Path $SourceRelativePath -Label "install manifest source"
  if ($normalizedSource.StartsWith('templates/', [System.StringComparison]::OrdinalIgnoreCase)) {
    return Join-Path (Join-Path $SourceRoot "docs\codex-harness-engineering") $normalizedSource
  }

  return Join-Path $SourceRoot $normalizedSource
}

function Resolve-ManifestTargetPath {
  param(
    [string]$ProjectRoot,
    [string]$DestinationRelativePath,
    [string]$Scope
  )

  $normalizedDestination = Normalize-ManifestPath -Path $DestinationRelativePath -Label "install manifest destination" -AllowEnvironmentPath
  if ($Scope -eq "user") {
    $expanded = [Environment]::ExpandEnvironmentVariables($normalizedDestination.Replace('/', '\'))
    return [System.IO.Path]::GetFullPath($expanded)
  }

  return [System.IO.Path]::GetFullPath((Join-Path $ProjectRoot $normalizedDestination))
}

function Read-InstallManifest {
  param(
    [string]$SourceRoot,
    [string]$ProjectRoot
  )

  $manifestPath = Join-Path $SourceRoot "install-manifest.json"
  if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
    throw "找不到 install manifest: $manifestPath"
  }

  try {
    $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
  }
  catch {
    throw "install manifest 不是合法 JSON: $manifestPath"
  }

  if ([string]$manifest.package -ne "codex-harness") {
    throw "install manifest package 必须是 codex-harness。"
  }

  if ([string]::IsNullOrWhiteSpace([string]$manifest.version)) {
    throw "install manifest version 不能为空。"
  }

  $installModes = @($manifest.installModes)
  if ($installModes.Count -eq 0) {
    throw "install manifest 缺少 installModes。"
  }

  $modeNames = @($installModes | ForEach-Object { [string]$_.name })
  foreach ($requiredMode in @("user", "project", "vendor")) {
    if ($modeNames -notcontains $requiredMode) {
      throw "install manifest 缺少 install mode: $requiredMode"
    }
  }

  $entries = @($manifest.entries)
  if ($entries.Count -eq 0) {
    throw "install manifest 缺少 entries。"
  }

  $scopes = @("project", "user", "vendor")
  $validatedEntries = New-Object System.Collections.Generic.List[object]
  for ($index = 0; $index -lt $entries.Count; $index++) {
    $entry = $entries[$index]
    $source = Normalize-ManifestPath -Path ([string]$entry.source) -Label "install manifest entries[$index].source"
    $destination = Normalize-ManifestPath -Path ([string]$entry.destination) -Label "install manifest entries[$index].destination" -AllowEnvironmentPath
    $scope = [string]$entry.scope
    $kind = [string]$entry.kind

    if ($scopes -notcontains $scope) {
      throw "install manifest entries[$index].scope 不支持: $scope"
    }

    if ([string]::IsNullOrWhiteSpace($kind)) {
      throw "install manifest entries[$index].kind 不能为空。"
    }

    $sourcePath = Resolve-ManifestSourcePath -SourceRoot $SourceRoot -SourceRelativePath $source
    $sourceKind = ""
    if (Test-Path -LiteralPath $sourcePath -PathType Leaf) {
      $sourceKind = "file"
    }
    elseif (Test-Path -LiteralPath $sourcePath -PathType Container) {
      $sourceKind = "directory"
    }
    else {
      throw "install manifest entries[$index].source 指向不存在路径: $source"
    }

    $validatedEntries.Add([pscustomobject]@{
      Source = $source
      SourcePath = [System.IO.Path]::GetFullPath($sourcePath)
      SourceKind = $sourceKind
      Destination = $destination
      TargetPath = Resolve-ManifestTargetPath -ProjectRoot $ProjectRoot -DestinationRelativePath $destination -Scope $scope
      Scope = $scope
      Kind = $kind
      Mutable = [bool]$entry.mutable
    })
  }

  return [pscustomobject]@{
    Path = $manifestPath
    Manifest = $manifest
    Entries = [object[]]$validatedEntries.ToArray()
  }
}

function Write-InstallPlan {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$AgentsRoot,
    [string]$InstallMode,
    [switch]$ThinProjectProfile
  )

  Write-Output "Install plan."
  Write-Output "PlanOnly: True"
  Write-Output "No files will be changed."
  Write-Output ("ProjectRoot: {0}" -f $ProjectRoot)
  Write-Output ("AgentsRoot: {0}" -f $AgentsRoot)
  Write-Output ("Mode: {0}" -f $InstallMode)
  Write-Output ("Manifest: {0}" -f $ManifestInfo.Path)
  Write-Output ("Package: {0}" -f [string]$ManifestInfo.Manifest.package)
  Write-Output ("SchemaVersion: {0}" -f [string]$ManifestInfo.Manifest.schemaVersion)
  Write-Output ("Status: {0}" -f [string]$ManifestInfo.Manifest.status)
  $plannedEntries = Get-ManifestEntriesForMode -ManifestInfo $ManifestInfo -InstallMode $InstallMode -ThinProjectProfile:$ThinProjectProfile.IsPresent
  Write-Output ("Entries: {0}" -f @($plannedEntries).Count)

  foreach ($entry in @($plannedEntries)) {
    Write-Output ("- scope={0} kind={1} mutable={2} sourceKind={3}" -f $entry.Scope, $entry.Kind, $entry.Mutable, $entry.SourceKind)
    Write-Output ("  source: {0}" -f $entry.Source)
    Write-Output ("  destination: {0}" -f $entry.Destination)
    Write-Output ("  target: {0}" -f $entry.TargetPath)
  }
}

function Get-ManifestEntriesForMode {
  param(
    [object]$ManifestInfo,
    [string]$InstallMode,
    [switch]$ThinProjectProfile
  )

  $entries = @($ManifestInfo.Entries)
  if ($InstallMode -eq "full") {
    return $entries
  }

  $modeEntries = @($entries | Where-Object { [string]$_.Scope -eq $InstallMode })
  if ($InstallMode -eq "project" -and $ThinProjectProfile.IsPresent) {
    $legacyProjectDestinations = @(
      "docs/testing/",
      "docs/harness/",
      "docs/requirement-prep-kit/",
      "docs/knowledge/"
    )

    return @($modeEntries | Where-Object {
      $destination = ([string]$_.Destination).Replace('\', '/')
      $isLegacyProjectDestination = $false
      foreach ($legacyDestination in $legacyProjectDestinations) {
        if ($destination.StartsWith($legacyDestination, [System.StringComparison]::OrdinalIgnoreCase)) {
          $isLegacyProjectDestination = $true
          break
        }
      }

      -not $isLegacyProjectDestination
    })
  }

  return $modeEntries
}

function Get-ThinProjectExcludedDestinations {
  return @(
    "docs/testing/",
    "docs/harness/",
    "docs/requirement-prep-kit/",
    "docs/knowledge/"
  )
}

function Get-InstallManifestVersion {
  param([object]$ManifestInfo)

  $version = [string]$ManifestInfo.Manifest.version
  if ([string]::IsNullOrWhiteSpace($version)) {
    throw "install manifest version 不能为空。"
  }

  return $version
}

function Get-PackageSourceCommit {
  param([string]$PackageRoot)

  $aiKitManifestPath = Join-Path $PackageRoot "ai-sync\ai-kit-manifest.json"
  if (-not (Test-Path -LiteralPath $aiKitManifestPath -PathType Leaf)) {
    return ""
  }

  try {
    $aiKitManifest = Get-Content -LiteralPath $aiKitManifestPath -Raw | ConvertFrom-Json
    return [string]$aiKitManifest.source.commit
  }
  catch {
    return ""
  }
}

function Read-PackageLockOrNull {
  param([string]$ProjectRoot)

  $lockPath = Join-Path $ProjectRoot ".codex-harness\locks\package.lock.json"
  if (-not (Test-Path -LiteralPath $lockPath -PathType Leaf)) {
    return $null
  }

  try {
    $lockDocument = Get-Content -LiteralPath $lockPath -Raw | ConvertFrom-Json
  }
  catch {
    throw "package lock 不是合法 JSON: $lockPath"
  }

  return [pscustomobject]@{
    Path = $lockPath
    Document = $lockDocument
  }
}

function Get-InstallFileSystemPath {
  param([string]$Path)
  $fullPath = [System.IO.Path]::GetFullPath($Path)
  if ($env:OS -eq "Windows_NT" -and -not $fullPath.StartsWith("\\?\", [System.StringComparison]::Ordinal)) {
    if ($fullPath.StartsWith("\\", [System.StringComparison]::Ordinal)) {
      return "\\?\UNC\" + $fullPath.Substring(2)
    }

    return "\\?\" + $fullPath
  }

  return $fullPath
}

function ConvertFrom-InstallFileSystemPath {
  param([string]$Path)

  if ($Path.StartsWith("\\?\UNC\", [System.StringComparison]::Ordinal)) {
    return "\\" + $Path.Substring(8)
  }

  if ($Path.StartsWith("\\?\", [System.StringComparison]::Ordinal)) {
    return $Path.Substring(4)
  }

  return $Path
}

function Get-InstallChildFiles {
  param([string]$Directory)

  $directoryInfo = [System.IO.DirectoryInfo]::new((Get-InstallFileSystemPath -Path $Directory))
  return @(
    $directoryInfo.EnumerateFiles("*", [System.IO.SearchOption]::AllDirectories) |
      ForEach-Object {
        [pscustomobject]@{
          FullName = ConvertFrom-InstallFileSystemPath -Path $_.FullName
        }
      }
  )
}

function Get-InstallFileSha256 {
  param([string]$Path)

  $fileSystemPath = Get-InstallFileSystemPath -Path $Path
  $hashAlgorithm = [System.Security.Cryptography.SHA256]::Create()
  $stream = $null
  try {
    $stream = [System.IO.File]::OpenRead($fileSystemPath)
    $hashBytes = $hashAlgorithm.ComputeHash($stream)
    return (($hashBytes | ForEach-Object { $_.ToString("x2") }) -join "")
  }
  finally {
    if ($null -ne $stream) {
      $stream.Dispose()
    }

    $hashAlgorithm.Dispose()
  }
}

function Get-LockManagedFileByPath {
  param(
    [object]$LockDocument,
    [string]$Destination
  )

  $normalizedDestination = Normalize-ManifestPath -Path $Destination -Label "managed file destination" -AllowEnvironmentPath
  foreach ($managedFile in @($LockDocument.managedFiles)) {
    $managedPath = Normalize-ManifestPath -Path ([string]$managedFile.path) -Label "managed file path" -AllowEnvironmentPath
    if ($managedPath.Equals($normalizedDestination, [System.StringComparison]::OrdinalIgnoreCase)) {
      return $managedFile
    }
  }

  return $null
}

function Get-UpgradePlanFileDecision {
  param(
    [object]$LockDocument,
    [string]$Destination,
    [string]$SourcePath,
    [string]$TargetPath
  )

  $lockManagedFile = Get-LockManagedFileByPath -LockDocument $LockDocument -Destination $Destination
  if ($null -eq $lockManagedFile) {
    return [pscustomobject]@{
      Action = "no-lock-entry"
      Reason = "lock 中缺少 managedFiles 记录。"
    }
  }

  if (-not (Test-Path -LiteralPath $TargetPath -PathType Leaf)) {
    return [pscustomobject]@{
      Action = "install-missing"
      Reason = "目标文件缺失，可从当前包补齐。"
    }
  }

  $actualSha256 = Get-InstallFileSha256 -Path $TargetPath
  $lockedSha256 = ([string]$lockManagedFile.sha256).ToLowerInvariant()
  $packageSha256 = Get-InstallFileSha256 -Path $SourcePath
  if ($actualSha256 -ne $lockedSha256) {
    return [pscustomobject]@{
      Action = "blocked-user-drift"
      Reason = "目标文件已偏离 lock，升级前需要人工确认。"
    }
  }

  if ($actualSha256 -eq $packageSha256) {
    return [pscustomobject]@{
      Action = "keep"
      Reason = "目标文件与当前包一致。"
    }
  }

  return [pscustomobject]@{
    Action = "update-immutable"
    Reason = "目标文件未被用户改动，可由升级命令覆盖。"
  }
}

function Write-UpgradePlanItem {
  param(
    [object]$Entry,
    [string]$Action,
    [string]$Source,
    [string]$Destination,
    [string]$Target,
    [string]$Reason
  )

  Write-Output ("- action={0} scope={1} kind={2} mutable={3}" -f $Action, $Entry.Scope, $Entry.Kind, $Entry.Mutable)
  Write-Output ("  source: {0}" -f $Source)
  Write-Output ("  destination: {0}" -f $Destination)
  Write-Output ("  target: {0}" -f $Target)
  Write-Output ("  reason: {0}" -f $Reason)
}

function Write-UpgradePlan {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$AgentsRoot,
    [string]$RequestedMode
  )

  Write-Output "Upgrade plan."
  Write-Output "PlanOnly: True"
  Write-Output "No files will be changed."
  Write-Output ("ProjectRoot: {0}" -f $ProjectRoot)
  Write-Output ("AgentsRoot: {0}" -f $AgentsRoot)
  Write-Output ("RequestedMode: {0}" -f $RequestedMode)
  Write-Output ("Manifest: {0}" -f $ManifestInfo.Path)

  $lockInfo = Read-PackageLockOrNull -ProjectRoot $ProjectRoot
  if ($null -eq $lockInfo) {
    Write-Output "Status: missing-lock"
    Write-Output "Reason: .codex-harness/locks/package.lock.json 不存在，无法安全区分包管理文件和用户文件。"
    return
  }

  $lockDocument = $lockInfo.Document
  $installedMode = [string]$lockDocument.installMode
  if ([string]::IsNullOrWhiteSpace($installedMode)) {
    $installedMode = $RequestedMode
  }

  Write-Output ("Status: ready")
  Write-Output ("Lock: {0}" -f $lockInfo.Path)
  Write-Output ("InstalledMode: {0}" -f $installedMode)
  Write-Output ("InstalledVendor: {0}" -f ([bool]$lockDocument.vendor))

  $plannedEntries = Get-ManifestEntriesForMode -ManifestInfo $ManifestInfo -InstallMode $installedMode
  Write-Output ("Entries: {0}" -f @($plannedEntries).Count)
  foreach ($entry in @($plannedEntries)) {
    if ([bool]$entry.Mutable) {
      Write-UpgradePlanItem `
        -Entry $entry `
        -Action "manual-review" `
        -Source ([string]$entry.Source) `
        -Destination ([string]$entry.Destination) `
        -Target ([string]$entry.TargetPath) `
        -Reason "mutable=true，升级计划不会直接覆盖用户可编辑文件。"
      continue
    }

    if ($entry.SourceKind -eq "directory") {
      $sourceDirectory = [System.IO.Path]::GetFullPath($entry.SourcePath).TrimEnd('\', '/')
      $sourceRoot = ([string]$entry.Source).TrimEnd('/')
      $destinationRoot = ([string]$entry.Destination).TrimEnd('/')
      foreach ($sourceFile in @(Get-InstallChildFiles -Directory $sourceDirectory | Sort-Object FullName)) {
        $relativeFilePath = $sourceFile.FullName.Substring($sourceDirectory.Length).TrimStart('\', '/').Replace('\', '/')
        $fileSource = $sourceRoot + "/" + $relativeFilePath
        $fileDestination = $destinationRoot + "/" + $relativeFilePath
        $fileTargetPath = Join-Path $entry.TargetPath ($relativeFilePath.Replace('/', '\'))
        $decision = Get-UpgradePlanFileDecision `
          -LockDocument $lockDocument `
          -Destination $fileDestination `
          -SourcePath $sourceFile.FullName `
          -TargetPath $fileTargetPath

        Write-UpgradePlanItem `
          -Entry $entry `
          -Action $decision.Action `
          -Source $fileSource `
          -Destination $fileDestination `
          -Target $fileTargetPath `
          -Reason $decision.Reason
      }

      continue
    }

    $decision = Get-UpgradePlanFileDecision `
      -LockDocument $lockDocument `
      -Destination ([string]$entry.Destination) `
      -SourcePath ([string]$entry.SourcePath) `
      -TargetPath ([string]$entry.TargetPath)

    Write-UpgradePlanItem `
      -Entry $entry `
      -Action $decision.Action `
      -Source ([string]$entry.Source) `
      -Destination ([string]$entry.Destination) `
      -Target ([string]$entry.TargetPath) `
      -Reason $decision.Reason
  }
}

function Write-UninstallPlan {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$AgentsRoot,
    [string]$RequestedMode
  )

  Write-Output "Uninstall plan."
  Write-Output "PlanOnly: True"
  Write-Output "No files will be changed."
  Write-Output ("ProjectRoot: {0}" -f $ProjectRoot)
  Write-Output ("AgentsRoot: {0}" -f $AgentsRoot)
  Write-Output ("RequestedMode: {0}" -f $RequestedMode)
  Write-Output ("Manifest: {0}" -f $ManifestInfo.Path)

  $lockInfo = Read-PackageLockOrNull -ProjectRoot $ProjectRoot
  $installedMode = $RequestedMode
  if ($null -ne $lockInfo) {
    $lockedMode = [string]$lockInfo.Document.installMode
    if (-not [string]::IsNullOrWhiteSpace($lockedMode)) {
      $installedMode = $lockedMode
    }

    Write-Output ("Lock: {0}" -f $lockInfo.Path)
  }
  else {
    Write-Output "Lock: missing"
  }

  Write-Output ("InstalledMode: {0}" -f $installedMode)
  $plannedEntries = Get-ManifestEntriesForMode -ManifestInfo $ManifestInfo -InstallMode $installedMode
  Write-Output ("Entries: {0}" -f @($plannedEntries).Count)
  foreach ($entry in @($plannedEntries)) {
    $action = "retain-project"
    $reason = "project scope 默认保留，避免删除 task/progress/docs 等 truth source。"

    if ($entry.Scope -eq "vendor") {
      $action = "remove-vendor"
      $reason = "vendor scope 可在执行阶段删除 manifest 管理的 .agents 内容。"
    }
    elseif ($entry.Scope -eq "user") {
      $action = "manual-user-cleanup"
      $reason = "user scope 位于共享用户目录，执行阶段必须逐项确认。"
    }

    Write-Output ("- action={0} scope={1} kind={2} mutable={3}" -f $action, $entry.Scope, $entry.Kind, $entry.Mutable)
    Write-Output ("  destination: {0}" -f $entry.Destination)
    Write-Output ("  target: {0}" -f $entry.TargetPath)
    Write-Output ("  reason: {0}" -f $reason)
  }
}

function Get-VendorManifestFileRecords {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot
  )

  $records = New-Object System.Collections.Generic.List[object]
  $packageRoot = Split-Path -Parent ([string]$ManifestInfo.Path)
  $templateRoot = Join-Path $packageRoot "docs\codex-harness-engineering\templates"
  $fullPackageRootMappings = @(
    @{ SourceRoot = $templateRoot; Source = "runtime\AGENTS.md"; Destination = ".agents/AGENTS.md"; Kind = "runtime" },
    @{ SourceRoot = $templateRoot; Source = "bootstrap-codex-harness.ps1"; Destination = ".agents/bootstrap-codex-harness.ps1"; Kind = "runtime" },
    @{ SourceRoot = $templateRoot; Source = "runtime\codex-loop.ps1"; Destination = ".agents/codex-loop.ps1"; Kind = "runtime" },
    @{ SourceRoot = $templateRoot; Source = "config\env-check.ps1"; Destination = ".agents/env-check.ps1"; Kind = "runtime" },
    @{ SourceRoot = $templateRoot; Source = "runtime\verify.ps1"; Destination = ".agents/verify.ps1"; Kind = "runtime" },
    @{ SourceRoot = $templateRoot; Source = "trace\trace.schema.json"; Destination = ".agents/trace.schema.json"; Kind = "runtime" }
  )

  foreach ($mapping in $fullPackageRootMappings) {
    $sourcePath = Join-Path ([string]$mapping.SourceRoot) ([string]$mapping.Source)
    if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) {
      continue
    }

    $destination = Normalize-ManifestPath -Path ([string]$mapping.Destination) -Label "vendor migration destination"
    $records.Add([pscustomobject]@{
      Source = "templates/" + ([string]$mapping.Source).Replace('\', '/')
      SourcePath = [System.IO.Path]::GetFullPath($sourcePath)
      Destination = $destination
      TargetPath = Resolve-ManifestTargetPath -ProjectRoot $ProjectRoot -DestinationRelativePath $destination -Scope "vendor"
      Scope = "vendor"
      Kind = [string]$mapping.Kind
    })
  }

  foreach ($entry in @($ManifestInfo.Entries | Where-Object { [string]$_.Scope -eq "vendor" })) {
    if ($entry.SourceKind -eq "file") {
      $records.Add([pscustomobject]@{
        Source = [string]$entry.Source
        SourcePath = [string]$entry.SourcePath
        Destination = Normalize-ManifestPath -Path ([string]$entry.Destination) -Label "vendor migration destination"
        TargetPath = [string]$entry.TargetPath
        Scope = [string]$entry.Scope
        Kind = [string]$entry.Kind
      })
      continue
    }

    if ($entry.SourceKind -ne "directory") {
      continue
    }

    $sourceDirectory = [System.IO.Path]::GetFullPath($entry.SourcePath).TrimEnd('\', '/')
    $sourceRoot = ([string]$entry.Source).TrimEnd('/')
    $destinationRoot = ([string]$entry.Destination).TrimEnd('/')
    foreach ($sourceFile in @(Get-InstallChildFiles -Directory $sourceDirectory | Sort-Object FullName)) {
      $relativeFilePath = $sourceFile.FullName.Substring($sourceDirectory.Length).TrimStart('\', '/').Replace('\', '/')
      $records.Add([pscustomobject]@{
        Source = $sourceRoot + "/" + $relativeFilePath
        SourcePath = $sourceFile.FullName
        Destination = Normalize-ManifestPath -Path ($destinationRoot + "/" + $relativeFilePath) -Label "vendor migration destination"
        TargetPath = Join-Path $entry.TargetPath ($relativeFilePath.Replace('/', '\'))
        Scope = [string]$entry.Scope
        Kind = [string]$entry.Kind
      })
    }
  }

  return @($records.ToArray())
}

function Write-MigrationPlanItem {
  param(
    [string]$Action,
    [string]$Scope,
    [string]$Kind,
    [string]$Path,
    [string]$Target,
    [string]$Reason
  )

  Write-Output ("- action={0} scope={1} kind={2} path={3}" -f $Action, $Scope, $Kind, $Path)
  if (-not [string]::IsNullOrWhiteSpace($Target)) {
    Write-Output ("  target: {0}" -f $Target)
  }
  Write-Output ("  reason: {0}" -f $Reason)
}

function New-MigrationPlanRecord {
  param(
    [string]$Action,
    [string]$Scope,
    [string]$Kind,
    [string]$Path,
    [string]$Target,
    [string]$Reason,
    [string]$Source = "",
    [string]$SourcePath = ""
  )

  return [pscustomobject]@{
    Action = $Action
    Scope = $Scope
    Kind = $Kind
    Path = $Path
    Target = $Target
    Reason = $Reason
    Source = $Source
    SourcePath = $SourcePath
  }
}

function Get-MigrationPlanRecords {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$AgentsRoot
  )

  $agentsExists = Test-Path -LiteralPath $AgentsRoot -PathType Container
  $records = New-Object System.Collections.Generic.List[object]

  $legacyResiduePaths = @(
    "docs/harness",
    "docs/testing",
    "docs/knowledge",
    "docs/requirement-prep-kit"
  )

  foreach ($relativePath in $legacyResiduePaths) {
    $candidate = Join-Path $ProjectRoot ($relativePath.Replace('/', '\'))
    if (Test-Path -LiteralPath $candidate) {
      $records.Add((New-MigrationPlanRecord `
        -Action "manual-review" `
        -Scope "legacy" `
        -Kind "legacy-residue" `
        -Path $relativePath `
        -Target $candidate `
        -Reason "legacy/vendor 残留需要人工确认，迁移计划不会自动删除。"))
    }
  }

  if (-not $agentsExists) {
    return [pscustomobject]@{
      AgentsExists = $false
      Records = @($records.ToArray())
    }
  }

  $vendorRecords = @(Get-VendorManifestFileRecords -ManifestInfo $ManifestInfo -ProjectRoot $ProjectRoot)
  $knownVendorPaths = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
  foreach ($record in $vendorRecords) {
    [void]$knownVendorPaths.Add([string]$record.Destination)
    $targetPath = [string]$record.TargetPath
    if (-not (Test-Path -LiteralPath $targetPath -PathType Leaf)) {
      $records.Add((New-MigrationPlanRecord `
        -Action "missing-vendor-file" `
        -Scope "vendor" `
        -Kind ([string]$record.Kind) `
        -Path ([string]$record.Destination) `
        -Target $targetPath `
        -Reason "当前 manifest 声明 vendor 文件，但目标项目中不存在。" `
        -Source ([string]$record.Source) `
        -SourcePath ([string]$record.SourcePath)))
      continue
    }

    $actualSha256 = Get-InstallFileSha256 -Path $targetPath
    $packageSha256 = Get-InstallFileSha256 -Path ([string]$record.SourcePath)
    if ($actualSha256 -eq $packageSha256) {
      $records.Add((New-MigrationPlanRecord `
        -Action "adopt-vendor-managed" `
        -Scope "vendor" `
        -Kind ([string]$record.Kind) `
        -Path ([string]$record.Destination) `
        -Target $targetPath `
        -Reason "目标 vendor 文件与当前 package 一致，可安全纳入 managedFiles。" `
        -Source ([string]$record.Source) `
        -SourcePath ([string]$record.SourcePath)))
    }
    else {
      $records.Add((New-MigrationPlanRecord `
        -Action "blocked-user-drift" `
        -Scope "vendor" `
        -Kind ([string]$record.Kind) `
        -Path ([string]$record.Destination) `
        -Target $targetPath `
        -Reason "目标 vendor 文件与当前 package 不一致，需要人工确认。" `
        -Source ([string]$record.Source) `
        -SourcePath ([string]$record.SourcePath)))
    }
  }

  $agentsFullPath = [System.IO.Path]::GetFullPath($AgentsRoot).TrimEnd('\', '/')
  foreach ($legacyFile in @(Get-InstallChildFiles -Directory $agentsFullPath | Sort-Object FullName)) {
    $relativeFilePath = $legacyFile.FullName.Substring($agentsFullPath.Length).TrimStart('\', '/').Replace('\', '/')
    $destination = ".agents/" + $relativeFilePath
    if ($knownVendorPaths.Contains($destination)) {
      continue
    }

    $records.Add((New-MigrationPlanRecord `
      -Action "unknown-legacy-file" `
      -Scope "legacy" `
      -Kind "unknown-file" `
      -Path $destination `
      -Target $legacyFile.FullName `
      -Reason ".agents/ 内存在当前 manifest 未声明的文件，需要人工确认。"))
  }

  return [pscustomobject]@{
    AgentsExists = $true
    Records = @($records.ToArray())
  }
}

function Write-MigrationPlanHeader {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$AgentsRoot,
    [string]$RequestedMode,
    [string]$Title,
    [bool]$PlanOnly
  )

  Write-Output $Title
  Write-Output ("PlanOnly: {0}" -f $PlanOnly)
  if ($PlanOnly) {
    Write-Output "No files will be changed."
  }

  Write-Output ("ProjectRoot: {0}" -f $ProjectRoot)
  Write-Output ("AgentsRoot: {0}" -f $AgentsRoot)
  Write-Output ("RequestedMode: {0}" -f $RequestedMode)
  Write-Output ("Manifest: {0}" -f $ManifestInfo.Path)

  $lockInfo = Read-PackageLockOrNull -ProjectRoot $ProjectRoot
  if ($null -ne $lockInfo) {
    $lockDocument = $lockInfo.Document
    Write-Output ("Lock: {0}" -f $lockInfo.Path)
    Write-Output ("InstalledMode: {0}" -f [string]$lockDocument.installMode)
    Write-Output ("InstalledVendor: {0}" -f [bool]$lockDocument.vendor)
    Write-Output ("InstalledVersion: {0}" -f [string]$lockDocument.version)
    Write-Output ("InstalledSourceCommit: {0}" -f [string]$lockDocument.sourceCommit)
  }
  else {
    Write-Output "Lock: missing"
  }
}

function Write-MigrationSummaryCounts {
  param([object[]]$Records)

  foreach ($action in @("adopt-vendor-managed", "missing-vendor-file", "blocked-user-drift", "unknown-legacy-file", "manual-review")) {
    $count = @($Records | Where-Object { [string]$_.Action -eq $action }).Count
    Write-Output ("summaryCounts.{0}: {1}" -f $action, $count)
  }
}

function Write-MigrationPlan {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$AgentsRoot,
    [string]$RequestedMode
  )

  Write-MigrationPlanHeader `
    -ManifestInfo $ManifestInfo `
    -ProjectRoot $ProjectRoot `
    -AgentsRoot $AgentsRoot `
    -RequestedMode $RequestedMode `
    -Title "Migration plan." `
    -PlanOnly $true

  $plan = Get-MigrationPlanRecords -ManifestInfo $ManifestInfo -ProjectRoot $ProjectRoot -AgentsRoot $AgentsRoot
  if (-not [bool]$plan.AgentsExists) {
    Write-Output "Status: no-legacy-vendor"
  }
  else {
    Write-Output "Status: ready"
  }

  foreach ($record in @($plan.Records)) {
    Write-MigrationPlanItem `
      -Action ([string]$record.Action) `
      -Scope ([string]$record.Scope) `
      -Kind ([string]$record.Kind) `
      -Path ([string]$record.Path) `
      -Target ([string]$record.Target) `
      -Reason ([string]$record.Reason)
  }
}

function Write-MigrationAdoptPackageLock {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$PackageRoot,
    [object[]]$AdoptRecords
  )

  $lockDirectory = Join-Path $ProjectRoot ".codex-harness\locks"
  if (-not (Test-Path -LiteralPath $lockDirectory -PathType Container)) {
    New-Item -ItemType Directory -Force -Path $lockDirectory | Out-Null
  }

  $sourceCommit = Get-PackageSourceCommit -PackageRoot $PackageRoot
  $packageVersion = Get-InstallManifestVersion -ManifestInfo $ManifestInfo
  $aiKitManifestPath = Join-Path $PackageRoot "ai-sync\ai-kit-manifest.json"
  $managedFiles = New-Object System.Collections.Generic.List[object]
  foreach ($record in @($AdoptRecords | Sort-Object Path)) {
    Add-InstallLockManagedFile `
      -ManagedFiles $managedFiles `
      -Path ([string]$record.Path) `
      -Source ([string]$record.Source) `
      -Scope "vendor" `
      -Kind ([string]$record.Kind) `
      -TargetPath ([string]$record.Target)
  }

  $lockDocument = [pscustomobject]@{
    lockVersion = "0.1"
    package = "codex-harness"
    version = $packageVersion
    sourceCommit = $sourceCommit
    installMode = "vendor"
    vendor = $true
    installedAt = (Get-Date).ToString("o")
    source = [pscustomobject]@{
      packageRoot = $PackageRoot
      commit = $sourceCommit
    }
    manifests = [pscustomobject]@{
      aiKit = if (Test-Path -LiteralPath $aiKitManifestPath -PathType Leaf) { "ai-sync/ai-kit-manifest.json" } else { "" }
      install = "install-manifest.json"
    }
    managedFiles = @($managedFiles.ToArray())
  }

  $lockPath = Join-Path $lockDirectory "package.lock.json"
  $lockDocument | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $lockPath -Encoding UTF8
  return $lockPath
}

function Invoke-MigrationApply {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$AgentsRoot,
    [string]$RequestedMode,
    [string]$PackageRoot,
    [bool]$Confirmed
  )

  Write-MigrationPlanHeader `
    -ManifestInfo $ManifestInfo `
    -ProjectRoot $ProjectRoot `
    -AgentsRoot $AgentsRoot `
    -RequestedMode $RequestedMode `
    -Title "Migration apply." `
    -PlanOnly (-not $Confirmed)

  $plan = Get-MigrationPlanRecords -ManifestInfo $ManifestInfo -ProjectRoot $ProjectRoot -AgentsRoot $AgentsRoot
  $records = @($plan.Records)
  $adoptRecords = @($records | Where-Object { [string]$_.Action -eq "adopt-vendor-managed" })
  $blockedRecords = @($records | Where-Object { @("blocked-user-drift", "unknown-legacy-file", "manual-review") -contains [string]$_.Action })
  $plannedWrites = @()
  if ($adoptRecords.Count -gt 0) {
    $plannedWrites = @(".codex-harness/locks/package.lock.json", ".codex-harness/state/config.json")
  }

  if (-not [bool]$plan.AgentsExists) {
    Write-Output "status=no-op"
    Write-Output "reason: no-legacy-vendor"
    Write-MigrationSummaryCounts -Records $records
    Write-Output "plannedWrites: 0"
    Write-Output "actualWrites: 0"
    Write-Output "nextSteps: 无 legacy vendor 内容需要迁移。"
    return
  }

  foreach ($record in $records) {
    Write-MigrationPlanItem `
      -Action ([string]$record.Action) `
      -Scope ([string]$record.Scope) `
      -Kind ([string]$record.Kind) `
      -Path ([string]$record.Path) `
      -Target ([string]$record.Target) `
      -Reason ([string]$record.Reason)
  }

  Write-MigrationSummaryCounts -Records $records
  if ($blockedRecords.Count -gt 0) {
    Write-Output "status=blocked"
    foreach ($record in $blockedRecords) {
      Write-Output ("blockedReasons: action={0} path={1} reason={2}" -f $record.Action, $record.Path, $record.Reason)
    }

    Write-Output ("plannedWrites: {0}" -f @($plannedWrites).Count)
    Write-Output "actualWrites: 0"
    Write-Output "nextSteps: 先人工处理 blockedReasons 中的 drift、unknown 或 legacy docs，再重新运行 -MigratePlan。"
    exit 1
  }

  if ($adoptRecords.Count -eq 0) {
    Write-Output "status=no-op"
    Write-Output "reason: 没有可写入的 adopt-vendor-managed 文件。"
    Write-Output "plannedWrites: 0"
    Write-Output "actualWrites: 0"
    Write-Output "nextSteps: 复核 missing-vendor-file 报告；需要 vendor 管理时先恢复对应 package 文件。"
    return
  }

  foreach ($plannedWrite in $plannedWrites) {
    Write-Output ("plannedWrites: {0}" -f $plannedWrite)
  }

  if (-not $Confirmed) {
    Write-Output "status=dry-run"
    Write-Output "actualWrites: 0"
    Write-Output "nextSteps: 确认只采纳 adopt-vendor-managed 后，重新运行 -MigrateApply -Confirm。"
    return
  }

  $actualWrites = New-Object System.Collections.Generic.List[string]
  $lockPath = Write-MigrationAdoptPackageLock `
    -ManifestInfo $ManifestInfo `
    -ProjectRoot $ProjectRoot `
    -PackageRoot $PackageRoot `
    -AdoptRecords $adoptRecords
  $actualWrites.Add($lockPath)

  $configPath = Write-ProjectHarnessConfig `
    -ManifestInfo $ManifestInfo `
    -ProjectRoot $ProjectRoot `
    -InstallScope "vendor" `
    -VendorInstalled:$true `
    -PackageRoot $PackageRoot
  $actualWrites.Add($configPath)

  Write-Output "status=confirmed-write"
  foreach ($actualWrite in @($actualWrites.ToArray())) {
    Write-Output ("actualWrites: {0}" -f $actualWrite)
  }

  Write-Output ("WrittenManagedFiles: {0}" -f $adoptRecords.Count)
  Write-Output "nextSteps: 运行 doctor.ps1 和 refresh-agent-package.ps1 -CheckOnly 验证迁移后的 lock。"
}

function Get-InstalledModeFromLock {
  param(
    [object]$LockDocument,
    [string]$RequestedMode
  )

  $installedMode = [string]$LockDocument.installMode
  if ([string]::IsNullOrWhiteSpace($installedMode)) {
    return $RequestedMode
  }

  return $installedMode
}

function Copy-InstallManagedFile {
  param(
    [string]$SourcePath,
    [string]$TargetPath
  )

  $targetParent = Split-Path -Parent $TargetPath
  if (-not (Test-Path -LiteralPath $targetParent -PathType Container)) {
    New-Item -ItemType Directory -Force -Path $targetParent | Out-Null
  }

  Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
}

function Add-InstallLockManagedFile {
  param(
    [System.Collections.Generic.List[object]]$ManagedFiles,
    [string]$Path,
    [string]$Source,
    [string]$Scope,
    [string]$Kind,
    [string]$TargetPath
  )

  $ManagedFiles.Add([pscustomobject]@{
    path = Normalize-ManifestPath -Path $Path -Label "lock managed file path" -AllowEnvironmentPath
    source = Normalize-ManifestPath -Path $Source -Label "lock managed file source"
    scope = $Scope
    kind = $Kind
    mutable = $false
    sha256 = Get-InstallFileSha256 -Path $TargetPath
  })
}

function Add-AbsoluteManagedFile {
  param(
    [System.Collections.Generic.List[object]]$ManagedFiles,
    [string]$Path,
    [string]$Source,
    [string]$Scope,
    [string]$Kind,
    [string]$TargetPath
  )

  $ManagedFiles.Add([pscustomobject]@{
    path = $Path
    source = Normalize-ManifestPath -Path $Source -Label "package lock managed file source"
    scope = $Scope
    kind = $Kind
    mutable = $false
    sha256 = Get-InstallFileSha256 -Path $TargetPath
  })
}

function Get-InstalledPackageManagedFiles {
  param(
    [string]$PackageRoot,
    [string]$InstallRoot
  )

  $managedFiles = New-Object System.Collections.Generic.List[object]
  $packageFullPath = [System.IO.Path]::GetFullPath($PackageRoot).TrimEnd('\', '/')
  foreach ($file in @(Get-InstallChildFiles -Directory $packageFullPath | Sort-Object FullName)) {
    $relativeFilePath = $file.FullName.Substring($packageFullPath.Length).TrimStart('\', '/').Replace('\', '/')
    $targetFilePath = Join-Path $InstallRoot ($relativeFilePath.Replace('/', '\'))
    if (-not (Test-Path -LiteralPath $targetFilePath -PathType Leaf)) {
      continue
    }

    Add-AbsoluteManagedFile `
      -ManagedFiles $managedFiles `
      -Path $relativeFilePath `
      -Source $relativeFilePath `
      -Scope "package" `
      -Kind "package-file" `
      -TargetPath $targetFilePath
  }

  return @($managedFiles.ToArray())
}

function Get-InstallLockManagedFiles {
  param(
    [object]$ManifestInfo,
    [string]$InstallMode,
    [bool]$VendorInstalled
  )

  $includedScopes = @("project")
  if ($InstallMode -eq "user") {
    $includedScopes = @("user")
  }
  elseif ($InstallMode -eq "vendor") {
    $includedScopes = @("vendor")
  }
  elseif ($VendorInstalled) {
    $includedScopes += "vendor"
  }

  $managedFiles = New-Object System.Collections.Generic.List[object]
  foreach ($entry in @($ManifestInfo.Entries)) {
    if ($includedScopes -notcontains [string]$entry.Scope) {
      continue
    }

    if ([bool]$entry.Mutable) {
      continue
    }

    if ([string]$entry.Kind -eq "generated-lock" -or [string]$entry.Destination -eq ".codex-harness/locks/package.lock.json") {
      continue
    }

    if ($entry.SourceKind -eq "file") {
      if (-not (Test-Path -LiteralPath $entry.TargetPath -PathType Leaf)) {
        continue
      }

      Add-InstallLockManagedFile `
        -ManagedFiles $managedFiles `
        -Path ([string]$entry.Destination) `
        -Source ([string]$entry.Source) `
        -Scope ([string]$entry.Scope) `
        -Kind ([string]$entry.Kind) `
        -TargetPath ([string]$entry.TargetPath)
      continue
    }

    if ($entry.SourceKind -ne "directory") {
      continue
    }

    if (-not (Test-Path -LiteralPath $entry.TargetPath -PathType Container)) {
      continue
    }

    $sourceDirectory = [System.IO.Path]::GetFullPath($entry.SourcePath).TrimEnd('\', '/')
    $sourceRoot = ([string]$entry.Source).TrimEnd('/')
    $destinationRoot = ([string]$entry.Destination).TrimEnd('/')
    foreach ($sourceFile in @(Get-InstallChildFiles -Directory $sourceDirectory | Sort-Object FullName)) {
      $relativeFilePath = $sourceFile.FullName.Substring($sourceDirectory.Length).TrimStart('\', '/').Replace('\', '/')
      $targetFilePath = Join-Path $entry.TargetPath ($relativeFilePath.Replace('/', '\'))
      if (-not (Test-Path -LiteralPath $targetFilePath -PathType Leaf)) {
        continue
      }

      Add-InstallLockManagedFile `
        -ManagedFiles $managedFiles `
        -Path ($destinationRoot + "/" + $relativeFilePath) `
        -Source ($sourceRoot + "/" + $relativeFilePath) `
        -Scope ([string]$entry.Scope) `
        -Kind ([string]$entry.Kind) `
        -TargetPath $targetFilePath
    }
  }

  return @($managedFiles.ToArray())
}

function Write-InstalledPackageLock {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$InstallMode,
    [bool]$VendorInstalled,
    [string]$PackageRoot
  )

  $sourceCommit = Get-PackageSourceCommit -PackageRoot $PackageRoot
  $packageVersion = Get-InstallManifestVersion -ManifestInfo $ManifestInfo
  $aiKitManifestPath = Join-Path $PackageRoot "ai-sync\ai-kit-manifest.json"

  $lockDirectory = Join-Path $ProjectRoot ".codex-harness\locks"
  if (-not (Test-Path -LiteralPath $lockDirectory -PathType Container)) {
    New-Item -ItemType Directory -Force -Path $lockDirectory | Out-Null
  }

  $lockDocument = [pscustomobject]@{
    lockVersion = "0.1"
    package = "codex-harness"
    version = $packageVersion
    sourceCommit = $sourceCommit
    installMode = $InstallMode
    vendor = $VendorInstalled
    installedAt = (Get-Date).ToString("o")
    source = [pscustomobject]@{
      packageRoot = $PackageRoot
      commit = $sourceCommit
    }
    manifests = [pscustomobject]@{
      aiKit = if (Test-Path -LiteralPath $aiKitManifestPath -PathType Leaf) { "ai-sync/ai-kit-manifest.json" } else { "" }
      install = "install-manifest.json"
    }
    managedFiles = @(Get-InstallLockManagedFiles `
      -ManifestInfo $ManifestInfo `
      -InstallMode $InstallMode `
      -VendorInstalled:$VendorInstalled)
  }

  $lockPath = Join-Path $lockDirectory "package.lock.json"
  $lockDocument | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $lockPath -Encoding UTF8
  return $lockPath
}

function Write-ProjectHarnessConfig {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$InstallScope,
    [bool]$VendorInstalled,
    [string]$PackageRoot
  )

  $configDirectory = Join-Path $ProjectRoot ".codex-harness\state"
  if (-not (Test-Path -LiteralPath $configDirectory -PathType Container)) {
    New-Item -ItemType Directory -Force -Path $configDirectory | Out-Null
  }

  $configDocument = [pscustomobject]@{
    package = "codex-harness"
    version = Get-InstallManifestVersion -ManifestInfo $ManifestInfo
    packageRoot = [System.IO.Path]::GetFullPath($PackageRoot)
    installScope = $InstallScope
    vendor = $VendorInstalled
  }

  $configPath = Join-Path $configDirectory "config.json"
  $configDocument | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $configPath -Encoding UTF8
  return $configPath
}

function Write-UserPackageLock {
  param(
    [object]$ManifestInfo,
    [string]$PackageRoot,
    [string]$InstallRoot
  )

  $lockDirectory = Split-Path -Parent $InstallRoot
  if (-not (Test-Path -LiteralPath $lockDirectory -PathType Container)) {
    New-Item -ItemType Directory -Force -Path $lockDirectory | Out-Null
  }

  $packageVersion = Get-InstallManifestVersion -ManifestInfo $ManifestInfo
  $sourceCommit = Get-PackageSourceCommit -PackageRoot $PackageRoot
  $lockDocument = [pscustomobject]@{
    lockVersion = "0.1"
    package = "codex-harness"
    version = $packageVersion
    latestVersion = $packageVersion
    sourceCommit = $sourceCommit
    installedAt = (Get-Date).ToString("o")
    packageRoot = [System.IO.Path]::GetFullPath($InstallRoot)
    source = [pscustomobject]@{
      packageRoot = [System.IO.Path]::GetFullPath($PackageRoot)
      commit = $sourceCommit
    }
    managedFiles = @(Get-InstalledPackageManagedFiles -PackageRoot $PackageRoot -InstallRoot $InstallRoot)
  }

  $lockPath = Join-Path $lockDirectory "package.lock.json"
  $lockDocument | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $lockPath -Encoding UTF8
  return $lockPath
}

function Get-UserPackageInstallRoot {
  param([object]$ManifestInfo)

  $packageVersion = Get-InstallManifestVersion -ManifestInfo $ManifestInfo
  return [System.IO.Path]::GetFullPath((Join-Path $env:USERPROFILE ".codex\packages\codex-harness\$packageVersion"))
}

function Install-UserPackageLayer {
  param(
    [object]$ManifestInfo,
    [string]$PackageRoot
  )

  $installRoot = Get-UserPackageInstallRoot -ManifestInfo $ManifestInfo
  if (-not (Test-Path -LiteralPath $installRoot -PathType Container)) {
    New-Item -ItemType Directory -Force -Path $installRoot | Out-Null
  }

  Copy-DirectoryContentExcludingNames -SourceDirectory $PackageRoot -DestinationDirectory $installRoot -ExcludeNames @(".git")
  $lockPath = Write-UserPackageLock -ManifestInfo $ManifestInfo -PackageRoot $PackageRoot -InstallRoot $installRoot

  return [pscustomobject]@{
    PackageRoot = $installRoot
    LockPath = $lockPath
  }
}

function Invoke-UpgradeExecution {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$AgentsRoot,
    [string]$RequestedMode,
    [string]$PackageRoot
  )

  Write-Output "Upgrade execution."
  Write-Output "PlanOnly: False"
  Write-Output ("ProjectRoot: {0}" -f $ProjectRoot)
  Write-Output ("AgentsRoot: {0}" -f $AgentsRoot)
  Write-Output ("RequestedMode: {0}" -f $RequestedMode)
  Write-Output ("Manifest: {0}" -f $ManifestInfo.Path)

  $lockInfo = Read-PackageLockOrNull -ProjectRoot $ProjectRoot
  if ($null -eq $lockInfo) {
    throw "missing-lock: .codex-harness/locks/package.lock.json 不存在，无法安全执行升级。"
  }

  $lockDocument = $lockInfo.Document
  $installedMode = Get-InstalledModeFromLock -LockDocument $lockDocument -RequestedMode $RequestedMode
  $vendorInstalled = [bool]$lockDocument.vendor

  Write-Output ("Lock: {0}" -f $lockInfo.Path)
  Write-Output ("InstalledMode: {0}" -f $installedMode)
  Write-Output ("InstalledVendor: {0}" -f $vendorInstalled)

  $actions = New-Object System.Collections.Generic.List[object]
  $plannedEntries = Get-ManifestEntriesForMode -ManifestInfo $ManifestInfo -InstallMode $installedMode
  foreach ($entry in @($plannedEntries)) {
    if ([bool]$entry.Mutable) {
      $actions.Add([pscustomobject]@{
        Entry = $entry
        Action = "manual-review"
        Source = [string]$entry.Source
        SourcePath = [string]$entry.SourcePath
        Destination = [string]$entry.Destination
        TargetPath = [string]$entry.TargetPath
        Reason = "mutable=true，升级执行不会直接覆盖用户可编辑文件。"
      })
      continue
    }

    if ($entry.SourceKind -eq "directory") {
      $sourceDirectory = [System.IO.Path]::GetFullPath($entry.SourcePath).TrimEnd('\', '/')
      $sourceRoot = ([string]$entry.Source).TrimEnd('/')
      $destinationRoot = ([string]$entry.Destination).TrimEnd('/')
      foreach ($sourceFile in @(Get-InstallChildFiles -Directory $sourceDirectory | Sort-Object FullName)) {
        $relativeFilePath = $sourceFile.FullName.Substring($sourceDirectory.Length).TrimStart('\', '/').Replace('\', '/')
        $fileSource = $sourceRoot + "/" + $relativeFilePath
        $fileDestination = $destinationRoot + "/" + $relativeFilePath
        $fileTargetPath = Join-Path $entry.TargetPath ($relativeFilePath.Replace('/', '\'))
        $decision = Get-UpgradePlanFileDecision `
          -LockDocument $lockDocument `
          -Destination $fileDestination `
          -SourcePath $sourceFile.FullName `
          -TargetPath $fileTargetPath

        $actions.Add([pscustomobject]@{
          Entry = $entry
          Action = $decision.Action
          Source = $fileSource
          SourcePath = $sourceFile.FullName
          Destination = $fileDestination
          TargetPath = $fileTargetPath
          Reason = $decision.Reason
        })
      }

      continue
    }

    $decision = Get-UpgradePlanFileDecision `
      -LockDocument $lockDocument `
      -Destination ([string]$entry.Destination) `
      -SourcePath ([string]$entry.SourcePath) `
      -TargetPath ([string]$entry.TargetPath)

    $actions.Add([pscustomobject]@{
      Entry = $entry
      Action = $decision.Action
      Source = [string]$entry.Source
      SourcePath = [string]$entry.SourcePath
      Destination = [string]$entry.Destination
      TargetPath = [string]$entry.TargetPath
      Reason = $decision.Reason
    })
  }

  $plannedActions = @($actions.ToArray())
  foreach ($action in $plannedActions) {
    Write-UpgradePlanItem `
      -Entry $action.Entry `
      -Action ([string]$action.Action) `
      -Source ([string]$action.Source) `
      -Destination ([string]$action.Destination) `
      -Target ([string]$action.TargetPath) `
      -Reason ([string]$action.Reason)
  }

  $blockedActions = @($plannedActions | Where-Object { [string]$_.Action -eq "blocked-user-drift" })
  if ($blockedActions.Count -gt 0) {
    Write-Output ("blocked-user-drift: {0} 个文件已偏离 lock，升级已停止。" -f $blockedActions.Count)
    exit 1
  }

  $changedCount = 0
  foreach ($action in $plannedActions) {
    if (@("update-immutable", "install-missing") -notcontains [string]$action.Action) {
      continue
    }

    Copy-InstallManagedFile -SourcePath ([string]$action.SourcePath) -TargetPath ([string]$action.TargetPath)
    $changedCount += 1
  }

  $effectiveInstallMode = if ($installedMode -eq "vendor") { "vendor" } elseif ($vendorInstalled) { "full" } else { "project" }
  $lockPath = Write-InstalledPackageLock `
    -ManifestInfo $ManifestInfo `
    -ProjectRoot $ProjectRoot `
    -InstallMode $effectiveInstallMode `
    -VendorInstalled:$vendorInstalled `
    -PackageRoot $PackageRoot

  Write-Output ("ChangedFiles: {0}" -f $changedCount)
  Write-Output ("LockWritten: {0}" -f $lockPath)
}

function Test-PathUnderRoot {
  param(
    [string]$Root,
    [string]$Path
  )

  $rootFullPath = [System.IO.Path]::GetFullPath($Root).TrimEnd('\', '/') + [System.IO.Path]::DirectorySeparatorChar
  $pathFullPath = [System.IO.Path]::GetFullPath($Path)
  return $pathFullPath.StartsWith($rootFullPath, [System.StringComparison]::OrdinalIgnoreCase)
}

function Remove-EmptyDirectoriesUpToRoot {
  param(
    [string]$StartDirectory,
    [string]$StopRoot
  )

  $stopFullPath = [System.IO.Path]::GetFullPath($StopRoot).TrimEnd('\', '/')
  $current = $StartDirectory
  while (-not [string]::IsNullOrWhiteSpace($current)) {
    $currentFullPath = [System.IO.Path]::GetFullPath($current).TrimEnd('\', '/')
    if (-not $currentFullPath.StartsWith($stopFullPath, [System.StringComparison]::OrdinalIgnoreCase)) {
      break
    }

    if ($currentFullPath.Equals($stopFullPath, [System.StringComparison]::OrdinalIgnoreCase)) {
      break
    }

    if (-not (Test-Path -LiteralPath $currentFullPath -PathType Container)) {
      $current = Split-Path -Parent $currentFullPath
      continue
    }

    if (@(Get-ChildItem -LiteralPath $currentFullPath -Force).Count -gt 0) {
      break
    }

    Remove-Item -LiteralPath $currentFullPath -Force
    $current = Split-Path -Parent $currentFullPath
  }
}

function Invoke-UninstallExecution {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [string]$AgentsRoot,
    [string]$RequestedMode,
    [string]$PackageRoot
  )

  Write-Output "Uninstall execution."
  Write-Output "PlanOnly: False"
  Write-Output ("ProjectRoot: {0}" -f $ProjectRoot)
  Write-Output ("AgentsRoot: {0}" -f $AgentsRoot)
  Write-Output ("RequestedMode: {0}" -f $RequestedMode)
  Write-Output ("Manifest: {0}" -f $ManifestInfo.Path)

  $lockInfo = Read-PackageLockOrNull -ProjectRoot $ProjectRoot
  if ($null -eq $lockInfo) {
    throw "missing-lock: .codex-harness/locks/package.lock.json 不存在，无法安全执行卸载。"
  }

  $lockDocument = $lockInfo.Document
  $installedMode = Get-InstalledModeFromLock -LockDocument $lockDocument -RequestedMode $RequestedMode
  $vendorInstalled = [bool]$lockDocument.vendor
  Write-Output ("Lock: {0}" -f $lockInfo.Path)
  Write-Output ("InstalledMode: {0}" -f $installedMode)
  Write-Output ("InstalledVendor: {0}" -f $vendorInstalled)

  if (-not $vendorInstalled) {
    $lockPath = Write-InstalledPackageLock `
      -ManifestInfo $ManifestInfo `
      -ProjectRoot $ProjectRoot `
      -InstallMode "project" `
      -VendorInstalled:$false `
      -PackageRoot $PackageRoot

    Write-Output "RemovedFiles: 0"
    Write-Output ("LockWritten: {0}" -f $lockPath)
    return
  }

  $vendorManagedFiles = @(
    @($lockDocument.managedFiles) |
      Where-Object { [string]$_.scope -eq "vendor" } |
      Sort-Object path -Descending
  )

  if ($vendorManagedFiles.Count -eq 0) {
    throw "missing-vendor-lock-entries: lock 中缺少 vendor managedFiles，无法安全执行卸载。"
  }

  $blockedFiles = New-Object System.Collections.Generic.List[object]
  foreach ($managedFile in $vendorManagedFiles) {
    $managedPath = Normalize-ManifestPath -Path ([string]$managedFile.path) -Label "vendor managed file path"
    $targetPath = [System.IO.Path]::GetFullPath((Join-Path $ProjectRoot ($managedPath.Replace('/', '\'))))
    if (-not (Test-PathUnderRoot -Root $AgentsRoot -Path $targetPath)) {
      throw "vendor managed file 不在 AgentsRoot 内，拒绝删除: $targetPath"
    }

    if (-not (Test-Path -LiteralPath $targetPath -PathType Leaf)) {
      continue
    }

    $actualSha256 = Get-InstallFileSha256 -Path $targetPath
    $lockedSha256 = ([string]$managedFile.sha256).ToLowerInvariant()
    if ($actualSha256 -ne $lockedSha256) {
      $blockedFiles.Add([pscustomobject]@{
        Path = $managedPath
        TargetPath = $targetPath
      })
    }
  }

  $blockedFileArray = @($blockedFiles.ToArray())
  if ($blockedFileArray.Count -gt 0) {
    foreach ($blockedFile in $blockedFileArray) {
      Write-Output ("- action=blocked-user-drift scope=vendor path={0}" -f $blockedFile.Path)
      Write-Output ("  target: {0}" -f $blockedFile.TargetPath)
      Write-Output "  reason: vendor 文件已偏离 lock，卸载前需要人工确认。"
    }

    Write-Output ("blocked-user-drift: {0} 个 vendor 文件已偏离 lock，卸载已停止。" -f $blockedFileArray.Count)
    exit 1
  }

  $removedCount = 0
  foreach ($managedFile in $vendorManagedFiles) {
    $managedPath = Normalize-ManifestPath -Path ([string]$managedFile.path) -Label "vendor managed file path"
    $targetPath = [System.IO.Path]::GetFullPath((Join-Path $ProjectRoot ($managedPath.Replace('/', '\'))))
    if (Test-Path -LiteralPath $targetPath -PathType Leaf) {
      Remove-Item -LiteralPath $targetPath -Force
      $removedCount += 1
      Remove-EmptyDirectoriesUpToRoot -StartDirectory (Split-Path -Parent $targetPath) -StopRoot $AgentsRoot
    }
  }

  $lockPath = Write-InstalledPackageLock `
    -ManifestInfo $ManifestInfo `
    -ProjectRoot $ProjectRoot `
    -InstallMode "project" `
    -VendorInstalled:$false `
    -PackageRoot $PackageRoot

  Write-Output ("RemovedFiles: {0}" -f $removedCount)
  Write-Output ("LockWritten: {0}" -f $lockPath)
}

function Test-GitRepository {
  param([string]$Root)

  $gitPath = (Get-Command git -ErrorAction Stop).Source
  $stdoutPath = [System.IO.Path]::GetTempFileName()
  $stderrPath = [System.IO.Path]::GetTempFileName()

  try {
    $process = Start-Process `
      -FilePath $gitPath `
      -ArgumentList @('-C', $Root, 'rev-parse', '--show-toplevel') `
      -NoNewWindow `
      -Wait `
      -PassThru `
      -RedirectStandardOutput $stdoutPath `
      -RedirectStandardError $stderrPath

    return ($process.ExitCode -eq 0)
  }
  finally {
    Remove-Item -LiteralPath $stdoutPath -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath $stderrPath -Force -ErrorAction SilentlyContinue
  }
}

function Assert-GitRepoOrInit {
  param(
    [string]$Root,
    [bool]$AllowInit
  )

  $repoExists = Test-GitRepository -Root $Root

  if ($repoExists) {
    return $true
  }

  if (-not $AllowInit) {
    throw "目标目录不是 Git 仓库: $Root。请先初始化 Git，或传入 -InitGitIfNeeded。"
  }

  $codeGraphCommand = Get-Command codegraph -ErrorAction SilentlyContinue
  if ($null -eq $codeGraphCommand) {
    throw "目标目录不是 Git 仓库: $Root。自动初始化前需要先执行 codegraph init -i，但当前环境找不到 codegraph 命令。"
  }

  Write-Step "目标目录不是 Git 仓库，先执行 codegraph init -i。"
  Push-Location $Root
  try {
    & $codeGraphCommand.Source init -i | Out-Host
    if ($LASTEXITCODE -ne 0) {
      throw "codegraph init -i 失败: $Root"
    }
  }
  finally {
    Pop-Location
  }

  Write-Step "目标目录不是 Git 仓库，执行 git init。"
  & git -C $Root init | Out-Null
  if ($LASTEXITCODE -ne 0) {
    throw "git init 失败: $Root"
  }

  return $false
}

function Get-GitStatusLines {
  param([string]$Root)

  $output = & git -C $Root status --short
  if ($LASTEXITCODE -ne 0) {
    throw "无法读取 Git 状态: $Root"
  }

  if ($null -eq $output) {
    return @()
  }

  return @($output)
}

function Test-GitHeadExists {
  param([string]$Root)

  $gitPath = (Get-Command git -ErrorAction Stop).Source
  $stdoutPath = [System.IO.Path]::GetTempFileName()
  $stderrPath = [System.IO.Path]::GetTempFileName()
  try {
    $process = Start-Process `
      -FilePath $gitPath `
      -ArgumentList @('-C', $Root, 'rev-parse', '--verify', 'HEAD') `
      -NoNewWindow `
      -Wait `
      -PassThru `
      -RedirectStandardOutput $stdoutPath `
      -RedirectStandardError $stderrPath

    return ($process.ExitCode -eq 0)
  }
  finally {
    Remove-Item -LiteralPath $stdoutPath -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath $stderrPath -Force -ErrorAction SilentlyContinue
  }
}

function Build-BaselineCommitMessage {
  param(
    [string]$InstallMode,
    [bool]$HadHeadBeforeInstall,
    [bool]$HadAgentsBeforeInstall
  )

  if ($HadHeadBeforeInstall) {
    if (-not $HadAgentsBeforeInstall) {
      return "chore: add codex agent package"
    }

    return "chore: refresh codex agent package"
  }

  return "chore: initialize codex agent package"
}

function Get-GitConfigValue {
  param([string[]]$Arguments)

  $gitPath = (Get-Command git -ErrorAction Stop).Source
  $stdoutPath = [System.IO.Path]::GetTempFileName()
  $stderrPath = [System.IO.Path]::GetTempFileName()

  try {
    $process = Start-Process `
      -FilePath $gitPath `
      -ArgumentList $Arguments `
      -NoNewWindow `
      -Wait `
      -PassThru `
      -RedirectStandardOutput $stdoutPath `
      -RedirectStandardError $stderrPath

    if ($process.ExitCode -ne 0) {
      return ""
    }

    return (Get-Content -LiteralPath $stdoutPath -Raw).Trim()
  }
  finally {
    Remove-Item -LiteralPath $stdoutPath -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath $stderrPath -Force -ErrorAction SilentlyContinue
  }
}

function Test-GitIdentityConfigured {
  param([string]$Root)

  $userName = Get-GitConfigValue -Arguments @('-C', $Root, 'config', '--get', 'user.name')
  if ([string]::IsNullOrWhiteSpace([string]$userName)) {
    $userName = Get-GitConfigValue -Arguments @('config', '--global', '--get', 'user.name')
  }

  $userEmail = Get-GitConfigValue -Arguments @('-C', $Root, 'config', '--get', 'user.email')
  if ([string]::IsNullOrWhiteSpace([string]$userEmail)) {
    $userEmail = Get-GitConfigValue -Arguments @('config', '--global', '--get', 'user.email')
  }

  return (-not [string]::IsNullOrWhiteSpace([string]$userName)) -and (-not [string]::IsNullOrWhiteSpace([string]$userEmail))
}

function Copy-DirectoryContent {
  param(
    [string]$SourceDirectory,
    [string]$DestinationDirectory
  )

  if (-not (Test-Path -LiteralPath $DestinationDirectory)) {
    New-Item -ItemType Directory -Path $DestinationDirectory | Out-Null
  }

  Get-ChildItem -LiteralPath $SourceDirectory -Force | ForEach-Object {
    $destinationPath = Join-Path $DestinationDirectory $_.Name
    Copy-Item -LiteralPath $_.FullName -Destination $destinationPath -Recurse -Force
  }
}

function Copy-DirectoryContentExcludingNames {
  param(
    [string]$SourceDirectory,
    [string]$DestinationDirectory,
    [string[]]$ExcludeNames = @()
  )

  if (-not (Test-Path -LiteralPath $DestinationDirectory)) {
    New-Item -ItemType Directory -Path $DestinationDirectory | Out-Null
  }

  foreach ($child in @(Get-ChildItem -LiteralPath $SourceDirectory -Force | Where-Object { $ExcludeNames -notcontains $_.Name })) {
    $destinationPath = Join-Path $DestinationDirectory $child.Name
    if ($child.PSIsContainer) {
      if (-not (Test-Path -LiteralPath $destinationPath -PathType Container)) {
        New-Item -ItemType Directory -Force -Path $destinationPath | Out-Null
      }

      $robocopyArgs = @(
        $child.FullName,
        $destinationPath,
        "/E",
        "/COPY:DAT",
        "/DCOPY:DAT",
        "/R:2",
        "/W:1",
        "/NFL",
        "/NDL",
        "/NJH",
        "/NJS",
        "/NP"
      )

      & robocopy @robocopyArgs | Out-Null
      $robocopyExitCode = $LASTEXITCODE
      if ($robocopyExitCode -ge 8) {
        throw "复制目录失败: $($child.FullName) -> $destinationPath (robocopy exit $robocopyExitCode)"
      }

      $global:LASTEXITCODE = 0
      continue
    }

    Copy-Item -LiteralPath $child.FullName -Destination $destinationPath -Force
  }
}

function Copy-ManagedFile {
  param(
    [string]$SourceRoot,
    [string]$SourceRelativePath,
    [string]$DestinationRoot,
    [string]$DestinationRelativePath
  )

  $sourcePath = Join-Path $SourceRoot $SourceRelativePath
  if (-not (Test-Path -LiteralPath $sourcePath)) {
    throw "模板文件不存在: $sourcePath"
  }

  $destinationPath = Join-Path $DestinationRoot $DestinationRelativePath
  $destinationDirectory = Split-Path -Parent $destinationPath
  if (-not (Test-Path -LiteralPath $destinationDirectory)) {
    New-Item -ItemType Directory -Force -Path $destinationDirectory | Out-Null
  }

  Copy-Item -LiteralPath $sourcePath -Destination $destinationPath -Force
}

function Copy-ManagedDirectory {
  param(
    [string]$SourceDirectory,
    [string]$DestinationDirectory,
    [bool]$Overwrite
  )

  if (-not (Test-Path -LiteralPath $SourceDirectory)) {
    throw "模板目录不存在: $SourceDirectory"
  }

  if (Test-Path -LiteralPath $DestinationDirectory) {
    if (-not $Overwrite) {
      return
    }

    Remove-Item -LiteralPath $DestinationDirectory -Recurse -Force
  }

  New-Item -ItemType Directory -Force -Path $DestinationDirectory | Out-Null
  Copy-DirectoryContent -SourceDirectory $SourceDirectory -DestinationDirectory $DestinationDirectory
}

function Copy-TemplatePackageCodex {
  param(
    [string]$TemplateRoot,
    [string]$DestinationRoot
  )

  $codexRoot = Join-Path $DestinationRoot ".codex"
  if (-not (Test-Path -LiteralPath $codexRoot)) {
    New-Item -ItemType Directory -Force -Path $codexRoot | Out-Null
  }

  $fileMappings = @(
    @{ Source = "config\codex-config.toml"; Destination = ".codex\config.toml" },
    @{ Source = "config\codex-readme.md"; Destination = ".codex\README.md" },
    @{ Source = "hooks\hooks.json"; Destination = ".codex\hooks.json" },
    @{ Source = "runtime\task-run-profile.json"; Destination = ".codex\task-run-profile.json" }
  )

  foreach ($mapping in $fileMappings) {
    Copy-ManagedFile `
      -SourceRoot $TemplateRoot `
      -SourceRelativePath $mapping.Source `
      -DestinationRoot $DestinationRoot `
      -DestinationRelativePath $mapping.Destination
  }

  Copy-DirectoryContent -SourceDirectory (Join-Path $TemplateRoot "config\agents") -DestinationDirectory (Join-Path $codexRoot "agents")
  Copy-DirectoryContent -SourceDirectory (Join-Path $TemplateRoot "config\rules") -DestinationDirectory (Join-Path $codexRoot "rules")
  Copy-DirectoryContent -SourceDirectory (Join-Path $TemplateRoot "prompts") -DestinationDirectory (Join-Path $codexRoot "prompts")
}

function Copy-AssetDirectory {
  param(
    [string]$AssetsRoot,
    [string]$SourceRelativePath,
    [string]$DestinationRoot,
    [string]$DestinationRelativePath
  )

  $sourcePath = Join-Path $AssetsRoot $SourceRelativePath
  if (-not (Test-Path -LiteralPath $sourcePath)) {
    throw "模板目录不存在: $sourcePath"
  }

  $destinationPath = Join-Path $DestinationRoot $DestinationRelativePath
  $destinationParent = Split-Path -Parent $destinationPath
  if (-not (Test-Path -LiteralPath $destinationParent)) {
    New-Item -ItemType Directory -Force -Path $destinationParent | Out-Null
  }

  if (-not (Test-Path -LiteralPath $destinationPath)) {
    New-Item -ItemType Directory -Force -Path $destinationPath | Out-Null
  }

  $robocopyArgs = @(
    $sourcePath,
    $destinationPath,
    "/E",
    "/COPY:DAT",
    "/DCOPY:DAT",
    "/R:2",
    "/W:1",
    "/NFL",
    "/NDL",
    "/NJH",
    "/NJS",
    "/NP"
  )

  & robocopy @robocopyArgs | Out-Null
  $robocopyExitCode = $LASTEXITCODE

  if ($robocopyExitCode -ge 8) {
    throw "复制模板目录失败: $sourcePath -> $destinationPath (robocopy exit $robocopyExitCode)"
  }

  $global:LASTEXITCODE = 0
}

function Copy-ManifestEntries {
  param(
    [object]$ManifestInfo,
    [string[]]$Scopes
  )

  $selectedEntries = @(
    $ManifestInfo.Entries |
      Where-Object { $Scopes -contains [string]$_.Scope }
  )

  foreach ($entry in $selectedEntries) {
    if ($entry.SourceKind -eq "file") {
      $targetParent = Split-Path -Parent $entry.TargetPath
      if (-not (Test-Path -LiteralPath $targetParent -PathType Container)) {
        New-Item -ItemType Directory -Force -Path $targetParent | Out-Null
      }

      Copy-Item -LiteralPath $entry.SourcePath -Destination $entry.TargetPath -Force
      continue
    }

    if ($entry.SourceKind -ne "directory") {
      throw "不支持的 manifest sourceKind: $($entry.SourceKind)"
    }

    if (-not (Test-Path -LiteralPath $entry.TargetPath -PathType Container)) {
      New-Item -ItemType Directory -Force -Path $entry.TargetPath | Out-Null
    }

    $robocopyArgs = @(
      $entry.SourcePath,
      $entry.TargetPath,
      "/E",
      "/COPY:DAT",
      "/DCOPY:DAT",
      "/R:2",
      "/W:1",
      "/NFL",
      "/NDL",
      "/NJH",
      "/NJS",
      "/NP"
    )

    & robocopy @robocopyArgs | Out-Null
    $robocopyExitCode = $LASTEXITCODE
    if ($robocopyExitCode -ge 8) {
      throw "复制 manifest 目录失败: $($entry.Source) -> $($entry.Destination) (robocopy exit $robocopyExitCode)"
    }

    $global:LASTEXITCODE = 0
  }
}

function Overlay-DirectoryIfPresent {
  param(
    [string]$SourceDirectory,
    [string]$DestinationDirectory
  )

  if (-not (Test-Path -LiteralPath $SourceDirectory -PathType Container)) {
    return
  }

  if (-not (Test-Path -LiteralPath $DestinationDirectory)) {
    New-Item -ItemType Directory -Force -Path $DestinationDirectory | Out-Null
  }

  foreach ($directory in Get-ChildItem -LiteralPath $SourceDirectory -Recurse -Force -Directory) {
    $relativePath = $directory.FullName.Substring($SourceDirectory.Length + 1)
    $targetDirectory = Join-Path $DestinationDirectory $relativePath
    if (-not (Test-Path -LiteralPath $targetDirectory)) {
      New-Item -ItemType Directory -Force -Path $targetDirectory | Out-Null
    }
  }

  foreach ($file in Get-ChildItem -LiteralPath $SourceDirectory -Recurse -Force -File) {
    $relativePath = $file.FullName.Substring($SourceDirectory.Length + 1)
    $targetPath = Join-Path $DestinationDirectory $relativePath
    $targetParent = Split-Path -Parent $targetPath
    if (-not (Test-Path -LiteralPath $targetParent)) {
      New-Item -ItemType Directory -Force -Path $targetParent | Out-Null
    }

    Copy-Item -LiteralPath $file.FullName -Destination $targetPath -Force
  }
}

function Ensure-ReadmeForSmoke {
  param([string]$Root)

  $readmePath = Join-Path $Root "README.md"
  if (Test-Path -LiteralPath $readmePath) {
    return $null
  }

  @(
    "# Project README",
    "",
    "This README was generated by install-agent.ps1 to support the initial smoke task.",
    "Replace it with project-specific content after harness setup is verified."
  ) | Set-Content -LiteralPath $readmePath -Encoding UTF8

  return $readmePath
}

function Assert-InstalledProjectScopeSurface {
  param(
    [object]$ManifestInfo,
    [string]$ProjectRoot,
    [switch]$ThinProjectProfile
  )

  $projectEntries = @(Get-ManifestEntriesForMode -ManifestInfo $ManifestInfo -InstallMode "project" -ThinProjectProfile:$ThinProjectProfile.IsPresent)

  $missingPaths = @()
  foreach ($entry in $projectEntries) {
    $targetPath = [string]$entry.TargetPath
    if ([string]::IsNullOrWhiteSpace($targetPath)) {
      continue
    }

    if ($entry.SourceKind -eq "directory") {
      if (-not (Test-Path -LiteralPath $targetPath -PathType Container)) {
        $missingPaths += $entry.Destination
      }
      continue
    }

    if (-not (Test-Path -LiteralPath $targetPath -PathType Leaf)) {
      $missingPaths += $entry.Destination
    }
  }

  if ($missingPaths.Count -gt 0) {
    $details = ($missingPaths | Sort-Object -Unique | ForEach-Object { "- $_" }) -join "`n"
    throw "project scope 安装后缺少以下真相源或运行文件:`n$details"
  }
}

function Copy-Package {
  param(
    [string]$SourceRoot,
    [string]$DestinationRoot,
    [string]$InstallMode
  )

  $templateRoot = Join-Path $SourceRoot "docs\codex-harness-engineering\templates"
  if (-not (Test-Path -LiteralPath $templateRoot)) {
    throw "找不到模板目录: $templateRoot"
  }

  $packageAssetsRoot = Join-Path $templateRoot "package-assets"
  if (-not (Test-Path -LiteralPath $packageAssetsRoot)) {
    throw "找不到 package-assets 模板目录: $packageAssetsRoot"
  }

  $rootFileMappings = @(
    @{ SourceRoot = $templateRoot; Source = "runtime\AGENTS.md"; Destination = "AGENTS.md" },
    @{ SourceRoot = $templateRoot; Source = "bootstrap-codex-harness.ps1"; Destination = "bootstrap-codex-harness.ps1" },
    @{ SourceRoot = $templateRoot; Source = "runtime\codex-loop.ps1"; Destination = "codex-loop.ps1" },
    @{ SourceRoot = $templateRoot; Source = "config\env-check.ps1"; Destination = "env-check.ps1" },
    @{ SourceRoot = $templateRoot; Source = "runtime\verify.ps1"; Destination = "verify.ps1" },
    @{ SourceRoot = $templateRoot; Source = "trace\trace.schema.json"; Destination = "trace.schema.json" },
    @{ SourceRoot = $SourceRoot; Source = "install-manifest.json"; Destination = "install-manifest.json" },
    @{ SourceRoot = $SourceRoot; Source = "install-manifest.schema.json"; Destination = "install-manifest.schema.json" },
    @{ SourceRoot = $packageAssetsRoot; Source = "root\install-agent.ps1"; Destination = "install-agent.ps1" },
    @{ SourceRoot = $packageAssetsRoot; Source = "root\install-agent-here.ps1"; Destination = "install-agent-here.ps1" },
    @{ SourceRoot = $packageAssetsRoot; Source = "root\README.md"; Destination = "README.md" },
    @{ SourceRoot = $packageAssetsRoot; Source = "root\PACKAGE.md"; Destination = "PACKAGE.md" }
  )

  foreach ($mapping in $rootFileMappings) {
    Copy-ManagedFile `
      -SourceRoot $mapping.SourceRoot `
      -SourceRelativePath $mapping.Source `
      -DestinationRoot $DestinationRoot `
      -DestinationRelativePath $mapping.Destination
  }

  $projectRoot = Split-Path -Parent $DestinationRoot

  Copy-AssetDirectory `
    -AssetsRoot $packageAssetsRoot `
    -SourceRelativePath "rules" `
    -DestinationRoot $projectRoot `
    -DestinationRelativePath ".codex\rules"

  $dirsToCopy = @("workflows", "scripts", ".specify", "skills")

  foreach ($dir in $dirsToCopy) {
    Copy-AssetDirectory `
      -AssetsRoot $packageAssetsRoot `
      -SourceRelativePath $dir `
      -DestinationRoot $DestinationRoot `
      -DestinationRelativePath $dir
  }

  Copy-AssetDirectory `
    -AssetsRoot (Join-Path $SourceRoot "docs\codex-harness-engineering") `
    -SourceRelativePath "templates" `
    -DestinationRoot $DestinationRoot `
    -DestinationRelativePath "docs\codex-harness-engineering\templates"
}

$sourceRoot = Get-PackageRoot
if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
  $ProjectRoot = $sourceRoot
}

$resolvedProjectRoot = [System.IO.Path]::GetFullPath($ProjectRoot)
$targetAgentsRoot = Join-Path $resolvedProjectRoot ".agents"
$stagedSourceRoot = $null
$effectiveSourceRoot = $sourceRoot
$scopeUserPackage = $null
$baselineCommitted = $false
$baselinePolicy = "skipped"

try {
  $modeWasProvided = $PSBoundParameters.ContainsKey("Mode")
  $scopeWasProvided = $PSBoundParameters.ContainsKey("Scope")
  $defaultProjectInstall = (-not $modeWasProvided) -and (-not $scopeWasProvided)
  if ((-not [string]::IsNullOrWhiteSpace($Scope)) -and $modeWasProvided) {
    throw "-Scope 和 -Mode 只能选择一个。"
  }

  $installScope = ""
  if ($defaultProjectInstall) {
    $Mode = "project"
    $installScope = "project"
  }
  elseif (-not [string]::IsNullOrWhiteSpace($Scope)) {
    $Mode = $Scope
    $installScope = $Scope
  }
  elseif ($Mode -ne "full") {
    $installScope = $Mode
  }
  else {
    $installScope = "full"
  }

  $installManifestInfo = Read-InstallManifest -SourceRoot $effectiveSourceRoot -ProjectRoot $resolvedProjectRoot
  $planSwitchCount = 0
  foreach ($selectedPlanSwitch in @($Plan.IsPresent, $UpgradePlan.IsPresent, $UninstallPlan.IsPresent, $MigratePlan.IsPresent, $MigrateApply.IsPresent, $Upgrade.IsPresent, $Uninstall.IsPresent)) {
    if ($selectedPlanSwitch) {
      $planSwitchCount += 1
    }
  }

  if ($planSwitchCount -gt 1) {
    throw "-Plan、-UpgradePlan、-UninstallPlan、-MigratePlan、-MigrateApply、-Upgrade 和 -Uninstall 只能选择一个。"
  }

  if ($Confirm.IsPresent -and -not $MigrateApply.IsPresent) {
    throw "-Confirm 只能与 -MigrateApply 一起使用。"
  }

  if ($Plan.IsPresent) {
    Write-InstallPlan `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -AgentsRoot $targetAgentsRoot `
      -InstallMode $Mode
    return
  }

  if ($UpgradePlan.IsPresent) {
    Write-UpgradePlan `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -AgentsRoot $targetAgentsRoot `
      -RequestedMode $Mode
    return
  }

  if ($UninstallPlan.IsPresent) {
    Write-UninstallPlan `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -AgentsRoot $targetAgentsRoot `
      -RequestedMode $Mode
    return
  }

  if ($MigratePlan.IsPresent) {
    Write-MigrationPlan `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -AgentsRoot $targetAgentsRoot `
      -RequestedMode $Mode
    return
  }

  if ($MigrateApply.IsPresent) {
    Invoke-MigrationApply `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -AgentsRoot $targetAgentsRoot `
      -RequestedMode $Mode `
      -PackageRoot $effectiveSourceRoot `
      -Confirmed:$Confirm.IsPresent
    return
  }

  if ($Upgrade.IsPresent) {
    Invoke-UpgradeExecution `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -AgentsRoot $targetAgentsRoot `
      -RequestedMode $Mode `
      -PackageRoot $effectiveSourceRoot
    return
  }

  if ($Uninstall.IsPresent) {
    Invoke-UninstallExecution `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -AgentsRoot $targetAgentsRoot `
      -RequestedMode $Mode `
      -PackageRoot $effectiveSourceRoot
    return
  }

  if (($Mode -eq "user" -or $Mode -eq "vendor") -and $InitSmoke.IsPresent) {
    throw "-InitSmoke 仅支持 full/project 模式。"
  }

  if ($Mode -eq "user") {
    Write-Step "安装用户级 codex-harness package"
    $userPackage = Install-UserPackageLayer -ManifestInfo $installManifestInfo -PackageRoot $effectiveSourceRoot
    Write-Step "安装共享 agent package 资源到用户目录"
    Copy-ManifestEntries -ManifestInfo $installManifestInfo -Scopes @("user")

    Write-Output ""
    Write-Output "Install completed."
    Write-Output ("Mode: {0}" -f $Mode)
    if (-not [string]::IsNullOrWhiteSpace($Scope)) {
      Write-Output ("Scope: {0}" -f $Scope)
    }
    Write-Output ("Manifest: {0}" -f $installManifestInfo.Path)
    Write-Output ("PackageRoot: {0}" -f $userPackage.PackageRoot)
    Write-Output ("UserPackageLock: {0}" -f $userPackage.LockPath)
    Write-Output ("Entries: {0}" -f @(Get-ManifestEntriesForMode -ManifestInfo $installManifestInfo -InstallMode $Mode).Count)
    return
  }

  if (-not (Test-Path -LiteralPath $resolvedProjectRoot)) {
    Write-Step "目标目录不存在，创建目录: $resolvedProjectRoot"
    New-Item -ItemType Directory -Path $resolvedProjectRoot | Out-Null
  }

  $targetAgentsExistedBeforeInstall = Test-Path -LiteralPath $targetAgentsRoot
  if (($Mode -ne "project") -and (Test-Path -LiteralPath $targetAgentsRoot) -and (-not $Force.IsPresent)) {
    throw "目标项目已存在 .agents 目录: $targetAgentsRoot。若要覆盖，请显式传入 -Force。"
  }

  $repoExistedBeforeInstall = Assert-GitRepoOrInit -Root $resolvedProjectRoot -AllowInit:$InitGitIfNeeded.IsPresent
  $hadGitHeadBeforeInstall = Test-GitHeadExists -Root $resolvedProjectRoot
  $statusBeforeInstall = if ($repoExistedBeforeInstall) { @(Get-GitStatusLines -Root $resolvedProjectRoot) } else { @() }

  if ((Test-Path -LiteralPath $targetAgentsRoot) -and
      ([System.StringComparer]::OrdinalIgnoreCase.Equals(
        [System.IO.Path]::GetFullPath($targetAgentsRoot),
        $sourceRoot
      ))) {
    $stagedSourceRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("codex-agent-stage-" + [System.Guid]::NewGuid().ToString("N"))
    New-Item -ItemType Directory -Path $stagedSourceRoot | Out-Null
    Copy-DirectoryContent -SourceDirectory $sourceRoot -DestinationDirectory $stagedSourceRoot
    $effectiveSourceRoot = $stagedSourceRoot
    $installManifestInfo = Read-InstallManifest -SourceRoot $effectiveSourceRoot -ProjectRoot $resolvedProjectRoot
    Write-Step "检测到从已安装的 .agents 自更新，已创建临时复制源: $effectiveSourceRoot"
  }

  $templateRoot = Join-Path $effectiveSourceRoot "docs\codex-harness-engineering\templates"

  if ($Mode -eq "project") {
    Write-Step "安装用户级 codex-harness package"
    $scopeUserPackage = Install-UserPackageLayer -ManifestInfo $installManifestInfo -PackageRoot $effectiveSourceRoot

    $bootstrapScript = Join-Path $effectiveSourceRoot "tools\install\bootstrap-codex-harness.ps1"
    Write-Step "同步项目根 runtime 文件 (mode=$Mode)"
    $bootstrapArgs = @(
      '-NoProfile',
      '-ExecutionPolicy', 'Bypass',
      '-File', $bootstrapScript,
      '-ProjectRoot', $resolvedProjectRoot,
      '-TemplateRoot', $templateRoot,
      '-InstallMode', 'project'
    )
    if ($Force.IsPresent) {
      $bootstrapArgs += '-Force'
    }

    & powershell @bootstrapArgs | Out-Host
    if ($LASTEXITCODE -ne 0) {
      throw "bootstrap-codex-harness.ps1 执行失败: $resolvedProjectRoot"
    }
  }
  elseif ($Mode -eq "vendor") {
    if (Test-Path -LiteralPath $targetAgentsRoot) {
      Write-Step "删除已存在的 .agents 目录: $targetAgentsRoot"
      Remove-Item -LiteralPath $targetAgentsRoot -Recurse -Force
    }

    Write-Step "复制 vendor agent package 到目标项目 .agents/ (mode=$Mode)"
    Copy-ManifestEntries -ManifestInfo $installManifestInfo -Scopes @("vendor")
    $null = Write-InstalledPackageLock `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -InstallMode "vendor" `
      -VendorInstalled:$true `
      -PackageRoot $effectiveSourceRoot
  }
  else {
    if (Test-Path -LiteralPath $targetAgentsRoot) {
      Write-Step "删除已存在的 .agents 目录: $targetAgentsRoot"
      Remove-Item -LiteralPath $targetAgentsRoot -Recurse -Force
    }

    Write-Step "复制 agent package 到目标项目 .agents/ (mode=$Mode)"
    New-Item -ItemType Directory -Path $targetAgentsRoot | Out-Null
    Copy-Package -SourceRoot $effectiveSourceRoot -DestinationRoot $targetAgentsRoot -InstallMode $Mode

    $bootstrapScript = Join-Path $targetAgentsRoot "bootstrap-codex-harness.ps1"

    Write-Step "调用 bootstrap-codex-harness.ps1 同步项目根 runtime 文件"
    $bootstrapArgs = @(
      '-NoProfile',
      '-ExecutionPolicy', 'Bypass',
      '-File', $bootstrapScript,
      '-ProjectRoot', $resolvedProjectRoot,
      '-TemplateRoot', $templateRoot,
      '-InstallMode', 'full',
      '-VendorInstalled'
    )
    if ($Force.IsPresent) {
      $bootstrapArgs += '-Force'
    }

    & powershell @bootstrapArgs | Out-Host
    if ($LASTEXITCODE -ne 0) {
      throw "bootstrap-codex-harness.ps1 执行失败: $resolvedProjectRoot"
    }
  }

  if ($Mode -eq "project") {
    $projectPackageRoot = if ($null -ne $scopeUserPackage) { [string]$scopeUserPackage.PackageRoot } else { $effectiveSourceRoot }
    $lockPath = Write-InstalledPackageLock `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -InstallMode "project" `
      -VendorInstalled:$false `
      -PackageRoot $projectPackageRoot
    Write-Step "已写入 package lock: $lockPath"
  }

  if ($Mode -eq "project" -or $Mode -eq "vendor") {
    $configPackageRoot = if ($Mode -eq "vendor") { $targetAgentsRoot } elseif ($null -ne $scopeUserPackage) { [string]$scopeUserPackage.PackageRoot } else { $effectiveSourceRoot }
    $configPath = Write-ProjectHarnessConfig `
      -ManifestInfo $installManifestInfo `
      -ProjectRoot $resolvedProjectRoot `
      -InstallScope $Mode `
      -VendorInstalled:($Mode -eq "vendor") `
      -PackageRoot $configPackageRoot
    Write-Step "已写入 harness config: $configPath"
  }

  if ($Mode -eq "project") {
    Assert-InstalledProjectScopeSurface -ManifestInfo $installManifestInfo -ProjectRoot $resolvedProjectRoot
    Write-Step "project scope 真相源与运行文件已按 manifest 落地"
  }

  $generatedReadme = $null
  if ($InitSmoke) {
    $generatedReadme = Ensure-ReadmeForSmoke -Root $resolvedProjectRoot
    if ($null -ne $generatedReadme) {
      Write-Step "为 smoke 初始化补充 README.md: $generatedReadme"
    }

    $smokeTaskPath = Join-Path $resolvedProjectRoot "tools\harness\templates\smoke-task.json"
    $taskPath = Join-Path $resolvedProjectRoot "task.json"
    if (-not (Test-Path -LiteralPath $smokeTaskPath)) {
      throw "找不到 smoke-task.json: $smokeTaskPath"
    }

    Copy-Item -LiteralPath $smokeTaskPath -Destination $taskPath -Force
    Write-Step "已将 smoke-task.json 覆盖到 task.json"
  }

  $shouldCommitBaseline = $false
  if ($SkipBaselineCommit.IsPresent) {
    $baselinePolicy = "disabled-by-flag"
  }
  elseif ($CommitBaseline.IsPresent) {
    $shouldCommitBaseline = $true
    $baselinePolicy = "explicit"
  }
  elseif (@($statusBeforeInstall).Count -eq 0) {
    $shouldCommitBaseline = $true
    $baselinePolicy = if ($hadGitHeadBeforeInstall -or $targetAgentsExistedBeforeInstall) { "auto-clean-repo" } else { "auto-fresh-repo" }
  }
  else {
    $baselinePolicy = "skipped-dirty-before-install"
  }

  if ($shouldCommitBaseline) {
    if ($repoExistedBeforeInstall -and (@($statusBeforeInstall).Count -gt 0)) {
      throw "目标项目在安装前已存在未提交改动。为避免把无关改动并入 baseline，本次拒绝自动提交。"
    }

    if (-not (Test-GitIdentityConfigured -Root $resolvedProjectRoot)) {
      if ($CommitBaseline.IsPresent) {
        throw "当前 Git 未配置 user.name / user.email，无法执行 baseline commit。"
      }

      Write-Step "当前 Git 未配置 user.name / user.email，跳过自动 baseline commit。"
      $baselinePolicy = "skipped-missing-git-identity"
    }
    else {
      $statusAfterInstall = @(Get-GitStatusLines -Root $resolvedProjectRoot)
      if ($statusAfterInstall.Count -eq 0) {
        Write-Step "安装后没有新增 Git 变更，跳过 baseline commit。"
        $baselinePolicy = "no-op"
      }
      else {
        Write-Step "创建 baseline commit"
        & git -C $resolvedProjectRoot -c core.longpaths=true add --all
        if ($LASTEXITCODE -ne 0) {
          throw "git add 失败: $resolvedProjectRoot"
        }

        $baselineMessage = Build-BaselineCommitMessage `
          -InstallMode $Mode `
          -HadHeadBeforeInstall:$hadGitHeadBeforeInstall `
          -HadAgentsBeforeInstall:$targetAgentsExistedBeforeInstall
        & git -C $resolvedProjectRoot -c core.longpaths=true commit -m $baselineMessage | Out-Host
        if ($LASTEXITCODE -ne 0) {
          throw "git commit 失败: $resolvedProjectRoot"
        }

        $baselineCommitted = $true
      }
    }
  }

  Write-Output ""
  Write-Output "Install completed."
  Write-Output ("ProjectRoot: {0}" -f $resolvedProjectRoot)
  Write-Output ("AgentsRoot: {0}" -f $targetAgentsRoot)
  Write-Output ("Mode: {0}" -f $Mode)
  if (-not [string]::IsNullOrWhiteSpace($Scope)) {
    Write-Output ("Scope: {0}" -f $Scope)
  }
  Write-Output ("SmokeInitialized: {0}" -f $InitSmoke.IsPresent)
  Write-Output ("BaselineCommitted: {0}" -f $baselineCommitted)
  Write-Output ("BaselinePolicy: {0}" -f $baselinePolicy)
  if ($Mode -eq "full" -or $Mode -eq "project") {
    Write-Output ""
    Write-Output "Next steps:"
    Write-Output ("- 先裁剪 `"{0}`"、`"{1}`" 和 `"{2}`" 为目标项目事实" -f (Join-Path $resolvedProjectRoot 'docs\ai\repo-map.md'), (Join-Path $resolvedProjectRoot 'docs\ai\architecture-brief.md'), (Join-Path $resolvedProjectRoot 'docs\ai\CURRENT_TASK.md'))
    Write-Output ("- 复杂项目继续补齐 `"{0}`"、`"{1}`"、`"{2}`"、`"{3}`"、`"{4}`"、`"{5}`"" -f (Join-Path $resolvedProjectRoot 'docs\ai\PROJECT_CONTEXT.md'), (Join-Path $resolvedProjectRoot 'docs\ai\DECISIONS.md'), (Join-Path $resolvedProjectRoot 'docs\ai\API_MAP.md'), (Join-Path $resolvedProjectRoot 'docs\ai\DB_SCHEMA.md'), (Join-Path $resolvedProjectRoot 'docs\ai\KNOWN_ISSUES.md'), (Join-Path $resolvedProjectRoot 'docs\ai\CHANGELOG_AI.md'))
    Write-Output ("- powershell -NoProfile -ExecutionPolicy Bypass -File `"{0}`"" -f (Join-Path $resolvedProjectRoot 'tools\install\env-check.ps1'))
    Write-Output ("- powershell -NoProfile -ExecutionPolicy Bypass -File `"{0}`"" -f (Join-Path $resolvedProjectRoot 'tools\harness\verify.ps1'))
    Write-Output ("- 如需接入外部知识或代码语义工具，先读 `"{0}`" 和 `"{1}`"" -f (Join-Path $resolvedProjectRoot 'docs\harness\mcp-knowledge-governance.md'), (Join-Path $resolvedProjectRoot 'docs\harness\code-semantics-and-navigation.md'))
    Write-Output ("- 如需 7x24 长时间推进，可在 Codex app 中使用 `/goal`；推荐先读 `"{0}`" 和 `"{1}`"" -f (Join-Path $resolvedProjectRoot 'docs\codex-harness-engineering\goal-harness-integration-guide.md'), (Join-Path $resolvedProjectRoot 'docs\codex-harness-engineering\examples\goal-templates.md'))
    Write-Output ("- powershell -NoProfile -ExecutionPolicy Bypass -File `"{0}`"" -f (Join-Path $resolvedProjectRoot 'tools\harness\codex-loop.ps1'))
  }
}
finally {
  if (($null -ne $stagedSourceRoot) -and (Test-Path -LiteralPath $stagedSourceRoot)) {
    Remove-Item -LiteralPath $stagedSourceRoot -Recurse -Force -ErrorAction SilentlyContinue
  }
}
