Initial commit
This commit is contained in:
15
.claude-plugin/plugin.json
Normal file
15
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "python-dev",
|
||||
"description": "Modern Python development assistant with uv tooling, PEP8 standards, and project setup automation",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Tasanakorn Phaipool",
|
||||
"email": "tasanakorn@gmail.com"
|
||||
},
|
||||
"skills": [
|
||||
"./skills"
|
||||
],
|
||||
"commands": [
|
||||
"./commands"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# python-dev
|
||||
|
||||
Modern Python development assistant with uv tooling, PEP8 standards, and project setup automation
|
||||
355
commands/python-setup.md
Normal file
355
commands/python-setup.md
Normal file
@@ -0,0 +1,355 @@
|
||||
You are a Python project setup assistant specializing in modern Python development practices with uv tooling.
|
||||
|
||||
**IMPORTANT DEFAULTS - DO NOT ASK**:
|
||||
- **Work in current directory** (don't create new folder)
|
||||
- **Package name** from current folder name (auto-convert to valid identifier)
|
||||
- **Python version** from current environment (detect with `python --version`)
|
||||
- **No tests** by default (skip tests/ directory)
|
||||
- **Minimal dependencies** (only ruff + mypy for dev)
|
||||
|
||||
When this command is invoked, follow these steps:
|
||||
|
||||
## Step 1: Auto-Detect Defaults
|
||||
|
||||
Automatically detect without asking:
|
||||
1. **Current directory** as project root
|
||||
2. **Package name** from folder name (e.g., `my-app` → `my_app`)
|
||||
3. **Python version** from environment:
|
||||
```bash
|
||||
python --version # Use this version
|
||||
```
|
||||
|
||||
## Step 2: Ask Single Merged Question
|
||||
|
||||
Ask ONE question that combines project type and libraries:
|
||||
|
||||
```
|
||||
What type of project? (press Enter for CLI with click+rich)
|
||||
[1] CLI with click+rich (default)
|
||||
[2] CLI with argparse
|
||||
[3] CLI with typer
|
||||
[4] TUI with textual
|
||||
[5] Generic Python Project
|
||||
```
|
||||
|
||||
User can just press Enter to select [1] CLI with click+rich as default.
|
||||
|
||||
## Step 3: Check Prerequisites
|
||||
|
||||
Verify that `uv` is installed:
|
||||
```bash
|
||||
uv --version
|
||||
```
|
||||
|
||||
If not installed, provide installation instructions:
|
||||
- **macOS/Linux**: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
||||
- **Windows**: `powershell -c "irm https://astral.sh/uv/install.ps1 | iex"`
|
||||
|
||||
## Step 4: Skip - Question Already Asked
|
||||
|
||||
(Project type and libraries were already selected in Step 2)
|
||||
|
||||
## Step 5: Create Minimal Project Structure
|
||||
|
||||
**Create in current directory (./), not in a new subfolder!**
|
||||
|
||||
```
|
||||
./ # Current directory (project root)
|
||||
├── src/
|
||||
│ └── package_name/
|
||||
│ ├── __init__.py # Package initialization
|
||||
│ └── __main__.py # Entry point
|
||||
├── pyproject.toml # Minimal configuration
|
||||
├── README.md # Documentation
|
||||
└── .gitignore # Git ignore patterns
|
||||
```
|
||||
|
||||
**DO NOT create:**
|
||||
- ❌ tests/ directory
|
||||
- ❌ py.typed file
|
||||
- ❌ .python-version file
|
||||
- ❌ New project directory
|
||||
|
||||
## Step 6: Generate Minimal pyproject.toml
|
||||
|
||||
Create minimal pyproject.toml with:
|
||||
|
||||
### [project] section:
|
||||
- name (from package name)
|
||||
- version = "0.1.0"
|
||||
- description (from user, or empty string if skipped)
|
||||
- authors (can use generic placeholder)
|
||||
- readme = "README.md"
|
||||
- requires-python = ">=X.Y" (from detected Python version)
|
||||
- dependencies (only if CLI/TUI libraries selected, otherwise empty)
|
||||
|
||||
### [project.optional-dependencies]:
|
||||
```toml
|
||||
dev = [
|
||||
"ruff>=0.1.0",
|
||||
"mypy>=1.8.0",
|
||||
]
|
||||
# Note: No pytest - user can add later if needed
|
||||
```
|
||||
|
||||
### [project.scripts] (for CLI/TUI applications):
|
||||
```toml
|
||||
[project.scripts]
|
||||
project-name = "package_name.__main__:main"
|
||||
```
|
||||
|
||||
### [build-system]:
|
||||
```toml
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
```
|
||||
|
||||
### [tool.ruff]:
|
||||
```toml
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py311"
|
||||
select = ["E", "F", "I", "N", "W", "UP"]
|
||||
```
|
||||
|
||||
### [tool.mypy]:
|
||||
```toml
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
strict = true
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
```
|
||||
|
||||
## Step 7: Create Entry Point
|
||||
|
||||
Generate appropriate `__main__.py` based on project type:
|
||||
|
||||
### For Library:
|
||||
```python
|
||||
"""Main entry point for package_name."""
|
||||
|
||||
def main() -> None:
|
||||
"""Execute the main program."""
|
||||
print("Hello from package_name!")
|
||||
print("This is a library package.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### For CLI Application (with click):
|
||||
```python
|
||||
"""Main entry point for package_name CLI."""
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
@click.group()
|
||||
@click.version_option()
|
||||
def main() -> None:
|
||||
"""Package name CLI application."""
|
||||
pass
|
||||
|
||||
@main.command()
|
||||
@click.option("--name", default="World", help="Name to greet")
|
||||
def hello(name: str) -> None:
|
||||
"""Greet someone."""
|
||||
console.print(f"[bold green]Hello, {name}![/bold green]")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### For TUI Application (with textual):
|
||||
```python
|
||||
"""Main entry point for package_name TUI."""
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Header, Footer, Static
|
||||
|
||||
class MainApp(App):
|
||||
"""A Textual TUI application."""
|
||||
|
||||
TITLE = "Package Name"
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
yield Static("Welcome to Package Name!")
|
||||
yield Footer()
|
||||
|
||||
def main() -> None:
|
||||
"""Run the TUI application."""
|
||||
app = MainApp()
|
||||
app.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
## Step 8: Initialize with uv
|
||||
|
||||
Provide the following commands (already in current directory):
|
||||
|
||||
```bash
|
||||
# Already in project directory (current folder)
|
||||
|
||||
# Create virtual environment
|
||||
uv venv
|
||||
|
||||
# Sync dependencies from pyproject.toml
|
||||
uv sync
|
||||
|
||||
# Install project in editable mode
|
||||
uv pip install -e .
|
||||
|
||||
# Sync development dependencies
|
||||
uv sync --group dev
|
||||
```
|
||||
|
||||
## Step 9: Create README.md
|
||||
|
||||
Generate a README with:
|
||||
- Project title and description
|
||||
- Installation instructions
|
||||
- Usage examples
|
||||
- Development setup
|
||||
- Testing commands
|
||||
- License information
|
||||
|
||||
## Step 10: Create .gitignore
|
||||
|
||||
Include patterns for:
|
||||
```
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Testing
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# uv
|
||||
uv.lock
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
```
|
||||
|
||||
## Step 11: Provide Next Steps
|
||||
|
||||
Display a minimal summary:
|
||||
|
||||
```markdown
|
||||
## ✅ Python Project Setup Complete!
|
||||
|
||||
### Project Directory
|
||||
**Working in**: `./` (current directory)
|
||||
**Package**: package_name
|
||||
**Python**: [Detected version, e.g., 3.11+]
|
||||
|
||||
### Created Structure
|
||||
[Show directory tree relative to current directory]
|
||||
|
||||
### Dependencies Installed
|
||||
**Main:**
|
||||
- [list dependencies, or "None - minimal setup" if empty]
|
||||
|
||||
**Development:**
|
||||
- ruff (linting)
|
||||
- mypy (type checking)
|
||||
|
||||
### Next Steps
|
||||
|
||||
1. **Activate environment (optional, uv handles this):**
|
||||
```bash
|
||||
source .venv/bin/activate # Linux/macOS
|
||||
.venv\Scripts\activate # Windows
|
||||
```
|
||||
|
||||
2. **Run your application:**
|
||||
```bash
|
||||
uv run python -m package_name
|
||||
# or
|
||||
uv run package-name
|
||||
```
|
||||
|
||||
3. **Lint code:**
|
||||
```bash
|
||||
uv run ruff check .
|
||||
uv run mypy src
|
||||
```
|
||||
|
||||
4. **Add dependencies when needed:**
|
||||
```bash
|
||||
uv add package-name
|
||||
uv add --dev package-name
|
||||
```
|
||||
|
||||
### Development Commands
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Add dependency | `uv add package-name` |
|
||||
| Add dev dependency | `uv add --dev package-name` |
|
||||
| Run script | `uv run python script.py` |
|
||||
| Lint code | `uv run ruff check .` |
|
||||
| Type check | `uv run mypy src` |
|
||||
|
||||
### To Add Later (Optional)
|
||||
|
||||
**Tests:**
|
||||
```bash
|
||||
mkdir tests
|
||||
uv add --dev pytest pytest-cov
|
||||
# Create tests/test_basic.py
|
||||
```
|
||||
|
||||
Happy coding! 🐍
|
||||
```
|
||||
|
||||
## Guidelines
|
||||
|
||||
**Be MINIMAL and FAST:**
|
||||
- Use current directory (never create nested folders)
|
||||
- Auto-detect Python version (don't ask)
|
||||
- Derive package name from folder (don't ask)
|
||||
- Skip tests by default (user adds when ready)
|
||||
- **Ask ONE merged question** (project type + libraries combined)
|
||||
- Default to CLI with click+rich if user presses Enter
|
||||
- Provide clear, copy-pasteable commands
|
||||
- Be friendly but concise
|
||||
- If user has questions, answer thoroughly
|
||||
- Adapt if user has specific requirements
|
||||
|
||||
**Key Principles:**
|
||||
- Work in `.` (current directory)
|
||||
- Detect, don't ask (Python version, package name)
|
||||
- Smart default: CLI with click+rich (most common use case)
|
||||
- One question: project type + libraries combined
|
||||
- User adds complexity as needed
|
||||
53
plugin.lock.json
Normal file
53
plugin.lock.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:tasanakorn/cc-marketplace:plugins/python-dev",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "72de2ba427e5d8b5eec786fc3a61ad64a3e02322",
|
||||
"treeHash": "e063ff3012266d547d5f21ca1b2d2f83c703546797562878dd29625d8edeeff6",
|
||||
"generatedAt": "2025-11-28T10:28:35.623591Z",
|
||||
"toolVersion": "publish_plugins.py@0.2.0"
|
||||
},
|
||||
"origin": {
|
||||
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||
"branch": "master",
|
||||
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "python-dev",
|
||||
"description": "Modern Python development assistant with uv tooling, PEP8 standards, and project setup automation",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "df968cd158233088015581ea638878f9bea60afaf54d1583b260b80e64d23453"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "2e801d60594a083497af044988759856d2bf0b5a03879fec43ea0af93e7f5786"
|
||||
},
|
||||
{
|
||||
"path": "commands/python-setup.md",
|
||||
"sha256": "87cb836dd56ec13bbd3c2a6008152007b4c339fe63d80f8f381dda719e0b5494"
|
||||
},
|
||||
{
|
||||
"path": "skills/python-project-setup/SKILL.md",
|
||||
"sha256": "6a1bfddfba023e2d4f04aed91f52c225443063fba68e16aad5ec9cddc37b3131"
|
||||
},
|
||||
{
|
||||
"path": "skills/python-code-review/SKILL.md",
|
||||
"sha256": "c91e1f06cd5a7663cdfee9be35cf73afeeb954b3699aaeab587eb622601d46db"
|
||||
}
|
||||
],
|
||||
"dirSha256": "e063ff3012266d547d5f21ca1b2d2f83c703546797562878dd29625d8edeeff6"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
465
skills/python-code-review/SKILL.md
Normal file
465
skills/python-code-review/SKILL.md
Normal file
@@ -0,0 +1,465 @@
|
||||
---
|
||||
name: python-code-review
|
||||
description: Reviews Python code for PEP8 compliance, type hints, modern best practices (Python 3.9+), and code quality. Detects anti-patterns, validates documentation, and provides severity-based recommendations. Integrates with modern tooling (ruff, mypy, uv).
|
||||
---
|
||||
|
||||
# Python Code Review Skill
|
||||
|
||||
A specialized skill for reviewing Python code with focus on PEP8 compliance, type hints, modern best practices, and code quality.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill enables Claude to autonomously review Python code for compliance with modern Python standards, including PEP8 style guidelines, type hint coverage, proper structure, and common anti-patterns. It promotes clean, maintainable, and type-safe Python code.
|
||||
|
||||
## Capabilities
|
||||
|
||||
When activated, this skill provides:
|
||||
|
||||
1. **PEP8 Compliance Review**
|
||||
- Check naming conventions (snake_case, PascalCase)
|
||||
- Verify line length (88 characters)
|
||||
- Review whitespace and indentation
|
||||
- Check import ordering and grouping
|
||||
- Validate docstring presence and format
|
||||
|
||||
2. **Type Hints Validation**
|
||||
- Ensure all functions have type hints
|
||||
- Check for proper return type annotations
|
||||
- Validate use of modern type syntax (list[], dict[] over List[], Dict[])
|
||||
- Verify Optional[] vs None union types
|
||||
- Check for Any types (discourage overuse)
|
||||
|
||||
3. **Code Structure & Organization**
|
||||
- Review module structure and imports
|
||||
- Check for proper `__init__.py` usage
|
||||
- Validate entry points (`__main__.py`)
|
||||
- Review function/class organization
|
||||
- Assess module coupling and cohesion
|
||||
|
||||
4. **Best Practices Enforcement**
|
||||
- Check for use of context managers (with statements)
|
||||
- Validate exception handling
|
||||
- Review string formatting (f-strings preferred)
|
||||
- Check for proper constant definitions
|
||||
- Assess use of comprehensions vs loops
|
||||
|
||||
5. **Common Anti-Patterns Detection**
|
||||
- Mutable default arguments
|
||||
- Bare except clauses
|
||||
- Using `global` keyword unnecessarily
|
||||
- Poor variable naming
|
||||
- Overly complex functions (high cyclomatic complexity)
|
||||
|
||||
6. **Documentation Quality**
|
||||
- Check for docstrings on public functions/classes
|
||||
- Validate docstring format (Google/NumPy style)
|
||||
- Review parameter documentation
|
||||
- Check for return value documentation
|
||||
- Assess example usage in docstrings
|
||||
|
||||
## Usage
|
||||
|
||||
This skill activates automatically when:
|
||||
- User requests: "Review this Python code"
|
||||
- User asks: "Check PEP8 compliance"
|
||||
- User mentions: "Validate type hints"
|
||||
- User wants: "Improve this Python code"
|
||||
- Code review is requested for .py files
|
||||
- User asks: "Is this code following best practices?"
|
||||
|
||||
## Review Approach
|
||||
|
||||
### 1. Initial Assessment
|
||||
- Identify Python version being used
|
||||
- Understand the code's purpose
|
||||
- Determine if it's application code or library code
|
||||
- Check project structure context
|
||||
|
||||
### 2. PEP8 Compliance Check
|
||||
|
||||
**Naming Conventions:**
|
||||
- ✅ Functions/variables: `snake_case`
|
||||
- ✅ Classes: `PascalCase`
|
||||
- ✅ Constants: `UPPER_SNAKE_CASE`
|
||||
- ✅ Private members: `_leading_underscore`
|
||||
- ✅ Module names: `lowercase` or `snake_case`
|
||||
|
||||
**Code Layout:**
|
||||
- ✅ Line length: 88 characters (Black standard)
|
||||
- ✅ Indentation: 4 spaces
|
||||
- ✅ Blank lines: 2 before top-level definitions, 1 between methods
|
||||
- ✅ Import order: stdlib → third-party → local
|
||||
- ✅ One import per line (except from imports)
|
||||
|
||||
### 3. Type Hints Review
|
||||
|
||||
**Check for:**
|
||||
```python
|
||||
# ❌ Missing type hints
|
||||
def process(data):
|
||||
return data.upper()
|
||||
|
||||
# ✅ Proper type hints
|
||||
def process(data: str) -> str:
|
||||
return data.upper()
|
||||
|
||||
# ❌ Old-style typing (Python <3.9)
|
||||
from typing import List, Dict
|
||||
def get_items() -> List[Dict[str, int]]:
|
||||
pass
|
||||
|
||||
# ✅ Modern type syntax (Python 3.9+)
|
||||
def get_items() -> list[dict[str, int]]:
|
||||
pass
|
||||
|
||||
# ❌ Overuse of Any
|
||||
from typing import Any
|
||||
def process(data: Any) -> Any:
|
||||
pass
|
||||
|
||||
# ✅ Specific types
|
||||
def process(data: str | int) -> str:
|
||||
return str(data)
|
||||
```
|
||||
|
||||
### 4. Common Issues Detection
|
||||
|
||||
**Mutable Default Arguments:**
|
||||
```python
|
||||
# ❌ Dangerous
|
||||
def append_to(element: str, target: list[str] = []) -> list[str]:
|
||||
target.append(element)
|
||||
return target
|
||||
|
||||
# ✅ Safe
|
||||
def append_to(element: str, target: list[str] | None = None) -> list[str]:
|
||||
if target is None:
|
||||
target = []
|
||||
target.append(element)
|
||||
return target
|
||||
```
|
||||
|
||||
**Exception Handling:**
|
||||
```python
|
||||
# ❌ Bare except
|
||||
try:
|
||||
risky_operation()
|
||||
except:
|
||||
pass
|
||||
|
||||
# ✅ Specific exception
|
||||
try:
|
||||
risky_operation()
|
||||
except ValueError as e:
|
||||
logger.error(f"Invalid value: {e}")
|
||||
raise
|
||||
```
|
||||
|
||||
**String Formatting:**
|
||||
```python
|
||||
# ❌ Old style
|
||||
name = "World"
|
||||
print("Hello %s" % name)
|
||||
print("Hello {}".format(name))
|
||||
|
||||
# ✅ Modern f-strings
|
||||
print(f"Hello {name}")
|
||||
```
|
||||
|
||||
**Context Managers:**
|
||||
```python
|
||||
# ❌ Manual resource management
|
||||
file = open("data.txt")
|
||||
data = file.read()
|
||||
file.close()
|
||||
|
||||
# ✅ Context manager
|
||||
with open("data.txt") as file:
|
||||
data = file.read()
|
||||
```
|
||||
|
||||
### 5. Code Quality Assessment
|
||||
|
||||
**Function Complexity:**
|
||||
- Functions should do one thing well
|
||||
- Aim for <20 lines per function
|
||||
- Cyclomatic complexity should be low (<10)
|
||||
- Deep nesting (>3 levels) indicates need for refactoring
|
||||
|
||||
**Example - Complex to Simple:**
|
||||
```python
|
||||
# ❌ Too complex
|
||||
def process_user(user):
|
||||
if user:
|
||||
if user.active:
|
||||
if user.email:
|
||||
if "@" in user.email:
|
||||
return user.email.lower()
|
||||
return None
|
||||
|
||||
# ✅ Simplified
|
||||
def process_user(user: User | None) -> str | None:
|
||||
if not user or not user.active or not user.email:
|
||||
return None
|
||||
|
||||
if "@" not in user.email:
|
||||
return None
|
||||
|
||||
return user.email.lower()
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Provide review results in this structured format:
|
||||
|
||||
```markdown
|
||||
## Python Code Review Results
|
||||
|
||||
### Summary
|
||||
[Brief overview of the code quality and main findings]
|
||||
|
||||
### PEP8 Compliance: [PASS/FAIL/PARTIAL]
|
||||
**Issues Found:**
|
||||
- Line 15: Line too long (102 characters, limit 88)
|
||||
- Line 23: Missing blank line after function definition
|
||||
- Line 45: Import should be at top of file
|
||||
|
||||
### Type Hints Coverage: [X%]
|
||||
**Missing Type Hints:**
|
||||
- Line 10: Function `process_data` missing return type
|
||||
- Line 18: Parameter `config` missing type annotation
|
||||
- Line 25: Using deprecated `List` instead of `list`
|
||||
|
||||
**Recommendations:**
|
||||
```python
|
||||
# Current
|
||||
def process_data(items, limit=10):
|
||||
return items[:limit]
|
||||
|
||||
# Improved
|
||||
def process_data(items: list[str], limit: int = 10) -> list[str]:
|
||||
return items[:limit]
|
||||
```
|
||||
|
||||
### Code Quality Issues
|
||||
|
||||
#### Critical 🔴
|
||||
- **Line 34**: Mutable default argument in `add_item(item, items=[])`
|
||||
- **Line 56**: Bare except clause catching all exceptions
|
||||
|
||||
#### Important 🟡
|
||||
- **Line 12**: Function complexity too high (15 branches)
|
||||
- **Line 78**: Using `global` keyword - consider refactoring
|
||||
- **Line 91**: Variable name `x` is not descriptive
|
||||
|
||||
#### Minor 🔵
|
||||
- **Line 23**: Could use f-string instead of .format()
|
||||
- **Line 45**: List comprehension would be more Pythonic
|
||||
|
||||
### Best Practices Violations
|
||||
|
||||
1. **Line 15-18: Manual file handling**
|
||||
- **Current:**
|
||||
```python
|
||||
f = open("data.txt")
|
||||
data = f.read()
|
||||
f.close()
|
||||
```
|
||||
- **Improved:**
|
||||
```python
|
||||
with open("data.txt") as f:
|
||||
data = f.read()
|
||||
```
|
||||
|
||||
2. **Line 34: Mutable default argument**
|
||||
- **Impact**: Can cause bugs when function is called multiple times
|
||||
- **Fix**: Use None as default, create new list inside function
|
||||
|
||||
### Positive Aspects ✅
|
||||
- Consistent naming conventions throughout
|
||||
- Good use of type hints in 80% of functions
|
||||
- Proper docstrings on all public methods
|
||||
- Clean import organization
|
||||
|
||||
### Documentation Review
|
||||
- ✅ All public functions have docstrings
|
||||
- ✅ Docstrings follow Google style format
|
||||
- ⚠️ Some docstrings missing return value description
|
||||
- ⚠️ No examples in docstrings (consider adding for complex functions)
|
||||
|
||||
### Modernization Suggestions
|
||||
- Update to Python 3.10+ syntax (use `|` for unions instead of `Union`)
|
||||
- Consider using `match/case` statement (Python 3.10+) for complex conditionals
|
||||
- Replace `Dict`, `List` with `dict`, `list` (Python 3.9+)
|
||||
|
||||
### Next Steps
|
||||
1. Fix critical issues (mutable defaults, bare excepts)
|
||||
2. Add missing type hints
|
||||
3. Refactor complex functions (>15 lines)
|
||||
4. Run automated tools:
|
||||
```bash
|
||||
uv run ruff check . # Linting
|
||||
uv run mypy . # Type checking
|
||||
uv run ruff format . # Auto-formatting
|
||||
```
|
||||
|
||||
### Recommended Tools
|
||||
- **ruff**: Fast linting and formatting (replaces black, flake8, isort)
|
||||
- **mypy**: Static type checking
|
||||
- **pytest**: Testing framework
|
||||
- **coverage**: Code coverage analysis
|
||||
|
||||
### Overall Assessment: [GOOD/NEEDS IMPROVEMENT/EXCELLENT]
|
||||
[Brief summary and priority recommendations]
|
||||
```
|
||||
|
||||
## Review Standards
|
||||
|
||||
### PEP8 Checklist
|
||||
|
||||
**Naming:**
|
||||
- [ ] Functions and variables use snake_case
|
||||
- [ ] Classes use PascalCase
|
||||
- [ ] Constants use UPPER_SNAKE_CASE
|
||||
- [ ] Private members start with underscore
|
||||
|
||||
**Formatting:**
|
||||
- [ ] Line length ≤ 88 characters
|
||||
- [ ] 4 spaces for indentation (no tabs)
|
||||
- [ ] 2 blank lines before class/function definitions
|
||||
- [ ] 1 blank line between methods
|
||||
|
||||
**Imports:**
|
||||
- [ ] Grouped: stdlib → third-party → local
|
||||
- [ ] Alphabetically sorted within groups
|
||||
- [ ] No wildcard imports (`from module import *`)
|
||||
- [ ] Unused imports removed
|
||||
|
||||
### Type Hints Standards
|
||||
|
||||
**Required:**
|
||||
```python
|
||||
# All function parameters
|
||||
def func(name: str, age: int) -> None:
|
||||
pass
|
||||
|
||||
# Return types (even None)
|
||||
def get_data() -> dict[str, Any]:
|
||||
pass
|
||||
|
||||
# Class attributes
|
||||
class User:
|
||||
name: str
|
||||
age: int
|
||||
|
||||
def __init__(self, name: str, age: int) -> None:
|
||||
self.name = name
|
||||
self.age = age
|
||||
```
|
||||
|
||||
**Modern Syntax (Python 3.9+):**
|
||||
- Use `list[]` not `List[]`
|
||||
- Use `dict[]` not `Dict[]`
|
||||
- Use `tuple[]` not `Tuple[]`
|
||||
- Use `set[]` not `Set[]`
|
||||
- Use `|` not `Union[]` (Python 3.10+)
|
||||
|
||||
### Documentation Standards
|
||||
|
||||
**Google Style Docstrings:**
|
||||
```python
|
||||
def function_name(param1: str, param2: int) -> bool:
|
||||
"""Brief description of function.
|
||||
|
||||
Longer description explaining what the function does,
|
||||
its behavior, and any important details.
|
||||
|
||||
Args:
|
||||
param1: Description of param1
|
||||
param2: Description of param2
|
||||
|
||||
Returns:
|
||||
Description of return value
|
||||
|
||||
Raises:
|
||||
ValueError: When param2 is negative
|
||||
|
||||
Example:
|
||||
>>> function_name("test", 5)
|
||||
True
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
## Best Practices to Promote
|
||||
|
||||
1. **Use type hints everywhere** - Improves IDE support and catches bugs early
|
||||
2. **Prefer f-strings** - More readable than format() or %
|
||||
3. **Use context managers** - For file handling, locks, connections
|
||||
4. **Write small functions** - Each function should do one thing well
|
||||
5. **Use comprehensions** - More Pythonic than loops for simple transformations
|
||||
6. **Avoid global state** - Pass dependencies explicitly
|
||||
7. **Use dataclasses** - For data containers (Python 3.7+)
|
||||
8. **Use pathlib** - Instead of os.path for file operations
|
||||
9. **Use enums** - For fixed sets of constants
|
||||
10. **Write docstrings** - For all public APIs
|
||||
|
||||
## Common Refactoring Patterns
|
||||
|
||||
### Replace Loop with Comprehension
|
||||
```python
|
||||
# ❌ Before
|
||||
result = []
|
||||
for item in items:
|
||||
if item.active:
|
||||
result.append(item.name)
|
||||
|
||||
# ✅ After
|
||||
result = [item.name for item in items if item.active]
|
||||
```
|
||||
|
||||
### Use Dataclass
|
||||
```python
|
||||
# ❌ Before
|
||||
class User:
|
||||
def __init__(self, name, email, age):
|
||||
self.name = name
|
||||
self.email = email
|
||||
self.age = age
|
||||
|
||||
# ✅ After
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
name: str
|
||||
email: str
|
||||
age: int
|
||||
```
|
||||
|
||||
### Use Pathlib
|
||||
```python
|
||||
# ❌ Before
|
||||
import os
|
||||
path = os.path.join("data", "file.txt")
|
||||
if os.path.exists(path):
|
||||
with open(path) as f:
|
||||
data = f.read()
|
||||
|
||||
# ✅ After
|
||||
from pathlib import Path
|
||||
path = Path("data") / "file.txt"
|
||||
if path.exists():
|
||||
data = path.read_text()
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
This skill works seamlessly with:
|
||||
- Code review workflows
|
||||
- Pull request reviews
|
||||
- Refactoring tasks
|
||||
- Code quality improvement projects
|
||||
- Pre-commit validation
|
||||
- Migration to modern Python versions
|
||||
- Type hint adoption projects
|
||||
486
skills/python-project-setup/SKILL.md
Normal file
486
skills/python-project-setup/SKILL.md
Normal file
@@ -0,0 +1,486 @@
|
||||
---
|
||||
name: python-project-setup
|
||||
description: Sets up modern Python projects with uv tooling, src layout, and PEP8 standards. Handles both new and existing projects, presents interactive library selection for CLI/TUI apps, generates pyproject.toml, and provides complete scaffolding with type hints and proper structure.
|
||||
---
|
||||
|
||||
# Python Project Setup Skill
|
||||
|
||||
A specialized skill for setting up modern Python projects using uv tooling, best practices, and standardized project structures.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill enables Claude to autonomously set up Python projects following modern best practices. It promotes the use of `uv` (the fast Python package manager), enforces PEP8 standards, type hints, and proper project structure with `src` layout.
|
||||
|
||||
## Capabilities
|
||||
|
||||
When activated, this skill provides:
|
||||
|
||||
1. **Modern Tooling with uv**
|
||||
- Use `uv` instead of pip/python directly
|
||||
- Initialize virtual environments: `uv venv`
|
||||
- Manage dependencies: `uv add`, `uv remove`
|
||||
- Sync dependencies: `uv sync`
|
||||
- Run scripts: `uv run`
|
||||
|
||||
2. **Project Structure Detection & Setup**
|
||||
- Detect if project is new or existing
|
||||
- Analyze current structure and adapt recommendations
|
||||
- Promote `src/package_name` layout
|
||||
- Create proper `__init__.py` files
|
||||
- Setup `__main__.py` entry points
|
||||
|
||||
3. **pyproject.toml Management**
|
||||
- Generate modern pyproject.toml
|
||||
- Configure project metadata
|
||||
- Setup dependencies and dev dependencies
|
||||
- Include tool configurations (ruff, mypy, pytest)
|
||||
- Define entry points for CLI applications
|
||||
|
||||
4. **Library Selection**
|
||||
- Present interactive menu for library choices
|
||||
- **CLI applications**: click (commands), rich (output), inquirer (interactive prompts)
|
||||
- **TUI applications**: textual (framework), rich (rendering)
|
||||
- **Pure libraries**: minimal dependencies
|
||||
- Smart recommendations based on project type
|
||||
|
||||
5. **Standards Enforcement**
|
||||
- PEP8 compliance via ruff
|
||||
- Type hints for all functions/methods
|
||||
- Proper docstring formats (Google/NumPy style)
|
||||
- Follow PEP8 and modern Python standards
|
||||
|
||||
## Usage
|
||||
|
||||
This skill activates automatically when:
|
||||
- User requests: "Create a new Python project"
|
||||
- User asks: "Setup Python project structure"
|
||||
- User mentions: "Initialize Python package"
|
||||
- User wants to: "Migrate to uv"
|
||||
- Claude detects Python project initialization
|
||||
- pyproject.toml creation or modification is discussed
|
||||
|
||||
**Automatic Defaults**:
|
||||
- Uses current directory as project root
|
||||
- Derives package name from directory name
|
||||
- Detects and uses current Python version
|
||||
- No tests (user can add later)
|
||||
- Minimal dependencies by default
|
||||
|
||||
**Only ONE Question**:
|
||||
- Project type + libraries combined (default: minimal)
|
||||
|
||||
## Project Setup Approach
|
||||
|
||||
### For New Projects
|
||||
|
||||
**IMPORTANT**: Always work in the current directory as the project root.
|
||||
|
||||
1. **Gather Requirements**
|
||||
- Package name (derive from current directory name by default)
|
||||
- **Ask ONE merged question**: Project type + libraries combined:
|
||||
```
|
||||
What type of project? (press Enter for CLI with click+rich)
|
||||
[1] CLI with click+rich (default)
|
||||
[2] CLI with argparse
|
||||
[3] CLI with typer
|
||||
[4] TUI with textual
|
||||
[5] Generic Python Project
|
||||
```
|
||||
- **Don't ask about tests** - skip by default
|
||||
|
||||
2. **Assume Current Directory & Python Version**
|
||||
- **Use current folder as project root** (don't create new directory)
|
||||
- Derive package name from current folder name (convert to valid Python identifier)
|
||||
- Example: `my-awesome-app/` → package name: `my_awesome_app`
|
||||
- **Detect current Python version** and use as project requirement
|
||||
- Example: `python --version` → `Python 3.11.5` → `requires-python = ">=3.11"`
|
||||
|
||||
3. **Check Environment**
|
||||
```bash
|
||||
# Detect current Python version
|
||||
python --version # Use this as the project's Python requirement
|
||||
|
||||
# Verify uv is installed
|
||||
uv --version
|
||||
|
||||
# If not installed, provide instructions:
|
||||
# macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
# Windows: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
```
|
||||
|
||||
4. **Create Minimal Project Structure in Current Directory**
|
||||
```
|
||||
./ # Current directory (project root)
|
||||
├── src/
|
||||
│ └── package_name/
|
||||
│ ├── __init__.py
|
||||
│ └── __main__.py # Entry point
|
||||
├── pyproject.toml # Minimal config
|
||||
├── README.md
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
**Note**: No tests/, no .python-version - keep it minimal!
|
||||
|
||||
5. **Skip Tests by Default**
|
||||
- Do NOT ask about tests
|
||||
- Do NOT create tests/ directory
|
||||
- Do NOT include pytest in dev dependencies
|
||||
- User can add tests later if needed
|
||||
|
||||
6. **Generate Minimal pyproject.toml**
|
||||
```toml
|
||||
[project]
|
||||
name = "package-name"
|
||||
version = "0.1.0"
|
||||
description = "Project description"
|
||||
authors = [
|
||||
{name = "Author Name", email = "author@example.com"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11" # Use detected Python version (e.g., 3.11, 3.12, etc.)
|
||||
dependencies = [
|
||||
# Based on library selection
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"ruff>=0.1.0",
|
||||
"mypy>=1.8.0",
|
||||
]
|
||||
# Note: No pytest by default - add when needed
|
||||
|
||||
[project.scripts]
|
||||
package-name = "package_name.__main__:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py311"
|
||||
select = ["E", "F", "I", "N", "W", "UP"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
strict = true
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
```
|
||||
|
||||
8. **Initialize with uv in Current Directory**
|
||||
```bash
|
||||
# Already in project directory (current folder)
|
||||
|
||||
# Create virtual environment
|
||||
uv venv
|
||||
|
||||
# Sync dependencies from pyproject.toml
|
||||
uv sync
|
||||
|
||||
# Add project in editable mode
|
||||
uv pip install -e .
|
||||
|
||||
# Add development dependencies (includes pytest if tests opted in)
|
||||
uv sync --group dev
|
||||
```
|
||||
|
||||
9. **Create Entry Point Examples**
|
||||
|
||||
For `src/package_name/__main__.py`:
|
||||
```python
|
||||
"""Main entry point for package_name."""
|
||||
|
||||
def main() -> None:
|
||||
"""Execute the main program."""
|
||||
print("Hello from package_name!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
For CLI apps with click:
|
||||
```python
|
||||
"""Main entry point for package_name CLI."""
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
@click.group()
|
||||
@click.version_option()
|
||||
def main() -> None:
|
||||
"""Package name CLI application."""
|
||||
pass
|
||||
|
||||
@main.command()
|
||||
@click.option("--name", default="World", help="Name to greet")
|
||||
def hello(name: str) -> None:
|
||||
"""Greet someone."""
|
||||
console.print(f"[bold green]Hello, {name}![/bold green]")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### For Existing Projects
|
||||
|
||||
**IMPORTANT**: Always work in the current directory, don't create subdirectories.
|
||||
|
||||
1. **Analyze Current Directory Structure**
|
||||
- Check for setup.py, requirements.txt, or pyproject.toml
|
||||
- Identify current package structure
|
||||
- Assess if src layout is used
|
||||
- **Detect existing tests/** directory
|
||||
|
||||
2. **Detect Context**
|
||||
- Is uv already being used?
|
||||
- What's the current dependency management approach?
|
||||
- Are there existing entry points?
|
||||
- **Are tests already present?** (skip test skeleton question if yes)
|
||||
|
||||
3. **Skip Tests Question**
|
||||
- Do NOT ask about tests for existing projects
|
||||
- User can add tests manually if needed
|
||||
|
||||
4. **Provide Migration Path**
|
||||
```bash
|
||||
# Already in project directory (current folder)
|
||||
|
||||
# Convert requirements.txt to pyproject.toml
|
||||
# Create pyproject.toml with [project.dependencies]
|
||||
|
||||
# Initialize uv for existing project
|
||||
uv venv
|
||||
|
||||
# Import existing requirements
|
||||
uv pip install -r requirements.txt
|
||||
|
||||
# Generate lock file
|
||||
uv sync
|
||||
```
|
||||
|
||||
5. **Recommend Structure Improvements**
|
||||
- Suggest moving to src layout if not present
|
||||
- Identify missing __init__.py files
|
||||
- Recommend adding __main__.py if needed
|
||||
- Suggest adding type hints if missing
|
||||
|
||||
6. **Adapt, Don't Force**
|
||||
- Respect existing conventions if reasonable
|
||||
- Provide gradual migration suggestions
|
||||
- Explain benefits of each recommendation
|
||||
|
||||
## Output Format
|
||||
|
||||
Provide setup results in this format:
|
||||
|
||||
```markdown
|
||||
## Python Project Setup Complete
|
||||
|
||||
### Project Directory
|
||||
**Working in**: `./` (current directory)
|
||||
**Package name**: `package_name`
|
||||
|
||||
### Project Structure
|
||||
[Show created directory tree relative to current directory]
|
||||
|
||||
### Configuration
|
||||
- **Python version**: [Detected from current environment, e.g., 3.11+]
|
||||
- **Package manager**: uv
|
||||
- **Layout**: src layout
|
||||
- **Entry point**: src/package_name/__main__.py
|
||||
- **Tests**: Not included (add later if needed)
|
||||
|
||||
### Dependencies Installed
|
||||
**Main dependencies:**
|
||||
- [list dependencies]
|
||||
|
||||
**Development dependencies:**
|
||||
- ruff (linting)
|
||||
- mypy (type checking)
|
||||
|
||||
### Next Steps
|
||||
1. Activate virtual environment:
|
||||
\`\`\`bash
|
||||
source .venv/bin/activate # Linux/macOS
|
||||
.venv\\Scripts\\activate # Windows
|
||||
\`\`\`
|
||||
|
||||
2. Run your application:
|
||||
\`\`\`bash
|
||||
uv run python -m package_name
|
||||
# or
|
||||
uv run package-name
|
||||
\`\`\`
|
||||
|
||||
3. Add new dependencies:
|
||||
\`\`\`bash
|
||||
uv add package-name
|
||||
\`\`\`
|
||||
|
||||
4. Lint code:
|
||||
\`\`\`bash
|
||||
uv run ruff check .
|
||||
uv run mypy src
|
||||
\`\`\`
|
||||
|
||||
### Development Commands
|
||||
- Add dependency: `uv add package-name`
|
||||
- Add dev dependency: `uv add --dev package-name`
|
||||
- Remove dependency: `uv remove package-name`
|
||||
- Update dependencies: `uv sync`
|
||||
- Run script: `uv run python script.py`
|
||||
```
|
||||
|
||||
## Standards Enforced
|
||||
|
||||
### Always Use uv Commands
|
||||
- ❌ `pip install package`
|
||||
- ✅ `uv add package`
|
||||
|
||||
- ❌ `pip install -r requirements.txt`
|
||||
- ✅ `uv sync`
|
||||
|
||||
- ❌ `python script.py`
|
||||
- ✅ `uv run python script.py`
|
||||
|
||||
- ❌ `python -m venv .venv`
|
||||
- ✅ `uv venv`
|
||||
|
||||
### Project Structure Standards
|
||||
```
|
||||
src/package_name/ # Use src layout
|
||||
├── __init__.py # Package marker (can be empty or contain version)
|
||||
├── __main__.py # Entry point with main() function
|
||||
├── module1.py # Application modules
|
||||
└── module2.py
|
||||
```
|
||||
|
||||
### Code Standards
|
||||
- All functions must have type hints
|
||||
- Use `"""Docstrings"""` for all public functions/classes
|
||||
- Follow PEP8 (enforced by ruff)
|
||||
- Line length: 88 characters (Black standard)
|
||||
- Use absolute imports from package root
|
||||
|
||||
### Example Type-Hinted Code
|
||||
```python
|
||||
from typing import Optional
|
||||
|
||||
def process_data(
|
||||
input_data: list[str],
|
||||
max_items: int = 10,
|
||||
filter_empty: bool = True
|
||||
) -> dict[str, int]:
|
||||
"""Process input data and return statistics.
|
||||
|
||||
Args:
|
||||
input_data: List of strings to process
|
||||
max_items: Maximum number of items to process
|
||||
filter_empty: Whether to filter out empty strings
|
||||
|
||||
Returns:
|
||||
Dictionary with processing statistics
|
||||
"""
|
||||
result: dict[str, int] = {}
|
||||
# Implementation
|
||||
return result
|
||||
```
|
||||
|
||||
## Library Recommendations
|
||||
|
||||
### For CLI Applications
|
||||
```toml
|
||||
dependencies = [
|
||||
"click>=8.1.0", # Command-line interface framework
|
||||
"rich>=13.7.0", # Beautiful terminal output
|
||||
"inquirer>=3.2.0", # Interactive prompts
|
||||
]
|
||||
```
|
||||
|
||||
### For TUI Applications
|
||||
```toml
|
||||
dependencies = [
|
||||
"textual>=0.47.0", # Modern TUI framework
|
||||
"rich>=13.7.0", # Rendering engine
|
||||
]
|
||||
```
|
||||
|
||||
### For Libraries
|
||||
```toml
|
||||
dependencies = [
|
||||
# Only essential dependencies
|
||||
# Keep it minimal
|
||||
]
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Work in current directory** - Don't create nested project folders
|
||||
2. **Use current Python version** - Detect from environment, don't ask
|
||||
3. **Ask one merged question** - Project type + libraries combined
|
||||
4. **Start with minimal dependencies** - Add more as needed
|
||||
5. **Always include type hints** - Helps with IDE support and catches bugs
|
||||
6. **Use src layout** - Prevents import issues and packaging problems
|
||||
7. **Pin major versions only** - e.g., `>=8.1.0` not `==8.1.0`
|
||||
8. **Separate dev dependencies** - Use `[project.optional-dependencies]`
|
||||
9. **Document with docstrings** - Every public function and class
|
||||
10. **Don't ask about tests** - Skip by default, users add when ready
|
||||
11. **Use ruff for linting** - Fast, comprehensive, replaces flake8/isort/black
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### CLI Application with Click + Rich
|
||||
```python
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
console = Console()
|
||||
|
||||
@click.command()
|
||||
@click.argument("name")
|
||||
@click.option("--count", default=1, help="Number of greetings")
|
||||
def greet(name: str, count: int) -> None:
|
||||
"""Greet NAME COUNT times."""
|
||||
for _ in range(count):
|
||||
console.print(f"[bold blue]Hello, {name}![/bold blue]")
|
||||
```
|
||||
|
||||
### TUI Application with Textual
|
||||
```python
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Header, Footer, Button
|
||||
|
||||
class MyApp(App):
|
||||
"""A simple Textual application."""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
yield Button("Click me!", id="btn")
|
||||
yield Footer()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""Handle button press."""
|
||||
self.notify("Button clicked!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = MyApp()
|
||||
app.run()
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
This skill works seamlessly with:
|
||||
- New Python project creation
|
||||
- Existing project modernization
|
||||
- Migration from pip to uv
|
||||
- Package structure refactoring
|
||||
- Dependency management tasks
|
||||
Reference in New Issue
Block a user