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,192 @@
|
|
|
1
|
+
# Quality: Type Annotations
|
|
2
|
+
|
|
3
|
+
**Purpose**: Appropriate type hints — useful, not noise.
|
|
4
|
+
|
|
5
|
+
**When to use**: All code generation. Quality type hints improve maintainability.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### When to Annotate
|
|
12
|
+
| Element | Annotate? |
|
|
13
|
+
|---------|-----------|
|
|
14
|
+
| Public function signature | Yes |
|
|
15
|
+
| Public method signature | Yes |
|
|
16
|
+
| Class attributes | Yes |
|
|
17
|
+
| Module-level constants | Yes |
|
|
18
|
+
| Local variables | Usually no (inference) |
|
|
19
|
+
| Loop variables | No |
|
|
20
|
+
| Trivial helpers | Optional |
|
|
21
|
+
|
|
22
|
+
### Useful vs Noise
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
# USEFUL — public API, complex types
|
|
26
|
+
def fetch_users(
|
|
27
|
+
client: APIClient,
|
|
28
|
+
filters: UserFilters | None = None,
|
|
29
|
+
limit: int = 100,
|
|
30
|
+
) -> list[User]:
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
# USEFUL — generic class
|
|
34
|
+
class Repository(Generic[T]):
|
|
35
|
+
def get(self, id: str) -> T | None: ...
|
|
36
|
+
|
|
37
|
+
# NOISE — obvious inference
|
|
38
|
+
count: int = 0
|
|
39
|
+
name: str = "default"
|
|
40
|
+
items: list[str] = []
|
|
41
|
+
for i, item in enumerate(items): # i inferred as int
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
# NOISE — over-specified
|
|
45
|
+
def add(a: int, b: int) -> int:
|
|
46
|
+
return a + b
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Type Hint Style
|
|
50
|
+
```python
|
|
51
|
+
# Python 3.9+ — prefer built-in generics
|
|
52
|
+
list[str]
|
|
53
|
+
dict[str, int]
|
|
54
|
+
set[int]
|
|
55
|
+
tuple[int, str]
|
|
56
|
+
tuple[int, ...] # variable length
|
|
57
|
+
|
|
58
|
+
# Union — Python 3.10+
|
|
59
|
+
int | str
|
|
60
|
+
list[int | str]
|
|
61
|
+
|
|
62
|
+
# Optional
|
|
63
|
+
str | None
|
|
64
|
+
|
|
65
|
+
# Callable
|
|
66
|
+
Callable[[int, str], bool]
|
|
67
|
+
# Or collections.abc.Callable
|
|
68
|
+
|
|
69
|
+
# Type alias (3.12+)
|
|
70
|
+
type UserID = int
|
|
71
|
+
type JSONValue = str | int | float | bool | None | list["JSONValue"] | dict[str, "JSONValue"]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Strictness Levels
|
|
75
|
+
```toml
|
|
76
|
+
# pyproject.toml — mypy config
|
|
77
|
+
[tool.mypy]
|
|
78
|
+
# Strict (recommended for new projects)
|
|
79
|
+
strict = true
|
|
80
|
+
warn_return_any = true
|
|
81
|
+
warn_unused_configs = true
|
|
82
|
+
disallow_untyped_defs = true
|
|
83
|
+
disallow_incomplete_defs = true
|
|
84
|
+
check_untyped_defs = true
|
|
85
|
+
no_implicit_optional = true
|
|
86
|
+
|
|
87
|
+
# Gradual adoption — per module
|
|
88
|
+
[[tool.mypy.overrides]]
|
|
89
|
+
module = "legacy.*"
|
|
90
|
+
disallow_untyped_defs = false
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### TypedDict for External Data
|
|
94
|
+
```python
|
|
95
|
+
from typing import TypedDict, NotRequired
|
|
96
|
+
|
|
97
|
+
class UserAPIResponse(TypedDict):
|
|
98
|
+
id: int
|
|
99
|
+
name: str
|
|
100
|
+
email: str
|
|
101
|
+
created_at: str # ISO format
|
|
102
|
+
metadata: NotRequired[dict[str, str]] # Optional key
|
|
103
|
+
|
|
104
|
+
def parse_user(data: UserAPIResponse) -> User:
|
|
105
|
+
return User(
|
|
106
|
+
id=data["id"],
|
|
107
|
+
name=data["name"],
|
|
108
|
+
email=data["email"],
|
|
109
|
+
created_at=datetime.fromisoformat(data["created_at"]),
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Protocol for Interfaces
|
|
114
|
+
```python
|
|
115
|
+
from typing import Protocol
|
|
116
|
+
|
|
117
|
+
class Cache(Protocol):
|
|
118
|
+
def get(self, key: str) -> bytes | None: ...
|
|
119
|
+
def set(self, key: str, value: bytes, ttl: int) -> None: ...
|
|
120
|
+
|
|
121
|
+
# Any object with get/set implements Cache (no inheritance needed)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Decision Rules
|
|
127
|
+
|
|
128
|
+
| Situation | Annotation Level |
|
|
129
|
+
|-----------|------------------|
|
|
130
|
+
| New project | Strict (`mypy --strict`) |
|
|
131
|
+
| Existing project | Match project config |
|
|
132
|
+
| Public library | Full annotations + `py.typed` |
|
|
133
|
+
| Internal app | Public API annotated, internal inferred |
|
|
134
|
+
| Prototyping | Minimal |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Preferred Patterns
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
# Function with full hints
|
|
142
|
+
def process_order(
|
|
143
|
+
order: Order,
|
|
144
|
+
inventory: InventoryService,
|
|
145
|
+
payment: PaymentGateway,
|
|
146
|
+
*,
|
|
147
|
+
idempotency_key: str | None = None,
|
|
148
|
+
) -> OrderResult:
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
# Class with typed attributes
|
|
152
|
+
class Config:
|
|
153
|
+
database_url: str
|
|
154
|
+
pool_size: int = 10
|
|
155
|
+
timeout: float = 30.0
|
|
156
|
+
debug: bool = False
|
|
157
|
+
|
|
158
|
+
# Generic with constraints
|
|
159
|
+
T = TypeVar("T", bound=Entity)
|
|
160
|
+
|
|
161
|
+
class Repository(Generic[T]):
|
|
162
|
+
def get(self, id: str) -> T | None: ...
|
|
163
|
+
def list(self) -> list[T]: ...
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Avoid
|
|
169
|
+
|
|
170
|
+
- `Any` without `# type: ignore[...]` justification
|
|
171
|
+
- `object` as "unknown type" (use `Any` or proper protocol)
|
|
172
|
+
- Overly complex nested types without aliases
|
|
173
|
+
- Type hints that are wrong (lying types)
|
|
174
|
+
- Ignoring mypy errors without comment
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Validation Considerations
|
|
179
|
+
|
|
180
|
+
- `mypy --strict` passes
|
|
181
|
+
- `pyright` passes
|
|
182
|
+
- No `# type: ignore` without error code
|
|
183
|
+
- `py.typed` marker for libraries
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Related Skills
|
|
188
|
+
|
|
189
|
+
- `generation/type_hints.md`
|
|
190
|
+
- `generation/protocols_generics.md`
|
|
191
|
+
- `engineering/pyproject_toml.md`
|
|
192
|
+
- `quality/readability.md`
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Refactoring: Behavior Preservation
|
|
2
|
+
|
|
3
|
+
**Purpose**: Ensure refactoring doesn't change observable behavior.
|
|
4
|
+
|
|
5
|
+
**When to use**: Every refactoring, especially complex ones.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Behavior Definition
|
|
12
|
+
Behavior = **observable outputs for given inputs**
|
|
13
|
+
- Return values
|
|
14
|
+
- Side effects (DB writes, API calls, files, logs)
|
|
15
|
+
- Exceptions raised
|
|
16
|
+
- Timing (if specified in requirements)
|
|
17
|
+
|
|
18
|
+
### Preservation Techniques
|
|
19
|
+
|
|
20
|
+
#### 1. Characterization Tests (Golden Master)
|
|
21
|
+
```python
|
|
22
|
+
# Before refactoring, capture current behavior
|
|
23
|
+
def test_order_calculation_golden_master():
|
|
24
|
+
"""Characterization test - captures current behavior"""
|
|
25
|
+
test_cases = [
|
|
26
|
+
{"items": [{"price": 10, "qty": 2}], "tax_rate": 0.1},
|
|
27
|
+
{"items": [{"price": 100, "qty": 1}], "tax_rate": 0.0},
|
|
28
|
+
{"items": [], "tax_rate": 0.1},
|
|
29
|
+
# ... 50+ real cases from production
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
for case in test_cases:
|
|
33
|
+
result = calculate_order_total(case["items"], case["tax_rate"])
|
|
34
|
+
# Save as golden master
|
|
35
|
+
assert result == case["expected"]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### 2. Property-Based Tests
|
|
39
|
+
```python
|
|
40
|
+
from hypothesis import given, strategies as st
|
|
41
|
+
|
|
42
|
+
@given(st.lists(st.fixed_dictionaries({
|
|
43
|
+
"price": st.integers(0, 10000),
|
|
44
|
+
"qty": st.integers(1, 100),
|
|
45
|
+
})), st.floats(0, 0.5))
|
|
46
|
+
def test_calculation_properties(items, tax_rate):
|
|
47
|
+
"""Properties that must hold after refactoring"""
|
|
48
|
+
result = calculate_order_total(items, tax_rate)
|
|
49
|
+
|
|
50
|
+
# Property: total >= subtotal
|
|
51
|
+
subtotal = sum(i["price"] * i["qty"] for i in items)
|
|
52
|
+
assert result >= subtotal
|
|
53
|
+
|
|
54
|
+
# Property: tax = subtotal * rate (approximately)
|
|
55
|
+
expected_tax = subtotal * tax_rate
|
|
56
|
+
actual_tax = result - subtotal
|
|
57
|
+
assert abs(actual_tax - expected_tax) < 0.01
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### 3. Contract Tests
|
|
61
|
+
```python
|
|
62
|
+
# Protocol defines contract
|
|
63
|
+
class OrderCalculator(Protocol):
|
|
64
|
+
def calculate(self, items: list[Item], tax_rate: float) -> Decimal: ...
|
|
65
|
+
|
|
66
|
+
# Both implementations must satisfy
|
|
67
|
+
@pytest.fixture(params=[LegacyCalculator, NewCalculator])
|
|
68
|
+
def calculator(request) -> OrderCalculator:
|
|
69
|
+
return request.param()
|
|
70
|
+
|
|
71
|
+
def test_calculator_contract(calculator: OrderCalculator):
|
|
72
|
+
# Same tests for both implementations
|
|
73
|
+
result = calculator.calculate([Item(10, 2)], 0.1)
|
|
74
|
+
assert result == Decimal("22.00")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### 4. Parallel Run (Production)
|
|
78
|
+
```python
|
|
79
|
+
# Shadow mode - run both, compare, log differences
|
|
80
|
+
def process_order(order):
|
|
81
|
+
legacy_result = legacy_processor.process(order)
|
|
82
|
+
new_result = new_processor.process(order)
|
|
83
|
+
|
|
84
|
+
if legacy_result != new_result:
|
|
85
|
+
logger.warning(
|
|
86
|
+
"Behavior mismatch",
|
|
87
|
+
legacy=legacy_result,
|
|
88
|
+
new=new_result,
|
|
89
|
+
order_id=order.id,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return legacy_result # Still return legacy during transition
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### What Must Be Preserved
|
|
96
|
+
| Aspect | Verify |
|
|
97
|
+
|--------|--------|
|
|
98
|
+
| Return values | Exact equality (or specified tolerance) |
|
|
99
|
+
| Exceptions | Same type, message, context |
|
|
100
|
+
| Side effects | Same DB writes, API calls, files |
|
|
101
|
+
| Timing | Within acceptable bounds |
|
|
102
|
+
| Logs | Same level, structure (optional) |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Decision Rules
|
|
107
|
+
|
|
108
|
+
| Refactor Type | Verification Method |
|
|
109
|
+
|---------------|---------------------|
|
|
110
|
+
| Simple extraction | Existing unit tests |
|
|
111
|
+
| Algorithm change | Property tests + golden master |
|
|
112
|
+
| Implementation swap | Contract tests + shadow mode |
|
|
113
|
+
| Large restructuring | Characterization tests + integration tests |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Preferred Patterns
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# Regression test for every behavior change
|
|
121
|
+
def test_refactor_preserves_bug_fix_123():
|
|
122
|
+
"""Ensures refactoring doesn't reintroduce bug #123"""
|
|
123
|
+
# Exact scenario from bug report
|
|
124
|
+
input_data = create_bug_scenario()
|
|
125
|
+
|
|
126
|
+
result = refactored_function(input_data)
|
|
127
|
+
|
|
128
|
+
# Bug was: returned wrong value for edge case
|
|
129
|
+
assert result == expected_correct_value
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Avoid
|
|
135
|
+
|
|
136
|
+
- Assuming "it's the same logic" without verification
|
|
137
|
+
- Deleting characterization tests after refactor
|
|
138
|
+
- Refactoring without any automated verification
|
|
139
|
+
- Changing behavior "while we're at it"
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Validation Considerations
|
|
144
|
+
|
|
145
|
+
- Run characterization tests before and after
|
|
146
|
+
- Property tests run on both implementations
|
|
147
|
+
- Shadow mode logs zero mismatches before cutover
|
|
148
|
+
- Integration tests cover full workflows
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Related Skills
|
|
153
|
+
|
|
154
|
+
- `refactoring/safe_refactoring.md`
|
|
155
|
+
- `refactoring/incremental.md`
|
|
156
|
+
- `testing/regression_tests.md`
|
|
157
|
+
- `testing/edge_cases.md`
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Refactoring: Incremental Changes
|
|
2
|
+
|
|
3
|
+
**Purpose**: Make refactoring safe through small, verifiable increments.
|
|
4
|
+
|
|
5
|
+
**When to use**: Any refactoring larger than a single function extraction.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Incremental Principles
|
|
12
|
+
1. **One logical change per commit**
|
|
13
|
+
2. **Each commit passes all tests**
|
|
14
|
+
3. **No "temporary" broken state committed**
|
|
15
|
+
4. **Reviewable diffs** (<200 lines ideal)
|
|
16
|
+
|
|
17
|
+
### Breaking Down Large Refactors
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# Goal: Replace legacy UserService with NewUserService
|
|
21
|
+
|
|
22
|
+
# Step 1: Create NewUserService with same interface
|
|
23
|
+
class NewUserService:
|
|
24
|
+
def get(self, id): ...
|
|
25
|
+
def create(self, data): ...
|
|
26
|
+
|
|
27
|
+
# Step 2: Add feature flag
|
|
28
|
+
class UserService:
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.legacy = LegacyUserService()
|
|
31
|
+
self.new = NewUserService()
|
|
32
|
+
self.use_new = settings.use_new_service
|
|
33
|
+
|
|
34
|
+
def get(self, id):
|
|
35
|
+
if self.use_new:
|
|
36
|
+
return self.new.get(id)
|
|
37
|
+
return self.legacy.get(id)
|
|
38
|
+
|
|
39
|
+
def create(self, data):
|
|
40
|
+
if self.use_new:
|
|
41
|
+
return self.new.create(data)
|
|
42
|
+
return self.legacy.create(data)
|
|
43
|
+
|
|
44
|
+
# Step 3: Enable for 1% of users (canary)
|
|
45
|
+
# Step 4: Enable for all
|
|
46
|
+
# Step 5: Remove legacy code
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Incremental Extraction
|
|
50
|
+
```python
|
|
51
|
+
# Large function -> small functions (one at a time)
|
|
52
|
+
|
|
53
|
+
# Original: 100-line process() function
|
|
54
|
+
|
|
55
|
+
# Commit 1: Extract validation
|
|
56
|
+
def validate_input(data):
|
|
57
|
+
# ... 20 lines extracted
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def process(data):
|
|
61
|
+
validate_input(data) # New call
|
|
62
|
+
# ... 80 lines remain
|
|
63
|
+
|
|
64
|
+
# Commit 2: Extract calculation
|
|
65
|
+
def calculate_totals(items):
|
|
66
|
+
# ... 30 lines extracted
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def process(data):
|
|
70
|
+
validate_input(data)
|
|
71
|
+
totals = calculate_totals(data.items) # New call
|
|
72
|
+
# ... 50 lines remain
|
|
73
|
+
|
|
74
|
+
# Commit 3: Extract persistence
|
|
75
|
+
def save_results(data, totals):
|
|
76
|
+
# ... 30 lines extracted
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def process(data):
|
|
80
|
+
validate_input(data)
|
|
81
|
+
totals = calculate_totals(data.items)
|
|
82
|
+
save_results(data, totals)
|
|
83
|
+
# ... 20 lines remain (orchestration)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Incremental Type Changes
|
|
87
|
+
```python
|
|
88
|
+
# Change function signature incrementally
|
|
89
|
+
|
|
90
|
+
# Before: func(a, b, c)
|
|
91
|
+
# Goal: func(config: Config)
|
|
92
|
+
|
|
93
|
+
# Step 1: Add config parameter with defaults
|
|
94
|
+
def func(a, b, c, config=None):
|
|
95
|
+
if config:
|
|
96
|
+
a = config.a
|
|
97
|
+
b = config.b
|
|
98
|
+
c = config.c
|
|
99
|
+
# ... rest unchanged
|
|
100
|
+
|
|
101
|
+
# Step 2: Update all callers to pass config
|
|
102
|
+
# Step 3: Make config required
|
|
103
|
+
def func(config: Config):
|
|
104
|
+
# ... use config.a, config.b, config.c
|
|
105
|
+
|
|
106
|
+
# Step 4: Remove old parameters
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Database Migration (Incremental)
|
|
110
|
+
```python
|
|
111
|
+
# Add column, migrate data, switch, remove old
|
|
112
|
+
|
|
113
|
+
# Migration 1: Add new column (nullable)
|
|
114
|
+
ALTER TABLE users ADD COLUMN email_normalized VARCHAR(255);
|
|
115
|
+
|
|
116
|
+
# Migration 2: Backfill (batch, with progress)
|
|
117
|
+
UPDATE users SET email_normalized = LOWER(email) WHERE email_normalized IS NULL;
|
|
118
|
+
|
|
119
|
+
# Migration 3: Add index, make not null
|
|
120
|
+
CREATE INDEX idx_users_email_norm ON users(email_normalized);
|
|
121
|
+
ALTER TABLE users ALTER COLUMN email_normalized SET NOT NULL;
|
|
122
|
+
|
|
123
|
+
# Migration 4: Switch application to new column
|
|
124
|
+
# (Deploy code that reads/writes email_normalized)
|
|
125
|
+
|
|
126
|
+
# Migration 5: Drop old column (after verification)
|
|
127
|
+
ALTER TABLE users DROP COLUMN email;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Decision Rules
|
|
133
|
+
|
|
134
|
+
| Refactor Size | Increments |
|
|
135
|
+
|---------------|------------|
|
|
136
|
+
| Single function | 1-3 commits |
|
|
137
|
+
| Class extraction | 3-10 commits |
|
|
138
|
+
| Module restructure | 10-30 commits |
|
|
139
|
+
| Architecture change | 30+ commits (feature flags) |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Preferred Patterns
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# Commit message template
|
|
147
|
+
# refactor: extract validation from process_order
|
|
148
|
+
#
|
|
149
|
+
# Extracted validate_order() function from process_order()
|
|
150
|
+
# to improve readability and testability.
|
|
151
|
+
# No behavior change.
|
|
152
|
+
|
|
153
|
+
# Git workflow
|
|
154
|
+
git checkout -b refactor/extract-validation
|
|
155
|
+
# ... make change ...
|
|
156
|
+
git add -p # Stage hunks selectively
|
|
157
|
+
git commit -m "refactor: extract validation from process_order"
|
|
158
|
+
# ... run tests ...
|
|
159
|
+
# ... next increment ...
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Avoid
|
|
165
|
+
|
|
166
|
+
- "Refactor everything in one PR"
|
|
167
|
+
- Commits that break tests
|
|
168
|
+
- Mixing refactor + feature + bugfix
|
|
169
|
+
- No feature flag for risky changes
|
|
170
|
+
- Deleting old code before new is verified
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Validation Considerations
|
|
175
|
+
|
|
176
|
+
- `git log --oneline` shows clear incremental steps
|
|
177
|
+
- Each commit: `pytest` passes
|
|
178
|
+
- Bisect works (each commit builds)
|
|
179
|
+
- Code review per commit or small PR
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Related Skills
|
|
184
|
+
|
|
185
|
+
- `refactoring/safe_refactoring.md`
|
|
186
|
+
- `refactoring/behavior_preservation.md`
|
|
187
|
+
- `testing/regression_tests.md`
|