Files
kompas3d-mcp/tools/AgentAssets.psm1
T
mikhail e3228abb26 feat(tools): режим -Remove у sync-agent-assets — снятие junction'ов перед checkout
При переключении на ветку или коммит без plugin/skills junction'ы навыков
повисают битыми, и git checkout падает на середине: часть рабочего дерева
уже удалена, а HEAD остаётся на прежней ветке.

Remove-AgentSkillLink удаляет сам reparse point через Directory.Delete
(без -Recurse), поэтому в target не заходит и содержимое plugin/skills не
трогает. Junction опознаётся по атрибуту ReparsePoint, а не по резолву
target — иначе режим не работал бы ровно в том случае, ради которого сделан.
Настоящий каталог не удаляется, а возвращает Skipped.

Режимы скрипта разведены parameter set'ами: -Remove -Check вместе теперь
дают ошибку разбора параметров вместо тихого выигрыша одного из ключей.

Покрыто пятью кейсами в tools/tests/AgentAssets.Tests.ps1 (включая junction
с исчезнувшим источником); порядок действий и восстановление после падения
чекаута описаны в CLAUDE.md.
2026-07-31 08:24:26 +03:00

97 lines
3.7 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Set-StrictMode -Version Latest
<#
.SYNOPSIS
Проверяет, что LinkPath — junction, указывающий на SourcePath.
#>
function Test-AgentSkillLink {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $SourcePath,
[Parameter(Mandatory)] [string] $LinkPath
)
$item = Get-Item -LiteralPath $LinkPath -Force -ErrorAction SilentlyContinue
if ($null -eq $item -or $item.LinkType -ne 'Junction') { return $false }
$actual = [System.IO.Path]::GetFullPath(($item.Target | Select-Object -First 1))
$expected = [System.IO.Path]::GetFullPath($SourcePath)
return [string]::Equals($actual.TrimEnd('\'), $expected.TrimEnd('\'), [StringComparison]::OrdinalIgnoreCase)
}
<#
.SYNOPSIS
Раскладывает навык по LinkPath junction'ом. Возвращает Created | AlreadyLinked | Copied.
Настоящий каталог никогда не удаляется автоматически.
#>
function Sync-AgentSkillLink {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $SourcePath,
[Parameter(Mandatory)] [string] $LinkPath,
[switch] $AllowCopy
)
if (-not (Test-Path -LiteralPath $SourcePath)) {
throw "нет исходного навыка: $SourcePath"
}
if (Test-AgentSkillLink -SourcePath $SourcePath -LinkPath $LinkPath) {
return 'AlreadyLinked'
}
$existing = Get-Item -LiteralPath $LinkPath -Force -ErrorAction SilentlyContinue
if ($null -ne $existing) {
if ($existing.LinkType -ne 'Junction') {
throw "$LinkPath существует и не является junction — удалите его вручную и повторите"
}
# Ссылка ведёт не туда: удаляем сам reparse point, не заходя в target.
[System.IO.Directory]::Delete($LinkPath)
}
$parent = Split-Path -Parent $LinkPath
if (-not (Test-Path -LiteralPath $parent)) {
New-Item -ItemType Directory -Path $parent -Force | Out-Null
}
try {
New-Item -ItemType Junction -Path $LinkPath -Value $SourcePath -ErrorAction Stop | Out-Null
return 'Created'
}
catch {
if (-not $AllowCopy) {
throw "не удалось создать junction $LinkPath -> $SourcePath ($($_.Exception.Message)). Повторите с -AllowCopy, если копия допустима."
}
Copy-Item -LiteralPath $SourcePath -Destination $LinkPath -Recurse -Force -ErrorAction Stop
return 'Copied'
}
}
<#
.SYNOPSIS
Снимает junction по LinkPath. Возвращает Removed | Missing | Skipped.
Настоящие каталоги не трогает, в target не заходит.
#>
function Remove-AgentSkillLink {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $LinkPath
)
$item = Get-Item -LiteralPath $LinkPath -Force -ErrorAction SilentlyContinue
if ($null -eq $item) { return 'Missing' }
# Проверяем именно атрибут, а не LinkType: junction с удалённым target
# (ровно наш случай после checkout без plugin/skills) остаётся reparse point,
# но полагаться на резолв target тут нельзя.
$isReparse = $item.Attributes.HasFlag([System.IO.FileAttributes]::ReparsePoint)
if (-not ($item -is [System.IO.DirectoryInfo]) -or -not $isReparse) {
return 'Skipped'
}
# Удаляем сам reparse point: без -Recurse содержимое target не затрагивается.
[System.IO.Directory]::Delete($LinkPath)
return 'Removed'
}
Export-ModuleMember -Function Test-AgentSkillLink, Sync-AgentSkillLink, Remove-AgentSkillLink