A configurable, rule-based linter for Claude Code plugins
Project description
claudelint
A configurable, rule-based linter for Claude Code plugins and plugin marketplaces.
Features
โจ Context-Aware - Automatically detects single plugin vs marketplace repositories
๐ฏ Rule-Based - Enable/disable individual rules with configurable severity levels
๐ Extensible - Load custom rules from Python files
๐ Comprehensive - Validates plugin structure, metadata, command format, and more
๐ณ Containerized - Run via Docker for consistent, isolated linting
โก Fast - Efficient validation with clear, actionable output
Installation
Via uvx (easiest - no install required)
# From git (works before PyPI release)
uvx --from 'git+https://github.com/stbenjam/claudelint' claudelint
# Once published to PyPI, simply:
uvx claudelint
# With specific path
uvx --from 'git+https://github.com/stbenjam/claudelint' claudelint /path/to/plugin
Via pip (recommended for regular use)
pip install claudelint
From source
git clone https://github.com/stbenjam/claudelint.git
cd claudelint
pip install -e .
Using Docker
docker pull ghcr.io/stbenjam/claudelint:latest
# Run on current directory
docker run -v $(pwd):/workspace ghcr.io/stbenjam/claudelint
Quick Start
# Lint current directory
claudelint
# Lint specific directory
claudelint /path/to/plugin
# Verbose output
claudelint -v
# Strict mode (warnings as errors)
claudelint --strict
# Generate default config
claudelint --init
# List all available rules
claudelint --list-rules
Repository Types
claudelint automatically detects your repository structure:
Single Plugin
my-plugin/
โโโ .claude-plugin/
โ โโโ plugin.json
โโโ commands/
โ โโโ my-command.md
โโโ README.md
Marketplace (Multiple Plugins)
claudelint supports multiple marketplace structures per the Claude Code specification:
Traditional Structure (plugins/ directory)
marketplace/
โโโ .claude-plugin/
โ โโโ marketplace.json
โโโ plugins/
โโโ plugin-one/
โ โโโ .claude-plugin/
โ โโโ commands/
โโโ plugin-two/
โโโ .claude-plugin/
โโโ commands/
Flat Structure (root-level plugin)
marketplace/
โโโ .claude-plugin/
โ โโโ marketplace.json # source: "./"
โโโ commands/ # Plugin components at root
โ โโโ my-command.md
โโโ skills/
โโโ my-skill/
Custom Paths
marketplace/
โโโ .claude-plugin/
โ โโโ marketplace.json # source: "./custom/my-plugin"
โโโ custom/
โโโ my-plugin/
โโโ commands/
โโโ skills/
Mixed Structures
Plugins from plugins/, custom paths, and remote sources can coexist in one marketplace. Only local sources are validated.
Marketplace Features
Flexible Plugin Sources
claudelint understands all plugin source types and validates any sources that resolve to local paths:
- Relative paths:
"source": "./"(flat structure),"source": "./custom/path" - GitHub repositories:
"source": {"source": "github", "repo": "owner/repo"} - Git URLs:
"source": {"source": "url", "url": "https://..."}
Remote sources (GitHub, git URLs) are logged and skipped during local validation. They are valid per spec but cannot be checked until the plugin is fetched locally.
Strict Mode
The strict field in marketplace entries controls validation behavior:
{
"name": "my-plugin",
"source": "./",
"strict": false, // plugin.json becomes optional
"description": "Plugin description can be in marketplace.json"
}
When strict: false:
plugin.jsonis optional- Marketplace entry serves as the complete plugin manifest
- Plugin metadata is validated from marketplace.json
- Skills, commands, and other components work normally
When strict: true (default):
plugin.jsonis required- Marketplace entry supplements plugin.json metadata
Configuration
Create .claudelint.yaml in your repository root:
# Enable/disable rules
rules:
plugin-json-required:
enabled: true
severity: error
plugin-naming:
enabled: true
severity: warning
command-sections:
enabled: true
severity: warning
# 'auto' enables only for marketplace repos
marketplace-registration:
enabled: auto
severity: error
# Load custom rules
custom-rules:
- ./my-custom-rules.py
# Exclude patterns
exclude:
- "**/node_modules/**"
- "**/.git/**"
# Treat warnings as errors
strict: false
Generating Default Config
claudelint --init
This creates .claudelint.yaml with all builtin rules enabled.
Builtin Rules
Plugin Structure
| Rule ID | Description | Default Severity | Notes |
|---|---|---|---|
plugin-json-required |
Plugin must have .claude-plugin/plugin.json |
error | Skipped when strict: false in marketplace |
plugin-json-valid |
Plugin.json must be valid with required fields | error | |
plugin-naming |
Plugin names should use kebab-case | warning | |
commands-dir-required |
Plugin should have a commands directory | warning (disabled by default) | |
commands-exist |
Plugin should have at least one command file | info (disabled by default) | |
plugin-readme |
Plugin should have a README.md file | warning |
Command Format
| Rule ID | Description | Default Severity |
|---|---|---|
command-naming |
Command files should use kebab-case | warning |
command-frontmatter |
Command files must have valid frontmatter | error |
command-sections |
Commands should have Name, Synopsis, Description, Implementation sections | warning |
command-name-format |
Command Name section should be plugin:command format |
warning |
Marketplace
| Rule ID | Description | Default Severity | Notes |
|---|---|---|---|
marketplace-json-valid |
Marketplace.json must be valid JSON | error (auto) | |
marketplace-registration |
Plugins must be registered in marketplace.json | error (auto) | Supports flat structures, custom paths, and remote sources |
Skills
| Rule ID | Description | Default Severity |
|---|---|---|
skill-frontmatter |
SKILL.md files should have frontmatter | warning |
Agents
| Rule ID | Description | Default Severity |
|---|---|---|
agent-frontmatter |
Agent files must have valid frontmatter with description and capabilities | error |
Hooks
| Rule ID | Description | Default Severity |
|---|---|---|
hooks-json-valid |
hooks.json must be valid JSON with proper hook configuration structure | error |
MCP (Model Context Protocol)
| Rule ID | Description | Default Severity | Notes |
|---|---|---|---|
mcp-valid-json |
MCP configuration must be valid JSON with proper mcpServers structure | error | Validates both .mcp.json and mcpServers in plugin.json |
mcp-prohibited |
Plugins should not enable MCP servers | error (disabled by default) | Security/policy rule - enable to prohibit MCP usage |
Custom Rules
Create custom validation rules by extending the Rule base class:
# my_custom_rules.py
from pathlib import Path
from typing import List
from claudelint import Rule, RuleViolation, Severity, RepositoryContext
class NoTodoCommentsRule(Rule):
@property
def rule_id(self) -> str:
return "no-todo-comments"
@property
def description(self) -> str:
return "Command files should not contain TODO comments"
def default_severity(self) -> Severity:
return Severity.WARNING
def check(self, context: RepositoryContext) -> List[RuleViolation]:
violations = []
for plugin_path in context.plugins:
commands_dir = plugin_path / "commands"
if not commands_dir.exists():
continue
for cmd_file in commands_dir.glob("*.md"):
with open(cmd_file, 'r') as f:
content = f.read()
if 'TODO' in content:
violations.append(
self.violation(
"Found TODO comment in command file",
file_path=cmd_file
)
)
return violations
Then reference it in .claudelint.yaml:
custom-rules:
- ./my_custom_rules.py
rules:
no-todo-comments:
enabled: true
severity: warning
CI/CD Integration
GitHub Actions
name: Lint Claude Plugins
on: [pull_request, push]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install claudelint
run: pip install claudelint
- name: Run linter
run: claudelint --strict
GitLab CI
lint-plugins:
image: python:3.11
script:
- pip install claudelint
- claudelint --strict
Docker
docker run -v $(pwd):/workspace -w /workspace ghcr.io/stbenjam/claudelint --strict
Exit Codes
0- Success (no errors, or warnings only in non-strict mode)1- Failure (errors found, or warnings in strict mode)
Examples
Example Output
Linting Claude plugins in: /path/to/marketplace
Errors:
โ ERROR [plugins/git/.claude-plugin/plugin.json]: Missing plugin.json
โ ERROR [.claude-plugin/marketplace.json]: Plugin 'new-plugin' not registered
Warnings:
โ WARNING [plugins/utils]: Missing README.md (recommended)
โ WARNING [plugins/jira/commands/solve.md]: Missing recommended section '## Implementation'
Summary:
Errors: 2
Warnings: 2
Disabling Specific Rules
rules:
plugin-readme:
enabled: false # Don't require README files
command-sections:
enabled: false # Don't check for specific sections
Changing Severity
rules:
plugin-naming:
severity: error # Make naming violations errors instead of warnings
command-name-format:
severity: info # Downgrade to info level
Development
Running Tests
pytest tests/
Building Docker Image
docker build -t claudelint .
Project Structure
claudelint/
โโโ src/
โ โโโ rule.py # Base Rule class
โ โโโ context.py # Repository detection
โ โโโ config.py # Configuration management
โ โโโ linter.py # Main linter orchestration
โโโ rules/
โ โโโ builtin/ # Builtin validation rules
โโโ tests/ # Test suite
โโโ examples/ # Example configs and custom rules
โโโ claudelint # CLI entry point
โโโ Dockerfile # Container image
โโโ pyproject.toml # Package metadata
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
Apache 2.0 - See LICENSE for details.
See Also
- Claude Code Documentation
- Claude Code Plugins Reference
- AI Helpers Marketplace - Example repository using claudelint
Support
Project details
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 claudelint-0.3.6.tar.gz.
File metadata
- Download URL: claudelint-0.3.6.tar.gz
- Upload date:
- Size: 38.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42d4c3364cc54f65d454834f3a0b8e68d2e1de8e1e0052f20ebf5bd8ebdca328
|
|
| MD5 |
79cb9e28d2796368f4196c1a48a18c44
|
|
| BLAKE2b-256 |
2ce53efdb4c94579bf0aa4ef5fc39c879af342db15029e82988b2c98db2f2b0d
|
Provenance
The following attestation bundles were made for claudelint-0.3.6.tar.gz:
Publisher:
release.yml on stbenjam/claudelint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claudelint-0.3.6.tar.gz -
Subject digest:
42d4c3364cc54f65d454834f3a0b8e68d2e1de8e1e0052f20ebf5bd8ebdca328 - Sigstore transparency entry: 1180522698
- Sigstore integration time:
-
Permalink:
stbenjam/claudelint@5b704b8945faf6489e7c5c8f9833241754c3c443 -
Branch / Tag:
refs/tags/v0.3.6 - Owner: https://github.com/stbenjam
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5b704b8945faf6489e7c5c8f9833241754c3c443 -
Trigger Event:
release
-
Statement type:
File details
Details for the file claudelint-0.3.6-py3-none-any.whl.
File metadata
- Download URL: claudelint-0.3.6-py3-none-any.whl
- Upload date:
- Size: 31.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bfae5513876d9866c2ceab4613c3a53abcc01973fcc464145e9cbce12791f0d0
|
|
| MD5 |
2a1c683d20297e1bbe51f083a8421b33
|
|
| BLAKE2b-256 |
60b9de653cac6b35533aab23b3f9723f79198e8ad9cae89e3b562fbe3ba41b37
|
Provenance
The following attestation bundles were made for claudelint-0.3.6-py3-none-any.whl:
Publisher:
release.yml on stbenjam/claudelint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claudelint-0.3.6-py3-none-any.whl -
Subject digest:
bfae5513876d9866c2ceab4613c3a53abcc01973fcc464145e9cbce12791f0d0 - Sigstore transparency entry: 1180522701
- Sigstore integration time:
-
Permalink:
stbenjam/claudelint@5b704b8945faf6489e7c5c8f9833241754c3c443 -
Branch / Tag:
refs/tags/v0.3.6 - Owner: https://github.com/stbenjam
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5b704b8945faf6489e7c5c8f9833241754c3c443 -
Trigger Event:
release
-
Statement type: