Claude-skill-registry code-migration

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

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/code-migration" ~/.claude/skills/majiayu000-claude-skill-registry-code-migration && rm -rf "$T"
manifest: skills/data/code-migration/SKILL.md
source content

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

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