sync: BOWEI 2026-09-07 tools-sync 改为 junction 模型(registry 双向 ingest + 修复)
- sync_repos.ps1: 每个 skill 只在 ~\.claude\skills 有一份物理副本,其余 registry 一律 junction;新增 registry-only skill 的自动 ingest、MISDIRECTED 链接修复、 同名内容分叉的 CONFLICT 报告,以及只对 -MirrorRegistry 做全量镜像。 - SKILL.md: 重写阶段 1/2,补 Codex MCP 存储模型(config.toml 惰性, codex mcp list --json 是唯一权威口径)与 CLI 升级会清空注册表的事实。 - sync_mcp_registrations.ps1 + references/codex_mcp_wanted_keys.json: 报告 codex-cli 无接口可设的键(tool_timeout_sec / env_vars)为 "registered, but incomplete" 而非失败。 - tests/test_sync_repos_registries.ps1: registry 对账的回归测试。 - .gitignore: orchestration(Orca 自带的发现桩,同 orca-cli)列为 local-only。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01438Zbcx49e4o46uSA71mFF
This commit is contained in:
@@ -38,8 +38,10 @@ command = "C:\\Users\\tester\\.codex\\runtimes\\node_repl.exe"
|
||||
|
||||
$oldErrorActionPreference = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
# -CodexSource Toml pins the read to the fixture; the default Auto path would query the
|
||||
# real `codex mcp list` and make this test measure the machine instead of the fixtures.
|
||||
$output = & powershell -NoProfile -ExecutionPolicy Bypass -File $scriptUnderTest `
|
||||
-ClaudeConfig $claudeConfig -CodexConfig $codexConfig -Verbose 2>&1 | Out-String
|
||||
-ClaudeConfig $claudeConfig -CodexConfig $codexConfig -CodexSource Toml -Verbose 2>&1 | Out-String
|
||||
$code = $LASTEXITCODE
|
||||
$ErrorActionPreference = $oldErrorActionPreference
|
||||
if ($code -ne 2) { throw "expected audit exit 2, got $code`n$output" }
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# Covers Phase 2 of sync_repos.ps1 -- the registry reconciliation that decides whether a
|
||||
# directory found on the Codex side gets ingested, collapsed, or reported. Runs against a
|
||||
# throwaway primary + registries + bare origin under TEMP; the real machine is never touched.
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$scriptUnderTest = Join-Path (Split-Path $PSScriptRoot -Parent) "scripts\sync_repos.ps1"
|
||||
$root = Join-Path ([IO.Path]::GetTempPath()) ("tools-sync-repos-test-" + [guid]::NewGuid().ToString("N"))
|
||||
|
||||
function New-Skill([string]$Path, [string]$Body) {
|
||||
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $Path "SKILL.md") -Value $Body -Encoding utf8
|
||||
}
|
||||
|
||||
try {
|
||||
$primary = Join-Path $root "primary"
|
||||
$codex = Join-Path $root "codex"
|
||||
$agents = Join-Path $root "agents"
|
||||
New-Item -ItemType Directory -Force -Path $primary, $codex, $agents | Out-Null
|
||||
|
||||
# One skill of each shape the reconciler must tell apart.
|
||||
New-Skill (Join-Path $primary "shared") "shared body"
|
||||
New-Skill (Join-Path $primary "forked") "primary body"
|
||||
New-Skill (Join-Path $agents "shared") "shared body" # identical duplicate -> collapse
|
||||
New-Skill (Join-Path $agents "forked") "registry body" # divergent -> conflict
|
||||
New-Skill (Join-Path $agents "codexborn") "born on the codex side" # registry-only -> ingest
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $agents "notaskill") | Out-Null # no SKILL.md
|
||||
# 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
|
||||
|
||||
# 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
|
||||
& git init --quiet --initial-branch=main $primary
|
||||
& git -C $primary remote add origin $origin
|
||||
& git -C $primary -c user.email=t@t -c user.name=t commit --quiet --allow-empty -m init
|
||||
& git -C $primary push --quiet -u origin main
|
||||
|
||||
# Invoked via -Command, not -File: -File stringifies every argument, so a [string[]]
|
||||
# parameter silently takes only its first element and the rest slide into later positions.
|
||||
$ErrorActionPreference = "Continue"
|
||||
$invocation = "& '$scriptUnderTest' -RepoUrl '$origin' -ClaudeSkills '$primary' " +
|
||||
"-Registry @('$codex','$agents') -MirrorRegistry '$codex'"
|
||||
$output = & powershell -NoProfile -ExecutionPolicy Bypass -Command $invocation 2>&1 | Out-String
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ($output -notmatch 'INGEST registry-only skill:\s*codexborn') { throw "codex-side skill was not ingested`n$output" }
|
||||
if (-not (Test-Path (Join-Path $primary "codexborn\SKILL.md"))) { throw "ingested skill is not in the primary`n$output" }
|
||||
if ((Get-Item (Join-Path $agents "codexborn")).LinkType -ne "Junction") { throw "ingested skill was not linked back`n$output" }
|
||||
|
||||
if ($output -notmatch 'MISDIRECTED link:\s*shared') { throw "link into another registry not detected`n$output" }
|
||||
$sharedTarget = (Get-Item (Join-Path $codex "shared")).Target | Select-Object -First 1
|
||||
if ($sharedTarget -ne (Join-Path $primary "shared")) { throw "misdirected link not repointed at the primary, got $sharedTarget`n$output" }
|
||||
if ((Get-Item (Join-Path $agents "shared")).LinkType -ne "Junction") { throw "identical duplicate did not collapse`n$output" }
|
||||
|
||||
if ($output -notmatch 'CONFLICT:\s*forked') { throw "divergent duplicate not reported`n$output" }
|
||||
if ((Get-Item (Join-Path $agents "forked")).LinkType -eq "Junction") { throw "divergent duplicate must be left untouched`n$output" }
|
||||
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" }
|
||||
# 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" }
|
||||
}
|
||||
if ($output -notmatch 'primary in sync with origin') { throw "Phase 1 did not complete`n$output" }
|
||||
|
||||
Write-Host "PASS: registry ingest / repair / conflict fixtures"
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Reference in New Issue
Block a user