Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"hooks": [
{
"type": "command",
"command": "poetry run python-claude session start"
"command": "uv run python-claude session start"
},
{
"type": "command",
"command": "poetry run python-claude git status"
"command": "uv run python-claude git status"
}
]
}
Expand All @@ -19,19 +19,19 @@
"hooks": [
{
"type": "command",
"command": "poetry run python-claude ruff format"
"command": "uv run python-claude ruff format"
},
{
"type": "command",
"command": "poetry run python-claude ruff check"
"command": "uv run python-claude ruff check"
},
{
"type": "command",
"command": "poetry run python-claude mypy"
"command": "uv run python-claude mypy"
},
{
"type": "command",
"command": "poetry run python-claude pytest"
"command": "uv run python-claude pytest"
}
]
}
Expand All @@ -42,7 +42,7 @@
"hooks": [
{
"type": "command",
"command": "poetry run python-claude edited"
"command": "uv run python-claude edited"
}
]
}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ on:
push:
paths:
- .github/workflows/ci.yml
- poetry.lock
- uv.lock
- pyproject.toml
- src/**
- tests/**
pull_request:
paths:
- .github/workflows/ci.yml
- poetry.lock
- uv.lock
- pyproject.toml
- src/**
- tests/**
Expand Down
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# python-claude

Python hooks for Claude Code for projects using:
- poetry
- uv
- ruff
- mypy
- pytest
Expand All @@ -21,7 +21,7 @@ Note: We defer all quality checks until Claude stops to avoid changing files whi
## Installation

```bash
poetry add python-claude
uv add python-claude
```

## Usage
Expand Down Expand Up @@ -51,11 +51,11 @@ Add hooks to your Claude Code settings.json:
"hooks": [
{
"type": "command",
"command": "poetry run python-claude session start"
"command": "uv run python-claude session start"
},
{
"type": "command",
"command": "poetry run python-claude git status"
"command": "uv run python-claude git status"
}
]
}
Expand All @@ -65,19 +65,19 @@ Add hooks to your Claude Code settings.json:
"hooks": [
{
"type": "command",
"command": "poetry run python-claude ruff format"
"command": "uv run python-claude ruff format"
},
{
"type": "command",
"command": "poetry run python-claude ruff check"
"command": "uv run python-claude ruff check"
},
{
"type": "command",
"command": "poetry run python-claude mypy"
"command": "uv run python-claude mypy"
},
{
"type": "command",
"command": "poetry run python-claude pytest"
"command": "uv run python-claude pytest"
}
]
}
Expand All @@ -88,7 +88,7 @@ Add hooks to your Claude Code settings.json:
"hooks": [
{
"type": "command",
"command": "poetry run python-claude edited"
"command": "uv run python-claude edited"
}
]
}
Expand Down Expand Up @@ -119,18 +119,18 @@ Each command will show whether the check is now enabled or disabled.
You can also use the CLI directly:

```bash
poetry run python-claude toggle pytest
poetry run python-claude toggle mypy
poetry run python-claude toggle ruff
uv run python-claude toggle pytest
uv run python-claude toggle mypy
uv run python-claude toggle ruff
```

The toggle state persists across Claude Code sessions, stored in `.claude/quality-checks.json` (which is gitignored). All checks are enabled by default.

## Development

```bash
poetry install
poetry run pytest
poetry run ruff check
poetry run mypy src
uv sync
uv run pytest
uv run ruff check
uv run mypy src
```
382 changes: 0 additions & 382 deletions poetry.lock

This file was deleted.

21 changes: 11 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,19 @@ classifiers = [
[project.scripts]
python-claude = "python_claude.cli:main"

[tool.poetry]
packages = [{ include = "python_claude", from = "src" }]
include = ["src/python_claude/py.typed"]

[tool.poetry.group.dev.dependencies]
pytest = "^8.3.5"
mypy = "^1.15.0"
ruff = "^0.11.10"
[dependency-groups]
dev = [
"pytest>=8.3.5,<9",
"mypy>=1.15.0,<2",
"ruff>=0.11.10,<1",
]

[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/python_claude"]

[tool.mypy]
strict = true
2 changes: 1 addition & 1 deletion src/python_claude/hooks/mypy_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def run(self) -> int:

# mypy writes errors to stdout, but only stderr is fed back to Claude
result = subprocess.run(
["poetry", "run", "mypy", mypy_target],
["uv", "run", "mypy", mypy_target],
cwd=self.project_dir,
stdout=sys.stderr, # Redirect stdout to stderr for Claude
)
Expand Down
2 changes: 1 addition & 1 deletion src/python_claude/hooks/pytest_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def run(self) -> int:
self.log("Running pytest")

result = subprocess.run(
["poetry", "run", "pytest"],
["uv", "run", "pytest"],
cwd=self.project_dir,
stdout=sys.stderr,
)
Expand Down
2 changes: 1 addition & 1 deletion src/python_claude/hooks/ruff_check_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def run(self) -> int:
self.log(f"Checking {len(files)} files: {' '.join(files)}")

result = subprocess.run(
["poetry", "run", "ruff", "check", "--fix", *files],
["uv", "run", "ruff", "check", "--fix", *files],
cwd=self.project_dir,
stdout=sys.stderr,
)
Expand Down
2 changes: 1 addition & 1 deletion src/python_claude/hooks/ruff_format_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def run(self) -> int:
self.log(f"Formatting {len(files)} files: {' '.join(files)}")

result = subprocess.run(
["poetry", "run", "ruff", "format", *files],
["uv", "run", "ruff", "format", *files],
cwd=self.project_dir,
stdout=sys.stderr,
)
Expand Down
8 changes: 4 additions & 4 deletions src/python_claude/hooks/session_start_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ def run(self) -> int:
f"{checks_list} after you edit a file. You don't need "
f"to run these commands manually. You can run these before making "
f"an edit using:\n"
f"- poetry run ruff format .\n"
f"- poetry run ruff check .\n"
f"- poetry run mypy .\n"
f"- poetry run pytest"
f"- uv run ruff format .\n"
f"- uv run ruff check .\n"
f"- uv run mypy .\n"
f"- uv run pytest"
)
else:
additional_context = (
Expand Down
6 changes: 3 additions & 3 deletions tests/test_mypy_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_python_file_success(self, tmp_path: Path) -> None:
mock_run.assert_called_once()
# Verify it ran mypy on the specific file
call_args = mock_run.call_args
assert call_args[0][0] == ["poetry", "run", "mypy", "/path/to/file.py"]
assert call_args[0][0] == ["uv", "run", "mypy", "/path/to/file.py"]

def test_python_file_type_errors(self, tmp_path: Path) -> None:
"""Test mypy type errors (exit 1) are transformed to exit code 2."""
Expand Down Expand Up @@ -99,7 +99,7 @@ def test_stop_hook_no_file_path_success(self, tmp_path: Path) -> None:
mock_run.assert_called_once()
# Verify it ran mypy on current directory
call_args = mock_run.call_args
assert call_args[0][0] == ["poetry", "run", "mypy", "."]
assert call_args[0][0] == ["uv", "run", "mypy", "."]
# Verify tracking file was cleaned up on success
assert not hook.track_file.exists()

Expand Down Expand Up @@ -166,7 +166,7 @@ def test_empty_file_path_runs_full_check(self, tmp_path: Path) -> None:
assert exit_code == 0
# Verify it ran mypy on current directory
call_args = mock_run.call_args
assert call_args[0][0] == ["poetry", "run", "mypy", "."]
assert call_args[0][0] == ["uv", "run", "mypy", "."]

def test_mypy_skipped_when_disabled(self, tmp_path: Path) -> None:
"""Test that mypy is skipped when disabled in state."""
Expand Down
Loading
Loading