python-skills 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_skills/__init__.py +10 -0
- python_skills/__main__.py +6 -0
- python_skills/adapters/__init__.py +48 -0
- python_skills/adapters/agent_skills.py +415 -0
- python_skills/adapters/aider_adapter.py +226 -0
- python_skills/adapters/base.py +153 -0
- python_skills/adapters/claude.py +474 -0
- python_skills/adapters/cline.py +332 -0
- python_skills/adapters/codex.py +24 -0
- python_skills/adapters/continue_adapter.py +198 -0
- python_skills/adapters/cursor.py +327 -0
- python_skills/adapters/gemini.py +26 -0
- python_skills/adapters/goose.py +26 -0
- python_skills/adapters/junie.py +25 -0
- python_skills/adapters/kiro.py +382 -0
- python_skills/adapters/opencode.py +27 -0
- python_skills/adapters/roo.py +25 -0
- python_skills/adapters/universal.py +203 -0
- python_skills/adapters/vscode.py +27 -0
- python_skills/adapters/windsurf.py +26 -0
- python_skills/adapters/zed.py +27 -0
- python_skills/cli.py +326 -0
- python_skills/config.py +160 -0
- python_skills/detector.py +152 -0
- python_skills/installer.py +163 -0
- python_skills/markers.py +115 -0
- python_skills/skills/__init__.py +14 -0
- python_skills/skills/loader.py +171 -0
- python_skills/skills/metadata.py +152 -0
- python_skills/skills/registry.py +101 -0
- python_skills/state.py +204 -0
- python_skills-1.0.0.dist-info/METADATA +99 -0
- python_skills-1.0.0.dist-info/RECORD +105 -0
- python_skills-1.0.0.dist-info/WHEEL +4 -0
- python_skills-1.0.0.dist-info/entry_points.txt +2 -0
- python_skills-1.0.0.dist-info/licenses/LICENSE +21 -0
- skills/advanced_python.md +239 -0
- skills/anti_patterns/index.md +406 -0
- skills/comprehensions.md +167 -0
- skills/control_flow.md +175 -0
- skills/data_structures.md +243 -0
- skills/debugging/common_bugs.md +222 -0
- skills/debugging/inspection_techniques.md +249 -0
- skills/debugging/root_cause.md +203 -0
- skills/engineering/application_logging.md +195 -0
- skills/engineering/cli_apps.md +207 -0
- skills/engineering/configuration.md +218 -0
- skills/engineering/database.md +240 -0
- skills/engineering/dependency_management.md +205 -0
- skills/engineering/http_clients.md +267 -0
- skills/engineering/modules_packages.md +211 -0
- skills/engineering/packaging.md +197 -0
- skills/engineering/project_structure.md +155 -0
- skills/engineering/pyproject_toml.md +302 -0
- skills/engineering/virtual_environments.md +206 -0
- skills/functions.md +244 -0
- skills/generation/async_concurrency.md +291 -0
- skills/generation/error_handling.md +276 -0
- skills/generation/protocols_generics.md +243 -0
- skills/generation/type_hints.md +290 -0
- skills/generation/validation_pipeline.md +274 -0
- skills/generation/workflow.md +190 -0
- skills/oop.md +228 -0
- skills/quality/abstractions.md +154 -0
- skills/quality/comments.md +177 -0
- skills/quality/documentation.md +176 -0
- skills/quality/duplication.md +137 -0
- skills/quality/maintainability.md +142 -0
- skills/quality/naming.md +171 -0
- skills/quality/quality_functions.md +245 -0
- skills/quality/readability.md +239 -0
- skills/quality/type_annotations.md +192 -0
- skills/refactoring/behavior_preservation.md +157 -0
- skills/refactoring/incremental.md +187 -0
- skills/refactoring/interface_stability.md +199 -0
- skills/refactoring/safe_refactoring.md +206 -0
- skills/security/auth_boundaries.md +200 -0
- skills/security/command_injection.md +207 -0
- skills/security/dependency_risks.md +282 -0
- skills/security/file_handling.md +156 -0
- skills/security/input_validation.md +190 -0
- skills/security/path_traversal.md +172 -0
- skills/security/secrets.md +171 -0
- skills/security/sql_injection.md +188 -0
- skills/security/unsafe_deserialization.md +164 -0
- skills/stdlib/argparse.md +178 -0
- skills/stdlib/collections.md +212 -0
- skills/stdlib/datetime.md +187 -0
- skills/stdlib/functools.md +238 -0
- skills/stdlib/itertools.md +183 -0
- skills/stdlib/json.md +162 -0
- skills/stdlib/logging.md +185 -0
- skills/stdlib/os_sys.md +184 -0
- skills/stdlib/pathlib.md +218 -0
- skills/stdlib/re.md +171 -0
- skills/stdlib/statistics.md +112 -0
- skills/stdlib/subprocess.md +211 -0
- skills/testing/async_tests.md +249 -0
- skills/testing/coverage.md +168 -0
- skills/testing/edge_cases.md +197 -0
- skills/testing/fixtures_mocks.md +203 -0
- skills/testing/organization.md +205 -0
- skills/testing/parameterized.md +174 -0
- skills/testing/regression_tests.md +165 -0
- skills/variables_types.md +107 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Quality: Comments
|
|
2
|
+
|
|
3
|
+
**Purpose**: When and how to comment code effectively.
|
|
4
|
+
|
|
5
|
+
**When to use**: All code generation and review.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Comment Philosophy
|
|
12
|
+
- **Code explains WHAT** — Comments explain WHY
|
|
13
|
+
- **Good code > Good comments** — Refactor to make code self-explanatory
|
|
14
|
+
- **Outdated comments are worse than no comments** — They mislead
|
|
15
|
+
|
|
16
|
+
### When to Comment
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
# GOOD — Why: business rule, not obvious
|
|
20
|
+
def calculate_tax(amount: Decimal, region: str) -> Decimal:
|
|
21
|
+
# Region X has special 0% rate for orders > $1000 per local law
|
|
22
|
+
if region == "X" and amount > 1000:
|
|
23
|
+
return Decimal("0")
|
|
24
|
+
return amount * TAX_RATES[region]
|
|
25
|
+
|
|
26
|
+
# GOOD — Why: workaround for external limitation
|
|
27
|
+
def fetch_data(url: str) -> Data:
|
|
28
|
+
# API returns 500 on HEAD requests, use GET with Range header
|
|
29
|
+
response = http.get(url, headers={"Range": "bytes=0-"})
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
# GOOD — Why: non-obvious algorithm choice
|
|
33
|
+
def sort_items(items: list[Item]) -> list[Item]:
|
|
34
|
+
# Timsort stable sort preserves insertion order for equal keys
|
|
35
|
+
# Required for consistent pagination
|
|
36
|
+
return sorted(items, key=lambda x: (x.priority, x.created_at))
|
|
37
|
+
|
|
38
|
+
# BAD — Restates code
|
|
39
|
+
def get_user(user_id: int) -> User:
|
|
40
|
+
# Get user by ID
|
|
41
|
+
return db.query(User).filter_by(id=user_id).first()
|
|
42
|
+
|
|
43
|
+
# BAD — Obvious
|
|
44
|
+
x = x + 1 # Increment x
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Docstrings (Not Comments)
|
|
48
|
+
```python
|
|
49
|
+
# Module docstring
|
|
50
|
+
"""User management service.
|
|
51
|
+
|
|
52
|
+
Provides CRUD operations for users and authentication.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# Class docstring
|
|
56
|
+
class UserService:
|
|
57
|
+
"""Manages user lifecycle and authentication.
|
|
58
|
+
|
|
59
|
+
Handles user creation, validation, and session management.
|
|
60
|
+
Uses UserRepository for persistence.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def create_user(self, data: UserData) -> User:
|
|
64
|
+
"""Create a new user.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
data: Validated user data.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Created user with assigned ID.
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ValidationError: If data is invalid.
|
|
74
|
+
ConflictError: If email already exists.
|
|
75
|
+
"""
|
|
76
|
+
...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Inline Comments (Rare)
|
|
80
|
+
```python
|
|
81
|
+
# Only for non-obvious logic
|
|
82
|
+
result = complex_calculation() # type: ignore[assignment] # mypy false positive
|
|
83
|
+
|
|
84
|
+
# Or algorithm explanation
|
|
85
|
+
# Use Fisher-Yates shuffle for uniform distribution
|
|
86
|
+
for i in range(len(items) - 1, 0, -1):
|
|
87
|
+
j = random.randint(0, i)
|
|
88
|
+
items[i], items[j] = items[j], items[i]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### TODO/FIXME Comments
|
|
92
|
+
```python
|
|
93
|
+
# Format: # TODO(author): description
|
|
94
|
+
# TODO(john): Add retry logic when API supports it
|
|
95
|
+
|
|
96
|
+
# FIXME: Known bug
|
|
97
|
+
# FIXME: Race condition on concurrent updates
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Commented Code
|
|
101
|
+
```python
|
|
102
|
+
# NEVER commit commented-out code
|
|
103
|
+
# def old_function():
|
|
104
|
+
# ...
|
|
105
|
+
# Use version control history instead
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Decision Rules
|
|
111
|
+
|
|
112
|
+
| Situation | Comment? |
|
|
113
|
+
|-----------|----------|
|
|
114
|
+
| Non-obvious business logic | Yes (WHY) |
|
|
115
|
+
| Workaround for external issue | Yes (WHY) |
|
|
116
|
+
| Algorithm choice rationale | Yes (WHY) |
|
|
117
|
+
| Complex regex | Yes (pattern explanation) |
|
|
118
|
+
| Obvious code | No |
|
|
119
|
+
| Restating code | No |
|
|
120
|
+
| TODO without issue reference | No (use issue tracker) |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Preferred Patterns
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# Docstring for public API
|
|
128
|
+
def process_payment(payment: Payment) -> PaymentResult:
|
|
129
|
+
"""Process a payment through the configured gateway.
|
|
130
|
+
|
|
131
|
+
Handles validation, authorization, and capture.
|
|
132
|
+
Retries on transient gateway errors (up to 3 attempts).
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
payment: Validated payment with amount and method.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
PaymentResult with transaction ID and status.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
PaymentError: If gateway rejects payment.
|
|
142
|
+
GatewayUnavailable: If gateway is unreachable.
|
|
143
|
+
"""
|
|
144
|
+
...
|
|
145
|
+
|
|
146
|
+
# Inline only for genuinely tricky code
|
|
147
|
+
def hash_password(password: str) -> str:
|
|
148
|
+
# Argon2id with memory=64MB, iterations=3, parallelism=4
|
|
149
|
+
# Parameters per OWASP 2023 recommendations
|
|
150
|
+
return argon2.hash(password)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Avoid
|
|
156
|
+
|
|
157
|
+
- Commented-out code blocks
|
|
158
|
+
- Redundant comments (`i = i + 1 # increment`)
|
|
159
|
+
- Comments that can be replaced by better naming
|
|
160
|
+
- Comments explaining standard library usage
|
|
161
|
+
- Outdated comments (delete when code changes)
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Validation Considerations
|
|
166
|
+
|
|
167
|
+
- `ruff` checks for TODO/FIXME format
|
|
168
|
+
- Docstring coverage (`pydocstyle`, `interrogate`)
|
|
169
|
+
- No commented code in diffs
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Related Skills
|
|
174
|
+
|
|
175
|
+
- `quality/documentation.md`
|
|
176
|
+
- `quality/readability.md`
|
|
177
|
+
- `quality/naming.md`
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Quality: Documentation
|
|
2
|
+
|
|
3
|
+
**Purpose**: Document public APIs and non-obvious behavior appropriately.
|
|
4
|
+
|
|
5
|
+
**When to use**: Public modules, classes, functions, complex systems.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Documentation Levels
|
|
12
|
+
|
|
13
|
+
| Level | Audience | Format |
|
|
14
|
+
|-------|----------|--------|
|
|
15
|
+
| **Docstrings** | Developers using API | In-code, accessible via `help()` |
|
|
16
|
+
| **README** | Users, contributors | Markdown in repo root |
|
|
17
|
+
| **Architecture docs** | Maintainers | Project-specific (e.g., `project-docs/architecture.md`) |
|
|
18
|
+
| **API reference** | External consumers | Generated (Sphinx, pdoc) |
|
|
19
|
+
| **Changelog** | Users | `CHANGELOG.md` |
|
|
20
|
+
|
|
21
|
+
### Docstring Format (Google/NumPy/Sphinx)
|
|
22
|
+
```python
|
|
23
|
+
def fetch_users(
|
|
24
|
+
client: APIClient,
|
|
25
|
+
filters: UserFilters | None = None,
|
|
26
|
+
limit: int = 100,
|
|
27
|
+
) -> list[User]:
|
|
28
|
+
"""Fetch users matching filters.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
client: Authenticated API client.
|
|
32
|
+
filters: Optional filters (name, status, role).
|
|
33
|
+
limit: Maximum results (default 100, max 1000).
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
List of users matching criteria.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
APIError: If request fails.
|
|
40
|
+
ValidationError: If limit exceeds maximum.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> client = APIClient(token="abc")
|
|
44
|
+
>>> users = fetch_users(client, UserFilters(active=True), limit=50)
|
|
45
|
+
"""
|
|
46
|
+
...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Module Docstring
|
|
50
|
+
```python
|
|
51
|
+
"""Payment processing module.
|
|
52
|
+
|
|
53
|
+
Provides payment authorization, capture, and refund operations.
|
|
54
|
+
Supports multiple gateways via the PaymentGateway protocol.
|
|
55
|
+
|
|
56
|
+
Typical usage:
|
|
57
|
+
gateway = StripeGateway(api_key="sk_...")
|
|
58
|
+
processor = PaymentProcessor(gateway)
|
|
59
|
+
result = processor.charge(amount=1000, currency="USD")
|
|
60
|
+
"""
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Class Docstring
|
|
64
|
+
```python
|
|
65
|
+
class PaymentProcessor:
|
|
66
|
+
"""Orchestrates payment operations across gateways.
|
|
67
|
+
|
|
68
|
+
Handles retry logic, idempotency, and gateway failover.
|
|
69
|
+
Not thread-safe; use one instance per request.
|
|
70
|
+
|
|
71
|
+
Attributes:
|
|
72
|
+
gateway: Primary payment gateway.
|
|
73
|
+
fallback: Optional fallback gateway.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, gateway: PaymentGateway, fallback: PaymentGateway | None = None):
|
|
77
|
+
self.gateway = gateway
|
|
78
|
+
self.fallback = fallback
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Decision Rules
|
|
84
|
+
|
|
85
|
+
| Element | Docstring Required? |
|
|
86
|
+
|---------|---------------------|
|
|
87
|
+
| Public module | Yes |
|
|
88
|
+
| Public class | Yes |
|
|
89
|
+
| Public function/method | Yes |
|
|
90
|
+
| Private (`_` prefix) | Optional |
|
|
91
|
+
| Override (same behavior) | No (inherit) |
|
|
92
|
+
| Property | Yes (describe what it returns) |
|
|
93
|
+
|
|
94
|
+
### README Structure
|
|
95
|
+
```markdown
|
|
96
|
+
# Package Name
|
|
97
|
+
|
|
98
|
+
One-line description.
|
|
99
|
+
|
|
100
|
+
## Installation
|
|
101
|
+
```bash
|
|
102
|
+
pip install package-name
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Quick Start
|
|
106
|
+
```python
|
|
107
|
+
from package import main
|
|
108
|
+
main()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Configuration
|
|
112
|
+
Environment variables...
|
|
113
|
+
|
|
114
|
+
## API Reference
|
|
115
|
+
Link to generated docs.
|
|
116
|
+
|
|
117
|
+
## Contributing
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
...
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Preferred Patterns
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# Type hints + docstring = complete API docs
|
|
130
|
+
def process(
|
|
131
|
+
data: InputData,
|
|
132
|
+
config: ProcessingConfig = ProcessingConfig(),
|
|
133
|
+
) -> OutputData:
|
|
134
|
+
"""Process input data according to configuration.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
data: Input to process. Must be validated.
|
|
138
|
+
config: Processing options. Defaults used if omitted.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Processed output data.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
ValidationError: If input data is invalid.
|
|
145
|
+
ProcessingError: If processing fails.
|
|
146
|
+
"""
|
|
147
|
+
...
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Avoid
|
|
153
|
+
|
|
154
|
+
- Missing docstrings on public API
|
|
155
|
+
- Docstrings that just repeat signature
|
|
156
|
+
- Outdated docstrings (update with code)
|
|
157
|
+
- Documenting private implementation details
|
|
158
|
+
- Redundant type information in docstring (use type hints)
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Validation Considerations
|
|
163
|
+
|
|
164
|
+
- `pydocstyle` / `ruff` docstring checks
|
|
165
|
+
- `interrogate` for coverage
|
|
166
|
+
- `pdoc` / `sphinx` build succeeds
|
|
167
|
+
- Examples in docstrings runnable (doctest)
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Related Skills
|
|
172
|
+
|
|
173
|
+
- `quality/comments.md`
|
|
174
|
+
- `generation/type_hints.md`
|
|
175
|
+
- `engineering/packaging.md`
|
|
176
|
+
- `quality/readability.md`
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Quality: Duplication
|
|
2
|
+
|
|
3
|
+
**Purpose**: Remove meaningful duplication without creating unnecessary abstractions.
|
|
4
|
+
|
|
5
|
+
**When to use**: Refactoring, code review, applying DRY principle correctly.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Types of Duplication
|
|
12
|
+
|
|
13
|
+
| Type | Example | Action |
|
|
14
|
+
|------|---------|--------|
|
|
15
|
+
| **True duplication** | Same logic, same reason to change | Extract |
|
|
16
|
+
| **Accidental duplication** | Same code, different reasons to change | Keep separate |
|
|
17
|
+
| **Structural duplication** | Similar structure, different domain | Keep separate |
|
|
18
|
+
|
|
19
|
+
### True Duplication (Extract)
|
|
20
|
+
```python
|
|
21
|
+
# Before — same validation logic
|
|
22
|
+
def create_user(data):
|
|
23
|
+
if not data.email or "@" not in data.email:
|
|
24
|
+
raise ValueError("Invalid email")
|
|
25
|
+
if not data.name or len(data.name) < 2:
|
|
26
|
+
raise ValueError("Invalid name")
|
|
27
|
+
return save_user(data)
|
|
28
|
+
|
|
29
|
+
def update_user(user_id, data):
|
|
30
|
+
if not data.email or "@" not in data.email:
|
|
31
|
+
raise ValueError("Invalid email")
|
|
32
|
+
if not data.name or len(data.name) < 2:
|
|
33
|
+
raise ValueError("Invalid name")
|
|
34
|
+
return update_user(user_id, data)
|
|
35
|
+
|
|
36
|
+
# After — extract
|
|
37
|
+
def validate_user_data(data):
|
|
38
|
+
if not data.email or "@" not in data.email:
|
|
39
|
+
raise ValueError("Invalid email")
|
|
40
|
+
if not data.name or len(data.name) < 2:
|
|
41
|
+
raise ValueError("Invalid name")
|
|
42
|
+
|
|
43
|
+
def create_user(data):
|
|
44
|
+
validate_user_data(data)
|
|
45
|
+
return save_user(data)
|
|
46
|
+
|
|
47
|
+
def update_user(user_id, data):
|
|
48
|
+
validate_user_data(data)
|
|
49
|
+
return update_user(user_id, data)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Accidental Duplication (Don't Extract)
|
|
53
|
+
```python
|
|
54
|
+
# User validation
|
|
55
|
+
def validate_user(user):
|
|
56
|
+
if not user.email or "@" not in user.email:
|
|
57
|
+
raise ValueError("Invalid email")
|
|
58
|
+
if user.age < 13:
|
|
59
|
+
raise ValueError("Too young")
|
|
60
|
+
|
|
61
|
+
# Product validation — LOOKS similar but DIFFERENT rules
|
|
62
|
+
def validate_product(product):
|
|
63
|
+
if not product.sku:
|
|
64
|
+
raise ValueError("SKU required")
|
|
65
|
+
if product.price <= 0:
|
|
66
|
+
raise ValueError("Price must be positive")
|
|
67
|
+
|
|
68
|
+
# DON'T extract "validate_required_fields" — different domains, different change reasons
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Rule of Three
|
|
72
|
+
- First time: Write code
|
|
73
|
+
- Second time: Note similarity, but don't extract yet
|
|
74
|
+
- Third time: Extract common abstraction
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Decision Rules
|
|
79
|
+
|
|
80
|
+
| Duplication Type | Action |
|
|
81
|
+
|------------------|--------|
|
|
82
|
+
| Same logic, same domain, same change reason | Extract |
|
|
83
|
+
| Similar structure, different domain | Keep separate |
|
|
84
|
+
| Similar structure, different change reasons | Keep separate |
|
|
85
|
+
| Boilerplate (imports, decorators) | Accept or use code gen |
|
|
86
|
+
| Test setup | Fixtures/helpers OK |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Preferred Patterns
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# Extract to function with clear name
|
|
94
|
+
def validate_email(email: str) -> None:
|
|
95
|
+
if not email or "@" not in email:
|
|
96
|
+
raise ValidationError("email", "Invalid email format")
|
|
97
|
+
|
|
98
|
+
# Extract to protocol for behavioral duplication
|
|
99
|
+
class Validator(Protocol):
|
|
100
|
+
def validate(self, data: Any) -> None: ...
|
|
101
|
+
|
|
102
|
+
# Use decorators for cross-cutting duplication
|
|
103
|
+
def validate_request(schema: type):
|
|
104
|
+
def decorator(func):
|
|
105
|
+
@wraps(func)
|
|
106
|
+
def wrapper(request):
|
|
107
|
+
data = schema.model_validate(request.json)
|
|
108
|
+
return func(data)
|
|
109
|
+
return wrapper
|
|
110
|
+
return decorator
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Avoid
|
|
116
|
+
|
|
117
|
+
- Extracting "utility" functions used once
|
|
118
|
+
- Creating base classes for accidental duplication
|
|
119
|
+
- Parameterizing extracted function to handle differences (creates complexity)
|
|
120
|
+
- DRY obsession — "Duplication is far cheaper than the wrong abstraction" (Sandi Metz)
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Validation Considerations
|
|
125
|
+
|
|
126
|
+
- `flake8-duplicates` / `pylint` duplicate-code detection
|
|
127
|
+
- Manual review: "If I change X, do I need to change Y?"
|
|
128
|
+
- Test each extracted unit independently
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Related Skills
|
|
133
|
+
|
|
134
|
+
- `quality/abstractions.md`
|
|
135
|
+
- `quality/functions.md`
|
|
136
|
+
- `quality/maintainability.md`
|
|
137
|
+
- `refactoring/safe_refactoring.md`
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Quality: Maintainability
|
|
2
|
+
|
|
3
|
+
**Purpose**: Avoid unnecessary coupling and complexity for long-term maintenance.
|
|
4
|
+
|
|
5
|
+
**When to use**: Architecture decisions, refactoring, code review.
|
|
6
|
+
---
|
|
7
|
+
---
|
|
8
|
+
name: quality_maintainability
|
|
9
|
+
purpose: Avoid unnecessary coupling and complexity for long-term maintenance
|
|
10
|
+
category: quality
|
|
11
|
+
triggers:
|
|
12
|
+
- coupling
|
|
13
|
+
- cohesion
|
|
14
|
+
- complexity
|
|
15
|
+
- architecture
|
|
16
|
+
- god class
|
|
17
|
+
- circular dependency
|
|
18
|
+
dependencies:
|
|
19
|
+
- quality/abstractions.md
|
|
20
|
+
- quality/duplication.md
|
|
21
|
+
- quality/functions.md
|
|
22
|
+
- generation/protocols_generics.md
|
|
23
|
+
- engineering/modules_packages.md
|
|
24
|
+
priority: primary
|
|
25
|
+
estimated_tokens: 1400
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Core Rules
|
|
29
|
+
|
|
30
|
+
### Coupling
|
|
31
|
+
- **Low coupling**: Modules interact through well-defined interfaces
|
|
32
|
+
- **High cohesion**: Related functionality grouped together
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# GOOD — low coupling via protocol
|
|
36
|
+
class PaymentProcessor(Protocol):
|
|
37
|
+
def charge(self, amount: int, token: str) -> ChargeResult: ...
|
|
38
|
+
|
|
39
|
+
def process_order(order: Order, processor: PaymentProcessor) -> Result:
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
# BAD — high coupling to concrete implementation
|
|
43
|
+
def process_order(order: Order, processor: StripeProcessor) -> Result:
|
|
44
|
+
...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Dependency Direction
|
|
48
|
+
```
|
|
49
|
+
Domain (business logic) <-- DOES NOT DEPEND ON --> Infrastructure (DB, HTTP, UI)
|
|
50
|
+
^ ^
|
|
51
|
+
+-- Depends on abstractions --+
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Domain defines interfaces (Protocols)
|
|
55
|
+
- Infrastructure implements them
|
|
56
|
+
- Application wires them together
|
|
57
|
+
|
|
58
|
+
### Complexity Metrics
|
|
59
|
+
| Metric | Threshold | Action |
|
|
60
|
+
|--------|-----------|--------|
|
|
61
|
+
| Cyclomatic complexity | >10 | Refactor |
|
|
62
|
+
| Function length | >50 lines | Split |
|
|
63
|
+
| Class length | >300 lines | Split |
|
|
64
|
+
| Parameters | >7 | Use config object |
|
|
65
|
+
| Nesting depth | >3 | Extract/guard clauses |
|
|
66
|
+
|
|
67
|
+
### Single Responsibility
|
|
68
|
+
- Each module/class/function has one reason to change
|
|
69
|
+
- If you can't name it simply, it does too much
|
|
70
|
+
|
|
71
|
+
### Change Amplification
|
|
72
|
+
- A change in one place should not require changes in many others
|
|
73
|
+
- If adding a field requires changes in 5+ files, design is too coupled
|
|
74
|
+
- Use dependency inversion to contain changes
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Decision Rules
|
|
79
|
+
|
|
80
|
+
| Situation | Approach |
|
|
81
|
+
|-----------|----------|
|
|
82
|
+
| New feature | Add to existing cohesive module or create new |
|
|
83
|
+
| Shared code | Extract to utility only if used 3+ times |
|
|
84
|
+
| Cross-cutting concern | Decorator / middleware / context manager |
|
|
85
|
+
| Configuration | Central config object, not global constants |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Preferred Patterns
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# Config object instead of many parameters
|
|
93
|
+
@dataclass
|
|
94
|
+
class ProcessingConfig:
|
|
95
|
+
timeout: float = 30.0
|
|
96
|
+
retries: int = 3
|
|
97
|
+
validate: bool = True
|
|
98
|
+
callback: Callable[[str], None] | None = None
|
|
99
|
+
|
|
100
|
+
def process(data: Data, config: ProcessingConfig) -> Result:
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
# Protocol for dependency inversion
|
|
104
|
+
class Cache(Protocol):
|
|
105
|
+
async def get(self, key: str) -> bytes | None: ...
|
|
106
|
+
async def set(self, key: str, value: bytes, ttl: int) -> None: ...
|
|
107
|
+
|
|
108
|
+
class Service:
|
|
109
|
+
def __init__(self, cache: Cache):
|
|
110
|
+
self.cache = cache # Depends on abstraction
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Avoid
|
|
116
|
+
|
|
117
|
+
- God classes (knows everything, does everything)
|
|
118
|
+
- Circular dependencies between modules
|
|
119
|
+
- Global mutable state
|
|
120
|
+
- Hardcoded infrastructure in domain logic
|
|
121
|
+
- Premature abstraction (wait for 3rd use case)
|
|
122
|
+
- Copy-paste modification (refactor instead)
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Validation Considerations
|
|
127
|
+
|
|
128
|
+
- `radon cc` for cyclomatic complexity
|
|
129
|
+
- `xenon` for complexity thresholds
|
|
130
|
+
- Import graph analysis (no cycles)
|
|
131
|
+
- Architecture tests (import-linter, pydeps)
|
|
132
|
+
- `ruff` B007 (loop variable in closure)
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Related Skills
|
|
137
|
+
|
|
138
|
+
- `quality/abstractions.md`
|
|
139
|
+
- `quality/duplication.md`
|
|
140
|
+
- `quality/functions.md`
|
|
141
|
+
- `generation/protocols_generics.md`
|
|
142
|
+
- `engineering/modules_packages.md`
|