Symmetric to install-side --prune (e871c20). Removes
dist/<name>.skill files whose <name> is not in skills/*.
Design choices match install:
- Combined flag (build + prune in one run)
- Global scan, ignores -Names / positional filter
- Default off, print-and-delete, no confirmation
Bash wrinkle: when build.sh delegates to powershell.exe -File
build.ps1 (Windows-without-zip case), --prune is NOT forwarded.
Bash runs prune itself at the end of the script against the
shared dist/. Keeps the delegation surface narrow and the prune
logic single-sourced per shell.
[skip-tdd: wrapper] — same carve-out as install-side, smoke-test
evidence: fake dist/fake-stale-{sh,ps}.skill files created in real
dist/, ran build.{sh,ps1} --prune, verified fakes removed, real
caveman.skill etc. left intact.
Wiki .wiki/concepts/install-cross-platform.md extended:
- title broadened (Install → Install / Build)
- new "Build-side --prune" section
- scope note: dist-hermes/ is separate (managed by build-hermes.py)
Closes [install-ps1-build-prune-followup].
6.3 KiB
title, type, updated
| title | type | updated |
|---|---|---|
| Install / Build Cross-Platform Parity (PS + Bash) | concept | 2026-05-25 |
Install / Build Cross-Platform Parity (PS + Bash)
Sibling concept to install-portability.md (POSIX-shell compat). This one is about the paired-script parity contract between scripts/install.ps1 / scripts/install.sh (install) and scripts/build.ps1 / scripts/build.sh (build). Both pairs share the same conventions and the same --prune / -Prune flag pattern.
Why two scripts
Windows hosts running CC outside a git-bash terminal have no reliable bash. Native PowerShell call (pwsh ./scripts/install.ps1) is the friction-free path. Linux / macOS users get bash. Both audiences are first-class — claude-skills is multi-machine by design (see project_deployment_goal memory).
Single-script "use bash everywhere" was rejected: forces every Windows user to install git-bash before bootstrap, contradicts "tool-light install path".
Parity contract
Both scripts MUST:
- Read sources from
<repo>/skills/<name>/. - Install to
$CLAUDE_SKILLS_DIRif set, else~/.claude/skills/. - Accept a name-list (positional in bash,
-Namesin PS); no args = all. - Skip with a warning when
skills/<name>/is missing or has noSKILL.md. - Idempotent:
rm -rf(orRemove-Item -Recurse -Force) the destination, then copy fresh. - Print one
installed: <name> -> <path>line per successfully installed skill.
Flag naming follows the host shell's convention — POSIX --prune in bash, PascalCase -Prune switch in PowerShell. Behaviour identical.
The --prune / -Prune flag
Added 2026-05-25 (commit 6cf0e98). Motivating case: after retiring using-synology-ops the installed dir ~/.claude/skills/using-synology-ops/ lingered until manual rm — the install scripts had no notion of stale-cleanup.
Behaviour: after the install loop, walk $target/* and remove any dir whose name is not in skills/*.
Design choices and their rejected alternatives:
-
Combined flag, not standalone mode. Single invocation does both. Alternative (
install --prune-only) was rejected — adds a mode that nobody asked for; users wanting "just cleanup" can pass an empty name list (install.sh --prunewith no positional args still walks the prune step at the end, no-op'ing the install loop because all source skills resolve to no-op overwrites of fresh installs). -
Global scan, ignores name filter. Even when called as
install.sh foo --prune, the prune step scans the full target against the full source. Rationale: stale-cleanup is a global concern. A user who explicitly opts into prune wants the cleanup to be useful — a names-filtered prune ("only prune dirs that match the name list AND are missing from source") rarely matches anyone's mental model. -
Print-and-delete, no confirmation prompt. Each removal prints
pruning: <name> (not in skills/) -> <path>. Confirmation prompts would block automation (CI, batch reinstalls). Visibility comes from the printed line; users wanting a dry-run pass-WhatIfin PowerShell (the cmdlet already supports it) or pipe toechoin bash (trivial to grep^pruning:before running for real). -
Default off. Must be passed explicitly. Idempotent re-installs (the common case) don't suddenly delete anything.
What install-side --prune does NOT do
- It does not touch plugin-installed skills under
~/.claude/plugins/<plugin>/skills/<name>/. Those are managed by the plugin system, not this repo. - It does not warn if the about-to-be-deleted dir contains user-edited content. The contract is that
~/.claude/skills/<name>/is a managed copy ofskills/<name>/— anything else is user-error. - It does not remove
dist/<name>.skillbuild artefacts. That's the build-script's--prune(see next section).
Build-side --prune / -Prune
Added 2026-05-25 — natural extension of the install-side flag to the build pair (scripts/build.sh and scripts/build.ps1). Same design choices, applied to files instead of directories: after the build loop, walk dist/*.skill and remove any whose <name> (basename minus .skill) is not in skills/*.
Identical to install-side: combined flag, global scan ignores the name filter, prints pruning: <name> -> <path> per removal, default off, no confirmation prompt.
The bash path has one extra wrinkle: when build.sh is run on Windows without zip and delegates to build.ps1 via powershell.exe -File, the --prune flag is not forwarded to the delegated PS process. Bash runs the prune step itself at the end of the script, against the same dist/ directory. This keeps the delegation surface narrow (no flag-translation bugs) and the prune logic single-sourced per shell.
build.ps1 invoked directly (without the bash wrapper) handles -Prune natively.
What build-side -Prune does NOT do
- It does not unblock build for skills that have been renamed mid-flight. A user who renamed
skills/foo/→skills/bar/should still run a fresh build (build.sh bar) — prune only catches stale archives whose source dir is gone, not stale archives whose source was renamed (those become orphans of a different source, indistinguishable from intentional foreign artefacts). - It does not touch
dist-hermes/— that directory is managed byscripts/build-hermes.pyand follows its own rules (whole-directory rebuild per skill). Hermes has no current prune mechanism; if needed, that's a separate concept page.
Test evidence
All four scripts smoke-tested 2026-05-25.
Install side — disposable target dirs (env-overridden CLAUDE_SKILLS_DIR). Pre-populated with 2 fake stale dirs, ran full install + prune, verified: stale dirs removed, all real skills installed, retired using-synology-ops absent.
Build side — fake dist/fake-stale.skill + dist/another-stale.skill files created directly in the real dist/. Ran build.sh --prune fake-stale-sh (positional arg triggers a no-op build via skip path; prune runs at the end) and build.ps1 -Names fake-stale-ps -Prune. Both removed the fakes, left real archives like caveman.skill untouched.
No automated test fixture in the repo — install / build scripts are wrapper-style, smoke-test evidence in the respective commit bodies suffices under the [skip-tdd: wrapper] carve-out.
[archive-roundtrip-test] (still ⚪ on the board) is a candidate place to add a real fixture once it lands.