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:
@@ -1,19 +1,23 @@
|
|||||||
# Install skills/<name>/ into BOTH agent skill dirs:
|
# Install skills/<name>/ into BOTH agent skill dirs:
|
||||||
# ~\.claude\skills\<name>\ (Claude Code — native, not configurable)
|
# ~\.claude\skills\<name>\ (Claude Code - native, not configurable)
|
||||||
# ~\.agents\skills\<name>\ (pi — native default scan path, agent-neutral namespace)
|
# ~\.agents\skills\<name>\ (pi - native default scan path, agent-neutral namespace)
|
||||||
# $env:CLAUDE_SKILLS_DIR overrides ONLY the claude target (for testing/CI).
|
# $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
|
# no args = install all skills/* into both targets
|
||||||
# -Prune = after install, remove target/<name>/ dirs that are NOT in skills/*
|
# -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()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[string[]]$Names = @(),
|
[string[]]$Names = @(),
|
||||||
[switch]$Prune
|
[switch]$Prune,
|
||||||
|
[switch]$Yes
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
@@ -21,7 +25,7 @@ $ErrorActionPreference = 'Stop'
|
|||||||
$root = Split-Path -Parent $PSScriptRoot
|
$root = Split-Path -Parent $PSScriptRoot
|
||||||
$src = Join-Path $root 'skills'
|
$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 = @()
|
$targets = @()
|
||||||
if ($env:CLAUDE_SKILLS_DIR) {
|
if ($env:CLAUDE_SKILLS_DIR) {
|
||||||
$targets += $env:CLAUDE_SKILLS_DIR
|
$targets += $env:CLAUDE_SKILLS_DIR
|
||||||
@@ -62,12 +66,28 @@ foreach ($target in $targets) {
|
|||||||
|
|
||||||
if ($Prune) {
|
if ($Prune) {
|
||||||
$sourceNames = @(Get-ChildItem -Path $src -Directory | Select-Object -ExpandProperty Name)
|
$sourceNames = @(Get-ChildItem -Path $src -Directory | Select-Object -ExpandProperty Name)
|
||||||
|
$interactive = -not [Console]::IsInputRedirected
|
||||||
foreach ($target in $targets) {
|
foreach ($target in $targets) {
|
||||||
$installedDirs = Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue
|
$installedDirs = Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue
|
||||||
foreach ($dir in $installedDirs) {
|
foreach ($dir in $installedDirs) {
|
||||||
if ($sourceNames -notcontains $dir.Name) {
|
if ($sourceNames -notcontains $dir.Name) {
|
||||||
Write-Host "pruning: $($dir.Name) (not in skills/) -> $($dir.FullName)"
|
$rel = $dir.Name
|
||||||
Remove-Item -Recurse -Force $dir.FullName
|
$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)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
# ~/.agents/skills/<name>/ (pi — native default scan path, agent-neutral namespace)
|
# ~/.agents/skills/<name>/ (pi — native default scan path, agent-neutral namespace)
|
||||||
# $CLAUDE_SKILLS_DIR overrides ONLY the claude target (for testing/CI).
|
# $CLAUDE_SKILLS_DIR overrides ONLY the claude target (for testing/CI).
|
||||||
#
|
#
|
||||||
# Usage: install.sh [--prune] [name...]
|
# Usage: install.sh [--prune] [--yes] [name...]
|
||||||
# no args = install all skills/* into both targets
|
# no args = install all skills/* into both targets
|
||||||
# --prune = after install, remove target/<name>/ dirs that are NOT in skills/*
|
# --prune = after install, remove target/<name>/ dirs that are NOT in skills/*
|
||||||
# (prune always scans full target, ignores name filter — global cleanup)
|
# (prune always scans full target, ignores name filter — 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.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
@@ -17,10 +20,12 @@ SRC="$ROOT/skills"
|
|||||||
TARGETS=("${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" "$HOME/.agents/skills")
|
TARGETS=("${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" "$HOME/.agents/skills")
|
||||||
|
|
||||||
prune=0
|
prune=0
|
||||||
|
yes=0
|
||||||
positional=()
|
positional=()
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--prune) prune=1 ;;
|
--prune) prune=1 ;;
|
||||||
|
--yes) yes=1 ;;
|
||||||
*) positional+=("$arg") ;;
|
*) positional+=("$arg") ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@@ -62,13 +67,29 @@ for target in "${TARGETS[@]}"; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ "$prune" -eq 1 ]; then
|
if [ "$prune" -eq 1 ]; then
|
||||||
|
interactive=1
|
||||||
|
if [ ! -t 0 ]; then interactive=0; fi
|
||||||
for target in "${TARGETS[@]}"; do
|
for target in "${TARGETS[@]}"; do
|
||||||
for d in "$target"/*/; do
|
for d in "$target"/*/; do
|
||||||
[ -d "$d" ] || continue
|
[ -d "$d" ] || continue
|
||||||
name="$(basename "$d")"
|
name="$(basename "$d")"
|
||||||
if [ ! -d "$SRC/$name" ]; then
|
if [ ! -d "$SRC/$name" ]; then
|
||||||
echo "pruning: $name (not in skills/) → $d"
|
if [ "$yes" -eq 1 ]; then
|
||||||
rm -rf "$d"
|
echo "WARN: prune: $name (not in skills/) — REMOVED from $d" >&2
|
||||||
|
rm -rf "$d"
|
||||||
|
elif [ "$interactive" -eq 1 ]; then
|
||||||
|
echo "WARN: prune: $name is in prod but has NO source in skills/ — possibly lost knowledge." >&2
|
||||||
|
printf ' Remove "%s"? [y/N] ' "$name"
|
||||||
|
read -r ans
|
||||||
|
if [ "$ans" = "y" ] || [ "$ans" = "Y" ]; then
|
||||||
|
rm -rf "$d"
|
||||||
|
echo "removed: $name"
|
||||||
|
else
|
||||||
|
echo "kept: $name (remove manually, or add it to skills/)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "WARN: prune skipped: $name (prod skill without source; non-interactive — nothing removed)" >&2
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|||||||
Reference in New Issue
Block a user