Skip to main content
WorldCentralKitchen

python-dev-framework

by WorldCentralKitchenv0.5.4

Opinionated Python development enforcement for Claude Code

Installation guide →
3 skillsMIT GitHub

Keywords

pythonlintingformattinggit-workflowtype-checking

Documentation

# Python Development Framework Plugin

Guardrails for LLM-assisted Python development with Claude Code.

This plugin guides Claude toward production-quality code by enforcing type safety, formatting standards, and git conventions in real-time. When Claude writes code that doesn't meet standards, it gets immediate feedback and can self-correct.

## Features

- **Real-time diagnostics**: LSP integration with Ruff for instant feedback
- **Type enforcement**: mypy --strict catches missing annotations as Claude codes
- **Auto-formatting**: ruff + black applied on every file write
- **Git conventions**: Branch names, commit messages, and protected branch push validation
- **Push protection**: Blocks direct pushes to main/master, enforcing PR workflows
- **Python version aware**: Detects target Python version and adjusts rules accordingly
- **`__future__` enforcement**: Requires `from __future__ import annotations` in strict mode
- **Configurable strictness**: strict (block - default), moderate (warn), or minimal (format only)

## Installation

This plugin is distributed via the [WCK Claude Plugins Marketplace](https://github.com/WorldCentralKitchen/wck-claude-plugins).

### Prerequisites

- [Claude Code CLI](https://claude.ai/code) installed
- [GitHub CLI](https://cli.github.com/) installed and authenticated (`gh auth login`)
- Access to WorldCentralKitchen GitHub org (private repos)

### Install Plugin

```bash
# Add WCK marketplace (one-time)
claude plugin marketplace add "https://oauth:$(gh auth token)@github.com/WorldCentralKitchen/wck-claude-plugins.git"

# Install plugin (project scope recommended)
claude plugin install python-dev-framework@WorldCentralKitchen --scope project

# Verify
claude plugin list
```

### Update to Latest Version

```bash
# Refresh marketplace catalog
claude plugin marketplace update WorldCentralKitchen

# Reinstall plugin to get latest
claude plugin install python-dev-framework@WorldCentralKitchen --scope project
```

## Configuration

Optional: Create `.claude/settings.json` to customize:

```json
{
  "plugins": {
    "python-dev-framework": {
      "level": "strict"
    }
  }
}
```

| Level | Formatting | Type Checking | Git Validation |
|-------|------------|---------------|----------------|
| strict | ruff + black | mypy --strict | Block invalid |
| moderate | ruff + black | Disabled | Warn only |
| minimal | black only | Disabled | Disabled |

### Python Version Detection

The plugin automatically detects your target Python version from `pyproject.toml`:

1. `tool.ruff.target-version` (e.g., `"py39"`) — highest priority
2. `project.requires-python` (e.g., `">=3.9"`) — parsed to extract version
3. Default: `py312`

Version-specific behavior:

| Version | `__future__` Check | FA Rules | UP036 Ignore |
|---------|-------------------|----------|--------------|
| py39 | ✓ (strict) | ✓ | ✓ (no match) |
| py310 | ✓ (strict) | ✓ | — |
| py311+ | ✓ (strict) | — | — |

- **FA rules**: `flake8-future-annotations` catches missing `__future__` imports
- **UP036**: Ignored for py39 since `match` statements aren't available

## Consumer Setup

Projects using this plugin need:

1. `pyproject.toml` with tool configurations
2. `.pre-commit-config.yaml` for git hooks
3. Dev dependencies: pytest, mypy, ruff, black, pre-commit
4. `from __future__ import annotations` at the top of all Python files (strict mode)
5. IDE setup for real-time feedback (see [IDE Setup](#ide-setup-vscode) below)

### Required: `__future__` Annotations

In strict mode, all Python files must include:

```python
from __future__ import annotations
```

This enables modern type syntax (`str | None`, `list[int]`) across all supported Python versions (3.9+). The plugin blocks writes that omit this import.

### Type Checking Configuration

In strict mode, mypy runs on every Python file after Write/Edit. The plugin passes `--python-version` based on your detected target version.

Configure exclusions in your `pyproject.toml`:

```toml
[project]
requires-python = ">=3.9"

[tool.ruff]
target-version = "py39"  # Must match requires-python

[tool.mypy]
strict = true
python_version = "3.9"   # Must match requires-python

[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
disallow_incomplete_defs = false
```

The plugin defers to your project's mypy configuration.

### Ruff Rules

The plugin enables comprehensive linting via Ruff:

| Category | Prefix | Purpose |
|----------|--------|---------|
| Pycodestyle | E, W | PEP 8 style |
| Pyflakes | F | Logic errors, unused imports |
| isort | I | Import sorting |
| Bugbear | B | Common bugs, mutable defaults |
| Comprehensions | C4 | List/dict comprehension style |
| Pyupgrade | UP | Python version upgrades |
| Unused args | ARG | Unused function arguments |
| Simplify | SIM | Code simplification |
| flake8-print | T | Bans `print()` in src/ (use structlog) |
| flake8-future | FA | `__future__` annotations (py39/py310) |
| Type checking | TCH | TYPE_CHECKING block usage |
| Pathlib | PTH | pathlib over os.path |
| Eradicate | ERA | Commented-out code |
| Pylint | PL | Additional checks |
| Ruff | RUF | Ruff-specific rules |

See [ADR-008](docs/adr/008-linting-rule-strategy.md) for expanded rules including security (S), async (ASYNC), and pytest (PT) categories.

### Print Ban (T201)

In strict mode, `print()` is banned in `src/` directories via Ruff T201. Use structlog instead:

```python
import structlog
log = structlog.get_logger()
log.info("event_name", user_id=123)
```

Exempt locations (via per-file-ignores):
- `tests/**/*.py` — print allowed in tests
- `hooks/scripts/*.py` — hooks use print for stdout protocol

See [TDD-002](docs/tdd/002-gcp-logging-integration.md) for structlog configuration patterns.

### Type Import Enforcement

The plugin auto-fixes deprecated type imports via Ruff pyupgrade (UP) rules:

| Before | After | Rule |
|--------|-------|------|
| `List[str]` | `list[str]` | UP006 |
| `Dict[str, int]` | `dict[str, int]` | UP006 |
| `Union[str, int]` | `str \| int` | UP007 |
| `Optional[str]` | `str \| None` | UP007 |
| `typing.Callable` | `collections.abc.Callable` | UP035 |

Behavior by strictness level:

| Level | UP Rules | TCH Rules |
|-------|----------|-----------|
| strict | Fix + Check | Fix + Check |
| moderate | Fix only | Disabled |
| minimal | Disabled | Disabled |

See [TDD-004](docs/tdd/004-type-import-patterns.md) for implementation details and [ADR-011](docs/adr/011-type-import-standards.md) for rationale.

### Directory Layout & Private Access

The plugin enforces private attribute access via Ruff SLF001 (flake8-self):

| Pattern | Enforcement | Rule |
|---------|-------------|------|
| `obj._private_attr` | Blocked | SLF001 |
| `obj._private_method()` | Blocked | SLF001 |
| `from pkg._internal import x` | Guidance only | — |

**Note:** SLF001 catches attribute/method access on objects, not module imports.
The `_internal/` naming convention is documented guidance.

Exempt locations (via per-file-ignores):
- `tests/**/*.py` — Tests can access private members
- `src/*/_internal/*.py` — Internal modules can access each other

Behavior by strictness level:

| Level | SLF001 |
|-------|--------|
| strict | Block violations |
| moderate | Disabled |
| minimal | Disabled |

See [TDD-005](docs/tdd/005-directory-layout-templates.md) for templates and [ADR-012](docs/adr/012-source-directory-layout.md) for rationale.

### Immutability Pattern Enforcement

The plugin enforces immutability patterns via Ruff rules:

| Rule | Pattern | Action |
|------|---------|--------|
| B006 | Mutable argument defaults `def f(x=[])` | Auto-fix |
| B039 | Mutable contextvar defaults | Block |
| RUF008 | Mutable defaults in `@dataclass` | Block |
| RUF012 | Mutable class attribute defaults | Block |

Additionally, mypy catches `Sequence[str]` footguns via `useful-types`:

```python
from useful_types import SequenceNotStr

def process(tags: SequenceNotStr[str]) -> None:
    pass

process("hello")  # Type error! str is not SequenceNotStr
```

Behavior by strictness level:

| Level | B006/B039/RUF008/RUF012 | mypy (SequenceNotStr) |
|-------|-------------------------|----------------------|
| strict | Block | Block |
| moderate | Fix + Warn | Skip |
| minimal | Skip | Skip |

See [TDD-006](docs/tdd/006-immutability-patterns.md) for implementation details and [ADR-013](docs/adr/013-immutability-safety-patterns.md) for rationale.

See [TDD-001](docs/tdd/001-plugin-implementation.md) for complete templates.

### Push Protection

The plugin prevents direct pushes to protected branches (main/master), enforcing PR workflows:

| Command | Action |
|---------|--------|
| `git push origin main` | Block |
| `git push origin master` | Block |
| `git push -u origin main` | Block |
| `git push --force origin main` | Block |
| `git push origin feature/foo` | Allow |
| `git push origin v1.0.0` | Allow (tag) |

Behavior by strictness level:

| Level | Push to main/master |
|-------|---------------------|
| strict | Block with error |
| moderate | Warn, allow push |
| minimal | No validation |

See [ADR-015](docs/adr/015-protected-branch-push-validation.md) for rationale.

### IDE Setup (VSCode)

Configure VSCode for real-time linting and formatting:

```bash
uv run --with "python-dev-framework @ git+https://github.com/WorldCentralKitchen/python-dev-framework" setup-ide
```

This creates `.vscode/settings.json` and `.vscode/extensions.json` with Ruff and Mypy integration. Install the recommended extensions when prompted.

## Using Without Claude Code

This framework works without Claude Code. The enforcement layers:

| Layer | Claude Code | Standalone |
|-------|-------------|------------|
| Real-time linting | Plugin hooks | VSCode + Ruff extension |
| Type checking | Plugin hooks | VSCode + Mypy extension |
| Git-level gate | Pre-commit hooks | Pre-commit hooks |

### Standalone Setup

1. **Copy configurations** from the [pyproject.toml template](docs/tdd/001-plugin-implementation.md)

2. **Install pre-commit hooks**:
   ```bash
   uv add --dev pre-commit ruff black mypy pytest
   pre-commit install
   ```

3. **Set up VSCode** for real-time feedback:
   ```bash
   uv run --with "python-dev-framework @ git+https://github.com/WorldCentralKitchen/python-dev-framework" setup-ide
   ```

Pre-commit provides the same enforcement as Claude Code hooks, but at commit time rather than during editing. The VSCode extensions provide real-time feedback equivalent to Claude Code's PostToolUse hooks.

---

## Development

### Prerequisites

- [uv](https://docs.astral.sh/uv/) for Python package management
- [Claude Code CLI](https://claude.ai/code) installed
- [ruff](https://docs.astral.sh/ruff/) globally installed for LSP diagnostics (`brew install ruff`)

### Install Dependencies

```bash
uv sync
```

### IDE Setup

For real-time linting in VSCode, run `make setup-ide` or see [IDE Setup](#ide-setup-vscode) above.

### Run Tests

```bash
# Unit tests only
uv run pytest tests/test_*.py

# E2E tests (requires Claude CLI + API key)
uv run pytest -m e2e

# All tests
uv run pytest
```

### Use the Plugin During Development

This plugin should be used during its own development. Choose the approach based on your workflow:

| Method | Command | Use When |
|--------|---------|----------|
| **Install from marketplace** | `claude plugin install python-dev-framework@WorldCentralKitchen --scope project` | Day-to-day development with stable version |
| **Load from source** | `claude --plugin-dir .` | Testing unreleased changes before release |

Both methods enable:
- **LSP diagnostics**: Real-time linting feedback from Ruff
- **PostToolUse hook**: Auto-formats Python files on Write/Edit
- **PreToolUse hook**: Validates git branch names and commit messages

#### Escape Hatches

| Problem               | Solution                              |
| --------------------- | ------------------------------------- |
| Hook blocks your work | Restart Claude without `--plugin-dir` |
| Can't commit          | `git commit --no-verify`              |
| Need to debug         | Run hook script manually              |

See [ADR-007](docs/adr/007-plugin-dogfooding.md) for rationale.

### Release & Distribution

When a new version is tagged, the GitHub Action:

1. Creates a GitHub Release
2. Syncs plugin essentials to the [marketplace repo](https://github.com/WorldCentralKitchen/wck-claude-plugins):
   - `.claude-plugin/`, `hooks/`, `skills/`, `CLAUDE.md`, `.lsp.json`
3. Updates version in `marketplace.json`
4. Opens a PR for review

Users install the bundled copy from the marketplace, not directly from this repo. See the [plugin-versioning skill](skills/plugin-versioning/SKILL.md) for release procedures.

### Repository Structure
```
python-dev-framework/
├── .claude-plugin/
│   └── plugin.json              # Plugin manifest
├── .lsp.json                    # LSP server configuration
├── hooks/
│   ├── hooks.json               # Hook definitions
│   └── scripts/
│       ├── config.py            # Shared configuration loader
│       ├── format_python.py     # PostToolUse: formats .py files
│       └── validate_git.py      # PreToolUse: validates git commands
├── skills/
│   ├── python-standards/        # Python standards skill
│   └── plugin-versioning/       # Plugin versioning guidance
├── tests/                       # Unit and E2E tests
└── docs/                        # ADRs and TDDs
```

## Documentation

| Doc | Purpose |
|-----|---------|
| [CLAUDE.md](CLAUDE.md) | Claude Code project instructions |
| [Architecture Decision Records](docs/adr/README.md) | Design decisions and rationale (ADR-001 through ADR-013) |

## License

MIT