cd /news/developer-tools/i-built-an-efficient-graph-search-pl… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-97101] src=github.com β†— pub= topic=developer-tools verified=true sentiment=↑ positive

I built an efficient graph-search plugin for Claude Code skills

Developer Daniel Lublinsky released Skill Atlas, a free, open-source graph-search plugin for Claude Code that adds a 'searchable' tier between enabled and disabled skills, cutting per-session token usage from 2,544 to 338 tokens (βˆ’86.7%) across 53 skills by keeping only 6 enabled. The plugin, installable via 'claude plugin marketplace add danielLublinsky/Skill_Atlas', builds a graph index and renders an HTML atlas, with a search costing about 2.4k tokens only when invoked.

read5 min views1 publishedAug 14, 2026
I built an efficient graph-search plugin for Claude Code skills
Image: Michielbdejong (auto-discovered)

A third tier for Claude Code skills β€” dormant, zero tokens, still findable. Search a graph of your collection instead of pre it.

53 skills, 6 kept enabled: 2,544 β†’ 338 tokens per session (βˆ’86.7%). The other 47 cost nothing until asked for.

claude plugin marketplace add danielLublinsky/Skill_Atlas
claude plugin install skill-atlas@skill-atlas

Python 3 only β€” no dependencies, no network (d3 is vendored). Later: claude plugin update skill-atlas@skill-atlas

.

From a local clone (for hacking on it) #

git clone https://github.com/danielLublinsky/Skill_Atlas.git
claude plugin marketplace add ./Skill_Atlas
claude plugin install skill-atlas@skill-atlas
Command What it does
/skill-atlas
Build the graph, categorize what's new, render atlas.html . Run this once to set up.
/skill-atlas:edit-searchable
Move plugins in and out of the searchable tier, interactively.
/skill-atlas:skill-search
Find the right skill for a task across the whole library, dormant tier included. Usually you never type this β€” Claude calls it on its own before a nontrivial task.
(automatic)
A SessionStart hook keeps everything fresh after that.

Then open ./.claude/skill-atlas/atlas.html

in a browser, and just ask for a task β€” the search runs itself, names the skill it picked in one line, and gets on with the work.

Without the plugin (Makefile) #

make build    # graph.json + catalog/ from your live manifests
make render   # atlas.html
make check    # CI gate: exit 1 on any broken reference or dangling mention
make test     # unit suite against fixtures β€” never touches the real ~/.claude

Claude Code injects every enabled skill's description into every session β€” ~48 tokens each. At 100+ skills that's thousands of tokens you pay for constantly, and near-duplicates quietly compete for the model's attention.

Disabling fixes the cost and loses the skill. So Skill Atlas adds a tier in between:

Tier In context? Findable? Cost / session
🟒 enabled
yes natively ~48 tokens each
🟣 searchable
no
via skill-search
0
⚫ disabled
no no 0

A searchable skill is dormant β€” its description never enters your context, but it stays discoverable on demand. All that advertises the whole dormant tier is one ~50-token line at session start.

flowchart LR
    A(["🧭 a task arrives<br/><i>β€œresolve this merge conflict”</i>"])

    subgraph R1["1️⃣ read the index Β· ~1.6k tok"]
        I["<b>_index.md</b> β€” 14 lines<br/><i>name Β· count Β· what it covers Β· member tokens</i>"]
    end

    subgraph R2["2️⃣ read one shard Β· ~0.6k tok"]
        S["<b>version-control.md</b><br/>using-git-worktrees<br/>resolving-merge-conflicts ← hit"]
    end

    X["the other 13 shards<br/><i>~10k tok Β· never opened</i>"]

    A --> I
    I -- "pick ONE category" --> S
    I -. skipped .-> X
    S --> E["🟒 <b>enabled</b><br/><i>invoke natively</i>"]
    S --> F["🟣 <b>searchable</b><br/><i>read its path,<br/>follow as instructions</i>"]

    classDef file stroke:#8a8578,stroke-width:1.5px
    classDef ghost fill:transparent,stroke:#8a8578,stroke-width:1.5px
    classDef hit fill:#1baf7a26,stroke:#1baf7a,stroke-width:2px
    classDef dormant fill:#8a5cd626,stroke:#8a5cd6,stroke-width:2px
    classDef muted fill:transparent,stroke:#8a8578,stroke-width:1px,stroke-dasharray:4 3
    class I,S file
    class A ghost
    class E hit
    class F dormant
    class X muted
    style R1 fill:#3987e514,stroke:#3987e5,stroke-width:1.5px
    style R2 fill:#3987e514,stroke:#3987e5,stroke-width:1.5px

A search costs ~2.4k tokens β€” the index (1.6k), one shard (~0.6k) and the search skill itself (0.4k) β€” and you pay it only when a search happens. The alternative it replaces is every dormant description sitting in context from session start, billed whether you search or not. The arbitrage is real when searches are occasional; it is not free, and the numbers above are measured rather than estimated.

One shard is the norm. A second is opened only when the entry you found lists a category you haven't read β€” the catalog pointing, not the model guessing β€” and never a third. Counts overlap on purpose, so the pick doesn't have to land on the right bucket, only a right one. Tier-off skills are excluded when shards are written, so a disabled plugin can never return through a side door. The result is announced in one line before the work continues: search is a lookup inside a task, not a deliverable.

The model categorizes once. The first run drafts 8–12 categories named for user-intent task shapes, not products, then freezes the taxonomy. Later runs only file new skills and re-confirm the ones whose description changed β€” each assignment carries a hash of the description it was made against.

Discovery is manifest-driven β€” installed_plugins.json

β†’ each plugin.json

β†’ settings β€” so marketplace catalogues and stale cached versions never pollute the picture. (The naive "every directory with a SKILL.md" count over-counts by ~60% on a real machine: build_graph.py --naive-count

.)

Skills are nodes. The edges are the interesting part:

Edge Caught
πŸ”— references β€” a skill β†’ its own bundled files
broken when the file isn't there
πŸ’¬ mentions β€” a skill naming another skill
dangling when the target is disabled , unregistered or absent

Mention matching is deliberately strict β€” only backticked, skills/<name>

, or unambiguous hyphenated names, code fences stripped first. Loose matching produced 91 edges on a 41-skill collection, almost all of them ordinary English words. You also get duplicate names, orphans, and an exit code to gate CI on.

atlas.html

is self-contained and opens from file://

with zero network requests: scope (origin, state, breakage in red) and categories (hub-and-spoke β€” solid edge to the home category, dashed to the rest; search cat:<name>

to isolate).

What's really registeredβ€” discovery is manifest-driven, so unregistered copies and stale plugin caches surface as their own nodes instead of quietly counting.Broken bundlesβ€” areferences

edge whose file isn't on disk draws red, so a typo'dreferences/foo.md

is visible without opening anything.Why it didn't triggerβ€” tier is the node's fill (enabled Β· searchable Β· disabled), and duplicate names are called out in the footer with both nodes drawn.Read it in placeβ€” pin any skill or bundled file and hit** open markdown**to read the source in a popup without leaving the graph.

docs/ β€” component guide, numbered 1–9: start at

overview, then jump to the component you're touching (discovery, graph build, categorization, catalog & search, rendering, hooks).

DESIGN.md β€” the historical record for both phases: what was considered, what was chosen, what was dropped, and why

Apache-2.0 β€” see LICENSE.

D3 is vendored in vendor/d3.v7.min.js and inlined into every generated atlas.html

. It is ISC-licensed, Β© 2010-2023 Mike Bostock β€” see vendor/LICENSE-d3.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @daniel lublinsky 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/i-built-an-efficient…] indexed:0 read:5min 2026-08-14 Β· β€”