tools-sync: 修复会删掉宿主自装 skill 的 registry 对账缺陷
本次同步实跑触发了数据丢失:orca-cli 的物理副本在 ~\.agents\skills(Orca 就是 这样装的——真目录放 .agents,再把每个工具链过去,本机 8 个工具全指向它), 而主副本里的同名条目本身是指向它的 junction。对账循环从不检查"主副本条目是不是 链接",于是 fingerprint 比对经链接解析到同一份内容判为"identical",删掉 registry 副本并建 junction → 主副本与 registry 互指成环,唯一的真副本没了。 orchestration 走 INGEST 分支,则把 Orca 的布局反了过来。 两者均已用 `orca skills install --skill <name>` 恢复为 Orca 自己的布局。 修复三处: - 对账循环:主副本条目本身是链接时,判定该 skill 归宿主所有,计入 foreign 跳过。 - INGEST:新增 Get-SkillDirLinkers,若 ~\.* 下其他工具的 skills 目录有链接指向 这个目录,说明它是宿主自装 skill 的物理家,不搬进主副本。 - 镜像阶段:跳过本身是链接的主副本条目,否则 Codex 会把同一 skill 注册两遍 (.agents 与 .codex 都在扫)。 回归测试新增 hostowned 夹具(真目录在 registry、主副本是链接):在修复前的脚本上 以"real dir was collapsed into a link"失败,修复后通过。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01438Zbcx49e4o46uSA71mFF
This commit is contained in:
@@ -117,6 +117,28 @@ function Get-DirFingerprint([string]$Path) {
|
||||
return (($lines | Sort-Object) -join "`n")
|
||||
}
|
||||
|
||||
# Agent tools each keep their own skills directory and a host installer may pick any one of them
|
||||
# as a skill's physical home, linking the rest at it. Finding those linkers is how we tell a
|
||||
# host-installed skill from one authored on the Codex side.
|
||||
function Get-SkillDirLinkers([string]$TargetPath) {
|
||||
$roots = @()
|
||||
foreach ($d in Get-ChildItem $env:USERPROFILE -Directory -Force -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -like ".*" }) {
|
||||
foreach ($sub in @("skills", "agent\skills")) {
|
||||
$cand = Join-Path $d.FullName $sub
|
||||
if (Test-Path $cand) { $roots += $cand }
|
||||
}
|
||||
}
|
||||
$hits = @()
|
||||
foreach ($r in $roots) {
|
||||
if (Test-UnderPath $TargetPath $r) { continue } # the home itself
|
||||
$link = Join-Path $r (Split-Path $TargetPath -Leaf)
|
||||
$item = Get-Item -LiteralPath $link -Force -ErrorAction SilentlyContinue
|
||||
if ($item -and (Test-IsLink $item) -and ((Get-LinkTarget $item) -eq $TargetPath)) { $hits += $r }
|
||||
}
|
||||
return $hits
|
||||
}
|
||||
|
||||
function New-SkillJunction([string]$LinkPath, [string]$TargetPath, [string]$Why) {
|
||||
if ($DryRun) { Write-Host " (dry-run) would link $LinkPath -> $TargetPath [$Why]"; return }
|
||||
New-Item -ItemType Junction -Path $LinkPath -Target $TargetPath | Out-Null
|
||||
@@ -272,6 +294,17 @@ foreach ($reg in $registries) {
|
||||
$name = Split-Path $copyPath -Leaf
|
||||
$primaryPath = Join-Path $ClaudeSkills $name
|
||||
if (-not (Test-Path $copyPath)) { continue } # already resolved earlier this run
|
||||
|
||||
# The primary entry is itself a link into a registry: this skill's physical home is the
|
||||
# registry, and the host installed it that way round (Orca does exactly this - it puts the
|
||||
# real directory in ~\.agents\skills and links every tool at it). Collapsing the registry
|
||||
# copy here would point the link at itself and DELETE the only copy. Leave it to its owner.
|
||||
$primaryItem = Get-Item -LiteralPath $primaryPath -Force -ErrorAction SilentlyContinue
|
||||
if ($primaryItem -and (Test-IsLink $primaryItem)) {
|
||||
$foreign++
|
||||
continue
|
||||
}
|
||||
|
||||
$repairLinks += $copyPath
|
||||
|
||||
# --- real directory holding a skill ---
|
||||
@@ -287,6 +320,15 @@ foreach ($reg in $registries) {
|
||||
Write-Host " registry-only skill NOT ingested (-NoIngest): $name"
|
||||
continue
|
||||
}
|
||||
# Other agent tools linking at this directory mean it is a host-installed skill's
|
||||
# physical home, not a skill authored here. Moving it inverts the host's layout and
|
||||
# its own updater then fights us for the location.
|
||||
$linkers = Get-SkillDirLinkers $copyPath
|
||||
if ($linkers) {
|
||||
$script:Notes += "$name lives in $copyPath and $($linkers.Count) other tool registr$(if ($linkers.Count -eq 1) { 'y links' } else { 'ies link' }) at it; treated as host-installed and left alone."
|
||||
Write-Host " host-installed skill (linked from $($linkers.Count) other tool(s)), not ingested: $name"
|
||||
continue
|
||||
}
|
||||
Write-Host " INGEST registry-only skill: $name ($copyPath -> $primaryPath)"
|
||||
if (-not $DryRun) { Move-Item -LiteralPath $copyPath -Destination $primaryPath }
|
||||
$script:Ingested += $name
|
||||
@@ -332,7 +374,10 @@ if ($MirrorRegistry) {
|
||||
if (-not $DryRun) { Remove-Item -Recurse -Force $mirrorGit }
|
||||
}
|
||||
|
||||
$primarySkills = Get-ChildItem $ClaudeSkills -Directory | Where-Object { $_.Name -notlike ".*" }
|
||||
# A primary entry that is itself a link belongs to whichever host installed it (its real home
|
||||
# is a registry the host already exposes to Codex). Mirroring it would register it twice.
|
||||
$primarySkills = Get-ChildItem $ClaudeSkills -Directory -Force |
|
||||
Where-Object { $_.Name -notlike ".*" -and -not (Test-IsLink $_) }
|
||||
foreach ($d in $primarySkills) {
|
||||
$link = Join-Path $MirrorRegistry $d.Name
|
||||
if (Test-Path $link) { continue }
|
||||
|
||||
Reference in New Issue
Block a user