| 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?
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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# New endpoint added
@app.route("/api/v2/users")
def get_users_v2():
pass
# Old endpoint unchanged: ✅ Safe
Detection Strategy
Automatic Checks
- Function signatures: Compare old vs new parameters, types, names
- API routes: Check for removed/renamed endpoints
- Data schemas: Validate field additions/removals/renames
- Return types: Detect type changes
When to Run
- ✅ Before committing changes to public APIs
- ✅ During code review
- ✅ Before releasing new version
Output Format
## 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
# 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
# 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
# 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