skill-inject (ski)
Local, model-agnostic automatic skill injection for Claude Code
and opencode.
A strong model often won't use a skill it should — on indirect prompts it
hand-rolls the task instead of invoking the skill built for it. ski is a local,
deterministic nudge that surfaces the right skill so the model actually reaches for it.
Why this exists
Skill systems dump every skill's description into the model's context and hope it picks
the right one. That works until it doesn't:
- The model skips skills it should use. On indirect prompts — "clean up this messy
CSV", "match our brand" — a capable host often just does the task by hand instead of
reaching for the skill built for it.
- It gets worse with more skills, and worse with weaker models. Picking one skill out
of a wall of descriptions is hard, and which one fires drifts with whatever model is
driving the session.
- Every description costs context, every turn — relevant or not.
ski does the picking for you. It embeds your prompt on CPU, ranks it against your skill
descriptions, and injects the matching skill only when one actually fits — same
result on any model, no API call, nothing leaving your machine. The model still decides
what to do with the skill; ski just makes sure the right one is in the room. Skills
the model finds on its own are tracked and never injected twice.
See it decide
ski scores every installed skill against your prompt and injects only the ones
above a fixed cutoff (-2.50 below); a higher score is a stronger match. Real ski why
output against a live library of 57 skills:
$ ski why "clean up this messy CSV"
xlsx -0.59 <- injected (clear winner)
pre-commit-setup -3.72 <- skipped
clean up this messy CSV never says spreadsheet or xlsx — the match is on meaning,
not vocabulary, and it lands far ahead of every other skill. Keyword or description
matching can't bridge that gap, and a model scanning 57 descriptions can easily miss it.
ski deliberately errs toward over-sending — a borderline skill is injected rather than
withheld, since a strong host simply ignores a skill it doesn't need but can't use one it
never saw, and when ski does inject it asks firmly ("invoke it now") rather than hedging.
$ ski why "what time is the meeting tomorrow"
handoff -3.08 <- best skill, still below the cutoff
An off-topic prompt leaves every skill under the cutoff, so ski injects nothing — no
false positives, no context pollution.
This repo is a single Rust binary (ski) plus the thin host adapters that drive it,
packaged as a one-plugin Claude Code marketplace. See DEVELOPING.md
for the dev workflow.
Speed and examples
Fast and entirely local — no API call, no token cost, nothing leaves your machine.
The whole pipeline (embed → retrieve → rerank) runs on CPU in about half a second per
prompt:
| operation (cold — every hook is a fresh process) | time |
|---|
rank + inject one prompt (ski hook) | ~0.61 s median |
| full index rebuild (57 skills) | ~0.73 s |
| incremental reindex, no change | ~0.19 s |
bge-small-en-v1.5 (384-dim) retrieval + jina-reranker-v1-turbo-en rerank, ~270 MB RAM.
Measured CPU-only on an AMD Ryzen AI MAX+ 395 — cold runs with model load included,
not warm microbenchmarks.
It matches on meaning, not keywords. Every row is a real ski why result against a
live library of 57 skills; a higher match score is a stronger match, and anything below
-2.50 is left out entirely (as in the off-topic example above):
| your prompt | skill ski injects | match score |
|---|
set up a python project with uv | uv-setup | 2.76 |
scaffold a new react typescript frontend | react-ts-setup | 3.38 |
how do I credit Claude in this git commit | git-attribution | 1.21 |
make an animated gif for slack | slack-gif-creator | 1.63 |
write a Word doc with a table of contents | docx | 0.12 |
extract tables from a pdf | pdf | 0.67 |
Reproduce any of it with ski index then ski why "<your prompt>".
Install
One command installs the prebuilt binary into ~/.local/bin (Linux x86_64) and wires
every host it finds on disk — Claude Code and opencode:
curl -fsSL https://raw.githubusercontent.com/bcmyguest/skill-injector/main/scripts/install.sh | sh
It auto-detects hosts (~/.claude → Claude hooks in settings.json; ~/.config/opencode
→ the opencode plugin). Pin one with SKI_HOST=claude|opencode|both|none. The host wiring
is additive and idempotent — re-running is safe, and any existing Claude settings.json
is backed up to settings.json.bak first.