fix(scripts): prune never removes prod skills silently

install.ps1/-sh -Prune: a prod skill with no source in skills/ is
potentially lost knowledge. Now:
- interactive: ask per skill (default No)
- non-interactive: skip with warning
- -Yes/--yes: remove, but warn loudly
Also fixes WinPS 5.1 parse break: removed em-dash (UTF-8 0x94 reads as
" in cp1251) from install.ps1 — file is ASCII-clean again.
Lesson: pi-add-models got pruned because it lived only in prod, no source.
This commit is contained in:
2026-08-20 13:38:14 +03:00
parent 3f3a0cdaa9
commit 550f87d239
2 changed files with 53 additions and 12 deletions

View File

@@ -1,19 +1,23 @@
# Install skills/<name>/ into BOTH agent skill dirs:
# ~\.claude\skills\<name>\ (Claude Code native, not configurable)
# ~\.agents\skills\<name>\ (pi native default scan path, agent-neutral namespace)
# ~\.claude\skills\<name>\ (Claude Code - native, not configurable)
# ~\.agents\skills\<name>\ (pi - native default scan path, agent-neutral namespace)
# $env:CLAUDE_SKILLS_DIR overrides ONLY the claude target (for testing/CI).
#
# Usage: install.ps1 [-Names <name1>,<name2>] [-Prune]
# Usage: install.ps1 [-Names <name1>,<name2>] [-Prune] [-Yes]
# no args = install all skills/* into both targets
# -Prune = after install, remove target/<name>/ dirs that are NOT in skills/*
# (prune always scans full target, ignores -Names filter it's a global cleanup)
# (prune always scans full target, ignores -Names filter - it's a global cleanup)
# -Yes = with -Prune: remove prod-only skills without asking (still warns loudly).
# Without -Yes, prune asks per skill (default No) and NEVER removes silently:
# a prod skill with no source in skills/ is potentially lost knowledge.
#
# PowerShell port of install.sh same behavior, native cmdlets, no bash dependency.
# PowerShell port of install.sh - same behavior, native cmdlets, no bash dependency.
[CmdletBinding()]
param(
[string[]]$Names = @(),
[switch]$Prune
[switch]$Prune,
[switch]$Yes
)
$ErrorActionPreference = 'Stop'
@@ -21,7 +25,7 @@ $ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$src = Join-Path $root 'skills'
# Dual install targets the "canon" is the git repo (skills/); both dirs are installs.
# Dual install targets - the "canon" is the git repo (skills/); both dirs are installs.
$targets = @()
if ($env:CLAUDE_SKILLS_DIR) {
$targets += $env:CLAUDE_SKILLS_DIR
@@ -62,12 +66,28 @@ foreach ($target in $targets) {
if ($Prune) {
$sourceNames = @(Get-ChildItem -Path $src -Directory | Select-Object -ExpandProperty Name)
$interactive = -not [Console]::IsInputRedirected
foreach ($target in $targets) {
$installedDirs = Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue
foreach ($dir in $installedDirs) {
if ($sourceNames -notcontains $dir.Name) {
Write-Host "pruning: $($dir.Name) (not in skills/) -> $($dir.FullName)"
Remove-Item -Recurse -Force $dir.FullName
$rel = $dir.Name
$loc = $dir.FullName
if ($Yes) {
Write-Warning "prune: $rel (not in skills/) - REMOVED from $loc"
Remove-Item -Recurse -Force $loc
} elseif ($interactive) {
Write-Warning "prune: $rel is in prod but has NO source in skills/ - possibly lost knowledge."
$ans = Read-Host " Remove '$rel'? [y/N]"
if ($ans -eq 'y' -or $ans -eq 'Y') {
Remove-Item -Recurse -Force $loc
Write-Host "removed: $rel"
} else {
Write-Host "kept: $rel (remove manually, or add it to skills/)"
}
} else {
Write-Warning "prune skipped: $rel (prod skill without source; non-interactive - nothing removed)"
}
}
}
}