Skip to main content

Cross-runtime AI agent skill housekeeping: spot duplicates, drift, broken symlinks, junk, and stale skills across Claude Code, Codex, Cursor, OpenClaw, and more.

Project description

30x Skill Doctor

Spring-cleaning for your AI agent skill library.

If you use Claude Code, Codex, Cursor, OpenClaw, or any combination of them, your ~/.claude/skills/, ~/.codex/skills/, ~/.cursor/skills/, and plugin directories are probably a graveyard. The same skill installed three times under three different names. Old version in one runtime, newer version in another. Symlinks pointing at deleted files.

30x-skill-doctor scans the lot, surfaces what's wrong, and walks you through a fix — interactively, with a backup, and a one-line undo.

Demo


Three steps

1. Install

pipx install skill-doctor

Or with uv:

uv tool install skill-doctor

Plain pip works too: pip install skill-doctor.

Optional: install asm (npm install -g agent-skill-manager) to unlock the SKILL.md write-quality dimension. Everything else works without it.

2. See what you have

skill-doctor

You'll get a one-screen report like this:

📂 You have 453 skills across 8 runtimes:

    Claude Code        154
    OpenClaw           151
    Plugin (Claude)     48
    Agents              44
    Codex               31
    Plugin (Codex)      14
    OpenCode             9
    Cursor               2

  Categories:
    Other (147) | SEO (91) | Marketing (59) | Dev (47) | Ads (41) | ...

🟠 70 duplicate groups (187 instances)
🟡 38 drift conflicts (same name, different content)
✗  25 broken symlinks

📋 SKILL.md write quality
   B:23 C:262 D:150 F:18

→ Clean up: skill-doctor clean

Dimensions with zero findings are hidden automatically. Clean machines just see the inventory and a green checkmark.

3. Tidy up

skill-doctor clean

It walks through every issue interactively:

[1/48] Merge ads-google (claude)
  ~/.claude/skills/ads-google → symlink to ~/.openclaw/skills/ads-google
  Apply? [y/N/q/a (a = yes-to-all-of-this-type)]
  • y — apply this one
  • N — skip (default; bare Enter also skips)
  • q — stop right here
  • a — yes-to-all of this action type (no more prompts for it)

Anything destructive is mv'd to ~/.skill-doctor/backup/<timestamp>/ first, not removed. Roll back the last apply with:

skill-doctor undo

Need to recover an older one? skill-doctor undo --pick.


Seven dimensions it checks

What it catches What clean does about it
📂 Categories Auto-tags every skill (SEO / Ads / Marketing / Dev / …)
🟠 Duplicates Identical SKILL.md (sha256) under multiple runtimes Replace copies with symlinks to a smart-elected master
🟡 Drift Same name, different content (e.g. v1.1 in Claude, v1.0 in OpenClaw) Surface only — you choose the source of truth
Broken Symlink whose target doesn't exist anymore Remove the dead link
🗑 Junk macOS * 2.md, .DS_Store, vim swap files, etc., anywhere in the tree Backup and delete
🕰 Stale Skill directory untouched for > 90 days (mtime) Just flagged — your call
📋 Write quality SKILL.md hygiene + suggested fixes (cached, near-instant after first scan)

Master election (the dedup heart)

When several copies of the same skill exist, one becomes the canonical source the others symlink to. The election score is transparent:

score = version (40%) + inbound symlinks (30%) + path depth (15%) + mtime (15%)

Tweak weights in ~/.skill-doctor/config.toml.


Methodology

Each diagnostic dimension is documented as a four-part contract: definition (what is being asserted), detection (the algorithm that produces the assertion), provenance (the standards or specifications the algorithm relies on), and failure modes (where the assertion can be wrong, and the direction of error). Objective facts derived from formal specifications are kept separate from heuristics calibrated by the maintainer.

📂 Categories

Definition. Every discovered skill receives a single label from a closed set: SEO, Marketing, Dev, Ads, Deploy, Data, Design, AI/Video, Other, Uncategorized.

Detection. Two-pass classifier evaluated in priority order:

  1. Path-prefix match against a hand-curated lookup table (30x-seo-* → SEO, ads-* → Ads, etc.).
  2. Description-keyword match against a per-category bag of substrings.
  3. Fallback to Other if at least one keyword fires; Uncategorized if the skill has no description.

Provenance. No external specification governs categorization. The lookup table lives in src/skill_doctor/classify.py and is the canonical authority. This dimension is a viewer convenience, not a correctness check.

Failure modes. Skills with bespoke naming and no domain keywords land in Other. On a 453-skill library, ~32% currently fall into Other; this is a classifier-coverage limit, not a user error.

🟠 Duplicates

Definition. A duplicate group is a set of two or more SKILL.md files with byte-identical normalized bodies AND identical directory basenames.

Detection.

key  = ( sha256( normalize(body) ), basename(skill_dir) )
dup  = { key | |instances(key)| ≥ 2 }

Body normalization strips trailing whitespace per line and collapses leading and trailing blank lines. Identical basenames are required to prevent unrelated skills with coincidentally-identical content from being merged.

Master election. For each duplicate group, exactly one instance is elected as the canonical source by a weighted score across four signals:

Signal Weight Rationale
metadata.version 40% Higher declared SemVer is the strongest signal of "newest writeable copy".
Inbound symlinks 30% If N sibling instances already resolve to this path, it is in fact the source.
Path depth 15% Monorepo conventions place canonical sources at deeper paths (monorepo/skills/x vs runtime/x).
mtime recency 15% Tiebreaker for instances with otherwise equal provenance.

All weights are configurable via ~/.skill-doctor/config.toml.

Provenance. SHA-256 is specified in NIST FIPS PUB 180-4 (August 2015). The content-addressable equivalence model follows Git's object model (Chacon & Straub, Pro Git, 2nd ed., ch. 10).

Failure modes. Detection is exact: SHA-256 collision probability is bounded at ≈ 2⁻²⁵⁶ and is treated as zero. Master election, by contrast, is a heuristic and may diverge from a user's project-specific source-of-truth convention; manual override is supported by editing the resulting plan before applying.

🟡 Drift

Definition. Two or more instances share a directory basename but differ in SHA-256 hash — i.e., the same skill identity has divergent content across runtimes.

Detection. Group by basename; emit a drift group whenever the set of distinct hashes inside that group has cardinality ≥ 2.

Policy. Drift is never auto-resolved. Divergence is frequently intentional — for example, when a user customizes a skill for a specific runtime — and silent merging risks data loss. The tool surfaces drift; the user designates the source of truth.

Provenance. Detection follows the same content-addressable model as duplicate detection. The non-resolution policy is a maintainer-chosen safety invariant, not derived from an external spec.

Failure modes. A single-byte difference (e.g., a trailing space) is sufficient to register drift. False-positive risk is non-trivial; this is a deliberate trade against the larger risk of silent data loss.

✗ Broken symlinks

Definition. A path that is itself a symlink, but whose target does not resolve to an existing filesystem entry.

Detection. path.is_symlink() and not path.exists(). Equivalent to POSIX find <root> -xtype l.

Policy. clean calls unlink(path). Symlinks store no payload, so removal is loss-free.

Provenance. POSIX.1-2017 (IEEE Std 1003.1™-2017) §4.1.7 (symbolic link resolution) and §lstat. Confirmed by macOS lstat(2).

Failure modes. None at the detection layer. The relation is exact under POSIX semantics.

🗑 Junk files

Definition. Files inside a skill tree that are not user content but rather artefacts of the host filesystem, sync layer, or editor.

Detection. Filename matches against a curated regex catalog:

Pattern Source
`* \d+.(md json
.DS_Store macOS Finder metadata (Apple Support HT204016)
._* (AppleDouble) macOS extended-attribute side-files
*.swp, *.swo Vim swap (:help swap-file)
*~, *.un~ Vim backup, undo
__MACOSX/ macOS-injected zip metadata directory

Scan is recursive across the entire skill directory tree (Path.rglob).

Provenance. Each entry corresponds to a documented behavior of the producing system. The set is curated against observed pollution on real machines, not generated from a single spec.

Failure modes. Deliberately-named files matching the patterns (e.g., top-10 2.md) will be flagged. Mitigated by mandatory pre-deletion backup under ~/.skill-doctor/backup/<timestamp>/ and one-command undo.

🕰 Stale

Definition. A skill whose tree has not received any modification within a configurable time window — flagged for review, not declared obsolete.

Detection. max(stat.st_mtime for f in tree) compared against now - threshold. Default threshold is 90 days; override with --stale-days N.

Provenance. POSIX stat(2) modification time. The 90-day default is a maintainer-chosen heuristic, not specification-derived.

Failure modes. This is the weakest dimension epistemologically. Modification time is not invocation time; the tool has no access to runtime telemetry. A skill that is stable, correct, and invoked daily will be flagged stale if its files have not been edited in 90 days. The output should be treated as a "consider reviewing" prompt, never as a deletion recommendation.

📋 Write quality

Definition. A per-skill score (B/C/D/F) and ordered list of concrete rewrite suggestions, evaluating SKILL.md hygiene against the canonical agent-skill authoring specification.

Detection. Aligned with Anthropic's official skill-creator specification — the source of truth for what constitutes a well-formed SKILL.md. Results are cached per file by mtime; first run on a 453-skill library takes ≈ 40 s, subsequent runs ≈ 0.4 s with no edits.

Provenance. Anthropic skill-creator skill defines the rule set (frontmatter required fields, body length, description format, naming conventions, progressive-disclosure structure).

Failure modes. Quality scoring is opt-in: requires the underlying evaluator to be available on PATH. When unavailable, the dimension is silently elided and the remaining six dimensions function unchanged.


Out of scope

Skill Doctor explicitly does not:

  • Measure invocation frequency. Runtimes do not expose skill-level telemetry; modification time is not a substitute.
  • Score overall "skill quality" beyond the SKILL.md hygiene captured above. Behavioral evaluation belongs to runtime-specific tooling.
  • Detect malicious or adversarial patterns. For supply-chain or prompt- injection concerns, use a dedicated security auditor.
  • Modify your filesystem without consent. Every state-changing operation is interactive (clean), backed up to ~/.skill-doctor/backup/<timestamp>/, and reversible via skill-doctor undo.

Common flags

skill-doctor                       # default report
skill-doctor --full                # one row per skill
skill-doctor --full --no-truncate  # don't shorten long paths
skill-doctor --version             # version info
skill-doctor --runtime claude      # filter by runtime
skill-doctor --category seo        # filter by category
skill-doctor --json                # machine-readable
skill-doctor --stale-days 90       # change stale threshold
skill-doctor --quality-n 10        # quick uncached quality sample
skill-doctor clean                 # interactive tidy
skill-doctor clean --yes           # non-interactive (3-second cancel)
skill-doctor undo                  # roll back last apply
skill-doctor undo --pick           # pick a past backup

Custom runtime paths

Have skills somewhere unusual? Drop them into ~/.skill-doctor/config.toml:

[[extra_runtimes]]
path = "~/my-skills"
runtime = "unknown"   # or any known tag: claude / codex / openclaw / agents / ...
glob = "*"

[weights]
version = 0.40
incoming_links = 0.30
path_depth = 0.15
mtime_freshness = 0.15

FAQ

Will it auto-delete anything? No. Everything destructive is mv'd to ~/.skill-doctor/backup/ first. Run skill-doctor undo and the last apply is fully reversible.

Why no auto-fix for "drift"? Two divergent copies might be intentional (you tweaked one for a specific runtime). The tool refuses to guess; it only flags.

First run takes ~40 seconds — what's it doing? Scoring every SKILL.md for write quality. Subsequent runs hit a per-file mtime cache and finish in < 1 second. Edit one SKILL.md and only that one is re-scored.

My skill folder isn't on the default list. Add it under [[extra_runtimes]] in ~/.skill-doctor/config.toml.

Where does the write-quality score come from? From asm (an open-source evaluator aligned with the Anthropic skill-creator spec). If asm isn't installed, the column is simply omitted — the other six dimensions don't depend on it.


Developer quick-start

git clone https://github.com/norahe0304-art/30x-skill-doctor.git
cd 30x-skill-doctor
uv sync
uv run --no-editable pytest          # 32 tests
uv run --no-editable ruff check .
uv run --no-editable skill-doctor    # try it on your machine

Project layout in AGENTS.md (project doctrine) and src/skill_doctor/AGENTS.md (module map).

License: MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

skill_doctor-0.3.1.tar.gz (61.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

skill_doctor-0.3.1-py3-none-any.whl (39.3 kB view details)

Uploaded Python 3

File details

Details for the file skill_doctor-0.3.1.tar.gz.

File metadata

  • Download URL: skill_doctor-0.3.1.tar.gz
  • Upload date:
  • Size: 61.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for skill_doctor-0.3.1.tar.gz
Algorithm Hash digest
SHA256 42d99c8aeb9355f43713a1232fc9bc4e58de5542dc834f9a6f90865ede3e5d89
MD5 18b06d588f6b4487cf854166e3b22fa5
BLAKE2b-256 13e2b4608b484fc1bc5957f1ee593a34b9c9bd7957a06c31e4c558ae09587041

See more details on using hashes here.

File details

Details for the file skill_doctor-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: skill_doctor-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 39.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for skill_doctor-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e1f208a19dcb95825cedd5465d1ef5ae031361326f4247408223d0cf2e6ff44a
MD5 5ea9f9e7386d4497b4d1d820fca6e76f
BLAKE2b-256 d1b2838a8568ac30a00207ba5be82dedd7b0a429145bb02e565de3fdb462f3f9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page