Legacy Modernization
SkillAI & modelsUse when incrementally modernizing a legacy system without a rewrite. Covers characterization tests, seams, the strangler pattern, and sequencing migrations so the system stays shippable throughout.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Legacy Modernization skill
What this skill tells your AI
The instructions your AI receives, as published by nimadorostkar/claude-skills-collection in skills/development/legacy-modernization/SKILL.md and read by ahel’s review.
Purpose
Replace a legacy system incrementally, keeping it in production the entire time. The failure mode this skill exists to prevent is the two-year rewrite that ships nothing and is cancelled.
When to Use
- A system that works but resists change.
- Migrating off an unsupported framework, runtime, or vendor.
- Extracting a service from a monolith.
- Any proposal that starts with "we should just rewrite it".
Capabilities
- Characterization testing to capture undocumented behavior.
- Seam identification and dependency breaking.
- Strangler-fig migration behind a routing facade.
- Data migration with dual writes and backfills.
- Sequencing work so every step is independently shippable.
Inputs
- The legacy system, its deployment, and its actual traffic patterns.
- The concrete pain: what change is expensive, and how expensive.
- Constraints: uptime requirements, data volume, compliance, team capacity.
Outputs
- A migration plan of independently shippable increments.
- Characterization tests covering the behavior being preserved.
- A facade or router that lets old and new coexist.
- A decommissioning checklist for the old path.
Workflow
- Resist the rewrite — Establish what the current system does that a rewrite would have to rediscover. This list is always longer than expected, and it is the argument.
- Pin the behavior — Characterization tests at the outermost boundary you can reach: HTTP, CLI, batch output. Record what it does, bugs included.
- Introduce a facade — Put a routing layer in front of the capability. Every request now flows through a point you control.
- Carve off one capability — Choose the smallest one with clear boundaries. Implement it in the new system. Route a percentage of traffic to it.
- Compare, then cut over — Run both and diff the outputs (shadow mode) until you trust the new path. Then flip the route.
- Delete the old path — Immediately. A migration that leaves both paths alive has doubled the maintenance burden, not reduced it.
- Repeat — Next capability. Each cycle ships.
Best Practices
- Never begin by changing the database. Data is the hardest thing to migrate and the least reversible — do it last, or behind a repository interface.
- Shadow traffic is the cheapest confidence you will ever buy. Send real requests to the new path, discard its response, and compare.
- Feature-flag every cutover so the rollback is a config change, not a deploy.
- Migrate the highest-churn code first. The legacy code nobody has touched in three years is not what is slowing you down.
- Keep a written kill list of old code paths, and cross them off. Otherwise the "temporary" fallback lives forever.
- Do not modernize style, structure, and behavior at once. Preserve behavior; the rest can follow.
Examples
Strangler routing with shadow comparison:
async def get_pricing(request: PricingRequest) -> Pricing:
legacy_result = await legacy_pricing.calculate(request)
if flags.enabled("pricing.shadow", request.tenant_id):
try:
new_result = await pricing_service.calculate(request)
if new_result != legacy_result:
logger.warning(
"pricing_mismatch",
extra={"tenant": request.tenant_id,
"legacy": legacy_result.total_cents,
"new": new_result.total_cents},
)
except Exception:
logger.exception("shadow_pricing_failed") # never affects the response
if flags.enabled("pricing.cutover", request.tenant_id):
return await pricing_service.calculate(request)
return legacy_result
Shadow mode surfaces every discrepancy on real traffic before a single user is affected by the new path.
Notes
- A mismatch rate that will not converge to zero usually means the legacy behavior is not what the spec says. The legacy behavior is the spec.
- Dual-writing to two data stores requires an idempotency key and a reconciliation job. Without both, they will drift.
- The decommission is part of the project. A migration is not done when the new path serves traffic; it is done when the old code is deleted.
Signals
- GitHub stars
- 26
- Forks
- 3
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
legacy-modernization- Source
- github.com/nimadorostkar/claude-skills-collection