AI Migration Assistants: Strangler Fig Cuts With Contract Tests

AI Migration Assistants: Strangler Fig Cuts With Contract Tests
10 views

This guide covers AI migration assistants with production-focused examples and trade-offs.

Big-bang rewrites fail. Strangler fig migrations succeed when every cut is proven — not when an agent “migrates the service” in one PR. Use coding agents to accelerate endpoint-sized cuts: generate adapters, dual-run harnesses, and contract tests that fail the cutover when response drift exceeds semantic tolerances you define.
⚡ TL;DR: Agents propose strangler adapters and characterization tests; humans own cutover. Dual-run old vs new on shadow traffic; compare with schema-aware matchers (not brittle string equality). Cut traffic only when drift < threshold for N windows. Illustrative pace: 1–3 endpoints/week with agent assist vs months of stalled rewrite.

Strangler shape the agent must respect — AI migration assistants

text
Client → Facade / Routing layer
            ├─ Legacy monolith handler (default)
            └─ New service handler (canary %)
                 └─ Dual-run comparator (shadow)
Encode this in Cursor rules / Claude instructions so the agent never deletes the legacy path in the same PR that adds the new one. See Cursor Rules for TypeScript Monorepos.
markdown
# migration rule excerpt
- One endpoint per PR unless a shared DTO must move first.
- Never remove legacy routes in the introducing PR.
- Always add dual-run tests under packages/migration-contracts.
- ❌ No “while we’re here” refactors outside the endpoint boundary.

Agent task: characterization tests first

Before writing the new handler, the agent should freeze legacy behavior:
typescript
// characterization from recorded fixtures — illustrative
import { legacyHandle } from "../legacy/invoices";
import fixtures from "./fixtures/invoices.get.json";

test.each(fixtures)("legacy characterization %s", async (fx) => {
  const res = await legacyHandle(fx.req);
  expect(res.status).toBe(fx.res.status);
  expect(normalize(res.body)).toEqual(normalize(fx.res.body));
});

function normalize(body: any) {
  const { requestId, serverTime, ...rest } = body;
  return rest; // strip volatile fields
}
✅ Agents generate fixtures from sanitized traffic.
❌ Agents invent expected JSON from memory of another codebase.

Dual-run contract tests with semantic tolerances

typescript
type Tolerance = {
  ignorePaths: string[]; // json-pointer
  numericEpsilon?: Record<string, number>;
  arrayOrderInsensitive?: string[];
};

export function semanticEqual(a: unknown, b: unknown, tol: Tolerance): string[] {
  const drifts: string[] = [];
  // walk both trees; skip ignorePaths; compare numbers with epsilon
  // return human-readable drift list for the PR bot
  return drifts;
}

test("dual-run GET /invoices/:id", async () => {
  const req = sampleRequest();
  const [legacy, next] = await Promise.all([
    legacyHandle(req),
    newHandle(req),
  ]);
  const drifts = semanticEqual(legacy.body, next.body, {
    ignorePaths: ["/requestId", "/serverTime"],
    numericEpsilon: { "/taxCents": 1 },
  });
  expect(drifts).toEqual([]);
});
Wire shadow traffic in staging: copy a % of GETs to the new path, log drifts to S3, fail the promotion pipeline when drift rate > 0.1%. Semantic review bots (companion) should treat migration PRs as high-signal.

Cutover playbook the agent cannot skip

bash
# cutover checklist enforced in CI labels
# 1. dual-run green for 48h on staging shadow
# 2. canary 5% → 25% → 100% with error-budget gates
# 3. only then open PR to remove legacy handler
gh pr edit "$PR" --add-label "migration/cutover-ready"
typescript
// routing facade — illustrative
export async function getInvoice(ctx, id: string) {
  const mode = await flags.get("invoices.get.impl", ctx.tenantId);
  if (mode === "new") return newHandle(ctx, id);
  if (mode === "shadow") {
    const legacy = await legacyHandle(ctx, id);
    void shadowCompare(legacy, () => newHandle(ctx, id));
    return legacy; // always return legacy until cut
  }
  return legacyHandle(ctx, id);
}
Keep agent tools sandboxed (Lambda sandboxes) — migration assistants should not apply production traffic flags themselves.

What to automate vs what stays human

Step Agent Human
Draft adapter + DTOs ✅ review
Characterization fixtures ✅ assist approve redaction
Dual-run matcher tuning propose set tolerances
Canary % changes ❌ ✅ on-call
Delete legacy draft PR merge after soak
Atomic commits help (Agentic Git Workflows): tests, adapter, flags, deletion as separate SHAs.

Closing checklist

✅ Dos – ✅ One endpoint (or tightly bound group) per migration PR – ✅ Characterization tests before new implementation – ✅ Semantic dual-run with explicit tolerances – ✅ Feature-flag cutover with soak windows – ✅ Delete legacy only after canary success ❌ Don’ts – ❌ Don’t let the agent rewrite the entire monolith in one branch – ❌ Don’t compare raw JSON including volatile fields – ❌ Don’t flip 100% traffic from an unattended agent tool – ❌ Don’t skip CODEOWNERS on migration facades – ❌ Don’t mix unrelated refactors into strangler PRs

Related reading

Last updated on September 11, 2026

Related: Day 60: Project: Migration Strangler Assistant · AI Test Generation: Property Tests That Catch Race Conditions · Evaluating AI Coding Tools: Blind A/B Tests on Real Tickets

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

1 Comment

Leave a comment

No account needed. Name and email are optional.