feat(tools): раскладка навыков junction'ами из plugin/skills
This commit is contained in:
+11
@@ -53,6 +53,9 @@ Desktop.ini
|
||||
# ---- Scratch: тестовые документы КОМПАС, снимки рендера --------------------
|
||||
.scratch/
|
||||
|
||||
# ---- pi extension node_modules (не коммитить, source остаётся) ----
|
||||
.pi/extensions/*/node_modules/
|
||||
|
||||
# ---- Use case'ы проработки MCP (локальная песочница, не разделяется) --------
|
||||
# Папки кейсов с описаниями (case.md) и артефактами построений (.m3d, .png …).
|
||||
# Каркас и правила — в usecases/README.md (см. там же шаблон _TEMPLATE/).
|
||||
@@ -67,6 +70,14 @@ usecases/
|
||||
# коммитим: если будет скачано локально снова — пусть остаётся проигнорированным.
|
||||
docs/KOMPAS_SDK_ru-RU/
|
||||
|
||||
# ---- Навыки: источник истины — plugin/skills, остальное создаётся junction'ами -----
|
||||
# tools/sync-agent-assets.ps1. Пути точечные: git видит junction как обычный каталог
|
||||
# и без этого проиндексировал бы содержимое повторно.
|
||||
/.claude/skills/kompas-3d/
|
||||
/.claude/skills/kompas-fdm-design/
|
||||
/.agents/skills/kompas-3d/
|
||||
/.agents/skills/kompas-fdm-design/
|
||||
|
||||
# ============================================================================
|
||||
# Отслеживается НАМЕРЕННО (нужно для сборки), не игнорировать:
|
||||
# libs/kompas-interop/*.dll — interop-сборки КОМПАС из SDK (Samples/Common)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
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))
|
||||
return $actual.TrimEnd('\') -eq ([System.IO.Path]::GetFullPath($SourcePath)).TrimEnd('\')
|
||||
}
|
||||
|
||||
<#
|
||||
.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
|
||||
return 'Copied'
|
||||
}
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Test-AgentSkillLink, Sync-AgentSkillLink
|
||||
@@ -0,0 +1,42 @@
|
||||
#requires -Version 5.1
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch] $Check,
|
||||
[switch] $AllowCopy
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Import-Module (Join-Path $PSScriptRoot 'AgentAssets.psm1') -Force
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$skills = @('kompas-3d', 'kompas-fdm-design')
|
||||
$targetRoots = @(
|
||||
(Join-Path $repoRoot '.claude\skills'),
|
||||
(Join-Path $repoRoot '.agents\skills')
|
||||
)
|
||||
|
||||
$failed = $false
|
||||
foreach ($root in $targetRoots) {
|
||||
foreach ($skill in $skills) {
|
||||
$source = Join-Path $repoRoot "plugin\skills\$skill"
|
||||
$link = Join-Path $root $skill
|
||||
|
||||
if ($Check) {
|
||||
if (Test-AgentSkillLink -SourcePath $source -LinkPath $link) {
|
||||
Write-Host "ok $link"
|
||||
}
|
||||
else {
|
||||
Write-Host "БИТО $link"
|
||||
$failed = $true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
$state = Sync-AgentSkillLink -SourcePath $source -LinkPath $link -AllowCopy:$AllowCopy
|
||||
Write-Host "$state $link"
|
||||
}
|
||||
}
|
||||
|
||||
if ($failed) { exit 1 }
|
||||
@@ -0,0 +1,50 @@
|
||||
BeforeAll {
|
||||
Import-Module (Join-Path $PSScriptRoot '..' 'AgentAssets.psm1') -Force
|
||||
|
||||
function New-TempTree {
|
||||
$root = Join-Path ([System.IO.Path]::GetTempPath()) ("agentassets_" + [guid]::NewGuid().ToString('N'))
|
||||
$source = Join-Path $root 'plugin/skills/demo'
|
||||
New-Item -ItemType Directory -Path $source -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $source 'SKILL.md') -Value 'demo' -Encoding utf8
|
||||
return [pscustomobject]@{ Root = $root; Source = $source; Link = (Join-Path $root '.claude/skills/demo') }
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Sync-AgentSkillLink' {
|
||||
It 'создаёт junction на исходный навык' {
|
||||
$t = New-TempTree
|
||||
try {
|
||||
Sync-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link | Should -Be 'Created'
|
||||
(Get-Item -LiteralPath $t.Link -Force).LinkType | Should -Be 'Junction'
|
||||
Get-Content -LiteralPath (Join-Path $t.Link 'SKILL.md') | Should -Be 'demo'
|
||||
} finally { Remove-Item -LiteralPath $t.Root -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
It 'идемпотентен: повторный запуск ничего не ломает' {
|
||||
$t = New-TempTree
|
||||
try {
|
||||
Sync-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link | Out-Null
|
||||
Sync-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link | Should -Be 'AlreadyLinked'
|
||||
} finally { Remove-Item -LiteralPath $t.Root -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
It 'не трогает настоящий каталог, а падает с понятной ошибкой' {
|
||||
$t = New-TempTree
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $t.Link -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $t.Link 'mine.md') -Value 'ручное' -Encoding utf8
|
||||
|
||||
{ Sync-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link } | Should -Throw '*не является junction*'
|
||||
Get-Content -LiteralPath (Join-Path $t.Link 'mine.md') | Should -Be 'ручное'
|
||||
} finally { Remove-Item -LiteralPath $t.Root -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
It 'Test-AgentSkillLink различает связанное и несвязанное состояние' {
|
||||
$t = New-TempTree
|
||||
try {
|
||||
Test-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link | Should -BeFalse
|
||||
Sync-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link | Out-Null
|
||||
Test-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link | Should -BeTrue
|
||||
} finally { Remove-Item -LiteralPath $t.Root -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#requires -Version 7.0
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Import-Module Pester -MinimumVersion 5.5.0
|
||||
|
||||
$config = New-PesterConfiguration
|
||||
$config.Run.Path = $PSScriptRoot
|
||||
$config.Run.Exit = $true
|
||||
$config.Output.Verbosity = 'Detailed'
|
||||
Invoke-Pester -Configuration $config
|
||||
Reference in New Issue
Block a user