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:
Myfro9
2026-09-07 16:29:20 +08:00
co-authored by Claude Opus 5
parent 58b90f16a6
commit 8566e23f49
2 changed files with 61 additions and 1 deletions
@@ -27,6 +27,12 @@ try {
# A link into the other registry rather than the primary: the drift case.
New-Item -ItemType Junction -Path (Join-Path $codex "shared") -Target (Join-Path $agents "shared") | Out-Null
# Host-installed shape: the real directory lives in a registry and the PRIMARY is the link
# (Orca installs this way). Collapsing the registry copy here would make the link point at
# itself and delete the only copy - the reconciler must recognise it and keep its hands off.
New-Skill (Join-Path $agents "hostowned") "installed by the host"
New-Item -ItemType Junction -Path (Join-Path $primary "hostowned") -Target (Join-Path $agents "hostowned") | Out-Null
# A local bare repo stands in for origin so Phase 1 completes and Phase 2 actually runs.
$origin = Join-Path $root "origin.git"
& git init --quiet --bare $origin
@@ -57,6 +63,15 @@ try {
if ((Get-Content -Raw (Join-Path $primary "forked\SKILL.md")) -notmatch 'primary body') { throw "primary was overwritten by a conflict`n$output" }
if ($output -notmatch 'not a skill dir') { throw "directory without SKILL.md not reported`n$output" }
# The host-installed skill must survive intact, with the primary still a link at the registry.
$hostReal = Get-Item -Force (Join-Path $agents "hostowned")
if ($hostReal.LinkType -eq "Junction") { throw "host-installed skill's real dir was collapsed into a link`n$output" }
if ((Get-Content -Raw (Join-Path $agents "hostowned\SKILL.md")) -notmatch 'installed by the host') { throw "host-installed skill's content was lost`n$output" }
$hostPrimary = Get-Item -Force (Join-Path $primary "hostowned")
if (($hostPrimary.Target | Select-Object -First 1) -ne (Join-Path $agents "hostowned")) { throw "primary link to the host-installed skill was repointed`n$output" }
# Mirroring it into the registry Codex scans would register the same skill twice.
if (Test-Path (Join-Path $codex "hostowned")) { throw "host-installed skill was mirrored, double-registering it`n$output" }
# Mirror registry must expose every primary skill, including the freshly ingested one.
foreach ($name in @("shared", "forked", "codexborn")) {
if (-not (Test-Path (Join-Path $codex "$name\SKILL.md"))) { throw "mirror missing $name`n$output" }