From skillsaw
Scaffolds a pip-installable Python package that adds custom lint rules to skillsaw. Use when sharing rules across repos or publishing them on PyPI.
How this skill is triggered — by the user, by Claude, or both
Slash command
/skillsaw:skillsaw-create-pluginThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Create a **skillsaw rule plugin**: a Python package that adds lint
Create a skillsaw rule plugin: a Python package that adds lint
rules to skillsaw. Once you build and publish to PyPI, anyone can
pip install it and skillsaw picks up the rules automatically — no config
required. Prefer this to per-repo custom-rules: files when sharing rules across repos.
Keep the terminology straight: a skillsaw plugin adds rules to the skillsaw linter itself. Never confuse it with the Claude Code plugins that skillsaw lints.
Review this reference material while you work:
examples/plugins/skillsaw-example-plugin/ in the skillsaw repoFollow each step in order, and keep the user updated on progress at each stage.
Review the following with the user:
no-todo-instructions).
Run skillsaw list-rules and confirm no ID collides with a builtin rule or
another installed plugin; skillsaw never loads colliding plugin rules.error (must fix), warning (should fix), or info (advisory).skillsaw-<name> on PyPI with module skillsaw_<name> (e.g. skillsaw-acme-rules / skillsaw_acme_rules).config_schema; never hardcode it.Create this layout:
skillsaw-<name>/
├── pyproject.toml
├── README.md
├── src/
│ └── skillsaw_<name>/
│ ├── __init__.py
│ └── rules.py
└── tests/
├── fixture/
│ └── CLAUDE.md # realistic fixture the rules run against
└── test_rules.py
Write pyproject.toml — the entry point is what makes it a plugin:
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "skillsaw-<name>"
version = "0.1.0"
description = "<one line: what the rules enforce>"
readme = "README.md"
requires-python = ">=3.9"
dependencies = ["skillsaw>=0.15"]
[project.entry-points."skillsaw.plugins"]
<name> = "skillsaw_<name>"
[tool.setuptools.packages.find]
where = ["src"]
Set the entry point name (left of =) as the plugin's short name — it appears in
skillsaw plugins output and in plugins: {disable: [...]} config. Use it to point at the module that declares the rules.
Write src/skillsaw_<name>/__init__.py:
from .rules import MyFirstRule
SKILLSAW_RULES = [MyFirstRule]
Declare every rule class the plugin provides in SKILLSAW_RULES — the explicit list skillsaw looks for.
Read references/rule-authoring.md in this skill's directory for the full
rule template, the requirements every rule must meet (lint tree discovery,
line numbers, config_schema, repo_types, default_enabled, YAML and
markdown parsing rules), and the deterministic-autofix template with its
scoping and idempotency requirements. Follow it exactly — the requirements
match what skillsaw's own review process enforces.
Implement each rule as a subclass of skillsaw.Rule in
src/skillsaw_<name>/rules.py: use context.lint_tree.find(NodeType) to find
files, add violations via self.violation(message, block=block, line=i), and
set tunable settings through config_schema.
Read references/extensions.md when the plugin needs any of:
skillsaw-<name> console script, dispatched as skillsaw <name> [args...] (registered plugins only).SKILLSAW_REPO_TYPES; detected types scope rules (string entries in repo_types) and pull the type's content_paths into content linting.SKILLSAW_TREE_CONTRIBUTORS to attach plugin-defined
blocks (ContentBlock for prose, JsonConfigBlock for machine config), and
let rules read them with lint_tree.find().Write tests in tests/test_rules.py that cover at minimum:
config_schema settings change behavior.invalidate_read_caches() (from skillsaw.rules.builtin.utils) — always call
it before you re-check, since skillsaw caches file reads within a process.Keep the fixture realistic — a CLAUDE.md that looks like a real project's, not a one-line stub. Follow the example plugin's tests for the full pattern.
Run the tests:
$ python -m venv .venv && .venv/bin/pip install -e . pytest
$ .venv/bin/pytest
Install the package with pip install -e . in the same environment as skillsaw:
skillsaw plugins — the plugin appears with its rules and no ERROR lines.skillsaw lint <repo-with-violations> — the rule fires, and the
violation's source is plugin:<name> in --format json output; verify it.skillsaw explain <rule-id> — it shows the rule's description and config.skillsaw fix --rule <rule-id> to apply it; a second
run changes nothing.skillsaw lint --no-plugins — the rule disappears, confirming it loads
via the entry point and never some other path.Write the plugin's README.md, documenting:
pip install skillsaw-<name>).config_schema options with a .skillsaw.yaml snippet.plugins: {disable: [<name>]} or per-rule enabled: false.Ask the user whether to publish now; if yes, run:
$ pip install build twine
$ python -m build
$ twine upload dist/*
For a GitHub-hosted plugin, offer to set up trusted publishing with a release workflow instead of long-lived API tokens; always pin action SHAs.
After publishing, verify a fresh pip install from PyPI works in a clean environment:
$ pip install skillsaw-<name>
$ skillsaw plugins
Report to the user:
pip install skillsaw-<name> next to skillsaw, then
rules run automatically; configure per rule ID in .skillsaw.yamlnpx claudepluginhub stbenjam/skillsaw --plugin skillsawOnboards a repository to skillsaw: runs the linter, applies autofixes, guides manual fixes, sets up CI, and creates a baseline. Useful when adopting skillsaw on new or existing projects.
Guides creation, modification, and debugging of Claude Code plugins with schemas, templates, checklists, validation workflows, and troubleshooting. Activates on .claude-plugin/, plugin.json, commands/, skills/, hooks/.
Scaffolds Claude Code plugin packages: gathers requirements, creates directory structure, generates manifest, adds initial skill and README, tests installation. Triggers on 'create plugin', 'new plugin', 'scaffold plugin'.