All skills
jeffallan avatar

/legacy-modernizer

@efebc44
by jeffallanjeffallan/claude-skills12k stars
1,124

Designs incremental migration strategies, identifies service boundaries, produces dependency maps and migration roadmaps, and generates API facade designs for aging codebases. Use when modernizing legacy systems, implementing strangler fig pattern or branch by abstraction, decomposing monoliths, upgrading frameworks or languages, or reducing technical debt without disrupting business operations.

Use this Skill: https://skilld.dev/gh/jeffallan/claude-skills/legacy-modernizer

This session only. Nothing lands on disk.

referenceslegacy-testing.md

≈2.9k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Legacy Testing Strategies

Characterization Tests

Tests that document current behavior (even if buggy) before refactoring.

# Legacy function with unknown behavior
def calculate_shipping_cost(order):
    """Legacy shipping calculator - behavior unclear"""
    cost = 0
    if order['weight'] > 10:
        cost += order['weight'] * 0.5
    if order['destination'] == 'international':
        cost *= 2
    if order['priority']:
        cost *= 1.5
    # ... more mysterious logic
    return round(cost, 2)

# Characterization test: Capture current behavior
import pytest

class TestShippingCostCharacterization:
    """These tests document existing behavior, not correct behavior"""

    def test_domestic_lightweight(self):
        order = {'weight': 5, 'destination': 'domestic', 'priority': False}
        # This IS the current behavior (0.0 might be wrong!)
        assert calculate_shipping_cost(order) == 0.0

    def test_domestic_heavy(self):
        order = {'weight': 15, 'destination': 'domestic', 'priority': False}
        assert calculate_shipping_cost(order) == 7.5  # weight * 0.5

    def test_international_heavy(self):
        order = {'weight': 15, 'destination': 'international', 'priority': False}
        assert calculate_shipping_cost(order) == 15.0  # (15 * 0.5) * 2

    def test_priority_international_heavy(self):
        order = {'weight': 15, 'destination': 'international', 'priority': True}
        assert calculate_shipping_cost(order) == 22.5  # ((15 * 0.5) * 2) * 1.5

# After characterization, refactor with confidence
def calculate_shipping_cost_v2(order: Order) -> Decimal:
    """Refactored with clear logic"""
    base_cost = Decimal('0')

    if order.weight > 10:
        base_cost = Decimal(str(order.weight)) * Decimal('0.5')

    if order.destination == Destination.INTERNATIONAL:
        base_cost *= Decimal('2')

    if order.priority:
        base_cost *= Decimal('1.5')

    return base_cost.quantize(Decimal('0.01'))

# Characterization tests should still pass

Golden Master Testing

Capture output snapshots for complex legacy systems.

# Legacy report generator with complex formatting
def generate_monthly_report(start_date, end_date):
    """Generates complex text report"""
    report = []
    report.append(f"Report Period: {start_date} to {end_date}")
    # ... 500 lines of complex logic
    return "\n".join(report)

# Golden master test
import hashlib
import os
from pathlib import Path

class TestMonthlyReportGoldenMaster:
    def test_january_2024_report(self):
        """Compare against known-good output"""
        report = generate_monthly_report('2024-01-01', '2024-01-31')

        # First run: Save golden master
        golden_path = Path(__file__).parent / 'golden_masters' / 'jan_2024.txt'
        if not golden_path.exists():
            golden_path.parent.mkdir(exist_ok=True)
            golden_path.write_text(report)
            pytest.skip("Golden master saved, run again to verify")

        # Subsequent runs: Compare
        expected = golden_path.read_text()
        assert report == expected, "Output differs from golden master"

    def test_report_hash_unchanged(self):
        """Faster comparison using hash"""
        report = generate_monthly_report('2024-01-01', '2024-01-31')
        report_hash = hashlib.sha256(report.encode()).hexdigest()

        # Known good hash
        expected_hash = "a3f5b2c8d9e1f4a7b6c5d8e9f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0"
        assert report_hash == expected_hash

# Approval testing library
from approvaltests import verify

def test_monthly_report_approval():
    """Uses approvaltests library for easy golden master testing"""
    report = generate_monthly_report('2024-01-01', '2024-01-31')
    verify(report)  # Creates .approved file first run, compares after

Snapshot Testing for APIs

# Legacy API with complex responses
@app.get("/api/dashboard")
async def get_dashboard():
    # Complex aggregation logic
    return {
        "user": {...},
        "stats": {...},
        "notifications": [...],
        # ... many nested fields
    }

# Snapshot test
import pytest
from syrupy import SnapshotAssertion

@pytest.mark.asyncio
async def test_dashboard_structure(snapshot: SnapshotAssertion):
    """Ensure dashboard structure doesn't change unexpectedly"""
    response = await client.get("/api/dashboard")

    # First run creates snapshot, subsequent runs compare
    assert response.json() == snapshot

# Custom snapshot serializer for stable output
from syrupy.extensions.json import JSONSnapshotExtension

class SortedJSONExtension(JSONSnapshotExtension):
    def serialize(self, data, **kwargs):
        # Sort keys for consistent snapshots
        return super().serialize(data, sort_keys=True, **kwargs)

@pytest.fixture
def snapshot(snapshot):
    return snapshot.use_extension(SortedJSONExtension)

Parallel Run Testing

Run old and new implementations side-by-side to compare.

# Parallel run decorator
import functools
import asyncio
from typing import Callable, Any

def parallel_run(legacy_func: Callable, new_func: Callable):
    """Run both implementations and compare results"""
    @functools.wraps(new_func)
    async def wrapper(*args, **kwargs):
        # Run both in parallel
        legacy_task = asyncio.create_task(
            asyncio.to_thread(legacy_func, *args, **kwargs)
        )
        new_task = asyncio.create_task(new_func(*args, **kwargs))

        legacy_result, new_result = await asyncio.gather(
            legacy_task, new_task, return_exceptions=True
        )

        # Log discrepancies
        if legacy_result != new_result:
            logger.warning(
                "Parallel run mismatch",
                extra={
                    "function": new_func.__name__,
                    "args": args,
                    "legacy_result": legacy_result,
                    "new_result": new_result,
                }
            )

        # Use legacy result in production (new is shadow)
        if isinstance(legacy_result, Exception):
            raise legacy_result
        return legacy_result

    return wrapper

# Usage
@parallel_run(legacy_func=legacy_calculate_price, new_func=new_calculate_price)
async def calculate_price(product_id: int, quantity: int):
    """This will run both and compare results"""
    pass

# In production, route to parallel_run
@app.get("/price/{product_id}")
async def get_price(product_id: int, quantity: int = 1):
    return await calculate_price(product_id, quantity)

Mutation Testing for Legacy Code

# Install: pip install mutmut

# Legacy function we want to refactor
def validate_email(email):
    if '@' not in email:
        return False
    if '.' not in email:
        return False
    if len(email) < 5:
        return False
    return True

# Basic tests
def test_validate_email():
    assert validate_email("user@example.com") is True
    assert validate_email("invalid") is False

# Run mutation testing to find missing test cases
# $ mutmut run --paths-to-mutate=validate.py

# Mutmut will create mutations like:
# - Change '@' to '!' (caught by test)
# - Change 5 to 6 (NOT caught - missing edge case!)
# - Remove conditions (caught by test)

# Add missing test cases discovered by mutation testing
def test_validate_email_comprehensive():
    # Original tests
    assert validate_email("user@example.com") is True
    assert validate_email("invalid") is False

    # Edge cases found by mutation testing
    assert validate_email("a@b.c") is True   # Exactly 5 chars
    assert validate_email("a@b.") is False   # Dot at end
    assert validate_email(".@b.c") is False  # Dot at start
    assert validate_email("a@.com") is False # Dot after @

Property-Based Testing for Legacy Logic

from hypothesis import given, strategies as st

# Legacy function with unclear edge cases
def calculate_discount(price, quantity, customer_type):
    """Legacy discount logic"""
    discount = 0
    if quantity > 10:
        discount += 0.1
    if customer_type == 'premium':
        discount += 0.15
    if price > 1000:
        discount += 0.05
    return price * (1 - min(discount, 0.5))

# Property-based tests discover edge cases
@given(
    price=st.floats(min_value=0.01, max_value=100000),
    quantity=st.integers(min_value=1, max_value=1000),
    customer_type=st.sampled_from(['regular', 'premium']),
)
def test_discount_properties(price, quantity, customer_type):
    result = calculate_discount(price, quantity, customer_type)

    # Property: Result should never be negative
    assert result >= 0

    # Property: Result should never exceed original price
    assert result <= price

    # Property: Discount should never exceed 50%
    assert result >= price * 0.5

# Run this 100+ times with random inputs
# Hypothesis will find edge cases that break these properties

Coverage-Guided Test Generation

# Use coverage.py to find untested code paths
# $ pytest --cov=legacy_module --cov-report=html

# Example: Legacy function with many branches
def process_order(order):
    if not order.get('items'):
        raise ValueError("Empty order")

    total = sum(item['price'] * item['qty'] for item in order['items'])

    if order.get('coupon'):
        discount = apply_coupon(order['coupon'], total)
        total -= discount

    if order.get('shipping_method') == 'express':
        total += 25
    elif order.get('shipping_method') == 'international':
        total += 50

    if total < 0:  # This line never tested!
        total = 0

    return {'total': total, 'order_id': generate_id()}

# Coverage report shows line "total = 0" never executed
# Add test case:
def test_process_order_negative_total():
    """Test case discovered from coverage analysis"""
    order = {
        'items': [{'price': 10, 'qty': 1}],
        'coupon': 'SUPER_DISCOUNT_100',  # 100% off
    }
    result = process_order(order)
    assert result['total'] == 0  # Should handle negative total

Database State Testing

# Test database-dependent legacy code
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

@pytest.fixture
def legacy_db():
    """Create test database matching legacy schema"""
    engine = create_engine("sqlite:///:memory:")

    # Recreate legacy schema (exact structure)
    engine.execute("""
        CREATE TABLE users (
            id INTEGER PRIMARY KEY,
            name TEXT,
            email TEXT,
            created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)

    Session = sessionmaker(bind=engine)
    session = Session()

    yield session

    session.close()

def test_legacy_user_creation(legacy_db):
    """Test legacy code against test database"""
    # Insert using legacy code
    legacy_create_user(legacy_db, "John", "john@example.com")

    # Verify using raw SQL
    result = legacy_db.execute("SELECT * FROM users WHERE name = 'John'")
    user = result.fetchone()

    assert user is not None
    assert user['email'] == "john@example.com"

Quick Reference

Test Type Use When Tool
Characterization Unknown behavior pytest
Golden Master Complex output approvaltests
Snapshot API responses syrupy
Parallel Run Comparing implementations Custom decorator
Mutation Finding gaps mutmut
Property-based Edge cases hypothesis
Coverage-guided Untested paths coverage.py

Source: SKILL.md on GitHub

1 alert17d5 checks · Risk CRITICAL
  • Gen Agent Trust Hub17d

    Automated security scanners flagged the skill's documentation URL as blacklisted and identified the main skill file as malicious. Technical analysis confirms the skill includes code for executing system commands (git) and analyzing local files without sanitization, presenting a high risk if deployed on untrusted environments.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer6mo

    4/6 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at efebc44. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 5 months ago
Other metadata
metadata
{
  "author": "https://github.com/Jeffallan",
  "version": "1.1.0",
  "domain": "specialized",
  "triggers": "legacy modernization, strangler fig, incremental migration, technical debt, legacy refactoring, system migration, legacy system, modernize codebase",
  "role": "specialist",
  "scope": "architecture",
  "output-format": "code+analysis",
  "related-skills": "test-master, devops-engineer"
}
  • Refactoring
  • legacy-modernization
  • strangler-fig
  • incremental-migration
  • technical-debt
  • monolith-decomposition
  • feature-flags
  • architecture

README badge

README badge for jeffallan/claude-skills/legacy-modernizer

Designs incremental migration strategies and produces dependency maps, service boundaries, and API facades for legacy system modernization. Guides the strangler fig pattern, branch by abstraction, and monolith decomposition with explicit rollback strategies and characterization testing to eliminate technical debt without disrupting production.

Generated from the current SKILL.md.

Does this skill work with languages other than Python?
Yes. The skill provides language-agnostic architectural patterns (strangler fig, branch by abstraction, characterization testing) and includes Python examples, but the core guidance applies to any codebase. You will need to adapt code examples to your language.
What does 'characterization testing' mean in this context?
Characterization tests capture the existing behavior of legacy code as a golden master before refactoring. They serve as a safety net to detect unintended changes during modernization, targeting 80%+ coverage of legacy behavior.
Can I use this skill for a big rewrite instead of incremental migration?
No. The skill explicitly forbids big bang rewrites and requires zero production disruption. It is designed for strangler fig, branch by abstraction, and other incremental patterns only.
Does this skill handle database migrations?
Yes. The skill includes reference guides for database migrations alongside UI, API, and framework migrations, and emphasizes zero-downtime deployment strategies.
What validation checkpoints does the workflow require?
The skill defines five validation checkpoints: documented integrations before planning, defined rollback triggers per phase, characterization tests passing on unmodified legacy code, error rates and latency within baseline after each traffic increment, and new code stability at 100% traffic for at least one release cycle before legacy code removal.

Generated from the current SKILL.md. These answers refresh after source changes.