From 348f6f3108ef0480c1eefc413ca8ea2b6ea29c0f Mon Sep 17 00:00:00 2001 From: vitya Date: Wed, 12 Aug 2026 21:43:00 +0300 Subject: [PATCH] =?UTF-8?q?feat(update-skills):=20dual=20install=20target?= =?UTF-8?q?=20=E2=80=94=20~/.claude/skills=20+=20~/.agents/skills=20(agent?= =?UTF-8?q?-neutral);=20pi=20settings=20hack=20removed=20(v0.2.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .tasks/STATUS.md | 11 +++--- dist/update-skills.skill | Bin 1943 -> 2417 bytes scripts/install.ps1 | 65 ++++++++++++++++++++-------------- scripts/install.sh | 62 +++++++++++++++++++------------- scripts/update.ps1 | 49 ++++++++++++++----------- scripts/update.sh | 40 ++++++++++++--------- skills/update-skills/SKILL.md | 22 ++++++++++-- 7 files changed, 155 insertions(+), 94 deletions(-) diff --git a/.tasks/STATUS.md b/.tasks/STATUS.md index dc22589..7b6bffb 100644 --- a/.tasks/STATUS.md +++ b/.tasks/STATUS.md @@ -397,13 +397,14 @@ needs-claude --- -## 🟡 [agent-neutral-skill-pipeline] — ДОЛГ (идея 19 п.5, буфер claude-to-agents): инсталл/апдейт-пайплайн скилов — claude-only. update-claude-skills ставит только в ~/.claude/skills; pi получил каталог вручную (settings skills array, идея 6). Цель: update-skills обслуживает всех агентов (единый канон + честный per-agent срез). Связано с ренеймом репо (rename-repo-to-skills) и чисткой ~/.agents/skills. Сейчас НЕ исполняем (долг, status=paused) — осознанно отложено. +## 🟢 [agent-neutral-skill-pipeline] — ДОЛГ (идея 19 п.5): инсталл/апдейт-пайплайн скилов — claude-only. update-claude-skills ставил только в ~/.claude/skills; pi получил каталог вручную (settings skills array, идея 6). Цель: update-skills обслуживает всех агентов (единый канон + честный per-agent срез). -**Status:** paused -**Where I stopped:** (not started) -**Next action:** (пауза) После rename-repo-to-skills: спроектировать update-skills с таргетами ~/.claude/skills + ~/.agents/skills + (pi settings), подтвердить у владельца -**Branch:** n/a +**Status:** done +**Where I stopped:** РЕШЕНИЕ ОПЕРАТОРА (2026-08-12, зафиксировано после вскрытия непротоколированного решения вчерашней сессии «канон = ~/.claude/skills»): **dual install target**. install.sh/ps1 + update.sh/ps1 ставят в ОБА каталога: ~/.claude/skills (Claude Code — читает только его нативно, кастомные диры не конфигурируются) + ~/.agents/skills (pi — нативный скан-путь, нейтральный неймспейс .agents/). Из ~/.pi/agent/settings.json убран skills-массив (вендор-лок устранён). update-skills SKILL.md v0.2.1 — секция «Install targets (dual, agent-neutral)», semver-bump, lint 46/0. Оба таргета синхронны (46/46, diff IDENTICAL). Источник истины — git-репо skills/, оба дира — инсталлы. +**Next action:** (none — kept until merged) pi перезапустить чтобы убедиться, что скилы грузятся из ~/.agents/skills (нативный скан без settings-хака). +**Branch:** master + --- diff --git a/dist/update-skills.skill b/dist/update-skills.skill index e302674bc56c9075c07a22e1f6b86569f570f41a..5ea5520efdd69a3e82670868008310040ef2f043 100644 GIT binary patch delta 2362 zcmV-A3B~r85AhNhP)h>@6aWAK2mnQ`3|;6H_Zi~|000FPkr*9+Sj%qQ$Q9iS&_7(r z16bW|vSZIAi?*S_k!KPijwOsd@gfLZ)nu`o7Re&2irwx&2$D_q3GySERW`{tXnrAI zl5?wy?3X54STO5xpXc0rtCJ)-J!xdEX4JL0bSklTWmVbJ6WO_?HZ#(l!PiO|Tk2*; zKS^Jt&reTsWiwNMw$86zoSqPU(^VB@RAu2P+hmoZLK~6|sn4Z#WwW3lnaajGSyeLJ zU6hVmT%E9tOgDMS>)79rQS+{>a{A5X4OwN_gh_up3Y(B!>NQ2{C@+fwOWk6jtYd*j zRa)jLy*DM+4B^aK@G}ZHo6wovG}-XUx6U5^{qT>6f8pm{@cj>e{^74! zhL`K5YRD}Wb+D7xvT>H2rnzCav0fS$8~yKE-eVh5x-{C_q(v|gteVbm^jewQrGkSn z^+Va@dJQM%(k^-7ZP|36r?t$kZ$~MMe4~}6ch~P}DVrQ2Ul^sDqEv|5Tx(~YkuCk_ zPk)CI(ml0*)EfOsWiA;uvGz&NAuC}ZoSr;6gu&^_1zk4^si|w|*t)d=Fdj=O6&)vU z>!sIkRI4ZZnd~#jQg;)WdOM!bhY-}#lIa?6$#{Ita013MpvHxqj>lfXehRGISP0&b zE@mY5GA<@0b~4VNkEHu(Ii#DH52YdhS*5L_l{BS)Prbw*Ctkzt!YJg94U4hLBtoSZ zX>jPBT6q`1HeDlrnMb~K2$S^@L12xe^O!%uRDtYdt^gR7thKolA%~uBtj9A7mqvg+ zN6+ge{HH%q95Iyx$k(1xVBE8TX3u;|VuZ_V$#RxTu1aNm;>yOUCNQ?o+l|o~j1ON= z=y^(iZ~iFU%v`xr17Sr7E)tM?7dc^NL2@O_ zO3o_~{2JHHy0mJ>hFldSzEI6-c6}>2k|H{P`0?F)!8j6EHm>+>XFBBOFMp$*vd~Jm zWV&u8`xD-sxOaL2J!~$Mj^zMOk{aD4xhiB=IjVH#xeD^-pE~ROX}(P|%e2XXFre^! zpi?b3s4y$VKyQ>U0^%rk-UHcawhO2+*wSX>p_46Dc1MCCmR!;+)^q{uRtazl2a=?J zv|N{^KMJ*{es^13h#SS_1HPH^vdTXmTJv$M_G*pwf-U*6Egsjk~tj ztc=kHRUfsK3nfqj@2rQ0=o=xTQ51=PZmNy&C#=u!kj|w9*nmN^Kpq02je5$e5fPMh zC2Qt=pEyn~ROkxHL;dc!ym z*V`z=gH7JW+N%(b%z16U&`i38;GyoC6FaEz&&29-yr=scE)(-WIvcJnoX$$^kq<#>z()Rf9q zj`nh^6rEkg>?0ftF5zzIJ{jYYjkNAghIk>rYqg)R@mB%!_JCMIE#ux-%RTxDtL`omfr-88Es4yvb4E&tM4E)J|RfeKuLkPH4 z`$nDcon0k;>pO@Aq6sQYLVlA*b(pxSQLg~;<#u6*rtSI7nnY|yuXr%OnBM;S>h0UK z&SAHR?s8*d`t_fo)Ad9|jnb$A7b_y`E^_rQ984Bas2ZLyl6Zs|IXF5Yyc z+z+}a(M7rF3}+OM?uk0*s);e9TVNvw@dgOCAmd=#0rKye7x!Co>=`o9fd)2=UWNv< zn^F?JRAy4GY%5WgAcsd}7`zIljBNl%zqow+`nxw@fAnd+{rKj8>RX>mKNmc*rGYx* zLFm0>3<|A2oblefz_hlkjBW(@MyqmCLmN- zJf>qfF$u;J;S&02VF9jU2vhp40-iRsE>Tm^IMC=Y(X{Nmq%bD;b+p=h zF>W6O((RxWeNYd7WZfMGfOBy?$N5ftxX!#8#mAG!Ii}U|__!5=MhvOrc~Q_gI0w@) ztN^EOGoe6LO?)jpX!7vk%OCfDfyaN-*d6OF@Xtiy^~odi z0pxwf^7>M_eal0@s+9k;#NqN!`SR`IqdzJSucE1sK8gM}3wUcV*wx-B-<6f*CkJbV z#GkRm8@y@V-jEkX?#~Oz&2}=#Cwt9b_XBy4*K15M=qeVaR`oBMUq13ckDAbM5U;}r zy6^@UD1k12aV26K`VRDk(E~3Un5)ttUL;A2++lbwD!GV)jjHu(=LZ;!=(H`6Qy-q% z6RSMzU)(~Di^nvQ>5%SI!Ea6@xjMj34ENv|(89`o7iA~NlMC ziq>q?=x0(xm+yKYyr|LdW^5z?avmjc5A@aQWxT>3+8>W4FQDg*(*FWbO928D02BZK g00;mMMXd~7=oI%E;|Kr%1r(E42Ra592><{90IY3~`~Uy| delta 1884 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 WWM>Hn005T{lQ9W820aG=0002V#FXj) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 729633e..b7ca16f 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1,6 +1,10 @@ -# Install skills// into ~\.claude\skills\\ (or $env:CLAUDE_SKILLS_DIR) +# Install skills// into BOTH agent skill dirs: +# ~\.claude\skills\\ (Claude Code — native, not configurable) +# ~\.agents\skills\\ (pi — native default scan path, agent-neutral namespace) +# $env:CLAUDE_SKILLS_DIR overrides ONLY the claude target (for testing/CI). +# # Usage: install.ps1 [-Names ,] [-Prune] -# no args = install all skills/* into target +# no args = install all skills/* into both targets # -Prune = after install, remove target// dirs that are NOT in skills/* # (prune always scans full target, ignores -Names filter — it's a global cleanup) # @@ -17,45 +21,54 @@ $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. +$targets = @() if ($env:CLAUDE_SKILLS_DIR) { - $target = $env:CLAUDE_SKILLS_DIR + $targets += $env:CLAUDE_SKILLS_DIR } else { - $target = Join-Path $env:USERPROFILE '.claude\skills' + $targets += (Join-Path $env:USERPROFILE '.claude\skills') } +$targets += (Join-Path $env:USERPROFILE '.agents\skills') -if (-not (Test-Path $target)) { - New-Item -ItemType Directory -Force -Path $target | Out-Null +foreach ($target in $targets) { + if (-not (Test-Path $target)) { + New-Item -ItemType Directory -Force -Path $target | Out-Null + } } if ($Names.Count -eq 0) { $Names = Get-ChildItem -Path $src -Directory | Sort-Object Name | Select-Object -ExpandProperty Name } -foreach ($name in $Names) { - $srcDir = Join-Path $src $name - $dstDir = Join-Path $target $name - if (-not (Test-Path $srcDir -PathType Container)) { - Write-Warning "skip: $name (not found in skills/)" - continue +foreach ($target in $targets) { + foreach ($name in $Names) { + $srcDir = Join-Path $src $name + $dstDir = Join-Path $target $name + if (-not (Test-Path $srcDir -PathType Container)) { + Write-Warning "skip: $name (not found in skills/)" + continue + } + if (-not (Test-Path (Join-Path $srcDir 'SKILL.md'))) { + Write-Warning "skip: $name (missing SKILL.md)" + continue + } + if (Test-Path $dstDir) { + Remove-Item -Recurse -Force $dstDir + } + Copy-Item -Recurse $srcDir $dstDir + Write-Host "installed: $name -> $dstDir" } - if (-not (Test-Path (Join-Path $srcDir 'SKILL.md'))) { - Write-Warning "skip: $name (missing SKILL.md)" - continue - } - if (Test-Path $dstDir) { - Remove-Item -Recurse -Force $dstDir - } - Copy-Item -Recurse $srcDir $dstDir - Write-Host "installed: $name -> $dstDir" } if ($Prune) { $sourceNames = @(Get-ChildItem -Path $src -Directory | Select-Object -ExpandProperty Name) - $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 + 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 + } } } } diff --git a/scripts/install.sh b/scripts/install.sh index 55b807c..304dc8f 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,14 +1,20 @@ #!/usr/bin/env bash -# Install skills// into ~/.claude/skills// (or $CLAUDE_SKILLS_DIR) +# Install skills// into BOTH agent skill dirs: +# ~/.claude/skills// (Claude Code — native, not configurable) +# ~/.agents/skills// (pi — native default scan path, agent-neutral namespace) +# $CLAUDE_SKILLS_DIR overrides ONLY the claude target (for testing/CI). +# # Usage: install.sh [--prune] [name...] -# no args = install all skills/* into target +# no args = install all skills/* into both targets # --prune = after install, remove target// dirs that are NOT in skills/* # (prune always scans full target, ignores name filter — global cleanup) set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SRC="$ROOT/skills" -TARGET="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" + +# Dual install targets — the "canon" is the git repo (skills/); both dirs are installs. +TARGETS=("${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" "$HOME/.agents/skills") prune=0 positional=() @@ -33,31 +39,37 @@ else names=("${positional[@]}") fi -mkdir -p "$TARGET" +for target in "${TARGETS[@]}"; do + mkdir -p "$target" +done -for name in "${names[@]}"; do - src_dir="$SRC/$name" - dst_dir="$TARGET/$name" - if [ ! -d "$src_dir" ]; then - echo "skip: $name (not found in skills/)" >&2 - continue - fi - if [ ! -f "$src_dir/SKILL.md" ]; then - echo "skip: $name (missing SKILL.md)" >&2 - continue - fi - rm -rf "$dst_dir" - cp -R "$src_dir" "$dst_dir" - echo "installed: $name → $dst_dir" +for target in "${TARGETS[@]}"; do + for name in "${names[@]}"; do + src_dir="$SRC/$name" + dst_dir="$target/$name" + if [ ! -d "$src_dir" ]; then + echo "skip: $name (not found in skills/)" >&2 + continue + fi + if [ ! -f "$src_dir/SKILL.md" ]; then + echo "skip: $name (missing SKILL.md)" >&2 + continue + fi + rm -rf "$dst_dir" + cp -R "$src_dir" "$dst_dir" + echo "installed: $name → $dst_dir" + done done if [ "$prune" -eq 1 ]; then - for d in "$TARGET"/*/; do - [ -d "$d" ] || continue - name="$(basename "$d")" - if [ ! -d "$SRC/$name" ]; then - echo "pruning: $name (not in skills/) → $d" - rm -rf "$d" - fi + for target in "${TARGETS[@]}"; do + for d in "$target"/*/; do + [ -d "$d" ] || continue + name="$(basename "$d")" + if [ ! -d "$SRC/$name" ]; then + echo "pruning: $name (not in skills/) → $d" + rm -rf "$d" + fi + done done fi diff --git a/scripts/update.ps1 b/scripts/update.ps1 index 786ec03..caf2759 100644 --- a/scripts/update.ps1 +++ b/scripts/update.ps1 @@ -22,7 +22,10 @@ $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot $skillsSrc = Join-Path $root 'skills' -$target = if ($env:CLAUDE_SKILLS_DIR) { $env:CLAUDE_SKILLS_DIR } else { Join-Path $env:USERPROFILE '.claude\skills' } +# 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 } else { $targets += (Join-Path $env:USERPROFILE '.claude\skills') } +$targets += (Join-Path $env:USERPROFILE '.agents\skills') $commonRoot = if ($env:COMMON_ROOT) { $env:COMMON_ROOT } else { Join-Path $env:USERPROFILE 'projects\.common' } $metaMcp = Join-Path $commonRoot 'lib\projects-meta-mcp' @@ -31,13 +34,15 @@ $internsMcp = Join-Path $commonRoot 'lib\interns-mcp' # -- Collect before-versions -------------------------------------------------- $beforeVersions = @{} -if (Test-Path $target) { - Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue | ForEach-Object { - $vf = Join-Path $_.FullName 'SKILL.md' - if (Test-Path $vf) { - $match = Select-String -Path $vf -Pattern '^version:\s*(\d+\.\d+\.\d+)' -ErrorAction SilentlyContinue | Select-Object -First 1 - $ver = if ($match) { $match.Matches.Groups[1].Value } else { '?' } - $beforeVersions[$_.Name] = $ver +foreach ($target in $targets) { + if (Test-Path $target) { + Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue | ForEach-Object { + $vf = Join-Path $_.FullName 'SKILL.md' + if (Test-Path $vf) { + $match = Select-String -Path $vf -Pattern '^version:\s*(\d+\.\d+\.\d+)' -ErrorAction SilentlyContinue | Select-Object -First 1 + $ver = if ($match) { $match.Matches.Groups[1].Value } else { '?' } + $beforeVersions[$_.Name] = $ver + } } } } @@ -144,16 +149,18 @@ Write-Host "[ok] Skills installed." -ForegroundColor Green Write-Host "[update] Version diff:" -ForegroundColor Cyan $changes = $false -Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue | ForEach-Object { - $vf = Join-Path $_.FullName 'SKILL.md' - if (Test-Path $vf) { - $match = Select-String -Path $vf -Pattern '^version:\s*(\d+\.\d+\.\d+)' -ErrorAction SilentlyContinue | Select-Object -First 1 - $after = if ($match) { $match.Matches.Groups[1].Value } else { '?' } - $before = $beforeVersions[$_.Name] - if (-not $before) { $before = 'new' } - if ($before -ne $after) { - Write-Host " $before -> $after ($($_.Name))" - $changes = $true +foreach ($target in $targets) { + Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue | ForEach-Object { + $vf = Join-Path $_.FullName 'SKILL.md' + if (Test-Path $vf) { + $match = Select-String -Path $vf -Pattern '^version:\s*(\d+\.\d+\.\d+)' -ErrorAction SilentlyContinue | Select-Object -First 1 + $after = if ($match) { $match.Matches.Groups[1].Value } else { '?' } + $before = $beforeVersions[$_.Name] + if (-not $before) { $before = 'new' } + if ($before -ne $after) { + Write-Host " $before -> $after ($($_.Name))" + $changes = $true + } } } } @@ -166,9 +173,11 @@ if (-not $changes) { $newSetup = @() Get-ChildItem -Path $skillsSrc -Directory | ForEach-Object { $name = $_.Name - if ($name -like 'setup-*' -and -not (Test-Path (Join-Path $target $name))) { - $newSetup += $name + $missing = $false + foreach ($target in $targets) { + if ($name -like 'setup-*' -and -not (Test-Path (Join-Path $target $name))) { $missing = $true } } + if ($missing) { $newSetup += $name } } if ($newSetup.Count -gt 0) { diff --git a/scripts/update.sh b/scripts/update.sh index 91638f9..987b154 100644 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -20,7 +20,8 @@ YES_MODE=false ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SKILLS_SRC="$ROOT/skills" -TARGET="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" +# Dual install targets — the "canon" is the git repo (skills/); both dirs are installs. +TARGETS=("${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" "$HOME/.agents/skills") COMMON_ROOT="${COMMON_ROOT:-$HOME/projects/.common}" META_MCP="$COMMON_ROOT/lib/projects-meta-mcp" @@ -43,8 +44,9 @@ confirm() { # ── Collect before-versions ──────────────────────────────────────────────── declare -A BEFORE_VERSIONS=() -if [[ -d "$TARGET" ]]; then - for skill_dir in "$TARGET"/*/; do +for target in "${TARGETS[@]}"; do + [[ -d "$target" ]] || continue + for skill_dir in "$target"/*/; do name="$(basename "$skill_dir")" vf="$skill_dir/SKILL.md" if [[ -f "$vf" ]]; then @@ -52,7 +54,7 @@ if [[ -d "$TARGET" ]]; then BEFORE_VERSIONS["$name"]="$ver" fi done -fi +done # ── Step 1: git pull ──────────────────────────────────────────────────────── @@ -133,17 +135,19 @@ ok "Skills installed." info "Version diff:" CHANGES=false -for skill_dir in "$TARGET"/*/; do - name="$(basename "$skill_dir")" - vf="$skill_dir/SKILL.md" - if [[ -f "$vf" ]]; then - after="$(grep -oP '^version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$vf" 2>/dev/null || echo '?')" - before="${BEFORE_VERSIONS[$name]:-new}" - if [[ "$before" != "$after" ]]; then - echo " $before → $after ($name)" - CHANGES=true +for target in "${TARGETS[@]}"; do + for skill_dir in "$target"/*/; do + name="$(basename "$skill_dir")" + vf="$skill_dir/SKILL.md" + if [[ -f "$vf" ]]; then + after="$(grep -oP '^version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$vf" 2>/dev/null || echo '?')" + before="${BEFORE_VERSIONS[$name]:-new}" + if [[ "$before" != "$after" ]]; then + echo " $before → $after ($name)" + CHANGES=true + fi fi - fi + done done if ! $CHANGES; then echo " (no version changes)" @@ -154,8 +158,12 @@ fi NEW_SETUP=() for skill_dir in "$SKILLS_SRC"/*/; do name="$(basename "$skill_dir")" - if [[ "$name" == setup-* ]] && [[ ! -d "$TARGET/$name" ]]; then - NEW_SETUP+=("$name") + if [[ "$name" == setup-* ]]; then + missing=false + for target in "${TARGETS[@]}"; do + [[ ! -d "$target/$name" ]] && missing=true + done + $missing && NEW_SETUP+=("$name") fi done diff --git a/skills/update-skills/SKILL.md b/skills/update-skills/SKILL.md index 1843fe7..b1adeb0 100644 --- a/skills/update-skills/SKILL.md +++ b/skills/update-skills/SKILL.md @@ -1,7 +1,7 @@ --- name: update-skills author: ours -version: 0.2.0 +version: 0.2.1 description: > Full uplift cycle for an existing skills installation: git pull, conditionally rebuild MCP servers, install skills, show version diff, suggest @@ -54,6 +54,10 @@ If no MCP changes: > Skills updated. Start a new session (or `/clear`) for changes to take full effect. +Both agents pick up the new catalog on their next session: Claude Code reads +`~/.claude/skills` natively; pi reads `~/.agents/skills` natively (default +scan path — no settings needed). See "Install targets" below. + ### 4. Offer to run new setup skills If the script detected new `setup-*` skills that aren't yet installed: @@ -62,6 +66,20 @@ If the script detected new `setup-*` skills that aren't yet installed: The user decides — never auto-run setup skills. +## Install targets (dual, agent-neutral) + +The canon is the git repo — `skills//SKILL.md`. `install.sh` / `install.ps1` +install into **both** agent dirs (identical copies, synced by the script — no drift): + +| Target | Agent | Why | +|---|---|---| +| `~/.claude/skills` | Claude Code | Claude Code reads this natively; custom skill dirs are NOT configurable in Claude Code (only plugin `skillsPaths`) | +| `~/.agents/skills` | pi | pi reads this natively (default scan path); the agent-neutral `.agents/` namespace is our convention | + +`$CLAUDE_SKILLS_DIR` / `$env:CLAUDE_SKILLS_DIR` overrides only the claude target (for testing/CI). +pi must NOT have `~/.claude/skills` in its settings `skills` array — that was the old vendor-locked +setup; pi loads `~/.agents/skills` by default. Verify with `pi --help` / a fresh session listing skills. + ## What the scripts do The update scripts (`scripts/update.sh` and `scripts/update.ps1`) handle: @@ -76,7 +94,7 @@ The update scripts (`scripts/update.sh` and `scripts/update.ps1`) handle: ## Out of scope - **Greenfield bootstrap** — use `project-bootstrap` for first-time setup on a new machine. -- **`--prune` flag** — removing skills not in `skills/` from `~/.claude/skills/`. Tracked separately in `[install-ps1]`. +- **`--prune` flag** — removing skills not in `skills/` from the install targets (both `~/.claude/skills/` and `~/.agents/skills/`). Tracked separately in `[install-ps1]`. - **Hermes-side update** — `hermes-installer-skill` handles the Hermes/Linux factory path. - **Auto-run setup skills** — the user decides which new setup skills to configure.