fix(install): migrate duplicate skills and resolve concurrency tokens
This commit is contained in:
1 parent
975e1fd5f0
commit
561e010c41
4 files changed
+65
-4
No files matched your search
@@ -70,6 +70,7 @@ function Sync-ManagedDestination {
|
||||
|
||||
Get-ChildItem $Source -File -Recurse | ForEach-Object {
|
||||
$relative = $_.FullName.Substring($Source.Length).TrimStart([char[]]@('\','/')).Replace('\','/')
|
||||
if ((Split-Path $Destination -Leaf) -eq '.codex' -and $relative.StartsWith('skills/')) { return }
|
||||
$target = Join-Path $Destination $relative
|
||||
$rawContent = Get-Content -LiteralPath $_.FullName -Raw -Encoding UTF8
|
||||
$filterFlashbang = -not $FlashbangEnabled -and $relative -in @('settings.json', 'config.toml')
|
||||
@@ -77,7 +78,7 @@ function Sync-ManagedDestination {
|
||||
$rawContent = (& python (Join-Path $PSScriptRoot 'install-options.py') filter --path $_.FullName --flashbang false | Out-String)
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not configure Flashbang.' }
|
||||
}
|
||||
$needsSubstitution = $filterFlashbang -or $rawContent.Contains('__AI_CONFIG_ROOT__') -or $rawContent.Contains('__HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_COMMAND__')
|
||||
$needsSubstitution = $filterFlashbang -or $rawContent.Contains('__AI_CONFIG_ROOT__') -or $rawContent.Contains('__HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_COMMAND__') -or $rawContent.Contains('__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__')
|
||||
if ($needsSubstitution) {
|
||||
$finalContent = $rawContent.Replace('__AI_CONFIG_ROOT__', $AiConfigRoot).Replace('__HOOK_COMMAND__', $ShellCommand).Replace('__POWERSHELL_HOOK_COMMAND__', $PowerShellCommand).Replace('__POWERSHELL_COMMAND__', $PowerShellCommand).Replace('__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__', $ClaudeConcurrency).Replace('__HOOK_SCRIPT__', 'flashbang.ps1').Replace('__POWERSHELL_HOOK_SCRIPT__', 'flashbang.ps1')
|
||||
$newBytes = [System.Text.Encoding]::UTF8.GetBytes($finalContent)
|
||||
|
||||
@@ -64,6 +64,7 @@ sync_managed_destination() {
|
||||
while IFS= read -r -d '' source_file; do
|
||||
local relative target content needs_sub new_hash exists current_hash action backup
|
||||
relative=${source_file#"$source/"}
|
||||
if [ "${destination##*/}" = .codex ] && [[ "$relative" = skills/* ]]; then continue; fi
|
||||
target="$destination/$relative"
|
||||
content=$(<"$source_file")
|
||||
needs_sub=false
|
||||
@@ -71,7 +72,7 @@ sync_managed_destination() {
|
||||
content=$(python3 "$(dirname -- "${BASH_SOURCE[0]}")/install-options.py" filter --path "$source_file" --flashbang false) || return
|
||||
needs_sub=true
|
||||
fi
|
||||
if [[ "$needs_sub" = true || "$content" == *'__AI_CONFIG_ROOT__'* || "$content" == *'__HOOK_COMMAND__'* || "$content" == *'__POWERSHELL_HOOK_COMMAND__'* || "$content" == *'__POWERSHELL_COMMAND__'* ]]; then
|
||||
if [[ "$needs_sub" = true || "$content" == *'__AI_CONFIG_ROOT__'* || "$content" == *'__HOOK_COMMAND__'* || "$content" == *'__POWERSHELL_HOOK_COMMAND__'* || "$content" == *'__POWERSHELL_COMMAND__'* || "$content" == *'__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__'* ]]; then
|
||||
needs_sub=true
|
||||
content=${content//__AI_CONFIG_ROOT__/$ai_config_root}
|
||||
content=${content//__HOOK_COMMAND__/$shell_command}
|
||||
|
||||
@@ -3,6 +3,7 @@ param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $PSScriptRoot 'lib/manifest.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/install-targets.ps1')
|
||||
|
||||
$work = Join-Path ([System.IO.Path]::GetTempPath()) ("ai-config-manifest-test-" + [Guid]::NewGuid())
|
||||
New-Item $work -ItemType Directory -Force | Out-Null
|
||||
@@ -72,7 +73,36 @@ try {
|
||||
if (@($outSummary | Where-Object { $_ -like 'CREATE*' -or $_ -like 'UPDATE*' -or $_ -like 'UNCHANGED*' -or $_ -like 'BACKUP*' }).Count -ne 0) { throw "Summary mode must not print per-file lines: $($outSummary -join '; ')" }
|
||||
if (@($outSummary | Where-Object { $_ -match '^SYNC .* unchanged' }).Count -ne 1) { throw "Summary mode must print exactly one SYNC tally line: $($outSummary -join '; ')" }
|
||||
|
||||
if ($Summary) { Write-Output 'Tests: PASS | managed install' } else { Write-Output 'PASS managed manifest: idempotent, stale removal, modified-file protection, foreign files preserved, dry run side-effect free' }
|
||||
$generated = Join-Path $work 'generated'
|
||||
$package = Join-Path $generated 'codex-powershell'
|
||||
$testHome = Join-Path $work 'home'
|
||||
$legacy = Join-Path $testHome '.codex'
|
||||
New-Item (Join-Path $package 'skills/example') -ItemType Directory -Force | Out-Null
|
||||
Set-Content (Join-Path $package 'skills/example/SKILL.md') 'managed skill' -NoNewline
|
||||
New-Item (Join-Path $legacy 'skills/example') -ItemType Directory -Force | Out-Null
|
||||
Copy-Item (Join-Path $package 'skills/example/SKILL.md') (Join-Path $legacy 'skills/example/SKILL.md')
|
||||
Set-Content (Join-Path $legacy 'skills/example/custom.md') 'customized' -NoNewline
|
||||
Set-Content (Join-Path $legacy 'skills/example/foreign.md') 'foreign' -NoNewline
|
||||
Write-ManagedManifest (Get-ManagedManifestPath $legacy) @{
|
||||
'skills/example/SKILL.md' = (Get-Sha256Hash (Join-Path $legacy 'skills/example/SKILL.md'))
|
||||
'skills/example/custom.md' = ('0' * 64)
|
||||
}
|
||||
$targets = @(Get-InstallTargets -Generated $generated -HomePath $testHome -Shell PowerShell -Client Codex)
|
||||
if ($targets[0].Destination -ne (Join-Path $testHome '.agents/skills')) { throw 'Canonical skills must be installed before legacy copies are removed.' }
|
||||
foreach ($item in $targets) { $null = Sync-ManagedDestination -Source $item.Source -Destination $item.Destination -Stamp 'migrate' }
|
||||
if (-not (Test-Path (Join-Path $testHome '.agents/skills/example/SKILL.md'))) { throw 'Canonical skill is missing.' }
|
||||
if (Test-Path (Join-Path $legacy 'skills/example/SKILL.md')) { throw 'Unmodified managed skill must not remain duplicated.' }
|
||||
if (-not (Test-Path (Join-Path $legacy 'backups/migrate/skills/example/SKILL.md'))) { throw 'Removed legacy skill must have a backup.' }
|
||||
foreach ($name in @('custom.md', 'foreign.md')) {
|
||||
if (-not (Test-Path (Join-Path $legacy "skills/example/$name"))) { throw 'Modified and foreign legacy skills must be preserved.' }
|
||||
}
|
||||
foreach ($item in $targets) { $null = Sync-ManagedDestination -Source $item.Source -Destination $item.Destination -Stamp 'migrate-repeat' }
|
||||
if (Test-Path (Join-Path $legacy 'backups/migrate-repeat')) { throw 'Skill migration must be idempotent.' }
|
||||
Set-Content (Join-Path $source 'concurrency.txt') '__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__' -NoNewline
|
||||
$null = Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'concurrency' -ClaudeConcurrency '7'
|
||||
if ((Get-Content (Join-Path $destination 'concurrency.txt') -Raw) -ne '7') { throw 'Standalone concurrency placeholder must be resolved.' }
|
||||
|
||||
if ($Summary) { Write-Output 'Tests: PASS | managed install' } else { Write-Output 'PASS managed manifest: idempotent, stale removal, modified-file protection, foreign files preserved, dry run side-effect free, skill migration' }
|
||||
} finally {
|
||||
Remove-Item $work -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
. "$root/scripts/lib/manifest.sh"
|
||||
. "$root/scripts/lib/install-targets.sh"
|
||||
|
||||
work=$(mktemp -d)
|
||||
cleanup() { rm -rf -- "$work"; }
|
||||
@@ -85,4 +86,32 @@ if [ "$(printf '%s\n' "$out_summary" | grep -c '^SYNC .* unchanged')" -ne 1 ]; t
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$summary" = true ]; then printf 'Tests: PASS | managed install\n'; else printf 'PASS managed manifest: idempotent, stale removal, modified-file protection, foreign files preserved, dry run side-effect free\n'; fi
|
||||
generated="$work/generated"
|
||||
package="$generated/codex-bash"
|
||||
test_home="$work/home"
|
||||
legacy="$test_home/.codex"
|
||||
mkdir -p "$package/skills/example" "$legacy/skills/example"
|
||||
printf 'managed skill' > "$package/skills/example/SKILL.md"
|
||||
cp "$package/skills/example/SKILL.md" "$legacy/skills/example/SKILL.md"
|
||||
printf 'customized' > "$legacy/skills/example/custom.md"
|
||||
printf 'foreign' > "$legacy/skills/example/foreign.md"
|
||||
declare -A legacy_manifest
|
||||
legacy_manifest[skills/example/SKILL.md]=$(sha256_of_file "$legacy/skills/example/SKILL.md")
|
||||
legacy_manifest[skills/example/custom.md]=$(printf '0%.0s' {1..64})
|
||||
write_managed_manifest "$(manifest_path "$legacy")" legacy_manifest
|
||||
mapfile -t targets < <(get_install_targets "$generated" "$test_home" bash codex)
|
||||
[[ "${targets[0]}" = *"|$test_home/.agents/skills" ]] || { printf 'Canonical skills must be installed first.\n' >&2; exit 1; }
|
||||
for target_pair in "${targets[@]}"; do
|
||||
sync_managed_destination "${target_pair%%|*}" "${target_pair#*|}" migrate "" "" "" false > /dev/null
|
||||
done
|
||||
[ -f "$test_home/.agents/skills/example/SKILL.md" ] && [ ! -f "$legacy/skills/example/SKILL.md" ] && [ -f "$legacy/backups/migrate/skills/example/SKILL.md" ] || { printf 'Skill migration must remove and back up the managed duplicate.\n' >&2; exit 1; }
|
||||
[ -f "$legacy/skills/example/custom.md" ] && [ -f "$legacy/skills/example/foreign.md" ] || { printf 'Skill migration must preserve modified and foreign files.\n' >&2; exit 1; }
|
||||
for target_pair in "${targets[@]}"; do
|
||||
sync_managed_destination "${target_pair%%|*}" "${target_pair#*|}" migrate-repeat "" "" "" false > /dev/null
|
||||
done
|
||||
[ ! -d "$legacy/backups/migrate-repeat" ] || { printf 'Skill migration must be idempotent.\n' >&2; exit 1; }
|
||||
printf '__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__' > "$source_dir/concurrency.txt"
|
||||
sync_managed_destination "$source_dir" "$destination" concurrency "" "" "" false false true 7 > /dev/null
|
||||
[ "$(cat "$destination/concurrency.txt")" = 7 ] || { printf 'Standalone concurrency placeholder must be resolved.\n' >&2; exit 1; }
|
||||
|
||||
if [ "$summary" = true ]; then printf 'Tests: PASS | managed install\n'; else printf 'PASS managed manifest: idempotent, stale removal, modified-file protection, foreign files preserved, dry run side-effect free, skill migration\n'; fi
|
||||
Reference in new issue
Block a user