Managing, Updating, and Organizing Agent Skills
Organize and manage AI agent skills with symlinks, automated syncing, overlap detection, and version tracking using the skill-organizer CLI tool.
Join the DZone community and get the full member experience.
Join For FreeNowadays, using skill files (SKILL.md) is a common way to provide context and knowledge (or new capabilities and expertise, as the official skills specification website describes) to an LLM or agent.
From an infrastructure point of view, a skill is a folder containing a SKILL.md file and all the necessary files for it to work: scripts, references, etc. This folder must be in .agents/skills (or .claude/skills, or whatever name your agent tool uses).
skill-name/
├── SKILL.md # Required: metadata + instructions
├── scripts/ # Optional: executable code
├── references/ # Optional: documentation
├── assets/ # Optional: templates, resources
└── ... # Any additional files or directories
The agentic tools only read directories at the first level of the .agents/skills folder, not subfolders, so as you create or download more and more skills, the skills folder becomes something like this:
├── api-testing-helper/
├── astro-content-auditor/
├── changelog-writer/
├── cli-release-checklist/
├── commit-message-linter/
├── css-animation-recipes/
├── design-token-curator/
├── docker-debug-playbook/
├── docs-style-enforcer/
├── feature-flag-rollout-guide/
├── frontend-performance-reviewer/
├── markdown-link-fixer/
├── newsletter-copy-editor/
├── seo-meta-validator/
├── shell-script-safety-checker/
├── sitemap-consistency-check/
├── slide-deck-outline-helper/
├── social-card-generator/
├── static-site-migration-guide/
├── storybook-docs-curator/
├── tailwind-class-auditor/
├── test-flake-investigator/
├── translation-qa-assistant/
├── typescript-error-explainer/
├── ui-copy-tone-reviewer/
├── ux-research-note-summarizer/
├── visual-regression-triager/
├── vite-config-tuner/
├── webhook-payload-inspector/
├── workflow-automation-designer/
├── writing-style-harmonizer/
├── yaml-frontmatter-repair/
├── youtube-embed-optimizer/
├── zod-schema-scaffolder/
...
This makes it almost impossible to organize the skills however you want, for example, by keeping your own skills and third-party skills in separate folders, or by topic: coding skills, text skills, etc.
This is especially problematic when you have a lot of skills or multiple skill sources. For example, you may have some skills you created, some downloaded from the community, and some provided by your company.
If your company provides shared skills in a repo, you cannot just clone that repo into a folder in the skills directory. You need to copy or create a symlink for each skill folder into the skills directory, mixing them with any other skill and making it hard to know which are yours and which are from the company or third parties.
A Simple Solution: Create a Script or Use a Tool
To solve the organizational issue, I thought that having a multilevel subfolder structure in the skills directory would be a nice and simple solution, but as I mentioned before, the tools only read directories at the first level, so that is not possible.
Well, it is not possible directly, but we can use a simple and smart solution:
Create an Organized Skills Folder
Use a different folder to store the organized skills, for example, organized-skills.
Here, we can create as many folders and subfolders as we want. For example:
organized-skills/
├── generic
├── starter
├── my-skills/
│ ├── coding-skills/
│ │ ├── astro-performance-auditor/
│ │ └── typescript-error-explainer/
│ ├── text-skills/
│ │ ├── newsletter-copy-editor/
│ │ └── writing-style-harmonizer/
│ └── personal-workflows/
│ └── weekly-review-assistant/
├── company-skills/
│ ├── coding-skills/
│ │ ├── internal-api-checklist/
│ │ └── release-train-coordinator/
│ ├── compliance/
│ │ └── pii-review-helper/
│ └── onboarding/
│ └── engineering-ramp-up-guide/
├── community-skills/
│ ├── frontend/
│ │ ├── design-token-curator/
│ │ └── visual-regression-triager/
│ └── content/
│ └── markdown-link-fixer/
└── experimental/
└── research/
└── prompt-pattern-lab/
Keep the Sync
We should have a script or tool to create symlinks for each skill in the organized-skills folder, flattened into the .agents/skills folder.
For example, organized-skills/my-skills/coding-skills/astro-performance-auditor will be symlinked to .agents/skills/my-skills-coding-skills-astro-performance-auditor.
Resulting in something like this:
.agents/skills/
├── my-skills--coding--skills--astro-performance-auditor/
├── my-skills--coding--skills--typescript-error-explainer/
├── my-skills--text--skills--newsletter-copy-editor/
├── my-skills--text--skills--writing-style-harmonizer/
├── my-skills--personal--workflows--weekly-review-assistant/
├── company-skills--coding--skills--internal-api-checklist/
├── company-skills--coding--skills--release-train-coordinator/
├── company-skills--compliance--pii-review-helper/
├── company-skills--onboarding--engineering-ramp-up-guide/
├── community-skills--frontend--design-token-curator/
├── community-skills--frontend--visual-regression-triager/
├── community-skills--content--markdown-link-fixer/
├── experimental--research--prompt-pattern-lab/
├── IMPORTANT.md # to notice this is a generated folder with symlinks and not the real skills
This way, after run the script we can have the skills organized in folders however we want (in the .agents/organized-skills folder), and the tools can still read the skills from the flattened symlinks in the .agents/skills folder.
This is the script I use to create the symlinks. You can customize it however you want:
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SOURCE_DIR="$ROOT_DIR/skills-organized"
TARGET_DIR="$ROOT_DIR/skills"
DRY_RUN=0
if [[ -t 1 ]]; then
COLOR_RESET=$'\033[0m'
COLOR_GREEN=$'\033[32m'
COLOR_YELLOW=$'\033[33m'
COLOR_RED=$'\033[31m'
COLOR_BLUE=$'\033[34m'
COLOR_BOLD=$'\033[1m'
else
COLOR_RESET=''
COLOR_GREEN=''
COLOR_YELLOW=''
COLOR_RED=''
COLOR_BLUE=''
COLOR_BOLD=''
fi
usage() {
cat <<'EOF'
Usage: scripts/sync-organized-skills.sh [--dry-run]
Sync skills from skills-organized/ into flattened symlinks under skills/.
Rules:
- Any directory containing SKILL.md is treated as a skill.
- Directories without SKILL.md are treated as organization folders.
- Organization folders may be nested to any depth.
- A skill at skills-organized/personal/pr-create becomes skills/personal--pr-create.
- A skill at skills-organized/personal/training/hevy becomes skills/personal--training--hevy.
- Once a directory contains SKILL.md, it is treated as a terminal skill and child folders are not scanned.
- Only symlinks that point into skills-organized/ are managed and cleaned up.
EOF
}
format_path() {
printf '%s%s%s' "$COLOR_BOLD$COLOR_BLUE" "$1" "$COLOR_RESET"
}
print_status() {
local color=$1
local status=$2
local message=$3
printf '%b%-6s%b %s\n' "$color" "$status" "$COLOR_RESET" "$message"
}
ok() {
print_status "$COLOR_GREEN" "OK" "$1"
}
info() {
print_status "$COLOR_BLUE" "INFO" "$1"
}
warn() {
print_status "$COLOR_YELLOW" "WARN" "$1" >&2
}
error() {
print_status "$COLOR_RED" "ERROR" "$1" >&2
}
run() {
if [[ "$DRY_RUN" -eq 1 ]]; then
info "DRY-RUN $(printf '%q ' "$@")"
return 0
fi
"$@"
}
# Compute a stable relative path without depending on the caller's cwd.
relative_path() {
local source=$1
local target=$2
python3 -c 'import os,sys; print(os.path.relpath(sys.argv[1], sys.argv[2]))' "$source" "$target"
}
declare -A DESIRED_TARGETS=()
# Walk the tree until we reach a directory that contains SKILL.md.
# That directory is the terminal skill; child directories are not scanned.
collect_skills() {
local dir=$1
if [[ -f "$dir/SKILL.md" ]]; then
local rel_path
rel_path=$(relative_path "$dir" "$SOURCE_DIR")
local flat_name=${rel_path//\//--}
local target_path="$TARGET_DIR/$flat_name"
if [[ -n "${DESIRED_TARGETS[$target_path]+x}" ]]; then
error "Flattening collision: $(format_path "${dir#$ROOT_DIR/}") and $(format_path "${DESIRED_TARGETS[$target_path]#$ROOT_DIR/}") both map to $(format_path "${target_path#$ROOT_DIR/}")"
exit 1
fi
DESIRED_TARGETS["$target_path"]="$dir"
return
fi
local child
while IFS= read -r -d '' child; do
collect_skills "$child"
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
}
# Managed links are the ones created by this sync process: top-level symlinks in
# skills/ that resolve into skills-organized/. Broken managed links cannot be
# resolved, so we also inspect the raw symlink target and normalize it.
is_managed_symlink() {
local path=$1
[[ -L "$path" ]] || return 1
local resolved
resolved=$(realpath "$path" 2>/dev/null || true)
if [[ -n "$resolved" && ( "$resolved" == "$SOURCE_DIR" || "$resolved" == "$SOURCE_DIR"/* ) ]]; then
return 0
fi
local link_target normalized
link_target=$(readlink "$path") || return 1
if [[ "$link_target" = /* ]]; then
normalized=$(realpath -m "$link_target")
else
normalized=$(realpath -m "$(dirname "$path")/$link_target")
fi
[[ "$normalized" == "$SOURCE_DIR" || "$normalized" == "$SOURCE_DIR"/* ]]
}
sync_target() {
local target_path=$1
local source_path=$2
if [[ ! -d "$source_path" || ! -f "$source_path/SKILL.md" ]]; then
error "Refusing to link missing skill source $(format_path "${source_path#$ROOT_DIR/}")"
return
fi
local parent_dir
parent_dir=$(dirname "$target_path")
local desired_link
desired_link=$(relative_path "$source_path" "$parent_dir")
if [[ -L "$target_path" ]]; then
local current_resolved desired_resolved
current_resolved=$(realpath "$target_path" 2>/dev/null || true)
desired_resolved=$(realpath -m "$source_path")
if [[ "$current_resolved" == "$desired_resolved" ]]; then
ok "$(format_path "${target_path#$ROOT_DIR/}")"
return
fi
if is_managed_symlink "$target_path"; then
info "LINK $(format_path "${target_path#$ROOT_DIR/}") -> $(format_path "${source_path#$ROOT_DIR/}")"
run ln -sfn "$desired_link" "$target_path"
return
fi
warn "Skipping $(format_path "${target_path#$ROOT_DIR/}"): existing symlink is not managed"
return
fi
if [[ -e "$target_path" ]]; then
warn "Skipping $(format_path "${target_path#$ROOT_DIR/}"): target already exists and is not a managed symlink"
return
fi
info "CREATE $(format_path "${target_path#$ROOT_DIR/}") -> $(format_path "${source_path#$ROOT_DIR/}")"
run ln -s "$desired_link" "$target_path"
}
cleanup_stale_links() {
local entry
while IFS= read -r -d '' entry; do
if ! is_managed_symlink "$entry"; then
continue
fi
if [[ -n "${DESIRED_TARGETS[$entry]+x}" ]]; then
continue
fi
info "REMOVE $(format_path "${entry#$ROOT_DIR/}")"
run rm "$entry"
done < <(find "$TARGET_DIR" -mindepth 1 -maxdepth 1 -type l -print0 | sort -z)
}
main() {
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run)
DRY_RUN=1
;;
-h|--help)
usage
exit 0
;;
*)
error "Unknown argument: $1"
usage
exit 1
;;
esac
shift
done
if [[ ! -d "$SOURCE_DIR" ]]; then
error "Missing source directory: $(format_path "$SOURCE_DIR")"
exit 1
fi
if [[ ! -d "$TARGET_DIR" ]]; then
error "Missing target directory: $(format_path "$TARGET_DIR")"
exit 1
fi
collect_skills "$SOURCE_DIR"
local target_path
while IFS= read -r target_path; do
sync_target "$target_path" "${DESIRED_TARGETS[$target_path]}"
done < <(printf '%s\n' "${!DESIRED_TARGETS[@]}" | sort)
cleanup_stale_links
}
main "$@"
Watch File Changes
You need to run the script or tool every time you create, delete, or move a skill in the organized-skills folder.
We can automate this using polling or, even better, inotify-watcher on Unix and a service to detect any change in the folder and run the script to keep the symlinks in sync.
skill-organizer CLI Tool
To simplify this, I created a CLI tool (for Linux and Mac) that does everything we mentioned above, and much more. You can start using it in two simple steps:
1. Install it.
brew install sergiocarracedo/tap/skill-organizer
// or
npm i -g skill-organizer
2. Execute the onboard.
skill-organizer onboard
The tool will guide you to create a "project" of managed skills.
Managed Skills Opportunities
When you manage the skill with a custom script or with the tool I created, new opportunities appear as the tool can provide some logic.
Disable Skills
With the conventional skills structure, when you don't want to use a skill for a while, for example, because you want to check a similar one and you don't want both to work at the same time, you must remove the skillś folder from the .agents/skills folder.
Why not just use the CLI to add some metadata to skills and not sync a disabled skill to the folder Claude Code (or any other agentic tool) uses?
skill-organizer disable "personal/react/react-component"
// or reenable it with
skill-organizer enable "personal/react/react-component"
Overlap Evaluation
When you have a lot of skills, you will probably have skills that are similar or overlap, giving the agent opposite instructions. The skill-organizer CLI tools provide a prompt that runs on your Agent (using your subscriptions) to check the skills overlap and recommend to you who to solve them.
skill-organizer skill check-overlap
This command output will look like this:
# Overlap Analysis
Tool: OpenCode (opencode)
Analyzed skills: 33
Included disabled skills: no
# Summary
Found 8 overlap groups. The most critical is a near-duplicate Remotion skill at
two different paths. Several mattpocock skills form adjacent workflow clusters
(PRD lifecycle, planning/design, issue management). The personal
project-bootstrap skills (frontend vs React) share an identical toolchain.
Overall the skill set is well-organized with intentional separation across most
domains.
# Potential Overlap Groups
┌────────────────────────────────── Group 1 ───────────────────────────────────┐
| Skills: |
| - 3rdparty/remotion-best-practices |
| - 3rdparty/tools/remotion-best-practices |
| Overlap: ■ Duplicate (100/100) |
| Why the overlap: |
| Identical description text and name. The same 'Best practices for Remotion - |
| Video creation in React' skill exists at two different paths (3rdparty/ vs |
| 3rdparty/tools/). This is a true duplicate. |
| Recommendation: |
| Remove one copy. Merge any unique content if present, otherwise delete |
| 3rdparty/tools/remotion-best-practices and keep |
| 3rdparty/remotion-best-practices (or vice versa). |
└──────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────── Group 2 ───────────────────────────────────┐
| Skills: |
| - personal/coding/frontend-project-bootstrap |
| - personal/react/react-project-bootstrap |
| Overlap: ■ Partial (75/100) |
| Why the overlap: |
| Both describe bootstrapping projects with the exact same toolchain: pnpm, |
| Vite, Oxc (oxlint/oxfmt), Vitest, Playwright, Lefthook, commitlint, optional |
| Tailwind CSS and Storybook. The frontend version is a superset (covers SPA |
| or library), while the React version is a subset (covers React codebase). |
| These are partial duplicates with one being a specialization of the other. |
| Recommendation: |
| Consolidate into a single skill with a parameter or section for React vs |
| generic TypeScript frontend. Or keep both but make the frontend skill |
| reference the React one as its specialization. |
└──────────────────────────────────────────────────────────────────────────────┘
# Recommendations
- Remove the duplicate Remotion skill (3rdparty/tools/remotion-best-practices) —
it's an exact copy at a different path.
- Consolidate personal--coding--frontend-project-bootstrap and
personal--react--react-project-bootstrap into one parameterized skill, or make
the React version a thin wrapper that delegates to the frontend version.
- Add cross-references between PRD workflow skills (write-a-prd → prd-to-plan →
prd-to-issues) so users can easily navigate the full pipeline.
- Cross-reference coral skills with each other.
- Cross-reference mattpocock adjacent pairs (improve-architecture ↔
request-refactor-plan, qa ↔ github-triage) in their triggering descriptions.
- The remaining skills (agent-browser, mcp-builder, skill-judge,
golang-spf13-cobra, security-review, shaders-com, worktree,
ubiquitous-language, react-component, skill-creator, text-correction,
web-frontend-design, import-fika-post) are well-separated with no significant
overlap.
Updating
Skills usually don't include the version or the source in the metadata, which makes it hard to keep them updated, and maybe you don't remember where you downloaded it.
When you use the CLI tool to install the skills, it adds all that metadata, automating the check and update process, even checking the diff of the changes to be sure you are interested in that update.
skill-organizer check-updates
Conclusion
Using managed skills opens new opportunities to make our lives as AI engineers a little bit easier, reducing the noise. I started just trying to find a way to organize skills in folders to reduce that noise, but see the possibilities, and finally I created the skill-organizer tool. I hope you find it as useful as I do.
Opinions expressed by DZone contributors are their own.
Comments