DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • I Got Tired of Debugging Curl at 2 AM, So I Built a CLI
  • Amazon Quick: AWS's Agentic Workspace, Explained for Engineers
  • Observability for Agents and Workflows: Tracing Prompts, Tool Calls, and Business Outcomes End-to-End
  • Migrate a Hardcoded LangGraph Agent to LaunchDarkly AI Configs in 20 Minutes

Trending

  • Architecting Zero-Trust AI Agents: How to Handle Data Safely
  • Building a Zero-Cost Approval Workflow With AWS Lambda Durable Functions
  • Contract-First Integration: Building Scalable Systems With Flyway, OpenAPI, and Kafka
  • Run Gemma 4 on Your Laptop: A Hands-On Guide to Google's Latest Open Multimodal LLM
  1. DZone
  2. Coding
  3. Tools
  4. Managing, Updating, and Organizing Agent Skills

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.

By 
Sergio Carracedo user avatar
Sergio Carracedo
·
Jun. 09, 26 · Analysis
Likes (0)
Comment
Save
Tweet
Share
48 Views

Join the DZone community and get the full member experience.

Join For Free

Nowadays, 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).

Plain Text
 
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:

Plain Text
 
├── 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:

Plain Text
 
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:

Plain Text
 
.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:

Shell
 
#!/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.

Shell
 
brew install sergiocarracedo/tap/skill-organizer
// or
npm i -g skill-organizer


2. Execute the onboard.

Shell
 
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?

Shell
 
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.

PowerShell
 
skill-organizer skill check-overlap


This command output will look like this:

Plain Text
 
# 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.

Shell
 
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.

Command-line interface Tool

Opinions expressed by DZone contributors are their own.

Related

  • I Got Tired of Debugging Curl at 2 AM, So I Built a CLI
  • Amazon Quick: AWS's Agentic Workspace, Explained for Engineers
  • Observability for Agents and Workflows: Tracing Prompts, Tool Calls, and Business Outcomes End-to-End
  • Migrate a Hardcoded LangGraph Agent to LaunchDarkly AI Configs in 20 Minutes

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook