Use when moving logic between layers, deprecating patterns, refactoring responsibilities, or enforcing canonical owners. Ensures migrations are complete with no legacy coexistence.
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.
When moving logic from one location to another:
If you can't check all boxes, the migration isn't done.
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.
Migrations are enforced at every layer:
Ban dangerous imports before code runs:
# 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()"
Prevent layer violations:
helpers cannot import from services
workflows cannot import from interfaces
only ml_backend_essentia_comp.py may import essentia
Every skill documents what IS canonical, not what WAS.
Catches stale references in skills:
python scripts/validate_skills.py --check-refs
# ā 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.
# ā 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.
# ā 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.
# ā 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.
# 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/
Move the logic to its proper layer (components for business logic, helpers for pure utilities).
# Find all files that import the old module
grep -r "from nomarr.helpers.files_helper import" nomarr/
If old code still exists and has callers, add a temporary ruff ban to prevent new usages:
# 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.
git rm nomarr/helpers/old_module.py
python scripts/validate_skills.py --check-refs
# 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
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.
Before considering a migration complete, run:
python scripts/check_migration.py nomarr.old.pattern
The script validates:
# TODO: remove comments remain--expect-ban) Ruff ban existsManual checks:
The migration is done when there's no trace of the old pattern.