Use when moving logic between layers, deprecating patterns, refactoring responsibilities, or enforcing canonical owners. Ensures migrations are complete with no legacy coexistence.
Scanned 2/12/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: code-migration
description: Use when moving logic between layers, deprecating patterns, refactoring responsibilities, or enforcing canonical owners. Ensures migrations are complete with no legacy coexistence.
---
# Code Migration
**Core principle:** When you move responsibility from A to B, delete A.
Half-migrations are technical debt. If `files_helper.py` and `path_comp.py` both construct paths, every developer must learn *which one to use*. That ambiguity is the bug.
---
## Migration Checklist
When moving logic from one location to another:
- [ ] **Move the code** to its canonical location
- [ ] **Update all call sites** (use grep, not hope)
- [ ] **Update skills** that reference the old location
- [ ] **Add ruff rules** to ban imports from the old location
- [ ] **Delete the old code** (not deprecate - delete)
- [ ] **Run validate_skills.py** to catch stale references
- [ ] **Run tests** to confirm nothing broke
If you can't check all boxes, the migration isn't done.
---
## Canonical Owners
Every responsibility has exactly ONE canonical owner:
| Responsibility | Canonical Owner | NOT |
|---------------|-----------------|-----|
| Library path construction | `path_comp.py` | `files_helper.py` |
| Wall-clock timestamps | `time_helper.now_ms()` | `time.time()` |
| Monotonic intervals | `time_helper.internal_ms()` | `time.monotonic()` |
| Essentia calls | `ml_backend_essentia_comp.py` | anywhere else |
| Logging setup | `logging_helper.get_logger()` | `logging.getLogger()` |
| Config access | Injected `AppConfig` | `os.environ`, `config.yaml` |
**If two places can do the same thing, one of them is wrong.**
---
## Enforcement Stack
Migrations are enforced at every layer:
### 1. Ruff Rules (Syntax-Level)
Ban dangerous imports before code runs:
```toml
# ruff.toml
[lint.flake8-tidy-imports.banned-api]
"time.time".msg = "Use nomarr.helpers.time_helper.now_ms() for timestamps"
"builtins.print".msg = "Use logging via get_logger()"
```
### 2. Import-Linter (Architecture-Level)
Prevent layer violations:
```
helpers cannot import from services
workflows cannot import from interfaces
only ml_backend_essentia_comp.py may import essentia
```
### 3. Skills (Documentation-Level)
Every skill documents what IS canonical, not what WAS.
### 4. validate_skills.py (Tooling-Level)
Catches stale references in skills:
```bash
python scripts/validate_skills.py --check-refs
```
---
## Anti-Patterns
### Deprecation Warnings
```python
# ❌ Wrong - deprecation is procrastination
import warnings
warnings.warn("Use path_comp instead", DeprecationWarning)
```
If it's deprecated, delete it. Pre-alpha means no backwards compatibility.
### Keeping It Around Just In Case
```python
# ❌ Wrong - dead code that looks alive
def old_path_builder(path: str) -> str:
"""DEPRECATED: Use path_comp.build_library_path_from_input()"""
...
```
Delete it. Git remembers.
### TODO: Remove After Migration
```python
# ❌ Wrong - TODOs are lies
# TODO: Remove this once all callers use the new API
def legacy_function():
...
```
Remove it now. The migration isn't done until it's gone.
### Wrapper For Compatibility
```python
# ❌ Wrong - shims become permanent
def get_path(path: str) -> str:
"""Compatibility wrapper."""
return path_comp.build_library_path_from_input(path).absolute
```
Update the callers directly.
---
## Migration Workflow
### Step 1: Identify the Migration
```bash
# Find all usages of the old pattern
python scripts/discover_import_chains.py nomarr.helpers.files_helper
# Or grep for specific functions
grep -r "build_path" nomarr/
```
### Step 2: Create the Canonical Location
Move the logic to its proper layer (components for business logic, helpers for pure utilities).
### Step 3: Update All Call Sites
```bash
# Find all files that import the old module
grep -r "from nomarr.helpers.files_helper import" nomarr/
```
### Step 4: Ban the Old Pattern (If Still Exists)
If old code still exists and has callers, add a temporary ruff ban to prevent new usages:
```toml
# Add to ruff.toml during migration
[lint.flake8-tidy-imports.banned-api]
"nomarr.helpers.files_helper.build_path".msg = "Use path_comp.build_library_path_from_input()"
```
**Remove the ban after deleting the old code.** Bans for deleted patterns are garbage.
### Step 5: Delete the Old Code
```bash
git rm nomarr/helpers/old_module.py
```
### Step 6: Update Skills
```bash
python scripts/validate_skills.py --check-refs
```
### Step 7: Verify Migration Complete
```bash
# Check that all traces are gone
python scripts/check_migration.py nomarr.helpers.old_module
# If migration plan included a ruff ban, verify it exists
python scripts/check_migration.py nomarr.helpers.old_module --expect-ban
# Full QC
python scripts/run_qc.py
pytest
```
---
## Decision Framework
When you find duplicate responsibilities:
```
Q: Is there a clear canonical owner?
├─ No → Decide which location should own it
└─ Yes → Q: Does the old location still exist?
├─ Yes → Delete it. Update callers first if needed.
└─ No → Good. Verify skills and rules match reality.
```
When someone proposes keeping both:
```
"Can we keep the old one for compatibility?"
→ No. Pre-alpha. Delete it.
"What if something still uses it?"
→ Find it and update it. That's the migration.
"What if we need it later?"
→ Git remembers. Delete it.
```
---
## Validation
Before considering a migration complete, run:
```bash
python scripts/check_migration.py nomarr.old.pattern
```
The script validates:
- [ ] Old code is **deleted**, not deprecated
- [ ] No imports of the old module remain
- [ ] No skill references to old pattern
- [ ] No `# TODO: remove` comments remain
- [ ] (With `--expect-ban`) Ruff ban exists
Manual checks:
- [ ] No wrapper/shim functions exist
- [ ] Tests pass
**The migration is done when there's no trace of the old pattern.**
No comments yet. Be the first to comment!