skill-forge
Point it at your code. Get a valid Claude skill. No API key required.
skill-forge turns a codebase, package, or doc into a well-formed Claude
SKILL.md — and the
skill it writes is valid by construction.
pip install claude-skill-forge # the CLI it installs is `skill-forge`
skill-forge forge ./my-tool # writes .claude/skills/my-tool/SKILL.md
Why
Writing a good skill is fiddly: the frontmatter has to be exactly right, the name has to
match its directory, and the description — the one field an agent actually reads to
decide whether to load the skill — has to say when to trigger, inside a tight
character budget. Get any of it wrong and the skill is silently undiscoverable.
Most "ask an LLM to write my SKILL.md" approaches are non-reproducible, need an API key,
and still emit invalid frontmatter. skill-forge is different on two axes:
- Offline & deterministic by default. It reads your source with static analysis — no
code execution, no network, no key — and emits the skill. Same input → same output.
The optional
--llmflag only refines the prose; it never owns the structure. - Valid by construction. Every generated skill passes the built-in linter (the same
rules a skill must satisfy to be discoverable).
skill-forgerefuses to write a skill that doesn't lint clean, so you never ship a broken one.
It's the forge half of a pair: forge generates, skillcheck checks. The linter is
bundled here too (skill-forge lint) so the tool stands alone.
Install
The package is published on PyPI as claude-skill-forge; it installs a CLI named
skill-forge (and the import package is skill_forge).
pip install claude-skill-forge # core: zero runtime dependencies
pip install 'claude-skill-forge[anthropic]' # adds the optional --llm refiner
Quickstart (30 seconds)
# From a Python/Node project, a package, or a single doc:
skill-forge forge ./my-tool # -> .claude/skills/my-tool/SKILL.md
skill-forge forge ./README.md # generate from docs
skill-forge forge ./pkg --name my-skill # override the skill name
skill-forge forge ./my-tool --stdout # preview, don't write
skill-forge forge ./my-tool --llm # sharpen the prose with Claude
# Validate any skill / folder of skills (the bundled checker):
skill-forge lint .claude/skills
# CI drift guard — fail if the skill no longer matches the code:
skill-forge check ./my-tool --name my-tool
# Refresh a drifted skill, keeping the parts you wrote by hand:
skill-forge update ./my-tool
skill-forge update ./my-tool --diff # preview the merge
What it extracts
| Source | What it reads |
|---|---|
| Python | pyproject.toml / setup.cfg (name, description, keywords, [project.scripts]), __all__ and public defs/classes via ast, and argparse / click / typer subcommands — never importing or running your code |
| Node | package.json (name, description, keywords, bin), TS/JS detection, README |
| Docs | A markdown/rst/txt file: H1 title, first paragraph, section headings, fenced code blocks |
| Any directory | README + a language histogram of the file tree |
| A CLI tool | skill-forge forge --from-cli "mytool" captures and parses mytool --help |
The result is a complete SKILL.md: trigger-oriented description, a ## When to use
section, an overview, and ## Commands / ## API / ## Usage sections built from what was
found.
Use it from Python
from skill_forge import forge, render_skill, write_skill
draft = forge("./my-tool") # a validated SkillDraft
print(render_skill(draft)) # the SKILL.md text
write_skill(draft, ".claude/skills")
The --llm refiner (optional)
--llm sends the extracted signals (not your source) to Claude to sharpen the
description's trigger phrasing and tighten the body. It is fail-safe: if the model is
unavailable it tells you how to fix it, and if its output is anything but a valid
improvement, skill-forge keeps the deterministic draft. You never get a worse skill than
the offline path. Set ANTHROPIC_API_KEY and install the extra to use it.
CI: catch stale skills
skill-forge check regenerates the skill in memory and diffs it against the one on disk,
exiting non-zero if they differ — so a skill that drifted from the code it describes fails
the build:
- run: pip install claude-skill-forge
- run: skill-forge check ./my-tool --name my-tool
Updating a skill you've edited
check tells you a skill drifted. The problem is what comes next: forge --force
overwrites the file, taking every hand edit with it — and hand edits are the point, since
a generated skill is a first draft.
skill-forge update regenerates and merges, section by section:
$ skill-forge update ./my-tool
✓ updated .claude/skills/my-tool/SKILL.md
refreshed: description, Notes
added: Configuration
kept your edits: When to use, Gotchas
Use --diff to see the merge before it's written.
To do that it has to tell the generator changed this from a human changed this, and a
two-way diff can't. So forge records a .skill-forge.json beside the skill holding a
hash of each section as generated. Then each section has an unambiguous answer:
| on disk | outcome | |
|---|---|---|
| matches the manifest | untouched | take the regenerated version |
| differs from the manifest | you edited it | keep yours |
| absent | new from the generator | add it |
| not in the regenerated output | you wrote it | keep it |
Commit .skill-forge.json. Without it — a skill forged before this existed, or the file
deleted — everything present is treated as hand-edited, so update only adds and never
silently reverts prose you wrote. That's the safe direction to be wrong in, and it says so
in the output.
Your edits stay marked as edits across repeated updates: re-baselining records the newly generated content but preserves the original hash for every section it kept, so an edit isn't quietly adopted as generated output on the next run.
What this is not
- Not a replacement for judgment. Generated skills are a strong first draft. The body is assembled from your structure, not written from deep understanding — read it, trim it, and add the hard-won "do this, not that" guidance only you know.
- Not a runtime. It writes
SKILL.mdfiles; it does not execute skills. - Not magic prose. The offline path is deterministic and a little formulaic by design.
Reach for
--llmwhen you want the description polished. - It does not run your code. The only time it executes anything is the explicit
--from-cliflag, which runs<cmd> --helpwith no shell and a timeout.
Configuration
Environment overrides (all optional):
| Variable | Default | Purpose |
|---|---|---|
SKILL_FORGE_OUTDIR |
.claude/skills |
Default output directory |
SKILL_FORGE_VERSION |
0.1.0 |
Version stamped on generated skills |
SKILL_FORGE_MODEL_ID |
claude-haiku-4-5 |
Model used by --llm |
ANTHROPIC_API_KEY |
— | Required for --llm |
Development
pip install -e ".[dev]"
ruff check .
pytest -q
The design is pinned in SPEC.md — the single source of truth for the public
API and behavior. See CONTRIBUTING.md before opening a PR.
License
MIT — see LICENSE.
Release files for claude-skill-forge 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| claude_skill_forge-0.2.0.tar.gz | 48.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| claude_skill_forge-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 88.8 kB
Release files / claude_skill_forge-0.2.0.tar.gz
| Download URL | claude_skill_forge-0.2.0.tar.gz |
|---|---|
| Size | 48.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d04bd5bccca941bb245711faa511d8ad4904d5515683437131331bc4cf9e0891
|
|
BLAKE2b-256 checksum How to use checksums |
998b00c61e7c8b018bb28d45682bf210b5fca038209ba9d9d389a12d87fc4510
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.3
|
Release files / claude_skill_forge-0.2.0-py3-none-any.whl
| Download URL | claude_skill_forge-0.2.0-py3-none-any.whl |
|---|---|
| Size | 40.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8f46c4fe8241b20bdbc4234a291749bea8ebcdcce20e95f50753a83838244fe5
|
|
BLAKE2b-256 checksum How to use checksums |
4a1c9573efb1ec91236a2f0c5a15490f9b9b63693baf778a331dfaa5923a2819
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.3
|