From fb7b8989912b5d6374df47302b7228eac6d12e80 Mon Sep 17 00:00:00 2001 From: Hovhannes Tsakanyan Date: Wed, 22 Apr 2026 23:28:05 +0400 Subject: [PATCH 1/2] feat: add codex adapter --- README.md | 10 ++++--- adapters/codex/AGENTS.md | 40 +++++++++++++++++++++++++++ adapters/codex/README.md | 44 +++++++++++++++++++++++++++++ docs/architecture.md | 5 ++-- docs/getting-started.md | 3 +- docs/per-harness/codex.md | 58 +++++++++++++++++++++++++++++++++++++++ install.ps1 | 31 +++++++++++++++++++-- install.sh | 29 ++++++++++++++++++-- 8 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 adapters/codex/AGENTS.md create mode 100644 adapters/codex/README.md create mode 100644 docs/per-harness/codex.md diff --git a/README.md b/README.md index ffc785a..bc33017 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Keep one portable memory-and-skills layer across coding-agent harnesses, so switching tools doesn't reset how your agent works.** -A portable `.agent/` folder (memory + skills + protocols) that plugs into Claude Code, Cursor, Windsurf, OpenCode, OpenClaw, Hermes, Pi Coding Agent, or a DIY Python loop — and keeps its knowledge when you switch. +A portable `.agent/` folder (memory + skills + protocols) that plugs into Claude Code, Cursor, Windsurf, OpenCode, OpenClaw, Hermes, Pi Coding Agent, Codex, or a DIY Python loop — and keeps its knowledge when you switch.

agentic-stack demo @@ -28,7 +28,7 @@ brew install agentic-stack # drop the brain into any project — the onboarding wizard runs automatically cd your-project agentic-stack claude-code -# or: cursor | windsurf | opencode | openclaw | hermes | pi | standalone-python | antigravity +# or: cursor | windsurf | opencode | openclaw | hermes | pi | codex | standalone-python | antigravity ``` ### Windows (PowerShell) @@ -52,7 +52,7 @@ brew update && brew upgrade agentic-stack git clone https://github.com/codejunkie99/agentic-stack.git cd agentic-stack && ./install.sh claude-code # mac / linux / git-bash # or on Windows PowerShell: .\install.ps1 claude-code -# adapters: claude-code | cursor | windsurf | opencode | openclaw | hermes | pi | standalone-python | antigravity +# adapters: claude-code | cursor | windsurf | opencode | openclaw | hermes | pi | codex | standalone-python | antigravity ``` ## Onboarding wizard @@ -122,7 +122,7 @@ See [`docs/architecture.md`](docs/architecture.md) for the full lifecycle. Every guide shows the folder structure. This repo gives you the folder structure **plus the files that actually go inside**: a working portable brain with five seed skills, four memory layers, enforced permissions, a -nightly staging cycle, host-agent review tools, and adapters for eight +nightly staging cycle, host-agent review tools, and adapters for multiple harnesses. - **Memory** — `working/`, `episodic/`, `semantic/`, `personal/`. Each @@ -197,6 +197,7 @@ adapters/ # one small shim per harness ├── openclaw/ (system-prompt include) ├── hermes/ (AGENTS.md) ├── pi/ (AGENTS.md + .pi/skills symlink) +├── codex/ (AGENTS.md) ├── standalone-python/ (DIY conductor entrypoint) └── antigravity/ (ANTIGRAVITY.md) @@ -225,6 +226,7 @@ verify_codex_fixes.py # v0.8.0 regression checks (33 checks) | **OpenClaw** | system-prompt include | varies by fork | | **Hermes Agent** | `AGENTS.md` (agentskills.io compatible) | partial (own memory) | | **Pi Coding Agent** | `AGENTS.md` + `.pi/skills/` | no (extension system) | +| **Codex** | `AGENTS.md` + `.agents/skills/` | no (manual reflect calls) | | **Standalone Python** | `run.py` (any LLM) | yes (full control) | | **Antigravity** | `ANTIGRAVITY.md` | yes (system context) | diff --git a/adapters/codex/AGENTS.md b/adapters/codex/AGENTS.md new file mode 100644 index 0000000..f64568f --- /dev/null +++ b/adapters/codex/AGENTS.md @@ -0,0 +1,40 @@ +# AGENTS.md — Codex adapter for agentic-stack + +Codex reads `AGENTS.md` before doing any work. This file points it at +the portable brain in `.agent/`. + +## Startup (read in order) +1. `.agent/AGENTS.md` — the map +2. `.agent/memory/personal/PREFERENCES.md` — user conventions +3. `.agent/memory/semantic/LESSONS.md` — distilled lessons +4. `.agent/protocols/permissions.md` — hard rules + +## Skills +Codex scans `.agents/skills/` for repository-scoped skills. The install +script symlinks or copies `.agents/skills` from `.agent/skills` so the +portable brain remains the one source of truth. Load a full `SKILL.md` +only when its triggers match the task (progressive disclosure). + +## Recall before non-trivial tasks +For deploy / ship / migration / schema / timestamp / date / failing test / +debug / refactor, FIRST run: + +```bash +python3 .agent/tools/recall.py "" +``` + +Surface results in a `Consulted lessons before acting:` block and follow +them. + +## Memory discipline +- Update `.agent/memory/working/WORKSPACE.md` as you work. +- After significant actions, run + `python3 .agent/tools/memory_reflect.py `. +- Never delete memory entries; archive only. +- Quick state: `python3 .agent/tools/show.py`. +- Teach a rule in one shot: + `python3 .agent/tools/learn.py "" --rationale ""`. + +## Hard rules +- No force push to `main`, `production`, `staging`. +- No modification of `.agent/protocols/permissions.md`. diff --git a/adapters/codex/README.md b/adapters/codex/README.md new file mode 100644 index 0000000..da1db02 --- /dev/null +++ b/adapters/codex/README.md @@ -0,0 +1,44 @@ +# Codex adapter + +## Install +```bash +./install.sh codex +``` + +Or on Windows PowerShell: +```powershell +.\install.ps1 codex C:\path\to\your-project +``` + +## What it wires up +- `AGENTS.md` — Codex reads this natively as project instructions. If + `AGENTS.md` already exists (for example from the pi, hermes, or + opencode adapters), the installer leaves it in place. +- `.agents/skills/` → `.agent/skills/` — Codex scans `.agents/skills/` + for repository skills. The installer creates a symlink when possible + and falls back to copying / merging when symlinks are unavailable. + +## Verify +Run Codex in the project and ask: + +```bash +codex --ask-for-approval never "Summarize the current instructions." +``` + +It should mention `.agent/AGENTS.md` and the portable memory files. + +Then ask: + +```bash +codex --ask-for-approval never "What's in my lessons file?" +``` + +It should read `.agent/memory/semantic/LESSONS.md`. + +## Notes +- This adapter does **not** install Codex hooks. Codex hooks are still + experimental, and the official docs note they are currently disabled + on Windows. The adapter therefore relies on manual `recall.py` and + `memory_reflect.py` calls, like the Cursor and Windsurf paths. +- If `.agents/skills/` is a copied directory rather than a symlink, + re-run the installer after editing `.agent/skills/` to sync updates. diff --git a/docs/architecture.md b/docs/architecture.md index 51d9c27..c3804c0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -33,7 +33,8 @@ Three modules, one principle: the harness is dumb, the knowledge is in files. ## Why the separation matters You can swap the harness for any of the adapters (Claude Code, Cursor, -Windsurf, OpenCode, OpenClaw, Hermes, standalone Python) and lose -nothing. The brain is portable; only the glue changes. +Windsurf, OpenCode, OpenClaw, Hermes, Pi, Codex, standalone Python, +Antigravity) and lose nothing. The brain is portable; only the glue +changes. See `diagram.svg` for a visual. diff --git a/docs/getting-started.md b/docs/getting-started.md index 40715cb..32bd8d4 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -16,7 +16,8 @@ cp /path/to/agentic-stack/install.sh ./ ```bash ./install.sh claude-code # or cursor, windsurf, opencode, - # openclaw, hermes, standalone-python + # openclaw, hermes, pi, codex, + # standalone-python, antigravity ``` Each adapter has its own `README.md` under `adapters//`. diff --git a/docs/per-harness/codex.md b/docs/per-harness/codex.md new file mode 100644 index 0000000..5b6af4c --- /dev/null +++ b/docs/per-harness/codex.md @@ -0,0 +1,58 @@ +# Codex setup + +[Codex](https://developers.openai.com/codex/) reads `AGENTS.md` natively +and scans `.agents/skills/` for repository-scoped skills. Our adapter +layers the portable `.agent/` brain on top so you keep one knowledge +base even if you later swap harnesses. + +## What the adapter installs +- `AGENTS.md` at project root. Skipped if one already exists, since + codex, pi, hermes, and opencode can all share the same file. +- `.agents/skills/` symlinked to `.agent/skills/` when possible. Falls + back to copying / merging on platforms without symlink support. + +## Install +```bash +npm install -g @openai/codex +./install.sh codex +codex +``` + +On Windows PowerShell: +```powershell +npm install -g @openai/codex +.\install.ps1 codex C:\path\to\your-project +codex +``` + +## How it works +- Codex loads `AGENTS.md` before starting work. The adapter file points + it at `.agent/AGENTS.md`, `PREFERENCES.md`, `LESSONS.md`, and + `permissions.md`. +- Codex scans `.agents/skills/` from the current working directory up to + the repository root. The adapter mirrors `.agent/skills/` there so the + portable skills are visible without duplication. +- The adapter intentionally does **not** install Codex hooks. The docs + mark hooks experimental, and Windows support is currently disabled, so + manual `recall.py` and `memory_reflect.py` calls remain the stable + cross-platform path. + +## Verify +```bash +codex --ask-for-approval never "Summarize the current instructions." +codex --ask-for-approval never "What's in my lessons file?" +``` + +Expected: +- the first command mentions `.agent/AGENTS.md` +- the second reads `.agent/memory/semantic/LESSONS.md` + +## Troubleshooting +- If Codex does not pick up `AGENTS.md`, restart it from the repository + root and run the `Summarize the current instructions` check again. +- If skills are missing, inspect `.agents/skills/`. On filesystems + without symlink support, the installer copies / merges the directory + instead; re-run the installer after updating `.agent/skills/`. +- On Windows, the native sandbox is the default and works fine for this + adapter. If your workflow needs Linux-native tooling, run Codex inside + WSL2 instead. diff --git a/install.ps1 b/install.ps1 index b1bb556..a1f3e42 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,6 +1,6 @@ # install.ps1 — Windows PowerShell installer (parallel to install.sh) # Usage: .\install.ps1 [target-dir] [-Yes] [-Reconfigure] [-Force] -# adapter-name: claude-code | cursor | windsurf | opencode | openclaw | hermes | standalone-python | antigravity +# adapter-name: claude-code | cursor | windsurf | opencode | openclaw | hermes | codex | standalone-python | antigravity # target-dir: where your project lives (default: current dir) # -Yes accept all wizard defaults (safe for CI) # -Reconfigure re-run the wizard on an existing project @@ -24,7 +24,7 @@ $Here = Split-Path -Parent $MyInvocation.MyCommand.Path $ValidAdapters = @( 'claude-code', 'cursor', 'windsurf', - 'opencode', 'openclaw', 'hermes', + 'opencode', 'openclaw', 'hermes', 'codex', 'standalone-python', 'antigravity' ) if ($Adapter -notin $ValidAdapters) { @@ -72,6 +72,33 @@ switch ($Adapter) { 'hermes' { Copy-Item (Join-Path $Src 'AGENTS.md') (Join-Path $TargetDir 'AGENTS.md') -Force } + 'codex' { + $agentsMd = Join-Path $TargetDir 'AGENTS.md' + if (Test-Path $agentsMd -PathType Leaf) { + Write-Host " ~ $agentsMd already exists — skipping (codex reads whatever is there)" + } else { + Copy-Item (Join-Path $Src 'AGENTS.md') $agentsMd -Force + Write-Host " + AGENTS.md" + } + + $agentsDir = Join-Path $TargetDir '.agents' + New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null + $skillsSrc = Join-Path $TargetAgent 'skills' + $skillsDst = Join-Path $agentsDir 'skills' + + if (Test-Path $skillsDst) { + Copy-Item -Path (Join-Path $skillsSrc '*') -Destination $skillsDst -Recurse -Force + Write-Host " ~ merged .agent/skills into existing .agents/skills" + } else { + try { + New-Item -ItemType SymbolicLink -Path $skillsDst -Target $skillsSrc -ErrorAction Stop | Out-Null + Write-Host " + .agents/skills -> $skillsSrc" + } catch { + Copy-Item -Path $skillsSrc -Destination $skillsDst -Recurse + Write-Host " + .agents/skills (copy; symlink not supported here)" + } + } + } 'standalone-python' { Copy-Item (Join-Path $Src 'run.py') (Join-Path $TargetDir 'run.py') -Force } diff --git a/install.sh b/install.sh index b1183b5..c3fef2a 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # install.sh — copy an adapter into the consuming project, then run the onboarding wizard # Usage: ./install.sh [target-dir] [--yes] [--reconfigure] -# adapter-name: claude-code | cursor | windsurf | opencode | openclaw | hermes | pi | standalone-python | antigravity +# adapter-name: claude-code | cursor | windsurf | opencode | openclaw | hermes | pi | codex | standalone-python | antigravity # target-dir: where your project lives (default: current dir) # --yes accept all wizard defaults without prompting (safe for CI) # --reconfigure re-run the wizard even if PREFERENCES.md is already filled @@ -13,7 +13,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)" if [[ -z "$ADAPTER" ]]; then echo "usage: $0 [target-dir]" >&2 - echo "adapters: claude-code cursor windsurf opencode openclaw hermes pi standalone-python antigravity" >&2 + echo "adapters: claude-code cursor windsurf opencode openclaw hermes pi codex standalone-python antigravity" >&2 exit 2 fi @@ -85,6 +85,31 @@ case "$ADAPTER" in echo " + .pi/skills (copy; symlink not supported here)" fi ;; + codex) + # codex, pi, hermes, and opencode can all read the same AGENTS.md + if [[ -f "$TARGET/AGENTS.md" ]]; then + echo " ~ $TARGET/AGENTS.md already exists — skipping (codex reads whatever is there)" + else + cp "$SRC/AGENTS.md" "$TARGET/AGENTS.md" + echo " + AGENTS.md" + fi + mkdir -p "$TARGET/.agents" + SKILLS_SRC="$(cd "$TARGET/.agent/skills" && pwd)" + SKILLS_DEST="$TARGET/.agents/skills" + if [[ -L "$SKILLS_DEST" ]]; then + ln -sfn "$SKILLS_SRC" "$SKILLS_DEST" + echo " + .agents/skills -> $SKILLS_SRC" + elif [[ -d "$SKILLS_DEST" ]]; then + cp -R "$SKILLS_SRC/." "$SKILLS_DEST/" + echo " ~ merged .agent/skills into existing .agents/skills" + elif ln -sfn "$SKILLS_SRC" "$SKILLS_DEST" 2>/dev/null; then + echo " + .agents/skills -> $SKILLS_SRC" + else + mkdir -p "$SKILLS_DEST" + cp -R "$SKILLS_SRC/." "$SKILLS_DEST/" + echo " + .agents/skills (copy; symlink not supported here)" + fi + ;; standalone-python) cp "$SRC/run.py" "$TARGET/run.py" ;; From 2e0a707d59adb692f1ca21ecaf0fb7804414f0a6 Mon Sep 17 00:00:00 2001 From: codejunkie99 Date: Thu, 23 Apr 2026 13:05:11 +0530 Subject: [PATCH 2/2] review: address codex-adapter review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-model review (Claude + Codex adversarial) flagged real gaps in the codex adapter. Fixes here: 1. AGENTS.md collision was "skip if exists" — silent-success when the user already has an Aider/Amp/Cline/old-codex AGENTS.md. Mirror the openclaw pattern: if existing AGENTS.md references .agent/, leave alone; if not, print a mergeable snippet and don't overwrite. Same change in install.sh and install.ps1. 2. .agents/skills/ sync was destructive (cp -R overlay) and non-healing (deleted skills lingered forever). Switch to rsync --delete when available, fall back to rm+cp, so the .agents/skills mirror stays in sync with the .agent/skills source of truth. 3. install.ps1 used `Test-Path` then `Copy-Item ... -Force`, which on PowerShell 5.1 (Windows default) writes through a symlink into the target — silently mutating .agent/skills via the link. Detect ReparsePoint via Get-Item.Attributes BEFORE Remove-Item; use .NET Directory.Delete($path, false) on links so only the link is removed, never the target. 4. adapters/codex/AGENTS.md: add a Windows note about python vs python3 (stock Windows only ships `python` on PATH). 5. Citation: link OpenAI's https://developers.openai.com/codex/skills in the adapter docs to make the .agents/skills/ contract explicit. Smoke tested: fresh install, re-run with real-dir mirror + orphan skill (orphan deleted via rsync), AGENTS.md collision branches both covered. --- adapters/codex/AGENTS.md | 14 +++++++---- install.ps1 | 51 ++++++++++++++++++++++++++++++++++++---- install.sh | 36 +++++++++++++++++++++++----- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/adapters/codex/AGENTS.md b/adapters/codex/AGENTS.md index f64568f..14bbc57 100644 --- a/adapters/codex/AGENTS.md +++ b/adapters/codex/AGENTS.md @@ -3,6 +3,9 @@ Codex reads `AGENTS.md` before doing any work. This file points it at the portable brain in `.agent/`. +> **Python invocation**: examples below use `python3`. On stock Windows +> only `python` is on PATH; use whichever resolves on your system. + ## Startup (read in order) 1. `.agent/AGENTS.md` — the map 2. `.agent/memory/personal/PREFERENCES.md` — user conventions @@ -10,10 +13,13 @@ the portable brain in `.agent/`. 4. `.agent/protocols/permissions.md` — hard rules ## Skills -Codex scans `.agents/skills/` for repository-scoped skills. The install -script symlinks or copies `.agents/skills` from `.agent/skills` so the -portable brain remains the one source of truth. Load a full `SKILL.md` -only when its triggers match the task (progressive disclosure). +Codex scans `.agents/skills/` for repository-scoped skills (per +[OpenAI Codex docs](https://developers.openai.com/codex/skills)). The +install script symlinks or syncs `.agents/skills` from `.agent/skills` +so the portable brain remains the one source of truth. Load a full +`SKILL.md` only when its triggers match the task (progressive +disclosure). Edit skills in `.agent/skills/` — `.agents/skills/` is a +mirror and re-running the installer will sync it back. ## Recall before non-trivial tasks For deploy / ship / migration / schema / timestamp / date / failing test / diff --git a/install.ps1 b/install.ps1 index a1f3e42..9d9b3de 100644 --- a/install.ps1 +++ b/install.ps1 @@ -73,22 +73,65 @@ switch ($Adapter) { Copy-Item (Join-Path $Src 'AGENTS.md') (Join-Path $TargetDir 'AGENTS.md') -Force } 'codex' { + # Mirror install.sh: openclaw-style merge-or-alert on existing AGENTS.md. $agentsMd = Join-Path $TargetDir 'AGENTS.md' if (Test-Path $agentsMd -PathType Leaf) { - Write-Host " ~ $agentsMd already exists — skipping (codex reads whatever is there)" + $existing = Get-Content -Path $agentsMd -Raw -ErrorAction SilentlyContinue + if ($existing -match '\.agent/') { + Write-Host " ~ AGENTS.md already references .agent/ — leaving alone" + } else { + Write-Host " ! AGENTS.md exists but does not reference .agent/; not overwriting." + Write-Host " merge this block into your AGENTS.md to wire the brain:" + Write-Host " ---8<---" + Get-Content -Path (Join-Path $Src 'AGENTS.md') | ForEach-Object { Write-Host " $_" } + Write-Host " --->8---" + } } else { Copy-Item (Join-Path $Src 'AGENTS.md') $agentsMd -Force Write-Host " + AGENTS.md" } + # Codex scans .agents/skills/ — keep the portable brain authoritative. $agentsDir = Join-Path $TargetDir '.agents' New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null $skillsSrc = Join-Path $TargetAgent 'skills' $skillsDst = Join-Path $agentsDir 'skills' - if (Test-Path $skillsDst) { - Copy-Item -Path (Join-Path $skillsSrc '*') -Destination $skillsDst -Recurse -Force - Write-Host " ~ merged .agent/skills into existing .agents/skills" + # Detect symlink/junction BEFORE Remove-Item: on PowerShell 5.1 + # `Remove-Item -Recurse` on a symlink can delete the target's + # contents. Use IsLink detection + .NET Delete (or repoint). + $skillsDstItem = Get-Item -LiteralPath $skillsDst -Force -ErrorAction SilentlyContinue + $isLink = $false + if ($skillsDstItem) { + $isLink = ($skillsDstItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq [System.IO.FileAttributes]::ReparsePoint + } + + if ($skillsDstItem -and $isLink) { + # Existing link: delete the link only (NOT its target), then re-create. + try { + [System.IO.Directory]::Delete($skillsDst, $false) + } catch { + # Some Windows configurations require File.Delete for file-style links. + [System.IO.File]::Delete($skillsDst) + } + try { + New-Item -ItemType SymbolicLink -Path $skillsDst -Target $skillsSrc -ErrorAction Stop | Out-Null + Write-Host " + .agents/skills -> $skillsSrc (relinked)" + } catch { + Copy-Item -Path $skillsSrc -Destination $skillsDst -Recurse + Write-Host " + .agents/skills (copy; symlink not supported here)" + } + } elseif ($skillsDstItem) { + # Real directory: sync with delete-orphans by replacing it whole. + # Removing a real directory with -Recurse is safe; only links are dangerous. + Remove-Item -LiteralPath $skillsDst -Recurse -Force + try { + New-Item -ItemType SymbolicLink -Path $skillsDst -Target $skillsSrc -ErrorAction Stop | Out-Null + Write-Host " + .agents/skills -> $skillsSrc (replaced stale copy)" + } catch { + Copy-Item -Path $skillsSrc -Destination $skillsDst -Recurse + Write-Host " ~ replaced .agents/skills with current .agent/skills (no symlink)" + } } else { try { New-Item -ItemType SymbolicLink -Path $skillsDst -Target $skillsSrc -ErrorAction Stop | Out-Null diff --git a/install.sh b/install.sh index c3fef2a..cd9d29c 100755 --- a/install.sh +++ b/install.sh @@ -86,27 +86,51 @@ case "$ADAPTER" in fi ;; codex) - # codex, pi, hermes, and opencode can all read the same AGENTS.md + # codex reads AGENTS.md (like pi, hermes, opencode). Many other tools + # also write AGENTS.md (aider, amp, cline, existing codex setups), so + # we follow the openclaw pattern: merge-or-alert, never blind overwrite, + # never blind skip. if [[ -f "$TARGET/AGENTS.md" ]]; then - echo " ~ $TARGET/AGENTS.md already exists — skipping (codex reads whatever is there)" + if grep -q '\.agent/' "$TARGET/AGENTS.md" 2>/dev/null; then + echo " ~ AGENTS.md already references .agent/ — leaving alone" + else + echo " ! AGENTS.md exists but does not reference .agent/; not overwriting." + echo " merge this block into your AGENTS.md to wire the brain:" + echo " ---8<---" + sed 's/^/ /' "$SRC/AGENTS.md" + echo " --->8---" + fi else cp "$SRC/AGENTS.md" "$TARGET/AGENTS.md" echo " + AGENTS.md" fi + + # Codex scans .agents/skills/ (plural) for repo-scoped skills — per + # OpenAI docs https://developers.openai.com/codex/skills. Keep the + # portable brain authoritative: .agents/skills mirrors .agent/skills. mkdir -p "$TARGET/.agents" SKILLS_SRC="$(cd "$TARGET/.agent/skills" && pwd)" SKILLS_DEST="$TARGET/.agents/skills" if [[ -L "$SKILLS_DEST" ]]; then + # Existing symlink: repoint at current .agent/skills (cheap, safe) ln -sfn "$SKILLS_SRC" "$SKILLS_DEST" echo " + .agents/skills -> $SKILLS_SRC" elif [[ -d "$SKILLS_DEST" ]]; then - cp -R "$SKILLS_SRC/." "$SKILLS_DEST/" - echo " ~ merged .agent/skills into existing .agents/skills" + # Real directory from a prior copy-fallback install: sync with + # delete-orphans so removed/renamed skills don't linger. Use rsync + # if available, otherwise rm+cp as a safe-but-blunt replacement. + if command -v rsync >/dev/null 2>&1; then + rsync -a --delete "$SKILLS_SRC/" "$SKILLS_DEST/" + echo " ~ synced .agent/skills → .agents/skills (rsync --delete)" + else + rm -rf "$SKILLS_DEST" + cp -R "$SKILLS_SRC" "$SKILLS_DEST" + echo " ~ replaced .agents/skills with current .agent/skills (no rsync)" + fi elif ln -sfn "$SKILLS_SRC" "$SKILLS_DEST" 2>/dev/null; then echo " + .agents/skills -> $SKILLS_SRC" else - mkdir -p "$SKILLS_DEST" - cp -R "$SKILLS_SRC/." "$SKILLS_DEST/" + cp -R "$SKILLS_SRC" "$SKILLS_DEST" echo " + .agents/skills (copy; symlink not supported here)" fi ;;