Skip to content
code-migration logo

Code Migration

code-migration

Use when moving logic between layers, deprecating patterns, refactoring responsibilities, or enforcing canonical owners. Ensures migrations are complete with no legacy coexistence.

SKILL.md

Full skill instructions

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:

ResponsibilityCanonical OwnerNOT
Library path constructionpath_comp.pyfiles_helper.py
Wall-clock timestampstime_helper.now_ms()time.time()
Monotonic intervalstime_helper.internal_ms()time.monotonic()
Essentia callsml_backend_essentia_comp.pyanywhere else
Logging setuplogging_helper.get_logger()logging.getLogger()
Config accessInjected AppConfigos.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:

# 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:

python scripts/​validate_skills.py --check-refs

Anti-Patterns

Deprecation Warnings

# ❌ 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

# ❌ 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

# ❌ 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

# ❌ 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

# 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

# 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:

# 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

git rm nomarr/​helpers/​old_module.py

Step 6: Update Skills

python scripts/​validate_skills.py --check-refs

Step 7: Verify Migration Complete

# 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:

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.