Skip to main content

A configurable, rule-based linter for Claude Code plugins

Project description

PyPI version PyPI Downloads Tests codecov License Python Versions

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.json is 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.json is 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:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

License

Apache 2.0 - See LICENSE for details.

See Also

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

claudelint-0.3.6.tar.gz (38.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

claudelint-0.3.6-py3-none-any.whl (31.1 kB view details)

Uploaded Python 3

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

Hashes for claudelint-0.3.6.tar.gz
Algorithm Hash digest
SHA256 42d4c3364cc54f65d454834f3a0b8e68d2e1de8e1e0052f20ebf5bd8ebdca328
MD5 79cb9e28d2796368f4196c1a48a18c44
BLAKE2b-256 2ce53efdb4c94579bf0aa4ef5fc39c879af342db15029e82988b2c98db2f2b0d

See more details on using hashes here.

Provenance

The following attestation bundles were made for claudelint-0.3.6.tar.gz:

Publisher: release.yml on stbenjam/claudelint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for claudelint-0.3.6-py3-none-any.whl
Algorithm Hash digest
SHA256 bfae5513876d9866c2ceab4613c3a53abcc01973fcc464145e9cbce12791f0d0
MD5 2a1c683d20297e1bbe51f083a8421b33
BLAKE2b-256 60b9de653cac6b35533aab23b3f9723f79198e8ad9cae89e3b562fbe3ba41b37

See more details on using hashes here.

Provenance

The following attestation bundles were made for claudelint-0.3.6-py3-none-any.whl:

Publisher: release.yml on stbenjam/claudelint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page