Initial commit
This commit is contained in:
398
.claude/skills/breaking-change-detector/SKILL.md
Normal file
398
.claude/skills/breaking-change-detector/SKILL.md
Normal file
@@ -0,0 +1,398 @@
|
||||
---
|
||||
name: breaking-change-detector
|
||||
description: Detects backward-incompatible changes to public APIs, function signatures, endpoints, and data schemas before they break production. Suggests migration paths.
|
||||
---
|
||||
|
||||
# Breaking Change Detector Skill
|
||||
|
||||
**Purpose**: Catch breaking changes early, not after customers complain.
|
||||
|
||||
**Trigger Words**: API, endpoint, route, public, schema, model, interface, contract, signature, rename, remove, delete
|
||||
|
||||
---
|
||||
|
||||
## Quick Decision: Is This Breaking?
|
||||
|
||||
```python
|
||||
def is_breaking_change(change: dict) -> tuple[bool, str]:
|
||||
"""Fast breaking change evaluation."""
|
||||
|
||||
breaking_patterns = {
|
||||
# Method signatures
|
||||
"removed_parameter": True,
|
||||
"renamed_parameter": True,
|
||||
"changed_parameter_type": True,
|
||||
"removed_method": True,
|
||||
"renamed_method": True,
|
||||
|
||||
# API endpoints
|
||||
"removed_endpoint": True,
|
||||
"renamed_endpoint": True,
|
||||
"changed_response_format": True,
|
||||
"removed_response_field": True,
|
||||
|
||||
# Data models
|
||||
"removed_field": True,
|
||||
"renamed_field": True,
|
||||
"changed_field_type": True,
|
||||
"made_required": True,
|
||||
|
||||
# Return types
|
||||
"changed_return_type": True,
|
||||
}
|
||||
|
||||
# Safe changes (backward compatible)
|
||||
safe_patterns = {
|
||||
"added_parameter_with_default": False,
|
||||
"added_optional_field": False,
|
||||
"added_endpoint": False,
|
||||
"added_response_field": False,
|
||||
"deprecated_but_kept": False,
|
||||
}
|
||||
|
||||
change_type = change.get("type")
|
||||
return breaking_patterns.get(change_type, False), change_type
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Breaking Changes (With Fixes)
|
||||
|
||||
### 1. **Removed Function Parameter** ❌ BREAKING
|
||||
```python
|
||||
# BEFORE (v1.0)
|
||||
def process_payment(amount, currency, user_id):
|
||||
pass
|
||||
|
||||
# AFTER (v2.0) - BREAKS EXISTING CODE
|
||||
def process_payment(amount, user_id): # Removed currency!
|
||||
pass
|
||||
|
||||
# ✅ FIX: Keep parameter with default
|
||||
def process_payment(amount, user_id, currency="USD"):
|
||||
"""
|
||||
Args:
|
||||
currency: Deprecated in v2.0, always uses USD
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
**Migration Path**: Add default value, deprecate, document.
|
||||
|
||||
---
|
||||
|
||||
### 2. **Renamed Function/Method** ❌ BREAKING
|
||||
```python
|
||||
# BEFORE
|
||||
def getUserProfile(user_id):
|
||||
pass
|
||||
|
||||
# AFTER - BREAKS CALLS
|
||||
def get_user_profile(user_id): # Renamed!
|
||||
pass
|
||||
|
||||
# ✅ FIX: Keep both, deprecate old
|
||||
def get_user_profile(user_id):
|
||||
"""Get user profile (v2.0+ naming)."""
|
||||
pass
|
||||
|
||||
def getUserProfile(user_id):
|
||||
"""Deprecated: Use get_user_profile() instead."""
|
||||
warnings.warn("getUserProfile is deprecated, use get_user_profile", DeprecationWarning)
|
||||
return get_user_profile(user_id)
|
||||
```
|
||||
|
||||
**Migration Path**: Alias old name → new name, add deprecation warning.
|
||||
|
||||
---
|
||||
|
||||
### 3. **Changed Response Format** ❌ BREAKING
|
||||
```python
|
||||
# BEFORE - Returns dict
|
||||
@app.route("/api/user/<id>")
|
||||
def get_user(id):
|
||||
return {"id": id, "name": "Alice", "email": "alice@example.com"}
|
||||
|
||||
# AFTER - Returns list - BREAKS CLIENTS!
|
||||
@app.route("/api/user/<id>")
|
||||
def get_user(id):
|
||||
return [{"id": id, "name": "Alice", "email": "alice@example.com"}]
|
||||
|
||||
# ✅ FIX: Keep format, add new endpoint
|
||||
@app.route("/api/v2/user/<id>") # New version
|
||||
def get_user_v2(id):
|
||||
return [{"id": id, "name": "Alice"}]
|
||||
|
||||
@app.route("/api/user/<id>") # Keep v1
|
||||
def get_user(id):
|
||||
return {"id": id, "name": "Alice", "email": "alice@example.com"}
|
||||
```
|
||||
|
||||
**Migration Path**: Version the API (v1, v2), keep old version alive.
|
||||
|
||||
---
|
||||
|
||||
### 4. **Removed Endpoint** ❌ BREAKING
|
||||
```python
|
||||
# BEFORE
|
||||
@app.route("/users")
|
||||
def get_users():
|
||||
pass
|
||||
|
||||
# AFTER - REMOVED! Breaks clients.
|
||||
# (endpoint deleted)
|
||||
|
||||
# ✅ FIX: Redirect to new endpoint
|
||||
@app.route("/users")
|
||||
def get_users():
|
||||
"""Deprecated: Use /api/v2/accounts instead."""
|
||||
return redirect("/api/v2/accounts", code=301) # Permanent redirect
|
||||
```
|
||||
|
||||
**Migration Path**: Keep endpoint, redirect with 301, document deprecation.
|
||||
|
||||
---
|
||||
|
||||
### 5. **Changed Required Fields** ❌ BREAKING
|
||||
```python
|
||||
# BEFORE - email optional
|
||||
class User:
|
||||
def __init__(self, name, email=None):
|
||||
self.name = name
|
||||
self.email = email
|
||||
|
||||
# AFTER - email required! Breaks existing code.
|
||||
class User:
|
||||
def __init__(self, name, email): # No default!
|
||||
self.name = name
|
||||
self.email = email
|
||||
|
||||
# ✅ FIX: Keep optional, validate separately
|
||||
class User:
|
||||
def __init__(self, name, email=None):
|
||||
self.name = name
|
||||
self.email = email
|
||||
|
||||
def validate(self):
|
||||
"""Validate required fields."""
|
||||
if not self.email:
|
||||
raise ValueError("Email is required (new in v2.0)")
|
||||
```
|
||||
|
||||
**Migration Path**: Keep optional in constructor, add validation method.
|
||||
|
||||
---
|
||||
|
||||
### 6. **Removed Response Field** ❌ BREAKING
|
||||
```python
|
||||
# BEFORE
|
||||
{
|
||||
"id": 123,
|
||||
"name": "Alice",
|
||||
"age": 30,
|
||||
"email": "alice@example.com"
|
||||
}
|
||||
|
||||
# AFTER - Removed age! Breaks clients expecting it.
|
||||
{
|
||||
"id": 123,
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com"
|
||||
}
|
||||
|
||||
# ✅ FIX: Keep field with null/default
|
||||
{
|
||||
"id": 123,
|
||||
"name": "Alice",
|
||||
"age": null, # Deprecated, always null in v2.0
|
||||
"email": "alice@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
**Migration Path**: Keep field with null, document deprecation.
|
||||
|
||||
---
|
||||
|
||||
## Non-Breaking Changes ✅ (Safe)
|
||||
|
||||
### 1. **Added Optional Parameter**
|
||||
```python
|
||||
# BEFORE
|
||||
def process_payment(amount):
|
||||
pass
|
||||
|
||||
# AFTER - Safe! Has default
|
||||
def process_payment(amount, currency="USD"):
|
||||
pass
|
||||
|
||||
# Old calls still work:
|
||||
process_payment(100) # ✅ Works
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. **Added Response Field**
|
||||
```python
|
||||
# BEFORE
|
||||
{"id": 123, "name": "Alice"}
|
||||
|
||||
# AFTER - Safe! Added field
|
||||
{"id": 123, "name": "Alice", "created_at": "2025-10-30"}
|
||||
|
||||
# Old clients ignore new field: ✅ Works
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. **Added New Endpoint**
|
||||
```python
|
||||
# New endpoint added
|
||||
@app.route("/api/v2/users")
|
||||
def get_users_v2():
|
||||
pass
|
||||
|
||||
# Old endpoint unchanged: ✅ Safe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Detection Strategy
|
||||
|
||||
### Automatic Checks
|
||||
1. **Function signatures**: Compare old vs new parameters, types, names
|
||||
2. **API routes**: Check for removed/renamed endpoints
|
||||
3. **Data schemas**: Validate field additions/removals/renames
|
||||
4. **Return types**: Detect type changes
|
||||
|
||||
### When to Run
|
||||
- ✅ Before committing changes to public APIs
|
||||
- ✅ During code review
|
||||
- ✅ Before releasing new version
|
||||
|
||||
---
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
## Breaking Change Report
|
||||
|
||||
**Status**: [✅ NO BREAKING CHANGES | ⚠️ BREAKING CHANGES DETECTED]
|
||||
|
||||
---
|
||||
|
||||
### Breaking Changes: 2
|
||||
|
||||
1. **[CRITICAL] Removed endpoint: GET /users**
|
||||
- **Impact**: External API clients will get 404
|
||||
- **File**: api/routes.py:45
|
||||
- **Fix**:
|
||||
```python
|
||||
# Keep endpoint, redirect to new one
|
||||
@app.route("/users")
|
||||
def get_users():
|
||||
return redirect("/api/v2/accounts", code=301)
|
||||
```
|
||||
- **Migration**: Add to CHANGELOG.md, notify users
|
||||
|
||||
2. **[HIGH] Renamed parameter: currency → currency_code**
|
||||
- **Impact**: Existing function calls will fail
|
||||
- **File**: payments.py:23
|
||||
- **Fix**:
|
||||
```python
|
||||
# Accept both, deprecate old name
|
||||
def process_payment(amount, currency_code=None, currency=None):
|
||||
# Support old name temporarily
|
||||
if currency is not None:
|
||||
warnings.warn("currency is deprecated, use currency_code")
|
||||
currency_code = currency
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Safe Changes: 1
|
||||
|
||||
1. **[SAFE] Added optional parameter: timeout (default=30)**
|
||||
- **File**: api_client.py:12
|
||||
- **Impact**: None, backward compatible
|
||||
|
||||
---
|
||||
|
||||
**Recommendation**:
|
||||
1. Fix 2 breaking changes before merge
|
||||
2. Document breaking changes in CHANGELOG.md
|
||||
3. Bump major version (v1.x → v2.0) per semver
|
||||
4. Notify API consumers 2 weeks before release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with Workflow
|
||||
|
||||
```bash
|
||||
# Automatic trigger when modifying APIs
|
||||
/lazy code "rename /users endpoint to /accounts"
|
||||
|
||||
→ breaking-change-detector triggers
|
||||
→ Detects: Endpoint rename is breaking
|
||||
→ Suggests: Keep /users, redirect to /accounts
|
||||
→ Developer applies fix
|
||||
→ Re-check: ✅ Backward compatible
|
||||
|
||||
# Before PR
|
||||
/lazy review US-3.4
|
||||
|
||||
→ breaking-change-detector runs
|
||||
→ Checks all API changes in PR
|
||||
→ Reports breaking changes
|
||||
→ PR blocked if breaking without migration plan
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Version Bumping Guide
|
||||
|
||||
```bash
|
||||
# Semantic versioning
|
||||
Given version: MAJOR.MINOR.PATCH
|
||||
|
||||
# Breaking change detected → Bump MAJOR
|
||||
1.2.3 → 2.0.0
|
||||
|
||||
# New feature (backward compatible) → Bump MINOR
|
||||
1.2.3 → 1.3.0
|
||||
|
||||
# Bug fix (backward compatible) → Bump PATCH
|
||||
1.2.3 → 1.2.4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What This Skill Does NOT Do
|
||||
|
||||
❌ Catch internal/private API changes (only public APIs)
|
||||
❌ Test runtime compatibility (use integration tests)
|
||||
❌ Manage database migrations (separate tool)
|
||||
❌ Generate full migration scripts
|
||||
|
||||
✅ **DOES**: Detect public API breaking changes, suggest fixes, enforce versioning.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
```bash
|
||||
# Strict mode: flag all changes (even safe ones)
|
||||
export LAZYDEV_BREAKING_STRICT=1
|
||||
|
||||
# Disable breaking change detection
|
||||
export LAZYDEV_DISABLE_BREAKING_DETECTOR=1
|
||||
|
||||
# Check only specific types
|
||||
export LAZYDEV_BREAKING_CHECK="endpoints,schemas"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Follows**: Semantic Versioning 2.0.0
|
||||
**Speed**: <3 seconds for typical PR
|
||||
Reference in New Issue
Block a user