Static quality linter for AI agent skill definitions (SKILL.md files)
Project description
agent-skill-lint
Static quality linter for AI agent skill definitions (
SKILL.mdfiles).
agent-skill-lint is the eslint of the agent skill world — pure static analysis, zero LLM calls, zero cloud dependency, pre-commit-friendly, CI-ready.
It catches bad skill definitions at development time, before they reach the agent.
Installation
pip install agent-skill-lint
Or from source:
git clone https://github.com/hananel-e/agent-skill-lint
cd agent-skill-lint
pip install -e ".[dev]"
Quick Start
# Lint all skill.md files found recursively under a path
skill-lint roo check .
# Lint a single file
skill-lint roo check .roo/skills-build/coder/skill.md
# Filter by severity
skill-lint roo check . --severity error # errors only
# Output formats
skill-lint roo check . --format json # machine-readable
skill-lint roo check . --format github # GitHub Actions annotations
# Disable specific rules
skill-lint roo check . --ignore ROO012,ROO031
# List all rules
skill-lint roo rules
# Explain a specific rule
skill-lint roo explain ROO013
# Check tool dependencies
skill-lint doctor
Exit Codes
| Code | Meaning |
|---|---|
0 |
Clean — no violations |
1 |
One or more violations found |
2 |
Tool error (file not found, parse failure, etc.) |
Rule Catalogue
Category A — Frontmatter Rules
| Rule ID | Severity | Description |
|---|---|---|
ROO001 |
error | name field missing from frontmatter |
ROO002 |
error | description field missing from frontmatter |
ROO003 |
error | modeSlugs field missing from frontmatter |
ROO004 |
error | modeSlugs is an empty list |
ROO005 |
warning | modeSlugs contains an unrecognised mode slug |
ROO006 |
warning | name value does not match the parent directory name |
ROO007 |
error | description is too short (< 50 characters) |
ROO008 |
warning | description is too long (> 400 characters) |
Category B — Description Quality Rules
| Rule ID | Severity | Description |
|---|---|---|
ROO010 |
warning | Description missing a trigger condition keyword |
ROO011 |
warning | Description does not start with an action verb or Use |
ROO012 |
warning | Description contains vague words |
ROO013 |
warning | Description has >75% token overlap with another skill |
ROO014 |
warning | Skill name mentioned in description is inconsistent with frontmatter |
Category C — Body Structure Rules
| Rule ID | Severity | Description |
|---|---|---|
ROO020 |
error | No H1 heading found after frontmatter |
ROO021 |
warning | H1 heading text does not match frontmatter name |
ROO022 |
warning | Body has no numbered sections — likely a stub |
ROO023 |
error | Markdown link target does not exist relative to repo root |
ROO024 |
warning | Body references a mode slug not present in .roomodes |
ROO025 |
warning | Body after frontmatter is < 100 characters — stub skill |
Category D — Cross-Skill Consistency Rules
| Rule ID | Severity | Description |
|---|---|---|
ROO030 |
warning | Body references a skill name with no corresponding skill.md |
ROO031 |
warning | Handoff file path uses a non-standard pattern |
ROO032 |
error | Two or more skills claim the same modeSlugs entry |
Configuration
Place a .skill-lint.yaml file at your repo root (copy from .skill-lint.yaml.example):
rules:
ROO007:
min_length: 60 # override default (50)
ROO008:
max_length: 300 # override default (400)
ROO013:
similarity_threshold: 0.80 # override default (0.75)
ignore:
- ".roo/skills/legacy/" # skip entire directory
- "ROO031" # globally disable a rule
# Custom vague words (appended to built-in list)
vague_words:
- "facilitates"
# Known mode slugs (appended to built-in list)
extra_mode_slugs:
- "my-custom-mode"
Per-rule overrides
Three rules accept numeric overrides under the rules: key:
| Rule | Config key | Default | Effect |
|---|---|---|---|
ROO007 |
rules.ROO007.min_length |
50 |
Minimum description length in characters. Raise it if your team requires more detail. |
ROO008 |
rules.ROO008.max_length |
400 |
Maximum description length in characters. Lower it to enforce tighter descriptions. |
ROO013 |
rules.ROO013.similarity_threshold |
0.75 |
Token-overlap ratio (0–1) above which two skill descriptions are flagged as too similar. Raise it to allow more overlap; lower it to enforce stricter uniqueness. |
Example — enforce 60-char minimum and flag descriptions that are 80%+ similar:
rules:
ROO007:
min_length: 60
ROO013:
similarity_threshold: 0.80
Ignoring rules and paths
ignore:
- "ROO031" # disable rule globally
- ".roo/skills/legacy/" # skip all skill.md files under this path prefix
You can also suppress rules for a single run without touching the config:
skill-lint roo check . --ignore ROO012,ROO031
Extending the mode slug allowlist
If your Roo setup uses custom modes not in the built-in list, add them so ROO005 does not flag them as unknown:
extra_mode_slugs:
- "my-workflow"
- "data-analyst"
Extending the vague word list
The built-in vague words are: handles, manages, does, performs, processes.
Append your own:
vague_words:
- "facilitates"
- "coordinates"
- "oversees"
Example Output
.roo/skills-build/coder/skill.md
✗ ROO008 description is 412 chars (max 400) [error]
⚠ ROO012 description uses vague word: "owns" [warning]
.roo/skills/api-schema-generator/skill.md
✗ ROO023 broken link: .roo/skills-build/coder/ui-patterns.md [error]
⚠ ROO022 no numbered sections found [warning]
────────────────────────────────────────────────────────────
2 errors | 2 warnings | 3 files scanned | 1 clean
Pre-commit Hook
# .pre-commit-config.yaml
repos:
- repo: https://github.com/hananel-e/agent-skill-lint
rev: v0.1.0
hooks:
- id: skill-lint-roo
name: skill-lint (Roo skills)
language: python
entry: skill-lint roo check
files: skill\.md$
pass_filenames: false
args: ["."]
Development
pip install -e ".[dev]"
pytest
Tech Stack
| Concern | Library |
|---|---|
| CLI | typer |
| Terminal output | rich |
| Frontmatter parsing | python-frontmatter |
| Markdown parsing | markdown-it-py |
| Token overlap (ROO013) | difflib.SequenceMatcher (stdlib) |
| Config file | pyyaml |
| Build backend | hatchling |
| Test framework | pytest |
Zero heavy dependencies — no scikit-learn, no sentence-transformers, no LLM calls.
License
Apache 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agent_skill_lint-0.1.0.tar.gz.
File metadata
- Download URL: agent_skill_lint-0.1.0.tar.gz
- Upload date:
- Size: 25.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ac904b224b61df80ed54d5d3d9efc3ac663e8463d0601ec8752455a275bb630
|
|
| MD5 |
8bf8aa8e66602b492eedb1707d941a5d
|
|
| BLAKE2b-256 |
9f16792c9f9ecbe7d1326f2eeef160fa8ae0eb96bfd886f3a38daab4eaee3bd7
|
Provenance
The following attestation bundles were made for agent_skill_lint-0.1.0.tar.gz:
Publisher:
publish.yml on hananel-e/agent-skill-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_skill_lint-0.1.0.tar.gz -
Subject digest:
6ac904b224b61df80ed54d5d3d9efc3ac663e8463d0601ec8752455a275bb630 - Sigstore transparency entry: 1964387593
- Sigstore integration time:
-
Permalink:
hananel-e/agent-skill-lint@559151ec87f7bb622dced9a1cc1f124851b778b3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hananel-e
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@559151ec87f7bb622dced9a1cc1f124851b778b3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file agent_skill_lint-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agent_skill_lint-0.1.0-py3-none-any.whl
- Upload date:
- Size: 30.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
074bde679e446ac525b717d05308ad362e70fa31b77b338fda358cce0e67a7ec
|
|
| MD5 |
339e78c53ce30da2353f9af5741e66e7
|
|
| BLAKE2b-256 |
43f198f838a0e8d69f6b2035c40a503c909b42a3eeb6c4df8f600f6af827aa15
|
Provenance
The following attestation bundles were made for agent_skill_lint-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on hananel-e/agent-skill-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_skill_lint-0.1.0-py3-none-any.whl -
Subject digest:
074bde679e446ac525b717d05308ad362e70fa31b77b338fda358cce0e67a7ec - Sigstore transparency entry: 1964387735
- Sigstore integration time:
-
Permalink:
hananel-e/agent-skill-lint@559151ec87f7bb622dced9a1cc1f124851b778b3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hananel-e
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@559151ec87f7bb622dced9a1cc1f124851b778b3 -
Trigger Event:
push
-
Statement type: